diff --git a/code/controllers/subsystems/throwing.dm b/code/controllers/subsystems/throwing.dm new file mode 100644 index 0000000000..beec070947 --- /dev/null +++ b/code/controllers/subsystems/throwing.dm @@ -0,0 +1,207 @@ +#define MAX_THROWING_DIST 1280 // 5 z-levels on default width +#define MAX_TICKS_TO_MAKE_UP 3 //how many missed ticks will we attempt to make up for this run. + +SUBSYSTEM_DEF(throwing) + name = "Throwing" + wait = 1 + flags = SS_NO_INIT|SS_KEEP_TIMING + + var/list/currentrun + var/list/processing = list() + +/datum/controller/subsystem/throwing/stat_entry() + ..("P:[processing.len]") + +/datum/controller/subsystem/throwing/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(length(currentrun)) + var/atom/movable/AM = currentrun[currentrun.len] + var/datum/thrownthing/TT = currentrun[AM] + currentrun.len-- + if (QDELETED(AM) || QDELETED(TT)) + processing -= AM + if (MC_TICK_CHECK) + return + continue + + TT.tick() + + if (MC_TICK_CHECK) + return + + currentrun = null + +/datum/thrownthing + var/atom/movable/thrownthing + var/atom/target + var/turf/target_turf + var/target_zone + var/init_dir + var/maxrange + var/speed + var/mob/thrower + var/start_time + var/dist_travelled = 0 + var/dist_x + var/dist_y + var/dx + var/dy + var/diagonal_error + var/pure_diagonal + var/datum/callback/callback + var/paused = FALSE + var/delayed_time = 0 + var/last_move = 0 + +/datum/thrownthing/New(var/atom/movable/thrownthing, var/atom/target, var/range, var/speed, var/mob/thrower, var/datum/callback/callback) + src.thrownthing = thrownthing + src.target = target + src.target_turf = get_turf(target) + src.init_dir = get_dir(thrownthing, target) + src.maxrange = range + src.speed = speed + src.thrower = thrower + src.callback = callback + if(!QDELETED(thrower)) + src.target_zone = thrower.zone_sel ? thrower.zone_sel.selecting : null + + dist_x = abs(target.x - thrownthing.x) + dist_y = abs(target.y - thrownthing.y) + dx = (target.x > thrownthing.x) ? EAST : WEST + dy = (target.y > thrownthing.y) ? NORTH : SOUTH//same up to here + + if (dist_x == dist_y) + pure_diagonal = TRUE + + else if(dist_x <= dist_y) + var/olddist_x = dist_x + var/olddx = dx + dist_x = dist_y + dist_y = olddist_x + dx = dy + dy = olddx + + diagonal_error = dist_x/2 - dist_y + + start_time = world.time + +/datum/thrownthing/Destroy() + SSthrowing.processing -= thrownthing + thrownthing.throwing = null + thrownthing = null + target = null + thrower = null + callback = null + return ..() + +/datum/thrownthing/proc/tick() + var/atom/movable/AM = thrownthing + if (!isturf(AM.loc) || !AM.throwing) + finalize() + return + + if(paused) + delayed_time += world.time - last_move + return + + if (dist_travelled && hitcheck(get_turf(thrownthing))) //to catch sneaky things moving on our tile while we slept + finalize() + return + + var/area/A = get_area(AM.loc) + var/atom/step + + last_move = world.time + + //calculate how many tiles to move, making up for any missed ticks. + var/tilestomove = CEILING(min(((((world.time+world.tick_lag) - start_time + delayed_time) * speed) - (dist_travelled ? dist_travelled : -1)), speed*MAX_TICKS_TO_MAKE_UP) * (world.tick_lag * SSthrowing.wait), 1) + while (tilestomove-- > 0) + if ((dist_travelled >= maxrange || AM.loc == target_turf) && (A && A.has_gravity())) + finalize() + return + + 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 + step = get_step(AM, init_dir) + + if (!pure_diagonal) // not a purely diagonal trajectory and we don't want all diagonal moves to be done first + if (diagonal_error >= 0 && max(dist_x,dist_y) - dist_travelled != 1) //we do a step forward unless we're right before the target + step = get_step(AM, dx) + diagonal_error += (diagonal_error < 0) ? dist_x/2 : -dist_y + + if (!step) // going off the edge of the map makes get_step return null, don't let things go off the edge + finalize() + return + + if (hitcheck(step)) + finalize() + return + + AM.Move(step, get_dir(AM, step)) + + if (!AM.throwing) // we hit something during our move + finalize(hit = TRUE) + return + + dist_travelled++ + + if (dist_travelled > MAX_THROWING_DIST) + finalize() + return + + A = get_area(AM.loc) + +/datum/thrownthing/proc/finalize(hit = FALSE, t_target=null) + set waitfor = FALSE + //done throwing, either because it hit something or it finished moving + if(QDELETED(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 = TRUE + thrownthing.throw_impact(A, src) + 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(ismob(thrownthing)) + var/mob/M = thrownthing + M.inertia_dir = init_dir + + if(t_target && !QDELETED(thrownthing)) + thrownthing.throw_impact(t_target, src) + + if (callback) + callback.Invoke() + + if (!QDELETED(thrownthing)) + thrownthing.fall() + + qdel(src) + +/datum/thrownthing/proc/hit_atom(atom/A) + finalize(hit=TRUE, t_target=A) + +/datum/thrownthing/proc/hitcheck(var/turf/T) + var/atom/movable/hit_thing + for (var/thing in T) + var/atom/movable/AM = thing + if (AM == thrownthing || (AM == thrower && !ismob(thrownthing))) + continue + if (!AM.density || AM.throwpass)//check if ATOM_FLAG_CHECKS_BORDER as an atom_flag is needed + continue + if (!hit_thing || AM.layer > hit_thing.layer) + hit_thing = AM + + if(hit_thing) + finalize(hit=TRUE, t_target=hit_thing) + return TRUE \ No newline at end of file diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 4a4447e951..195e02e8fa 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -418,92 +418,24 @@ continue src.throw_impact(A,speed) -/atom/movable/proc/throw_at(atom/target, range, speed, thrower) - if(!target || !src) - return 0 - if(target.z != src.z) - return 0 - //use a modified version of Bresenham's algorithm to get from the atom's current position to that of the target - src.throwing = 1 - src.thrower = thrower - src.throw_source = get_turf(src) //store the origin turf - src.pixel_z = 0 - if(usr) - if(HULK in usr.mutations) - src.throwing = 2 // really strong throw! +/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, datum/callback/callback) //If this returns FALSE then callback will not be called. + . = TRUE + if (!target || speed <= 0 || QDELETED(src) || (target.z != src.z)) + return FALSE - var/dist_travelled = 0 - var/dist_since_sleep = 0 - var/area/a = get_area(src.loc) + if (pulledby) + pulledby.stop_pulling() - var/dist_x = abs(target.x - src.x) - var/dist_y = abs(target.y - src.y) + var/datum/thrownthing/TT = new(src, target, range, speed, thrower, callback) + throwing = TT - var/dx - if (target.x > src.x) - dx = EAST - else - dx = WEST - - var/dy - if (target.y > src.y) - dy = NORTH - else - dy = SOUTH - - var/error - var/major_dir - var/major_dist - var/minor_dir - var/minor_dist - if(dist_x > dist_y) - error = dist_x/2 - dist_y - major_dir = dx - major_dist = dist_x - minor_dir = dy - minor_dist = dist_y - else - error = dist_y/2 - dist_x - major_dir = dy - major_dist = dist_y - minor_dir = dx - minor_dist = dist_x - - range = min(dist_x + dist_y, range) - - while(src && target && src.throwing && istype(src.loc, /turf) \ - && ((abs(target.x - src.x)+abs(target.y - src.y) > 0 && dist_travelled < range) \ - || (a && a.has_gravity == 0) \ - || istype(src.loc, /turf/space))) - // only stop when we've gone the whole distance (or max throw range) and are on a non-space tile, or hit something, or hit the end of the map, or someone picks it up - var/atom/step - if(error >= 0) - step = get_step(src, major_dir) - error -= minor_dist - else - step = get_step(src, minor_dir) - error += major_dist - if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge - break - src.Move(step) - hit_check(speed) - dist_travelled++ - dist_since_sleep++ - if(dist_since_sleep >= speed) - dist_since_sleep = 0 - sleep(1) - a = get_area(src.loc) - // and yet it moves - if(src.does_spin) - src.SpinAnimation(speed = 4, loops = 1) - - //done throwing, either because it hit something or it finished moving - if(isobj(src)) src.throw_impact(get_turf(src),speed) - src.throwing = 0 - src.thrower = null - src.throw_source = null - fall() + pixel_z = 0 + if(spin && does_spin) + SpinAnimation(4,1) + SSthrowing.processing[src] = TT + if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun)) + SSthrowing.currentrun[src] = TT //Overlays /atom/movable/overlay diff --git a/vorestation.dme b/vorestation.dme index 22205cdf13..4e6e48e932 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -302,6 +302,7 @@ #include "code\controllers\subsystems\sun.dm" #include "code\controllers\subsystems\supply.dm" #include "code\controllers\subsystems\tgui.dm" +#include "code\controllers\subsystems\throwing.dm" #include "code\controllers\subsystems\ticker.dm" #include "code\controllers\subsystems\time_track.dm" #include "code\controllers\subsystems\timer.dm"