#define MAX_THROWING_DIST 512 // 2 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 offline_implications = "Thrown objects may not react properly. Shuttle call recommended." cpu_display = SS_CPUDISPLAY_LOW var/list/currentrun var/list/processing = list() /datum/controller/subsystem/throwing/get_stat_details() return "P:[length(processing)]" /datum/controller/subsystem/throwing/get_metrics() . = ..() var/list/cust = list() cust["processing"] = length(processing) .["custom"] = cust /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(!AM || !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 ///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 /// Can a thrown mob move themselves to stop the throw? var/block_movement = TRUE /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) && has_gravity(AM, AM.loc)) hitcheck() //Just to be sure 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)) dist_travelled++ if(dist_travelled > MAX_THROWING_DIST) finalize() return /datum/thrownthing/proc/finalize(hit = FALSE, target = null) set waitfor = 0 SSthrowing.processing -= thrownthing //done throwing, either because it hit something or it finished moving 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) 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, speed) 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) /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