mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-31 12:41:46 +00:00
* Improved efficiency of playsound. * Improve performance of turf/Enter * Improve performace of thrown objects. * Improved performance of /atom/movable/CanPass. * Improved pipeline destruction performance. * Apply suggestions from code review Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com> Signed-off-by: Charlie Nolan <funnyman3595@gmail.com> --------- Signed-off-by: Charlie Nolan <funnyman3595@gmail.com> Co-authored-by: FunnyMan3595 (Charlie Nolan) <funnyman@google.com> Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
273 lines
10 KiB
Plaintext
273 lines
10 KiB
Plaintext
#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()
|
|
|
|
/// How many throw impact sounds have happened this tick.
|
|
var/impact_sounds = 0
|
|
/// How many throw impact sounds we allow per tick.
|
|
var/impact_sounds_cap = 20
|
|
/// How many sounds we've skipped due to hitting the per-tick cap.
|
|
var/skipped_sounds = 0
|
|
/// How many sounds there were last tick.
|
|
var/last_impact_sounds = 0
|
|
|
|
/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()
|
|
last_impact_sounds = impact_sounds
|
|
impact_sounds = 0
|
|
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
|
|
while(length(currentrun))
|
|
var/atom/movable/AM = currentrun[length(currentrun)]
|
|
var/datum/thrownthing/thrown_thing = currentrun[AM]
|
|
currentrun.len--
|
|
if(QDELETED(AM) || QDELETED(thrown_thing))
|
|
processing -= AM
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
continue
|
|
|
|
thrown_thing.tick()
|
|
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
currentrun = null
|
|
|
|
/datum/controller/subsystem/throwing/proc/playsound_capped(atom/source, soundin, vol, vary, extrarange, falloff_exponent = SOUND_FALLOFF_EXPONENT, frequency, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE, falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, use_reverb = TRUE)
|
|
if(impact_sounds < impact_sounds_cap)
|
|
impact_sounds++
|
|
playsound(source, soundin, vol, vary, extrarange, falloff_exponent, frequency, channel, pressure_affected, ignore_walls, falloff_distance, use_reverb)
|
|
else
|
|
skipped_sounds++
|
|
|
|
/datum/thrownthing
|
|
///Thrown atom this datum is attached to
|
|
var/atom/movable/thrownthing
|
|
///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
|
|
///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
|
|
///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
|
|
/// Can a thrown mob move themselves to stop the throw?
|
|
var/should_block_movement = TRUE
|
|
/// 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)
|
|
finalize()
|
|
return
|
|
|
|
if(paused)
|
|
delayed_time += world.time - last_move
|
|
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
|
|
|
|
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)
|
|
var/gravity
|
|
if(ismob(AM))
|
|
var/mob/mob = AM
|
|
gravity = mob.mob_has_gravity(mob.loc)
|
|
else
|
|
gravity = has_gravity(AM, AM.loc)
|
|
|
|
if((dist_travelled >= maxrange || AM.loc == target_turf) && gravity)
|
|
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
|
|
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
|
|
|
|
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 = FALSE
|
|
//done throwing, either because it hit something or it finished moving
|
|
if(!thrownthing)
|
|
return
|
|
thrownthing.throwing = null
|
|
if(!hit)
|
|
if(get_turf(target) == get_turf(thrownthing))
|
|
hit = TRUE
|
|
thrownthing.throw_impact(target, src)
|
|
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)
|
|
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()
|
|
|
|
if(thrownthing)
|
|
SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src)
|
|
thrownthing.end_throw()
|
|
|
|
qdel(src)
|
|
|
|
#undef MAX_THROWING_DIST
|
|
#undef MAX_TICKS_TO_MAKE_UP
|