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)