Files
Bubberstation/code/controllers/subsystem/throwing.dm
blessedmulligan 54dc879f19 Cyborg spin throwing now depends on intent (#48891)
Trying this again. When *spin-ing as a cyborg, the way mobs are thrown off you depends on your intent. On help intent, they will be thrown a shorter distance from you and will never be damaged or stunned (they will still be knocked down). On harm intent, they will be thrown the current distance and will be damaged and stunned if they hit a wall or another person.

WHAT THIS ACTUALLY CHANGES:
Currently, whether or not *spin-ing will actually hurt/stun someone depends on whether or not the *spin-ing borg is emagged. This PR also reduces the range of the help intent throw, but since the knockdown happens whether or not the thrown mob hits something this is in most circumstances a cosmetic change. So, in summary, if you are a non-emagged borg in help intent the only thing this changes is you will chuck people a slightly shorter distance.
Why It's Good For The Game

This isn't exactly the most important change, but a borg should be able to control their behavior. Having damage/stun from throwing determined solely by emag-ness seems more like a hacky kludge than a legitimate design choice. Also, having the behavior of borg-throwing determined in the code for carbons is messy.
Changelog

🆑
add: The behavior of cyborg's *spin emote now changes with intent; on harm intent, it will throw its passenger farther and damage and stun them if they hit a wall or another person.
/🆑
2020-02-01 20:30:57 +13:00

170 lines
4.5 KiB
Plaintext

#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()
..("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/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/gentle = FALSE
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))
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.zfalling) // I don't think you can zfall while thrown but hey, just in case.
var/turf/T = get_turf(thrownthing)
if(T && thrownthing.has_gravity(T))
T.zFall(thrownthing)
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