mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-19 22:23:11 +00:00
## About The Pull Request This PR significantly enhances how zero-g movement works. Its no longer locked to one of 8 directions, everything now has inertia and is affected by weight. This means that throwing a piece of wire will no longer completely reverse your movement direction, and that being thrown out of mass driver no longer will slow you down to a halt at some point. This leads to following gameplay changes: * Guns now accelerate you. Ballistics have higher acceleration than lasers, and higher calibers have higher acceleration than smaller ones. This means that firing full-auto weapons in zero-g will make you drift and accelerate significantly. While this can be a hilarious way to travel in space, it makes using them trickier. * Impacting a wall or an object while moving at high speeds will cause you to violently crash into it as if you were thrown. Careful when exploring! * Jetpacks now have inertia. Changes introduced in #84712 have been mostly reverted, although speed buff has been reduced to 0.3 instead of 0.5 (although this is compensated by new movement mechanics, so overall speed should be roughly equal). All MODsuit jetpacks now possess the speed boost. Advanced MODsuit jets (which has also been added back) and captain's jetpack instead have higher acceleration and stabilization power, providing much more precise control over your movement. * Firing guns while moving on a jetpack will partially negate your pack's acceleration, slowing you down. Non-advanced jetpacks' stabilization is not enough to compensate for heavy caliber weaponry as sniper rifles, shotguns or rocket launchers. * You no longer instantly decelerate upon sliding along a wall. Instead, it may take a few tiles if you are moving at extreme speeds. Passing over lattices still allows you to grab onto them! As space movement is angle-based instead of dir-based now, its much more smooth than before due to using new movement logic. Example of jetpack stabilization in action: https://github.com/tgstation/tgstation/assets/44720187/6761a4fd-b7de-4523-97ea-38144b8aab41 And, of course, you can do this now.  **This pull request requires extensive gameplay testing before merging**, as a large amount of numbers have been picked arbitrarily in an attempt to keep consistency with previous behavior (guns and normal-sized items applying 1 drift force, which is equal to what everything applied before this PR). Jetpacks and impacts may also require adjustments as to not be frustrating to use. Closes #85165 ## Why It's Good For The Game Zero-G refactor - currently our zero-g movement is rather ugly and can be uncomfortable to work with. A piece of cable being able to accelerate you the same as a duffelbag full of items when thrown makes no sense, and so does instantly changing directions. Inertia-based version is smoother and more intuitive. This also makes being thrown into space more of a hazard (possibly opening the door for explosive decompressions?) Jetpack inertia and gun changes - this is mostly a consequence of inertia-based movement. However, zero-g combat being preferred during modes like warops was an issue due to it negatively affecting everyone without jetpacks which are in limited supply onboard. This reverts the mobility changes which severely impacted space exploration, while making zero-g combat more dangerous and having it require more skill to be a viable option. ## What's left - [x] Refactor moth wings to use jetpack code - [x] Refactor functional wings to use jetpack code - [x] Locate and fix a recursion runtime that sometimes occurs upon splattering against a wall - [x] Add craftable tethers and modify engineering MOD tethers to use the same system ## Changelog 🆑 add: You can now craft tether anchors, which can be secured with a wrench and attached to with right click. They won't let you drift into space and you can adjust tether length/cut it via lmb/rmb/ctrl click on the wire. add: MOD tethers now remotely place and connect to tether anchors instead of throwing you at where they landed. balance: MOD tethers can now be used in gravity balance: Jetpacks are now inertia-based. balance: Guns can accelerate you significantly in zero-g. balance: All jetpacks now give you equal speed buff, however advanced MOD ion jets and captain's jetpack have higher acceleration/deceleration values. refactor: Refactored zero-g movement to be inertia-based and utilize angles instead of directions. /🆑
248 lines
8.9 KiB
Plaintext
248 lines
8.9 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(msg)
|
|
msg = "P:[length(processing)]"
|
|
return ..()
|
|
|
|
|
|
/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
|
|
///Defines the atom that has been thrown (Objects and Mobs, mostly.)
|
|
var/atom/movable/thrownthing
|
|
///Weakref to the original intended target of the throw, to prevent hardDels
|
|
var/datum/weakref/initial_target
|
|
///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 its target.
|
|
var/maxrange
|
|
///Turfs to travel per tick
|
|
var/speed
|
|
///If a mob is the one who has thrown the object, then it's moved here. This can be null and must be null checked before trying to use it.
|
|
var/datum/weakref/thrower
|
|
///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
|
|
///If the throw is gentle, then the thrownthing is harmless on impact.
|
|
var/gentle = FALSE
|
|
///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
|
|
|
|
/datum/thrownthing/New(thrownthing, target, init_dir, maxrange, speed, thrower, diagonals_first, force, gentle, callback, target_zone)
|
|
. = ..()
|
|
src.thrownthing = thrownthing
|
|
RegisterSignal(thrownthing, COMSIG_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 = WEAKREF(target)
|
|
src.init_dir = init_dir
|
|
src.maxrange = maxrange
|
|
src.speed = speed
|
|
if(thrower)
|
|
src.thrower = WEAKREF(thrower)
|
|
src.diagonals_first = diagonals_first
|
|
src.force = force
|
|
src.gentle = gentle
|
|
src.callback = callback
|
|
src.target_zone = target_zone
|
|
|
|
/datum/thrownthing/Destroy()
|
|
SSthrowing.processing -= thrownthing
|
|
SSthrowing.currentrun -= thrownthing
|
|
thrownthing.throwing = null
|
|
thrownthing = null
|
|
thrower = null
|
|
initial_target = 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
|
|
|
|
qdel(src)
|
|
|
|
/// Returns the mob thrower, or null
|
|
/datum/thrownthing/proc/get_thrower()
|
|
. = thrower?.resolve()
|
|
if(isnull(.))
|
|
thrower = 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 = initial_target?.resolve()
|
|
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_1 & ON_BORDER_1) && !(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)
|
|
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
|
|
|
|
if(!AM.Move(step, get_dir(AM, step), DELAY_TO_GLIDE_SIZE(1 / speed))) // 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
|
|
var/drift_force = speed
|
|
if (isitem(thrownthing))
|
|
var/obj/item/thrownitem = thrownthing
|
|
drift_force *= WEIGHT_TO_NEWTONS(thrownitem.w_class)
|
|
|
|
if (!hit)
|
|
for (var/atom/movable/obstacle as anything in get_turf(thrownthing)) //looking for our target on the turf we land on.
|
|
if (obstacle == target)
|
|
hit = TRUE
|
|
thrownthing.throw_impact(obstacle, src)
|
|
if(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
|
|
return //deletion should already be handled by on_thrownthing_qdel()
|
|
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(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
|
|
return //deletion should already be handled by on_thrownthing_qdel()
|
|
thrownthing.newtonian_move(delta_to_angle(dist_x, dist_y), drift_force = drift_force)
|
|
else
|
|
thrownthing.newtonian_move(delta_to_angle(dist_x, dist_y), drift_force = drift_force)
|
|
|
|
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.currently_z_moving) // I don't think you can zfall while thrown but hey, just in case.
|
|
var/turf/T = get_turf(thrownthing)
|
|
T?.zFall(thrownthing)
|
|
|
|
if(thrownthing)
|
|
SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src)
|
|
var/turf/landed_turf = get_turf(thrownthing)
|
|
if(landed_turf)
|
|
SEND_SIGNAL(landed_turf, COMSIG_TURF_MOVABLE_THROW_LANDED, thrownthing)
|
|
|
|
qdel(src)
|
|
|
|
#undef MAX_THROWING_DIST
|
|
#undef MAX_TICKS_TO_MAKE_UP
|