From da42a1c2f655e424e874572dcccaa61163b6a4c0 Mon Sep 17 00:00:00 2001
From: chuga-git <98280110+chuga-git@users.noreply.github.com>
Date: Wed, 26 Jun 2024 16:03:51 -0500
Subject: [PATCH] Refactors throwing (#25946)
* refactors datum/thrownthing
* cleanup
* lewc review
* calling parent doesn't do anything lmao
---
code/controllers/subsystem/SSthrowing.dm | 164 +++++++++++++++++------
code/datums/components/boomerang.dm | 16 +--
code/game/atoms.dm | 7 +-
code/game/atoms_movable.dm | 64 +++++----
code/game/objects/items/sport.dm | 3 +-
code/game/objects/items/theft_items.dm | 4 +-
code/game/objects/structures/window.dm | 2 +-
code/modules/mob/mob_movement.dm | 2 +-
code/modules/recycling/disposal.dm | 3 +-
9 files changed, 173 insertions(+), 92 deletions(-)
diff --git a/code/controllers/subsystem/SSthrowing.dm b/code/controllers/subsystem/SSthrowing.dm
index 95d137e0db4..46ecde8aa5a 100644
--- a/code/controllers/subsystem/SSthrowing.dm
+++ b/code/controllers/subsystem/SSthrowing.dm
@@ -42,15 +42,15 @@ SUBSYSTEM_DEF(throwing)
while(length(currentrun))
var/atom/movable/AM = currentrun[length(currentrun)]
- var/datum/thrownthing/TT = currentrun[AM]
+ var/datum/thrownthing/thrown_thing = currentrun[AM]
currentrun.len--
- if(!AM || !TT)
+ if(QDELETED(AM) || QDELETED(thrown_thing))
processing -= AM
if(MC_TICK_CHECK)
return
continue
- TT.tick()
+ thrown_thing.tick()
if(MC_TICK_CHECK)
return
@@ -65,26 +65,51 @@ SUBSYSTEM_DEF(throwing)
skipped_sounds++
/datum/thrownthing
+ ///Thrown atom this datum is attached to
var/atom/movable/thrownthing
- var/atom/target
+ ///UID of the original intended target of the throw, to prevent hardDels
+ var/initial_target_uid
+ ///The turf that the target was on, if it's not a turf itself.
var/turf/target_turf
+ ///The turf that we were thrown from.
+ var/turf/starting_turf
+ ///If the target happens to be a carbon and that carbon has a body zone aimed at, this is carried on here.
+ var/target_zone
+ ///The initial direction of the thrower of the thrownthing for building the trajectory of the throw.
var/init_dir
+ ///The maximum number of turfs that the thrownthing will travel to reach it's target.
var/maxrange
+ ///Turfs to travel per tick
var/speed
- var/mob/thrower
+ ///If a mob is the one who has thrown the object, then its UID is stored here. This can be null and must be null checked before trying to use it.
+ var/thrower_uid
+ ///A variable that helps in describing objects thrown at an angle, if it should be moved diagonally first or last.
var/diagonals_first
- var/dist_travelled = 0
- var/start_time
- var/dist_x
- var/dist_y
- var/dx
- var/dy
- var/force = MOVE_FORCE_DEFAULT
+ ///Set to TRUE if the throw is exclusively diagonal (45 Degree angle throws for example)
var/pure_diagonal
+ ///Tracks how far a thrownthing has traveled mid-throw for the purposes of maxrange
+ var/dist_travelled = 0
+ ///The start_time obtained via world.time for the purposes of tiles moved/tick.
+ var/start_time
+ ///Distance to travel in the X axis/direction.
+ var/dist_x
+ ///Distance to travel in the y axis/direction.
+ var/dist_y
+ ///The Horizontal direction we're traveling (EAST or WEST)
+ var/dx
+ ///The VERTICAL direction we're traveling (NORTH or SOUTH)
+ var/dy
+ ///The movement force provided to a given object in transit. More info on these in move_force.dm
+ var/force = MOVE_FORCE_DEFAULT
+ ///How many tiles that need to be moved in order to travel to the target.
var/diagonal_error
+ ///If a thrown thing has a callback, it can be invoked here within thrownthing.
var/datum/callback/callback
+ ///Mainly exists for things that would freeze a thrown object in place, like a timestop'd tile. Or a Tractor Beam.
var/paused = FALSE
+ ///How long an object has been paused for, to be added to the travel time.
var/delayed_time = 0
+ ///The last world.time value stored when the thrownthing was moving.
var/last_move = 0
///When this variable is false, non dense mobs will be hit by a thrown item. useful for things that you dont want to be cheesed by crawling, EG. gravitational anomalies
var/dodgeable = TRUE
@@ -93,6 +118,47 @@ SUBSYSTEM_DEF(throwing)
/// Will thrownthing datum actually block movement? this might be FALSE with some circumstances even if var/should_block_movement is TRUE. This variable change automatically during the throw
var/block_movement = TRUE
+/datum/thrownthing/New(thrownthing, atom/target, init_dir, maxrange, speed, atom/thrower, diagonals_first, force, callback, dodgeable, block_movement, target_zone)
+ src.thrownthing = thrownthing
+ RegisterSignal(thrownthing, COMSIG_PARENT_QDELETING, PROC_REF(on_thrownthing_qdel))
+ src.starting_turf = get_turf(thrownthing)
+ src.target_turf = get_turf(target)
+ if(target_turf != target)
+ src.initial_target_uid = target.UID()
+ src.init_dir = init_dir
+ src.maxrange = maxrange
+ src.speed = speed
+ if(thrower)
+ src.thrower_uid = thrower.UID()
+ src.diagonals_first = diagonals_first
+ src.force = force
+ src.callback = callback
+ src.dodgeable = dodgeable
+ src.should_block_movement = block_movement
+ src.target_zone = target_zone
+
+/datum/thrownthing/Destroy()
+ SSthrowing.processing -= thrownthing
+ SSthrowing.currentrun -= thrownthing
+ thrownthing.throwing = null
+ thrownthing = null
+ thrower_uid = null
+ initial_target_uid = null
+ callback = null
+ return ..()
+
+///Defines the datum behavior on the thrownthing's qdeletion event.
+/datum/thrownthing/proc/on_thrownthing_qdel(atom/movable/source, force)
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+
+ qdel(src)
+
+/// Returns the mob thrower, or null
+/datum/thrownthing/proc/get_thrower()
+ . = locateUID(thrower_uid)
+ if(isnull(.))
+ thrower_uid = null
+
/datum/thrownthing/proc/tick()
var/atom/movable/AM = thrownthing
if(!isturf(AM.loc) || !AM.throwing)
@@ -103,9 +169,20 @@ SUBSYSTEM_DEF(throwing)
delayed_time += world.time - last_move
return
- if(dist_travelled && hitcheck()) //to catch sneaky things moving on our tile while we slept
- finalize()
- return
+ var/atom/movable/actual_target = locateUID(initial_target_uid)
+ var/mob/mob_thrower = get_thrower()
+
+ if(dist_travelled) //to catch sneaky things moving on our tile while we slept
+ for(var/atom/movable/obstacle as anything in get_turf(thrownthing))
+ if(obstacle == thrownthing || (obstacle == mob_thrower && !ismob(thrownthing)))
+ continue
+ if(ismob(obstacle) && (thrownthing.pass_flags & PASSMOB) && (obstacle != actual_target))
+ continue
+ if(obstacle.pass_flags_self & LETPASSTHROW)
+ continue
+ if(obstacle == actual_target || (obstacle.density && !(obstacle.flags & ON_BORDER) && !(obstacle in AM.buckled_mobs)))
+ finalize(TRUE, obstacle)
+ return
var/atom/step
@@ -121,16 +198,15 @@ SUBSYSTEM_DEF(throwing)
else
gravity = has_gravity(AM, AM.loc)
- if(!gravity)
- block_movement = FALSE // you should be able to move if there is no gravity, supports jetpack movement during throw
- else
- block_movement = should_block_movement
-
if((dist_travelled >= maxrange || AM.loc == target_turf) && gravity)
- hitcheck() //Just to be sure
finalize()
return
+ if(gravity)
+ block_movement = should_block_movement
+ else
+ block_movement = FALSE // you should be able to move if there is no gravity, supports jetpack movement during throw
+
if(dist_travelled <= max(dist_x, dist_y)) //if we haven't reached the target yet we home in on it, otherwise we use the initial direction
step = get_step(AM, get_dir(AM, target_turf))
else
@@ -145,51 +221,55 @@ SUBSYSTEM_DEF(throwing)
finalize()
return
- AM.Move(step, get_dir(AM, step))
+ if(!AM.Move(step, get_dir(AM, step))) // we hit something during our move...
+ if(AM.throwing) // ...but finalize() wasn't called on Bump() because of a higher level definition that doesn't always call parent.
+ finalize()
+ return
dist_travelled++
+ if(actual_target && !(actual_target.pass_flags_self & LETPASSTHROW) && actual_target.loc == AM.loc) // we crossed a movable with no density (e.g. a mouse or APC) we intend to hit anyway.
+ finalize(TRUE, actual_target)
+ return
+
if(dist_travelled > MAX_THROWING_DIST)
finalize()
return
/datum/thrownthing/proc/finalize(hit = FALSE, target = null)
- set waitfor = 0
- SSthrowing.processing -= thrownthing
+ set waitfor = FALSE
//done throwing, either because it hit something or it finished moving
+ if(!thrownthing)
+ return
thrownthing.throwing = null
if(!hit)
- for(var/thing in get_turf(thrownthing)) //looking for our target on the turf we land on.
- var/atom/A = thing
- if(A == target)
- hit = 1
- thrownthing.throw_impact(A, src)
+ for(var/atom/movable/obstacle as anything in get_turf(thrownthing)) //looking for our target on the turf we land on.
+ if(obstacle == target)
+ hit = TRUE
+ thrownthing.throw_impact(obstacle, src)
+ if(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
+ return //deletion should already be handled by on_thrownthing_qdel()
break
if(!hit)
thrownthing.throw_impact(get_turf(thrownthing), src) // we haven't hit something yet and we still must, let's hit the ground.
+ if(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
+ return //deletion should already be handled by on_thrownthing_qdel()
thrownthing.newtonian_move(init_dir)
else
thrownthing.newtonian_move(init_dir)
if(target)
- thrownthing.throw_impact(target, src, speed)
+ thrownthing.throw_impact(target, src)
+ if(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
+ return //deletion should already be handled by on_thrownthing_qdel()
if(callback)
callback.Invoke()
- SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src)
- thrownthing.end_throw()
-/datum/thrownthing/proc/hit_atom(atom/A)
- finalize(hit = TRUE, target = A)
+ if(thrownthing)
+ SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src)
-/datum/thrownthing/proc/hitcheck()
- for(var/thing in get_turf(thrownthing))
- var/atom/movable/AM = thing
- if(AM == thrownthing || AM == thrower)
- continue
- if((AM.density || isliving(AM) && !dodgeable && !HAS_TRAIT(AM, TRAIT_DODGE_ALL_OBJECTS)) && !(AM.pass_flags & LETPASSTHROW) && !(AM.flags & ON_BORDER))
- finalize(hit = TRUE, target = AM)
- return TRUE
+ qdel(src)
#undef MAX_THROWING_DIST
#undef MAX_TICKS_TO_MAKE_UP
diff --git a/code/datums/components/boomerang.dm b/code/datums/components/boomerang.dm
index 973f3c0229f..637f11df183 100644
--- a/code/datums/components/boomerang.dm
+++ b/code/datums/components/boomerang.dm
@@ -38,9 +38,10 @@
*/
/datum/component/boomerang/proc/prepare_throw(datum/source, datum/thrownthing/thrown_thing, spin)
SIGNAL_HANDLER
- if(thrower_easy_catch_enabled && iscarbon(thrown_thing?.thrower))
- var/mob/living/carbon/C = thrown_thing.thrower
- C.throw_mode_on()
+ var/mob/thrower = thrown_thing?.get_thrower()
+ if(thrower_easy_catch_enabled && iscarbon(thrower))
+ var/mob/living/carbon/carbon_mob = thrower
+ carbon_mob.throw_mode_on()
/**
* Proc that triggers when the thrown boomerang hits an object.
@@ -75,11 +76,10 @@
/datum/component/boomerang/proc/aerodynamic_swing(datum/thrownthing/throwing_datum, obj/item/true_parent)
var/mob/thrown_by = locateUID(true_parent.thrownby)
if(istype(thrown_by))
- var/dir = get_dir(true_parent, thrown_by)
- var/turf/T = get_ranged_target_turf(thrown_by, dir, 2)
- addtimer(CALLBACK(true_parent, TYPE_PROC_REF(/atom/movable, throw_at), T, boomerang_throw_range, throwing_datum.speed, null, TRUE), 1)
- last_boomerang_throw = world.time + BOOMERANG_REBOUND_INTERVAL
- true_parent.visible_message("[true_parent] is flying back at [throwing_datum.thrower]!", \
+ addtimer(CALLBACK(true_parent, TYPE_PROC_REF(/atom/movable, throw_at), thrown_by, boomerang_throw_range, throwing_datum.speed, null, TRUE), 0.1 SECONDS)
+ COOLDOWN_START(src, last_boomerang_throw, BOOMERANG_REBOUND_INTERVAL)
+ var/mob/thrower = throwing_datum?.get_thrower()
+ true_parent.visible_message("[true_parent] is flying back at [thrower]!", \
"You see [true_parent] fly back at you!", \
"You hear an aerodynamic woosh!")
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 98bcca85a89..0fb03895d8e 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -609,9 +609,10 @@
// Atoms that return TRUE prevent RPDs placing any kind of pipes on their turf.
return FALSE
-/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
- if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
- addtimer(CALLBACK(src, PROC_REF(hitby_react), AM), 2)
+/atom/proc/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ SEND_SIGNAL(src, COMSIG_ATOM_HITBY, hitting_atom, skipcatch, hitpush, blocked, throwingdatum)
+ if(density && !has_gravity(hitting_atom)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
+ addtimer(CALLBACK(src, PROC_REF(hitby_react), hitting_atom), 2)
/// This proc applies special effects of a carbon mob hitting something, be it a wall, structure, or window. You can set mob_hurt to false to avoid double dipping through subtypes if returning ..()
/atom/proc/hit_by_thrown_mob(mob/living/C, datum/thrownthing/throwingdatum, damage, mob_hurt = FALSE, self_hurt = FALSE)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 262113c47f5..8b7b67c6934 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -287,15 +287,15 @@
/atom/movable/Uncrossed(atom/movable/AM)
SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
-/atom/movable/Bump(atom/A, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump().
- if(A && yes)
- SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
- if(throwing)
- throwing.hit_atom(A)
- . = 1
- if(!A || QDELETED(A))
+/atom/movable/Bump(atom/bumped_atom, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump(). // suffering
+ if(bumped_atom && yes)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom)
+ if(!QDELETED(throwing))
+ throwing.finalize(TRUE, bumped_atom)
+ . = TRUE
+ if(QDELETED(bumped_atom))
return
- A.Bumped(src)
+ bumped_atom.Bumped(src)
/atom/movable/proc/forceMove(atom/destination)
var/turf/old_loc = loc
@@ -385,10 +385,10 @@
//called when src is thrown into hit_atom
/atom/movable/proc/throw_impact(atom/hit_atom, throwingdatum)
- set waitfor = 0
+ set waitfor = FALSE
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
if(!QDELETED(hit_atom))
- return hit_atom.hitby(src)
+ return hit_atom.hitby(src, throwingdatum=throwingdatum)
/// called after an items throw is ended.
/atom/movable/proc/end_throw()
@@ -400,6 +400,9 @@
..()
/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, dodgeable = TRUE, block_movement = TRUE)
+ if(QDELETED(src))
+ CRASH("Qdeleted thing being thrown around.")
+
if(!target || (flags & NODROP) || speed <= 0)
return FALSE
@@ -429,18 +432,13 @@
if(speed <= 0)
return //no throw speed, the user was moving too fast.
- var/datum/thrownthing/TT = new()
- TT.thrownthing = src
- TT.target = target
- TT.target_turf = get_turf(target)
- TT.init_dir = get_dir(src, target)
- TT.maxrange = range
- TT.speed = speed
- TT.thrower = thrower
- TT.diagonals_first = diagonals_first
- TT.callback = callback
- TT.dodgeable = dodgeable
- TT.should_block_movement = block_movement
+ var/target_zone
+ if(QDELETED(thrower))
+ thrower = null //Let's not pass a qdeleting reference if any.
+ else
+ target_zone = thrower.zone_selected
+
+ var/datum/thrownthing/thrown_thing = new(src, target, get_dir(src, target), range, speed, thrower, diagonals_first, force, callback, dodgeable, block_movement, target_zone)
var/dist_x = abs(target.x - src.x)
var/dist_y = abs(target.y - src.y)
@@ -448,7 +446,7 @@
var/dy = (target.y > src.y) ? NORTH : SOUTH
if(dist_x == dist_y)
- TT.pure_diagonal = 1
+ thrown_thing.pure_diagonal = 1
else if(dist_x <= dist_y)
var/olddist_x = dist_x
@@ -457,23 +455,23 @@
dist_y = olddist_x
dx = dy
dy = olddx
- TT.dist_x = dist_x
- TT.dist_y = dist_y
- TT.dx = dx
- TT.dy = dy
- TT.diagonal_error = dist_x / 2 - dist_y
- TT.start_time = world.time
+ thrown_thing.dist_x = dist_x
+ thrown_thing.dist_y = dist_y
+ thrown_thing.dx = dx
+ thrown_thing.dy = dy
+ thrown_thing.diagonal_error = dist_x / 2 - dist_y
+ thrown_thing.start_time = world.time
if(pulledby)
pulledby.stop_pulling()
- throwing = TT
+ throwing = thrown_thing
if(spin && !no_spin && !no_spin_thrown)
SpinAnimation(5, 1)
- SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
- SSthrowing.processing[src] = TT
- TT.tick()
+ SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, thrown_thing, spin)
+ SSthrowing.processing[src] = thrown_thing
+ thrown_thing.tick()
return TRUE
diff --git a/code/game/objects/items/sport.dm b/code/game/objects/items/sport.dm
index 862cdeecce2..5c96c579eb4 100644
--- a/code/game/objects/items/sport.dm
+++ b/code/game/objects/items/sport.dm
@@ -87,7 +87,8 @@
/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(isitem(AM) && !isprojectile(AM))
- if(prob(50) || HAS_TRAIT(throwingdatum.thrower, TRAIT_BADASS))
+ var/atom/movable/thrower = throwingdatum?.get_thrower()
+ if(prob(50) || HAS_TRAIT(thrower, TRAIT_BADASS))
AM.forceMove(get_turf(src))
visible_message("Swish! [AM] lands in [src].")
else
diff --git a/code/game/objects/items/theft_items.dm b/code/game/objects/items/theft_items.dm
index f033792ae27..2f01761618c 100644
--- a/code/game/objects/items/theft_items.dm
+++ b/code/game/objects/items/theft_items.dm
@@ -210,8 +210,8 @@
var/mob/living/victim = hit_atom
if(victim.incorporeal_move || victim.status_flags & GODMODE || HAS_TRAIT(victim, TRAIT_SUPERMATTER_IMMUNE)) //try to keep this in sync with supermatter's consume fail conditions
return ..()
- if(throwingdatum?.thrower)
- var/mob/user = throwingdatum.thrower
+ var/mob/user = throwingdatum?.get_thrower()
+ if(user)
add_attack_logs(user, victim, "[victim] consumed by [src] thrown by [user] ")
message_admins("[src] has consumed [key_name_admin(victim)] [ADMIN_JMP(src)], thrown by [key_name_admin(user)].")
investigate_log("has consumed [key_name(victim)], thrown by [key_name(user)]", "supermatter")
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index d4d5181faea..b310d735cc9 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -490,7 +490,7 @@
self_hurt = TRUE
..()
if(shattered)
- C.throw_at(throwingdatum.target, throwingdatum.maxrange - 1, throwingdatum.speed - 1) //Annnnnnnd yeet them into space, but slower, now that everything is dealt with
+ C.throw_at(locateUID(throwingdatum.initial_target_uid), throwingdatum.maxrange - 1, throwingdatum.speed - 1) //Annnnnnnd yeet them into space, but slower, now that everything is dealt with
/obj/structure/window/GetExplosionBlock()
return reinf && fulltile ? real_explosion_block : 0
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index ed5f11cf99a..bd78c00c286 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -9,7 +9,7 @@
if(isprojectile(mover))
return projectile_hit_check(mover)
if(mover.throwing)
- return (!density || horizontal || (mover.throwing.thrower == src))
+ return (!density || horizontal || (mover.throwing?.get_thrower() == src))
if(mover.checkpass(PASSMOB))
return 1
if(buckled == mover)
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index e9ce7102fc0..647f48e0a62 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -527,7 +527,8 @@
var/obj/item/I = mover
if(isprojectile(I))
return
- if(prob(75) || (istype(mover.throwing.thrower) && (HAS_TRAIT(mover.throwing.thrower, TRAIT_BADASS) || HAS_TRAIT(mover.throwing.thrower, TRAIT_NEVER_MISSES_DISPOSALS))))
+ var/atom/movable/thrower = mover.throwing?.get_thrower()
+ if(prob(75) || (istype(thrower) && (HAS_TRAIT(thrower, TRAIT_BADASS) || HAS_TRAIT(thrower, TRAIT_NEVER_MISSES_DISPOSALS))))
I.forceMove(src)
for(var/mob/M in viewers(src))
M.show_message("[I] lands in [src].", 3)