#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" priority = FIRE_PRIORITY_THROWING wait = 1 flags = SS_NO_INIT|SS_KEEP_TIMING|SS_TICKER runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME var/list/currentrun var/list/processing = list() /datum/controller/subsystem/throwing/stat_entry(msg) msg = "P:[length(processing)]" return ..() /datum/controller/subsystem/throwing/get_metrics() . = ..() .["queued"] = length(processing) /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/init_dir var/maxrange var/speed var/mob/thrower 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 var/pure_diagonal var/diagonal_error var/datum/callback/callback var/paused = FALSE var/delayed_time = 0 var/last_move = 0 /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()) //to catch sneaky things moving on our tile while we slept finalize() return 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) && AM.has_gravity(AM.loc)) 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 && !diagonals_first) // 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 AM.Move(step, get_dir(AM, step), DELAY_TO_GLIDE_SIZE(1 / speed)) if (!AM.throwing) // we hit something during our move finalize(hit = TRUE) return dist_travelled++ if (dist_travelled > MAX_THROWING_DIST) finalize() return /datum/thrownthing/proc/finalize(hit = FALSE, target=null) 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 = 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. thrownthing.newtonian_move(init_dir) else thrownthing.newtonian_move(init_dir) if(target) thrownthing.throw_impact(target, src) if (callback) callback.Invoke() if(thrownthing) SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src) qdel(src) /datum/thrownthing/proc/hit_atom(atom/A) finalize(hit=TRUE, target=A) /datum/thrownthing/proc/hitcheck() for (var/thing in get_turf(thrownthing)) 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)) finalize(hit=TRUE, target=AM) return TRUE