mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-31 12:41:46 +00:00
* refactor: Movement cross/uncross implementation.
* wrong var name
* fix unit tests dropping PDAs into nowhere
* Add documentation.
* remove unused constants
* say which procs are off limits
* fix simpleanimal z change runtime
* helps not to leave merge conflicts
* kill me
* fix typecast
* fix projectile/table collision
* treadmills don't cause MC to crash anymore
* connect_loc is appropriate here
* fix windoors and teleporters
* fix bonfires and clarify docs
* fix proximity sensors
Tested with sensors in crates, sensors in modsuits
Tested new proximity component with firing projectiles at singularity
Tested new proximity component with portable flashes
Tested new proximity component with facehuggers
* lint
* fix: polarized access helper false positives
* Revert "fix: polarized access helper false positives"
This reverts commit 9814f98cf6.
* hopefully the right change for mindflayer steam
* Changes following cameras
* fix glass table collision
* appears to fix doorspam
* fix ore bags not picking up ore
* fix signatures of /Exited
* remove debug log
* remove duplicate signal registrar
* fix emptying bags into locations
* I don't trust these nested Move calls
* use connect_loc for upgraded resonator fields
* use moveToNullspace
* fix spiderweb crossing
* fix pass checking for windows from a tile off
* fix bluespace closet/transparency issues
* fix mechs not interacting with doors and probably other things
* fix debug
* fix telepete
* add some docs
* stop trying to shoehorn prox monitor into cards
* I should make sure things build
* kill override signal warning
* undef signal
* not many prox monitors survive going off like this
* small fixes to storage
* make moving wormholes respect signals
* use correct signals for pulse demon
* fix pulse heart too
* fix smoke signals
* may have fucked singulo projectile swerve
* fix singulo projectile arcing
* remove duplicate define
* just look at it
* hopefully last cleanups of incorrect signal usage
* fix squeaking
* may god have mercy on my soul
* Apply suggestions from code review
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
* lewc review
* Apply suggestions from code review
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
* burza review
* fix bad args for grenade assemblies
* Update code/__DEFINES/is_helpers.dm
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
---------
Signed-off-by: warriorstar-orion <orion@snowfrost.garden>
Co-authored-by: DGamerL <daan.lyklema@gmail.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
477 lines
18 KiB
Plaintext
477 lines
18 KiB
Plaintext
/**
|
|
* Code to handle atoms orbiting other atoms, following them as they move.
|
|
* The basic logic is simple. We register a signal, COMSIG_MOVABLE_MOVED onto orbited atoms.
|
|
* When the orbited atom moves, any ghosts orbiting them are moved to their turf.
|
|
* We also register a MOVED signal onto the ghosts to cancel their orbit if they move themselves.
|
|
* Complexities come in for items within other items (such as the NAD in a box in a backpack on an assistant pretending to be the captain),
|
|
* as items in containers do **not** fire COMSIG_MOVABLE_MOVED when their container moves.
|
|
*
|
|
* The signal logic for items in containers is as follows:
|
|
* Assume 1 is some item (for example, the NAD) and 2 is a box.
|
|
* When 1 is added to 2, we register the typical orbit COMSIG_MOVABLE_MOVED onto 2.
|
|
* This in essence makes 2 the "leader", the atom that ghosts follow in movement.
|
|
* As 2 is moved around (say, dragged on the floor) ghosts will follow it.
|
|
* We also register a new intermediate COMSIG_MOVABLE_MOVED signal onto 1 that tracks if 1 is moved.
|
|
* Remember, this will only fire if 1 is moved around containers, since it's impossible for it to actually move on its own.
|
|
* If 1 is moved out of 2, this signal makes 1 the new leader.
|
|
* Lastly, we add a COMSIG_ATOM_EXITED to 2, which tracks if 1 is removed from 2.
|
|
* This EXITED signal cleans up any orbiting signals on and above 2.
|
|
* If 2 is added to another item, (say a backpack, 3)
|
|
* 3 becomes the new leader
|
|
* 2 becomes an intermediate
|
|
* 1 is unchanged (but still carries the orbiter datum)
|
|
*
|
|
*
|
|
* You may be asking yourself: is this overengineered?
|
|
* In part, yes. However, MOVED signals don't get fired for items in containers, so this is
|
|
* really the next best way.
|
|
* Also, is this really optimal?
|
|
* As much as it can be, I believe. This signal-shuffling will not happen for any item that is just moving from turf to turf,
|
|
* which should apply to 95% of cases (read: ghosts orbiting mobs).
|
|
*/
|
|
|
|
#define ORBIT_LOCK_IN (1<<0)
|
|
#define ORBIT_FORCE_MOVE (1<<1)
|
|
|
|
/datum/component/orbiter
|
|
can_transfer = TRUE
|
|
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
|
/// List of observers orbiting the parent
|
|
var/list/orbiter_list
|
|
/// Cached transforms from before the orbiter started orbiting, to be restored on stopping their orbit
|
|
var/list/orbit_data
|
|
|
|
/// See atom/movable/proc/orbit for parameter definitions
|
|
/datum/component/orbiter/Initialize(atom/movable/orbiter, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lock_in_orbit = FALSE, force_move = FALSE, orbit_layer = FLY_LAYER)
|
|
if(!istype(orbiter) || !isatom(parent) || isarea(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
orbiter_list = list()
|
|
orbit_data = list()
|
|
|
|
begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
|
|
|
|
/datum/component/orbiter/RegisterWithParent()
|
|
var/atom/target = parent
|
|
register_signals(target)
|
|
|
|
/datum/component/orbiter/UnregisterFromParent()
|
|
var/atom/target = parent
|
|
remove_signals(target)
|
|
|
|
/datum/component/orbiter/Destroy()
|
|
remove_signals(parent)
|
|
for(var/i in orbiter_list)
|
|
end_orbit(i)
|
|
orbiter_list = null
|
|
orbit_data = null
|
|
return ..()
|
|
|
|
/// See atom/movable/proc/orbit for parameter definitions
|
|
/datum/component/orbiter/InheritComponent(datum/component/orbiter/new_comp, original, atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
|
|
// No transfer happening
|
|
if(!new_comp)
|
|
begin_orbit(arglist(args.Copy(3)))
|
|
return
|
|
|
|
orbiter_list += new_comp.orbiter_list
|
|
orbit_data += new_comp.orbit_data
|
|
new_comp.orbiter_list = list()
|
|
new_comp.orbit_data = list()
|
|
QDEL_NULL(new_comp)
|
|
|
|
/datum/component/orbiter/PostTransfer()
|
|
if(!isatom(parent) || isarea(parent) || !get_turf(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
/// See atom/movable/proc/orbit for parameter definitions
|
|
/datum/component/orbiter/proc/begin_orbit(atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
|
|
if(!istype(orbiter))
|
|
return
|
|
|
|
var/was_refreshing = FALSE
|
|
var/atom/currently_orbiting = locateUID(orbiter.orbiting_uid)
|
|
|
|
if(currently_orbiting)
|
|
if(orbiter in orbiter_list)
|
|
was_refreshing = TRUE
|
|
end_orbit(orbiter, TRUE)
|
|
else
|
|
var/datum/component/orbiter/orbit_comp = currently_orbiting.GetComponent(/datum/component/orbiter)
|
|
orbit_comp.end_orbit(orbiter)
|
|
|
|
orbiter_list += orbiter
|
|
|
|
// make sure orbits get cleaned up nicely if the parent qdels
|
|
RegisterSignal(orbiter, COMSIG_PARENT_QDELETING, PROC_REF(end_orbit))
|
|
|
|
var/orbit_flags = 0
|
|
if(lock_in_orbit)
|
|
orbit_flags |= ORBIT_LOCK_IN
|
|
if(force_move)
|
|
orbit_flags |= ORBIT_FORCE_MOVE
|
|
|
|
orbiter.orbiting_uid = parent.UID()
|
|
store_orbit_data(orbiter, orbit_flags)
|
|
|
|
if(!lock_in_orbit)
|
|
RegisterSignal(orbiter, COMSIG_MOVABLE_MOVED, PROC_REF(orbiter_move_react))
|
|
|
|
// Head first!
|
|
if(pre_rotation)
|
|
var/matrix/M = matrix(orbiter.transform)
|
|
var/pre_rot = 90
|
|
if(!clockwise)
|
|
pre_rot = -90
|
|
M.Turn(pre_rot)
|
|
orbiter.transform = M
|
|
|
|
var/matrix/shift = matrix(orbiter.transform)
|
|
shift.Translate(0,radius)
|
|
orbiter.transform = shift
|
|
|
|
orbiter.layer = orbit_layer
|
|
|
|
SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_BEGIN, orbiter)
|
|
|
|
// If we changed orbits, we didn't stop our rotation, and don't need to start it up again
|
|
if(!was_refreshing)
|
|
orbiter.SpinAnimation(rotation_speed, -1, clockwise, rotation_segments, parallel = FALSE)
|
|
|
|
var/target_loc = get_turf(parent)
|
|
var/current_loc = orbiter.loc
|
|
if(force_move)
|
|
orbiter.forceMove(target_loc)
|
|
else
|
|
orbiter.loc = target_loc
|
|
// Setting loc directly doesn't fire COMSIG_MOVABLE_MOVED, so we need to do it ourselves
|
|
SEND_SIGNAL(orbiter, COMSIG_MOVABLE_MOVED, current_loc, target_loc, null)
|
|
orbiter.animate_movement = SYNC_STEPS
|
|
|
|
/**
|
|
* End the orbit and clean up our transformation.
|
|
* If this removes the last atom orbiting us, then qdel ourselves.
|
|
* Howver, if refreshing == TRUE, src will not be qdeleted if this leaves us with 0 orbiters.
|
|
*/
|
|
/datum/component/orbiter/proc/end_orbit(atom/movable/orbiter, refreshing = FALSE)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!(orbiter in orbiter_list))
|
|
return
|
|
|
|
if(orbiter)
|
|
orbiter.animate_movement = SLIDE_STEPS
|
|
if(!QDELETED(parent))
|
|
SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_STOP, orbiter)
|
|
SEND_SIGNAL(orbiter, COMSIG_ATOM_ORBITER_STOP, parent)
|
|
|
|
orbiter.transform = get_cached_transform(orbiter)
|
|
orbiter.layer = get_orbiter_layer(orbiter)
|
|
|
|
UnregisterSignal(orbiter, COMSIG_MOVABLE_MOVED)
|
|
UnregisterSignal(orbiter, COMSIG_PARENT_QDELETING)
|
|
|
|
orbiter.orbiting_uid = null
|
|
|
|
if(!refreshing)
|
|
orbiter.SpinAnimation(0, 0, parallel = FALSE)
|
|
|
|
// If it's null, still remove it from the list
|
|
orbiter_list -= orbiter
|
|
orbit_data -= orbiter
|
|
|
|
if(!length(orbiter_list) && !QDELING(src) && !refreshing)
|
|
qdel(src)
|
|
|
|
/**
|
|
* The actual implementation function of the move react.
|
|
* **if you're trying to call this from a signal, call parent_move_react instead.**
|
|
* This implementation is separate so the orbited atom's old location and new location can be passed in separately.
|
|
*/
|
|
/datum/component/orbiter/proc/handle_parent_move(atom/movable/orbited, atom/old_loc, atom/new_loc, direction)
|
|
|
|
if(new_loc == old_loc)
|
|
return
|
|
|
|
var/turf/new_turf = get_turf(new_loc)
|
|
if(!new_turf)
|
|
// don't follow someone to nullspace
|
|
qdel(src)
|
|
|
|
var/atom/cur_loc = new_loc
|
|
|
|
// If something's only moving between turfs, don't bother changing signals, just move ghosts.
|
|
// Honestly, that should apply to 95% of orbiting cases, which should be a nice optimization.
|
|
if(!(isturf(old_loc) && isturf(new_loc)))
|
|
|
|
// Clear any signals that may still exist upstream of where this object used to be
|
|
remove_signals(old_loc)
|
|
// ...and create a new signal hierarchy upstream of where the object is now.
|
|
// cur_loc is the current "leader" atom
|
|
cur_loc = register_signals(orbited)
|
|
|
|
var/orbit_params
|
|
var/orbiter_turf
|
|
new_turf = get_turf(cur_loc)
|
|
for(var/atom/movable/movable_orbiter in orbiter_list)
|
|
orbiter_turf = get_turf(movable_orbiter)
|
|
if(QDELETED(movable_orbiter) || orbiter_turf == new_turf)
|
|
continue
|
|
|
|
orbit_params = get_orbit_params(movable_orbiter)
|
|
|
|
if(orbit_params & ORBIT_FORCE_MOVE)
|
|
movable_orbiter.forceMove(new_turf)
|
|
else
|
|
var/orbiter_loc = movable_orbiter.loc
|
|
movable_orbiter.loc = new_turf
|
|
SEND_SIGNAL(movable_orbiter, COMSIG_MOVABLE_MOVED, orbiter_loc, new_turf, null)
|
|
|
|
if(CHECK_TICK && new_turf != get_turf(movable_orbiter))
|
|
// We moved again during the checktick, cancel current operation
|
|
break
|
|
/**
|
|
* Signal handler for COMSIG_MOVABLE_MOVED. Special wrapper to handle the arguments that come from the signal.
|
|
* If you want to call this directly, just call handle_parent_move.
|
|
*/
|
|
/datum/component/orbiter/proc/parent_move_react(atom/movable/orbited, atom/old_loc, direction)
|
|
set waitfor = FALSE // Transfer calls this directly and it doesnt care if the ghosts arent done moving
|
|
handle_parent_move(orbited, old_loc, orbited.loc, direction)
|
|
|
|
/**
|
|
* Called when the orbiter themselves moves.
|
|
*/
|
|
/datum/component/orbiter/proc/orbiter_move_react(atom/movable/orbiter, atom/oldloc, direction)
|
|
SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
|
|
|
|
if(get_turf(orbiter) == get_turf(parent) || get_turf(orbiter) == get_turf(oldloc) || get_turf(orbiter) == oldloc || orbiter.loc == oldloc)
|
|
return
|
|
|
|
if(orbiter in orbiter_list)
|
|
// Only end the spin animation when we're actually ending an orbit, not just changing targets
|
|
orbiter.SpinAnimation(0, 0, parallel = FALSE)
|
|
end_orbit(orbiter)
|
|
|
|
/**
|
|
* Remove all orbit-related signals in the object hierarchy above start.
|
|
*/
|
|
/datum/component/orbiter/proc/remove_signals(atom/start)
|
|
var/atom/cur_atom = start
|
|
while(cur_atom && !isturf(cur_atom) && !(cur_atom in orbiter_list) && cur_atom != parent)
|
|
UnregisterSignal(cur_atom, COMSIG_ATOM_EXITED)
|
|
UnregisterSignal(cur_atom, COMSIG_MOVABLE_MOVED)
|
|
cur_atom = cur_atom.loc
|
|
|
|
/**
|
|
* Register signals up the hierarchy, adding them to each parent (and their parent, and so on) up to the turf they're on.
|
|
* The last atom in the hierarchy (the one whose .loc is the turf) becomes the leader, the atom ghosts will follow.
|
|
* Registers on_intermediate_move for every non-leader atom so that if they move (removing them from the hierarchy), they will be set as the new leader.
|
|
* Also registers on_remove_child to remove signals up the hierarchy when a child gets removed.
|
|
* start: the first atom to register signals on. If start isn't inside of anything (or if its .loc is a turf), then it will become the leader.
|
|
* Returns the new "leader", the atom that ghosts will follow.
|
|
*/
|
|
/datum/component/orbiter/proc/register_signals(atom/start)
|
|
if(isturf(start))
|
|
return
|
|
var/atom/cur_atom = start
|
|
while(cur_atom.loc && !isturf(cur_atom.loc) && !(cur_atom.loc in orbiter_list))
|
|
RegisterSignal(cur_atom, COMSIG_MOVABLE_MOVED, PROC_REF(on_intermediate_move), TRUE)
|
|
RegisterSignal(cur_atom, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
|
|
cur_atom = cur_atom.loc
|
|
|
|
// Set the topmost atom (right before the turf) to be our new leader
|
|
RegisterSignal(cur_atom, COMSIG_MOVABLE_MOVED, PROC_REF(parent_move_react), TRUE)
|
|
RegisterSignal(cur_atom, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
|
|
return cur_atom
|
|
|
|
/**
|
|
* Callback fired when an item is removed from a tracked atom.
|
|
* Removes all orbit-related signals up its hierarchy and moves orbiters to the current child.
|
|
* As this will never be called by a turf, this should not conflict with parent_move_react.
|
|
*/
|
|
/datum/component/orbiter/proc/on_remove_child(datum/source, atom/movable/exiting, direction)
|
|
SIGNAL_HANDLER // COMSIG_ATOM_EXITED
|
|
|
|
// ensure the child is actually connected to the orbited atom
|
|
if(!is_in_hierarchy(exiting) || (exiting in orbiter_list))
|
|
return
|
|
// Remove all signals upwards of the child and re-register them as the new parent
|
|
remove_signals(exiting)
|
|
RegisterSignal(exiting, COMSIG_MOVABLE_MOVED, PROC_REF(parent_move_react), TRUE)
|
|
RegisterSignal(exiting, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
|
|
var/new_loc = get_step(exiting, direction)
|
|
INVOKE_ASYNC(src, PROC_REF(handle_parent_move), exiting, exiting.loc, new_loc)
|
|
|
|
/**
|
|
* Called when an intermediate (somewhere between the topmost and the orbited) atom moves.
|
|
* This atom will now become the leader.
|
|
*/
|
|
/datum/component/orbiter/proc/on_intermediate_move(atom/movable/tracked, atom/old_loc)
|
|
SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
|
|
|
|
// Make sure we don't trigger off an orbiter following!
|
|
if(!is_in_hierarchy(tracked) || (tracked in orbiter_list))
|
|
return
|
|
|
|
remove_signals(old_loc) // TODO this doesn't work if something's removed from hand
|
|
RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(parent_move_react), TRUE)
|
|
RegisterSignal(tracked, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
|
|
INVOKE_ASYNC(src, PROC_REF(handle_parent_move), tracked, old_loc, tracked.loc)
|
|
|
|
/**
|
|
* Returns TRUE if atom_to_find is transitively a parent of src.
|
|
*/
|
|
/datum/component/orbiter/proc/is_in_hierarchy(atom/movable/atom_to_find)
|
|
var/atom/check = parent
|
|
while(check)
|
|
if(check == atom_to_find)
|
|
return TRUE
|
|
check = check.loc
|
|
return FALSE
|
|
|
|
///////////////////////////////////
|
|
/// orbit data helper functions
|
|
///////////////////////////////////
|
|
|
|
/**
|
|
* Store a collection of data for an orbiter.
|
|
* orbiter: orbiter atom itself. The orbiter's transform and layer at this point will be captured and cached.
|
|
* orbit_flags: bitfield consisting of flags describing the orbit.
|
|
*/
|
|
/datum/component/orbiter/proc/store_orbit_data(atom/movable/orbiter, orbit_flags)
|
|
var/list/new_orbit_data = list(
|
|
orbiter.transform, // cached transform
|
|
orbit_flags, // params about the orbit
|
|
orbiter.layer // cached layer from the orbiter
|
|
)
|
|
orbit_data[orbiter] = new_orbit_data
|
|
return new_orbit_data
|
|
|
|
/**
|
|
* Get cached transform of the given orbiter.
|
|
*/
|
|
/datum/component/orbiter/proc/get_cached_transform(atom/movable/orbiter)
|
|
if(orbiter)
|
|
var/list/orbit_params = orbit_data[orbiter]
|
|
if(orbit_params)
|
|
return orbit_params[1]
|
|
|
|
/**
|
|
* Get the given orbiter's orbit parameters bitfield
|
|
*/
|
|
/datum/component/orbiter/proc/get_orbit_params(atom/movable/orbiter)
|
|
if(orbiter)
|
|
var/list/orbit_params = orbit_data[orbiter]
|
|
if(orbit_params)
|
|
return orbit_params[2]
|
|
|
|
/**
|
|
* Get the layer the given orbiter was on before they started orbiting
|
|
*/
|
|
/datum/component/orbiter/proc/get_orbiter_layer(atom/movable/orbiter)
|
|
if(orbiter)
|
|
var/list/orbit_params = orbit_data[orbiter]
|
|
if(orbit_params)
|
|
return orbit_params[3]
|
|
|
|
///////////////////////////////////
|
|
// Atom procs/vars
|
|
///////////////////////////////////
|
|
|
|
/**
|
|
* Set an atom to orbit around another one. This atom will follow the base atom's movement and rotate around it.
|
|
*
|
|
* orbiter: atom which will be doing the orbiting
|
|
* radius: range to orbit at, radius of the circle formed by orbiting
|
|
* clockwise: whether you orbit clockwise or anti clockwise
|
|
* rotation_speed: how fast to rotate
|
|
* rotation_segments: the resolution of the orbit circle, less = a more block circle, this can be used to produce hexagons (6 segments) triangles (3 segments), and so on, 36 is the best default.
|
|
* pre_rotation: Chooses to rotate src 90 degress towards the orbit dir (clockwise/anticlockwise), useful for things to go "head first" like ghosts
|
|
* lock_in_orbit: Forces src to always be on A's turf, otherwise the orbit cancels when src gets too far away (eg: ghosts)
|
|
* force_move: If true, ghosts will be ForceMoved instead of having their .loc updated directly.
|
|
* orbit_layer: layer that the orbiter should be on. The original layer will be restored on orbit end.
|
|
*/
|
|
/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lock_in_orbit = FALSE, force_move = FALSE, orbit_layer = FLY_LAYER)
|
|
if(!istype(A) || !get_turf(A) || A == src)
|
|
return
|
|
// Adding a new component every time works as our dupe type will make us just inherit the new orbiter
|
|
return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lock_in_orbit, force_move, orbit_layer)
|
|
|
|
/**
|
|
* Stop this atom from orbiting whatever it's orbiting.
|
|
*/
|
|
/atom/movable/proc/stop_orbit()
|
|
var/atom/orbited = locateUID(orbiting_uid)
|
|
if(!orbited)
|
|
return
|
|
var/datum/component/orbiter/C = orbited.GetComponent(/datum/component/orbiter)
|
|
if(!C)
|
|
return
|
|
C.end_orbit(src)
|
|
|
|
/**
|
|
* Simple helper proc to get a list of everything directly orbiting the current atom, without checking contents, or null if nothing is.
|
|
*/
|
|
/atom/proc/get_orbiters()
|
|
var/datum/component/orbiter/C = GetComponent(/datum/component/orbiter)
|
|
if(C && C.orbiter_list)
|
|
return C.orbiter_list
|
|
else
|
|
return null
|
|
|
|
/**
|
|
* Remove an orbiter from the atom it's orbiting.
|
|
*/
|
|
/atom/proc/remove_orbiter(atom/movable/orbiter)
|
|
var/datum/component/orbiter/C = GetComponent(/datum/component/orbiter)
|
|
if(C && C.orbiter_list && (orbiter in C.orbiter_list))
|
|
C.end_orbit(orbiter)
|
|
|
|
/**
|
|
* Recursive getter method to return a list of all ghosts transitively orbiting this atom.
|
|
* This will find orbiters either directly orbiting the followed atom, or any orbiters orbiting them (and so on).
|
|
*
|
|
* This shouldn't be passed arugments.
|
|
*/
|
|
/atom/proc/get_orbiters_recursive(list/processed, source = TRUE)
|
|
var/list/output = list()
|
|
if(!processed)
|
|
processed = list()
|
|
if(src in processed)
|
|
return output
|
|
|
|
processed += src
|
|
// Make sure we don't shadow outer orbiters
|
|
for(var/atom/movable/atom_orbiter in get_orbiters())
|
|
if(isobserver(atom_orbiter))
|
|
output |= atom_orbiter
|
|
output += atom_orbiter.get_orbiters_recursive(processed, source = FALSE)
|
|
return output
|
|
|
|
/**
|
|
* Check every object in the hierarchy above ourselves for orbiters, and return the full list of them.
|
|
* If an object is being held in a backpack, returns orbiters of the backpack, the person
|
|
* If recursive == TRUE, this will also check recursively through any ghosts seen to make sure we find *everything* upstream
|
|
*/
|
|
/atom/proc/get_orbiters_up_hierarchy(list/processed, source = TRUE, recursive = FALSE)
|
|
var/list/output = list()
|
|
if(!processed)
|
|
processed = list()
|
|
if((src in processed) || isturf(src))
|
|
return output
|
|
|
|
processed += src
|
|
for(var/atom/movable/atom_orbiter in get_orbiters())
|
|
if(isobserver(atom_orbiter))
|
|
if(recursive)
|
|
output += atom_orbiter.get_orbiters_recursive()
|
|
output += atom_orbiter
|
|
|
|
if(loc)
|
|
output += loc.get_orbiters_up_hierarchy(processed, source = FALSE)
|
|
|
|
return output
|
|
|
|
#undef ORBIT_LOCK_IN
|
|
#undef ORBIT_FORCE_MOVE
|