mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-28 13:56:30 +01:00
275 lines
8.6 KiB
Plaintext
275 lines
8.6 KiB
Plaintext
//! Reachability System
|
|
/**
|
|
* Checks if we can reach something.
|
|
*
|
|
* How this works:
|
|
* - we check our direct access
|
|
* - we "reach outwards" as much as we can. If we reach the turf, cool! if not, those atoms are added
|
|
* to direct access list. this sounds slow, but isn't bad worst case unless someone's somehow nested
|
|
* in a thousand atoms or a proc breaks.
|
|
* - max depth is a thing; it applies to both reaching out, and reaching in.
|
|
* it however applies separately, e.g. you can reach out of a backpack inside a locker
|
|
* (you can't in game because those objects set their procs but for the sake of argument pretend you can)
|
|
* you can still reach a pill bottle inside a box inside a backpack as opposed to just on the turf
|
|
* or in the backpack, assuming max_depth is set to 3
|
|
* - range: when we reach the turf, we'll do our best-estimate byond step_towards path-cast
|
|
* to try to get TurfAdjacency to the user
|
|
*
|
|
* time complexity analysis of
|
|
* "person tries to reach into a 3 nested deep item in
|
|
* a pill bottle in a box in a backpack from standing"
|
|
*
|
|
* - directaccesscache cost
|
|
* - first check fails
|
|
* - first for loop runs; we're on turf, so tadj gets set and we return
|
|
* - isarea check runs and fails
|
|
* - check cache list init, same with other vars
|
|
* - for loop runs 3 times, hits turf, doesn't add turf area
|
|
* - TurfAdjacency is checked since it isn't a ranged attack
|
|
*
|
|
* Caveats:
|
|
* * This is not enough for 'can we physically reach'; that should be Adjacency. This is because telekinesis is a thing, and will be added later.
|
|
* * For this reason, the cost is higher than just checking 'can telekinesis', because telekinesis is checked after physical, as physical is stronger.
|
|
*
|
|
* @params
|
|
* - target - the target
|
|
* - depth - max depth - should be at least 1 in most cases.
|
|
* - range - max range
|
|
* - tool - the item we're using to reach; not important
|
|
*/
|
|
/atom/movable/proc/Reachability(atom/target, depth = DEFAULT_REACHABILITY_DEPTH, range = 1, obj/item/tool)
|
|
if(!target)
|
|
// apologies sir, you may not grasp the void...
|
|
return REACH_FAILED
|
|
// direct cache - check if we can access something using if dc[atom]
|
|
var/list/dc = DirectAccessCache()
|
|
// optimization - a lot of the time we're clicking on ourselves/things on ourselves
|
|
if(dc[target])
|
|
return REACH_PHYSICAL
|
|
// turf adjacency enabled? stores if we can try to path to our turf
|
|
var/turf/tadj
|
|
// loc checking
|
|
var/atom/l = loc
|
|
// reach out from where we are as far as we can bound by depth
|
|
for(var/i in 1 to depth)
|
|
if(!l)
|
|
break
|
|
if(isturf(l))
|
|
tadj = l
|
|
break
|
|
if(!l.CanReachOut(src, target, tool, dc))
|
|
break
|
|
l = l.loc
|
|
// if target isn't anywhere (this should be at the top but maybe someone
|
|
// snowflakes a clickable object in directaccess but without loc someday)
|
|
// we can only access through DC
|
|
if(!target.loc)
|
|
return !!dc[target]
|
|
// special checks
|
|
if(isarea(target))
|
|
// area checks don't support range, because
|
|
// 1. why are you trying to reach an are awith this proc
|
|
// 2. it would be expensive as shit and require a snowflake check
|
|
// that i have no intention of writing right now
|
|
return tadj && (l.loc == target)
|
|
else if(isturf(target) && (range <= 1))
|
|
// adjacent turf checks are just an adjacency check
|
|
return tadj?.TurfAdjacency(target)
|
|
|
|
// now that cache is assembled and turf is set, go to main loop
|
|
|
|
// we don't cut or make lists because heehoo byond speed ecks dee
|
|
// we just advance
|
|
// this has the advantage of sped up protection against infinite recursion
|
|
// in the old system we used a closed_cache[thing] = true to prevent
|
|
// infinite loops, now it's built in, and iteration is just as fast!
|
|
|
|
// current index in check cache
|
|
var/i = 1
|
|
// did we reach turf? turf heuristic - usually the first turf we found
|
|
var/turf/th
|
|
if(isturf(target))
|
|
th = target
|
|
else
|
|
// check cache
|
|
var/list/cc = list(target.loc = TRUE)
|
|
// current length
|
|
var/cl = 1
|
|
// reach *upwards* from the target
|
|
for(var/d in 1 to depth)
|
|
cl = length(cc)
|
|
if(i > cl)
|
|
// hit top, didn't find, break
|
|
break
|
|
for(i in i to cl)
|
|
l = cc[i]
|
|
// process the rest of checking
|
|
if(!l.CanReachIn(src, target, tool, cc))
|
|
// couldn't reach in, l is irrelevant
|
|
continue
|
|
if(dc[l])
|
|
// found
|
|
return REACH_PHYSICAL
|
|
if(isturf(l) && !th)
|
|
// is turf; turf adjacency enabled
|
|
th = l
|
|
if(isarea(l.loc))
|
|
// don't recurse into areas
|
|
continue
|
|
cc[l.loc] = TRUE
|
|
// don't overlap
|
|
++i
|
|
if(!(tadj && th))
|
|
// didn't hit both, fail
|
|
return REACH_FAILED
|
|
// at this point, we're on a turf
|
|
if(range == 1)
|
|
// most common case: reach directly aronud yourself
|
|
return tadj.TurfAdjacency(th, target, src)
|
|
else if(!range)
|
|
// rare but cheap case: only stuff on your tile
|
|
return th == tadj
|
|
else
|
|
// less common but expensive case - long range tool reach
|
|
var/atom/movable/reachability_delegate/D = new(tadj)
|
|
D.pass_flags |= pass_flags
|
|
// next turf
|
|
var/turf/n = D.loc
|
|
for(i in 1 to range)
|
|
ASSERT(isturf(n))
|
|
if(n.TurfAdjacency(th, target, src))
|
|
// succeeded
|
|
qdel(D)
|
|
return REACH_PHYSICAL
|
|
// dumb directional pathfinding both for cheapness and for practical purposes
|
|
// so you can't snake-arms round a row of windows or something crazy
|
|
n = get_step(D, get_dir(D, th))
|
|
if(!D.Move(n))
|
|
// failed
|
|
qdel(D)
|
|
return REACH_FAILED
|
|
// keep going
|
|
// at this point, we failed
|
|
qdel(D)
|
|
return REACH_FAILED
|
|
|
|
/**
|
|
* quick and dirty reachability check
|
|
*
|
|
* todo: range doesn't currently work
|
|
*/
|
|
/atom/movable/proc/CheapReachability(atom/target, depth = DEFAULT_REACHABILITY_DEPTH, range, obj/item/tool)
|
|
var/turf/curr = target.loc
|
|
var/turf/source
|
|
if(isturf(curr))
|
|
source = get_turf(src)
|
|
if(!source)
|
|
return REACH_FAILED
|
|
return curr.TurfAdjacency(source)
|
|
do
|
|
if(curr == src)
|
|
return REACH_PHYSICAL
|
|
if(!curr)
|
|
return REACH_FAILED
|
|
curr = curr.loc
|
|
while(!isturf(curr))
|
|
source = get_turf(src)
|
|
if(!source)
|
|
return REACH_FAILED
|
|
return curr.TurfAdjacency(source)
|
|
|
|
/atom/movable/reachability_delegate
|
|
atom_flags = ATOM_NONWORLD
|
|
pass_flags = ATOM_PASS_CLICK | ATOM_PASS_TABLE
|
|
invisibility = INVISIBILITY_ABSTRACT
|
|
|
|
//! Direct Access
|
|
/**
|
|
* checks what we can directly reach
|
|
*/
|
|
/atom/movable/proc/DirectAccess()
|
|
. = list(src, loc)
|
|
SEND_SIGNAL(src, COMSIG_ATOM_REACHABILITY_DIRECTACCESS, .)
|
|
|
|
/mob/DirectAccess()
|
|
return ..() + get_equipped_items()
|
|
|
|
/mob/living/silicon/robot/DirectAccess()
|
|
return ..() + robot_inventory?.provided_items
|
|
|
|
/**
|
|
* gets DirectAccess as a hashed list for quick lookups
|
|
*/
|
|
/atom/movable/proc/DirectAccessCache()
|
|
. = list()
|
|
// procs like these make me wish ss13 was run on node.js or something equally stupid
|
|
// return Object.assign({}, ...data.map((atom) => ({[atom]: true})));
|
|
for(var/i in DirectAccess())
|
|
.[i] = TRUE
|
|
|
|
//! Reaching out of
|
|
/**
|
|
* called to see if we can reach out of this atom
|
|
*
|
|
* **When overriding, DO NOT FUCK WITH CACHE unless you KNOW WHAT YOU ARE DOING.**
|
|
*
|
|
* @params
|
|
* - mover - thing reaching
|
|
* - target - what we're reaching at
|
|
* - tool - what mover is using to reach with if applicable
|
|
* - cache - direct access to the "directly open" cache list. Add things to this with cache[obj] = TRUE
|
|
*/
|
|
/atom/proc/CanReachOut(atom/movable/mover, atom/target, obj/item/tool, list/cache)
|
|
// todo: signal when we care about signals
|
|
return FALSE
|
|
|
|
//! Reaching into
|
|
/**
|
|
* called to see if we can reach into this atom
|
|
*
|
|
* **When overriding, DO NOT FUCK WITH CACHE unless you KNOW WHAT YOU ARE DOING.**
|
|
*
|
|
* @params
|
|
* - mover - thing reaching
|
|
* - target - what we're reaching at
|
|
* - tool - what mover is using to reach with if applicable
|
|
* - cache - direct access to the "checking" cache list. Add things to this with cache[obj] = TRUE
|
|
*/
|
|
/atom/proc/CanReachIn(atom/movable/mover, atom/target, obj/item/tool, list/cache)
|
|
// todo: signal when we care about signals
|
|
return TRUE
|
|
|
|
//! Clicking out of - for ranged attack chains
|
|
/**
|
|
* called to see if we're allowed to proc ranged_interaction_chain from in here at a target
|
|
*
|
|
* this proc needs refactored at some point to allow for complexity if necessary
|
|
* but for now it works so don't fuck with it
|
|
*/
|
|
/atom/proc/AllowClick(atom/movable/mover, atom/target, obj/item/tool)
|
|
return FALSE
|
|
|
|
/turf/AllowClick(atom/movable/mover, atom/target, obj/item/tool)
|
|
return TRUE
|
|
|
|
//! Obfuscation
|
|
/**
|
|
* is the atom obscured by a OBJ_PREVENT_CLICK_UNDER object above it
|
|
*/
|
|
/atom/proc/IsObsecured()
|
|
if(!isturf(loc))
|
|
return FALSE
|
|
var/turf/T = get_turf_pixel(src)
|
|
if(!T)
|
|
return FALSE
|
|
for(var/obj/O in T)
|
|
if((O.obj_flags & OBJ_PREVENT_CLICK_UNDER) && O.layer > layer && O.plane >= plane)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/turf/IsObsecured()
|
|
for(var/obj/O in contents)
|
|
if(O.obj_flags & OBJ_PREVENT_CLICK_UNDER)
|
|
return TRUE
|
|
return FALSE
|