mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-29 23:17:02 +01:00
Port /tg/'s spatial grid, Round 2 (#30567)
* port /tg/'s spatial grid. * fix lint * fix identical variables --------- Co-authored-by: warriorstar-orion <orion@snowfrost.garden>
This commit is contained in:
co-authored by
warriorstar-orion
parent
96d875e374
commit
96495fab84
@@ -137,3 +137,10 @@
|
||||
#define COMSIG_RULESET_FAILED_SPECIES "failed_species"
|
||||
|
||||
#define COMSIGN_TICKET_COUNT_UPDATE "ticket_count_updated"
|
||||
|
||||
//spatial grid signals
|
||||
|
||||
/// Called from base of /datum/controller/subsystem/spatial_grid/proc/enter_cell: (/atom/movable)
|
||||
#define SPATIAL_GRID_CELL_ENTERED(contents_type) "spatial_grid_cell_entered_[contents_type]"
|
||||
/// Called from base of /datum/controller/subsystem/spatial_grid/proc/exit_cell: (/atom/movable)
|
||||
#define SPATIAL_GRID_CELL_EXITED(contents_type) "spatial_grid_cell_exited_[contents_type]"
|
||||
|
||||
@@ -27,3 +27,6 @@
|
||||
|
||||
/// Used when a shelter capsule deploys
|
||||
#define COMSIG_GLOB_SHELTER_PLACED "!shelter_placed"
|
||||
|
||||
/// sent after world.maxx and/or world.maxy are expanded: (has_expanded_world_maxx, has_expanded_world_maxy)
|
||||
#define COMSIG_GLOB_EXPANDED_WORLD_BOUNDS "!expanded_world_bounds"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
///the area channel of the important_recursive_contents list, everything in here will be sent a signal when their last holding object changes areas
|
||||
#define RECURSIVE_CONTENTS_AREA_SENSITIVE "recursive_contents_area_sensitive"
|
||||
///the hearing channel of the important_recursive_contents list, everything in here will count as a hearing atom
|
||||
#define RECURSIVE_CONTENTS_HEARING_SENSITIVE "recursive_contents_hearing_sensitive"
|
||||
///the client mobs channel of the important_recursive_contents list, everything in here will be a mob with an attached client
|
||||
///this is given to both a clients mob, and a clients eye, both point to the clients mob
|
||||
#define RECURSIVE_CONTENTS_CLIENT_MOBS "recursive_contents_client_mobs"
|
||||
///the parent of storage components currently shown to some client mob get this. gets removed when nothing is viewing the parent
|
||||
#define RECURSIVE_CONTENTS_ACTIVE_STORAGE "recursive_contents_active_storage"
|
||||
@@ -0,0 +1,55 @@
|
||||
/// each cell in a spatial_grid is this many turfs in length and width (with world.max(x or y) being 255, 15 of these fit on each side of a z level)
|
||||
#define SPATIAL_GRID_CELLSIZE 17
|
||||
/// Takes a coordinate, and spits out the spatial grid index (x or y) it's inside
|
||||
#define GET_SPATIAL_INDEX(coord) ROUND_UP((coord) / SPATIAL_GRID_CELLSIZE)
|
||||
/// changes the cell_(x or y) vars on /datum/spatial_grid_cell to the x or y coordinate on the map for the LOWER LEFT CORNER of the grid cell.
|
||||
/// index is from 1 to SPATIAL_GRID_CELLS_PER_SIDE
|
||||
#define GRID_INDEX_TO_COORDS(index) ((((index) - 1) * SPATIAL_GRID_CELLSIZE) + 1)
|
||||
/// number of grid cells per x or y side of all z levels. pass in world.maxx or world.maxy
|
||||
#define SPATIAL_GRID_CELLS_PER_SIDE(world_bounds) GET_SPATIAL_INDEX(world_bounds)
|
||||
|
||||
//grid contents channels
|
||||
|
||||
///everything that is hearing sensitive is stored in this channel
|
||||
#define SPATIAL_GRID_CONTENTS_TYPE_HEARING RECURSIVE_CONTENTS_HEARING_SENSITIVE
|
||||
///every movable that has a client in it is stored in this channel
|
||||
#define SPATIAL_GRID_CONTENTS_TYPE_CLIENTS RECURSIVE_CONTENTS_CLIENT_MOBS
|
||||
///all atmos machines are stored in this channel (I'm sorry kyler)
|
||||
#define SPATIAL_GRID_CONTENTS_TYPE_ATMOS "spatial_grid_contents_type_atmos"
|
||||
|
||||
#define ALL_CONTENTS_OF_CELL(cell) (cell.hearing_contents | cell.client_contents | cell.atmos_contents)
|
||||
|
||||
///whether movable is itself or containing something which should be in one of the spatial grid channels.
|
||||
#define HAS_SPATIAL_GRID_CONTENTS(movable) (movable.spatial_grid_key)
|
||||
|
||||
// macros meant specifically to add/remove movables from the internal lists of /datum/spatial_grid_cell,
|
||||
// when empty they become references to a single list in SSspatial_grid and when filled they become their own list
|
||||
// this is to save memory without making them lazylists as that slows down iteration through them
|
||||
#define GRID_CELL_ADD(cell_contents_list, movable_or_list) \
|
||||
if(!length(cell_contents_list)) { \
|
||||
cell_contents_list = list(); \
|
||||
cell_contents_list += movable_or_list; \
|
||||
} else { \
|
||||
cell_contents_list += movable_or_list; \
|
||||
};
|
||||
|
||||
#define GRID_CELL_SET(cell_contents_list, movable_or_list) \
|
||||
if(!length(cell_contents_list)) { \
|
||||
cell_contents_list = list(); \
|
||||
cell_contents_list += movable_or_list; \
|
||||
} else { \
|
||||
cell_contents_list |= movable_or_list; \
|
||||
};
|
||||
|
||||
//dont use these outside of SSspatial_grid's scope use the procs it has for this purpose
|
||||
#define GRID_CELL_REMOVE(cell_contents_list, movable_or_list) \
|
||||
cell_contents_list -= movable_or_list; \
|
||||
if(!length(cell_contents_list)) {\
|
||||
cell_contents_list = dummy_list; \
|
||||
};
|
||||
|
||||
///remove from every list
|
||||
#define GRID_CELL_REMOVE_ALL(cell, movable) \
|
||||
GRID_CELL_REMOVE(cell.hearing_contents, movable) \
|
||||
GRID_CELL_REMOVE(cell.client_contents, movable) \
|
||||
GRID_CELL_REMOVE(cell.atmos_contents, movable)
|
||||
@@ -50,23 +50,25 @@
|
||||
#define INIT_ORDER_PROFILER 101
|
||||
#define INIT_ORDER_QUEUE 100 // Load this quickly so people cant queue skip
|
||||
#define INIT_ORDER_TITLE 99 // Load this quickly so people dont see a blank lobby screen
|
||||
#define INIT_ORDER_GARBAGE 25
|
||||
#define INIT_ORDER_DBCORE 24
|
||||
#define INIT_ORDER_REDIS 23 // Make sure we dont miss any events
|
||||
#define INIT_ORDER_BLACKBOX 22
|
||||
#define INIT_ORDER_CLEANUP 21
|
||||
#define INIT_ORDER_INPUT 20
|
||||
#define INIT_ORDER_SOUNDS 19
|
||||
#define INIT_ORDER_INSTRUMENTS 18
|
||||
#define INIT_ORDER_RESEARCH 17 // SoonTM
|
||||
#define INIT_ORDER_VIS 16
|
||||
#define INIT_ORDER_STATION 15 //This is high priority because it manipulates a lot of the subsystems that will initialize after it.
|
||||
#define INIT_ORDER_EVENTS 14
|
||||
#define INIT_ORDER_JOBS 13
|
||||
#define INIT_ORDER_AI_MOVEMENT 12
|
||||
#define INIT_ORDER_AI_CONTROLLERS 11
|
||||
#define INIT_ORDER_TICKER 10
|
||||
#define INIT_ORDER_MAPPING 9
|
||||
#define INIT_ORDER_GARBAGE 27
|
||||
#define INIT_ORDER_DBCORE 26
|
||||
#define INIT_ORDER_REDIS 25 // Make sure we dont miss any events
|
||||
#define INIT_ORDER_BLACKBOX 24
|
||||
#define INIT_ORDER_CLEANUP 23
|
||||
#define INIT_ORDER_INPUT 22
|
||||
#define INIT_ORDER_SOUNDS 21
|
||||
#define INIT_ORDER_INSTRUMENTS 20
|
||||
#define INIT_ORDER_RESEARCH 19 // SoonTM
|
||||
#define INIT_ORDER_VIS 18
|
||||
#define INIT_ORDER_STATION 17 //This is high priority because it manipulates a lot of the subsystems that will initialize after it.
|
||||
#define INIT_ORDER_EVENTS 16
|
||||
#define INIT_ORDER_JOBS 15
|
||||
#define INIT_ORDER_AI_MOVEMENT 14
|
||||
#define INIT_ORDER_AI_CONTROLLERS 13
|
||||
#define INIT_ORDER_TICKER 12
|
||||
#define INIT_ORDER_MAPPING 11
|
||||
#define INIT_ORDER_SPATIAL_GRID 10
|
||||
#define INIT_ORDER_AI_IDLE_CONTROLLERS 9
|
||||
#define INIT_ORDER_EARLY_ASSETS 8
|
||||
#define INIT_ORDER_ATOMS 7
|
||||
#define INIT_ORDER_MACHINES 5
|
||||
|
||||
@@ -22,3 +22,6 @@
|
||||
CORNER_BLOCK_OFFSET(corner, 1, height, width, 0))
|
||||
|
||||
#define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS)
|
||||
|
||||
/// Returns all turfs in a zlevel
|
||||
#define Z_TURFS(ZLEVEL) block(1, 1, ZLEVEL, world.maxx, world.maxy, ZLEVEL)
|
||||
|
||||
@@ -1029,3 +1029,10 @@
|
||||
/proc/lists_equal_unordered(list/list_one, list/list_two)
|
||||
// This ensures that both lists contain the same elements by checking if the difference between them is empty in both directions.
|
||||
return !length(list_one ^ list_two)
|
||||
|
||||
/**
|
||||
* Removes any null entries from the list
|
||||
* Returns TRUE if the list had nulls, FALSE otherwise
|
||||
**/
|
||||
/proc/list_clear_nulls(list/list_to_clear)
|
||||
return (list_to_clear.RemoveAll(null) > 0)
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
/** # Oranges Ear
|
||||
*
|
||||
* turns out view() spends a significant portion of its processing time generating lists of contents of viewable turfs which includes EVERYTHING on it visible
|
||||
* and the turf itself. there is an optimization to view() which makes it only iterate through either /obj or /mob contents, as well as normal list typechecking filters
|
||||
*
|
||||
* a fuckton of these are generated as part of its SS's init and stored in a list, when requested for a list of movables returned by the spatial grid or by some
|
||||
* superset of the final output that must be narrowed down by view(), one of these gets put on every turf that contains the movables that need filtering
|
||||
* and each is given references to the movables they represent. that way you can do for(var/mob/oranges_ear/ear in view(...)) and check what they reference
|
||||
* as opposed to for(var/atom/movable/target in view(...)) and checking if they have the properties you want which leads to much larger lists generated by view()
|
||||
* and also leads to iterating through more movables to filter them.
|
||||
*
|
||||
* TLDR: iterating through just mobs is much faster than all movables when iterating through view() on average, this system leverages that to boost speed
|
||||
* enough to offset the cost of allocating the mobs
|
||||
*
|
||||
* named because the idea was first made by oranges and i didnt know what else to call it (note that this system was originally made for get_hearers_in_view())
|
||||
*/
|
||||
/mob/oranges_ear
|
||||
icon_state = null
|
||||
density = FALSE
|
||||
move_resist = INFINITY
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
faction = null
|
||||
alerts = null
|
||||
screens = null
|
||||
hud_possible = null
|
||||
/// references to everything "on" the turf we are assigned to, that we care about. populated in assign() and cleared in unassign().
|
||||
/// movables iside of other movables count as being "on" if they have get_turf(them) == our turf. intentionally not a lazylist
|
||||
var/list/references = list()
|
||||
|
||||
/mob/oranges_ear/Initialize(mapload)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
if(initialized)
|
||||
stack_trace("Warning: [src]([type]) initialized multiple times!")
|
||||
initialized = TRUE
|
||||
return INITIALIZE_HINT_NORMAL
|
||||
|
||||
/mob/oranges_ear/Destroy(force)
|
||||
var/old_length = length(SSspatial_grid.pregenerated_oranges_ears)
|
||||
SSspatial_grid.pregenerated_oranges_ears -= src
|
||||
if(length(SSspatial_grid.pregenerated_oranges_ears) < old_length)
|
||||
SSspatial_grid.number_of_oranges_ears -= 1
|
||||
|
||||
var/turf/our_loc = get_turf(src)
|
||||
if(our_loc && our_loc.assigned_oranges_ear == src)
|
||||
our_loc.assigned_oranges_ear = null
|
||||
|
||||
. = ..()
|
||||
|
||||
/mob/oranges_ear/Move()
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
stack_trace("SOMEHOW A /mob/oranges_ear MOVED")
|
||||
return FALSE
|
||||
|
||||
/mob/oranges_ear/abstract_move(atom/destination)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
stack_trace("SOMEHOW A /mob/oranges_ear MOVED")
|
||||
return FALSE
|
||||
|
||||
/mob/oranges_ear/Bump()
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
return FALSE
|
||||
|
||||
///clean this oranges_ear up for future use
|
||||
/mob/oranges_ear/proc/unassign()
|
||||
var/turf/turf_loc = loc
|
||||
turf_loc.assigned_oranges_ear = null//trollface. our loc should ALWAYS be a turf, no exceptions. if it isnt then this doubles as an error message ;)
|
||||
loc = null
|
||||
references.Cut()
|
||||
|
||||
/**
|
||||
* returns every hearaing movable in view to the turf of source not taking into account lighting
|
||||
* useful when you need to maintain always being able to hear something if a sound is emitted from it and you can see it (and youre in range).
|
||||
* otherwise this is just a more expensive version of get_hearers_in_LOS().
|
||||
*
|
||||
* * view_radius - what radius search circle we are using, worse performance as this increases
|
||||
* * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area
|
||||
* * contents_type - the type of contents we want to be looking for. defaults to hearing sensitive
|
||||
*/
|
||||
/proc/get_hearers_in_view(view_radius, atom/source, contents_type=RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
var/turf/center_turf = get_turf(source)
|
||||
if(!center_turf)
|
||||
return
|
||||
|
||||
. = list()
|
||||
|
||||
if(view_radius <= 0)//special case for if only source cares
|
||||
for(var/atom/movable/target as anything in center_turf)
|
||||
var/list/recursive_contents = target.important_recursive_contents?[contents_type]
|
||||
if(recursive_contents)
|
||||
. += recursive_contents
|
||||
return .
|
||||
|
||||
var/list/hearables_from_grid = SSspatial_grid.orthogonal_range_search(source, contents_type, view_radius)
|
||||
|
||||
if(!length(hearables_from_grid))//we know that something is returned by the grid, but we dont know if we need to actually filter down the output
|
||||
return .
|
||||
|
||||
var/list/assigned_oranges_ears = SSspatial_grid.assign_oranges_ears(hearables_from_grid)
|
||||
|
||||
//this is the ENTIRE reason all this shit is worth it due to how view()-like procs and the contents list works and can be optimized
|
||||
//internally, the contents list is secretly two linked lists, one for /obj's and one for /mob's (/atom/movable counts as /obj here)
|
||||
//by default, for(var/atom/name in view()) iterates through both the /obj linked list then the /mob linked list of each turf
|
||||
//but because what we want are only a tiny proportion of all movables, most of the things in the /obj contents list are not what we're looking for
|
||||
//while every mob can hear. for this case view() and similar procs have an optimization to only look through 1 of these lists if it can (eg youre only looking for mobs)
|
||||
//so by representing every hearing contents on a turf with a single /mob/oranges_ear containing references to all of them, we are:
|
||||
//1. making view() only go through the smallest of the two linked lists per turf, which contains the type we're looking for at the end
|
||||
//2. typechecking all mobs in the output to only actually return mobs of type /mob/oranges_ear
|
||||
//on a whole this can outperform iterating through all movables in view() by ~2x especially when hearables are a tiny percentage of movables in view
|
||||
//using hearers is a further optimization of that because for our purposes its the same as view except we dont have to set center's luminosity to 6 and then unset it
|
||||
for(var/mob/oranges_ear/ear in hearers(view_radius, center_turf))
|
||||
. += ear.references
|
||||
|
||||
for(var/mob/oranges_ear/remaining_ear as anything in assigned_oranges_ears)//we need to clean up our mess
|
||||
remaining_ear.unassign()
|
||||
|
||||
return .
|
||||
|
||||
/**
|
||||
* The exact same as get_hearers_in_view, but not limited by visibility. Does no filtering for traits, line of sight, or any other such criteria.
|
||||
* Filtering is intended to be done by whatever calls this function.
|
||||
*
|
||||
* This function exists to allow for mobs to hear speech without line of sight, if such a thing is needed.
|
||||
*
|
||||
* * radius - what radius search circle we are using, worse performance as this increases
|
||||
* * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area
|
||||
* * contents_type - the type of contents we want to be looking for. defaults to hearing sensitive
|
||||
*/
|
||||
/proc/get_hearers_in_range(range, atom/source, contents_type=RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
var/turf/center_turf = get_turf(source)
|
||||
if(!center_turf)
|
||||
return
|
||||
|
||||
. = list()
|
||||
|
||||
if(range <= 0)//special case for if only source cares
|
||||
for(var/atom/movable/target as anything in center_turf)
|
||||
var/list/recursive_contents = target.important_recursive_contents?[contents_type]
|
||||
if(recursive_contents)
|
||||
. += recursive_contents
|
||||
return .
|
||||
|
||||
var/list/hearables_from_grid = SSspatial_grid.orthogonal_range_search(source, contents_type, range)
|
||||
|
||||
if(!length(hearables_from_grid))//we know that something is returned by the grid, but we dont know if we need to actually filter down the output
|
||||
return .
|
||||
|
||||
for(var/atom/movable/hearable as anything in hearables_from_grid)
|
||||
if(get_dist(center_turf, hearable) <= range)
|
||||
. += hearable
|
||||
|
||||
return .
|
||||
|
||||
/**
|
||||
* Returns a list of movable atoms that are hearing sensitive in view_radius and line of sight to source
|
||||
* the majority of the work is passed off to the spatial grid if view_radius > 0
|
||||
* because view() isnt a raycasting algorithm, this does not hold symmetry to it. something in view might not be hearable with this.
|
||||
* if you want that use get_hearers_in_view() - however thats significantly more expensive
|
||||
*
|
||||
* * view_radius - what radius search circle we are using, worse performance as this increases but not as much as it used to
|
||||
* * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area
|
||||
*/
|
||||
/proc/get_hearers_in_LOS(view_radius, atom/source, contents_type=RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
var/turf/center_turf = get_turf(source)
|
||||
if(!center_turf)
|
||||
return
|
||||
|
||||
if(view_radius <= 0)//special case for if only source cares
|
||||
. = list()
|
||||
for(var/atom/movable/target as anything in center_turf)
|
||||
var/list/hearing_contents = target.important_recursive_contents?[contents_type]
|
||||
if(hearing_contents)
|
||||
. += hearing_contents
|
||||
return
|
||||
|
||||
. = SSspatial_grid.orthogonal_range_search(source, contents_type, view_radius)
|
||||
|
||||
for(var/atom/movable/target as anything in .)
|
||||
var/turf/target_turf = get_turf(target)
|
||||
|
||||
var/distance = get_dist(center_turf, target_turf)
|
||||
|
||||
if(distance > view_radius)
|
||||
. -= target
|
||||
continue
|
||||
|
||||
else if(distance < 2) //we should always be able to see something 0 or 1 tiles away
|
||||
continue
|
||||
|
||||
//this turf search algorithm is the worst scaling part of this proc, scaling worse than view() for small-moderate ranges and > 50 length contents_to_return
|
||||
//luckily its significantly faster than view for large ranges in large spaces and/or relatively few contents_to_return
|
||||
//i can do things that would scale better, but they would be slower for low volume searches which is the vast majority of the current workload
|
||||
//maybe in the future a high volume algorithm would be worth it
|
||||
var/turf/inbetween_turf = center_turf
|
||||
|
||||
//this is the lowest overhead way of doing a loop in dm other than a goto. distance is guaranteed to be >= steps taken to target by this algorithm
|
||||
for(var/step_counter in 1 to distance)
|
||||
inbetween_turf = get_step_towards(inbetween_turf, target_turf)
|
||||
|
||||
if(inbetween_turf == target_turf)//we've gotten to target's turf without returning due to turf opacity, so we must be able to see target
|
||||
break
|
||||
|
||||
if(IS_OPAQUE_TURF(inbetween_turf))//this turf or something on it is opaque so we cant see through it
|
||||
. -= target
|
||||
break
|
||||
|
||||
/proc/get_hearers_in_radio_ranges(list/obj/item/radio/radios)
|
||||
. = list()
|
||||
// Returns a list of mobs who can hear any of the radios given in @radios
|
||||
for(var/obj/item/radio/radio as anything in radios)
|
||||
. |= get_hearers_in_LOS(radio.canhear_range, radio)
|
||||
|
||||
/proc/is_in_sight(atom/first_atom, atom/second_atom)
|
||||
var/turf/first_turf = get_turf(first_atom)
|
||||
var/turf/second_turf = get_turf(second_atom)
|
||||
|
||||
if(!first_turf || !second_turf)
|
||||
return FALSE
|
||||
|
||||
return inLineOfSight(first_turf.x, first_turf.y, second_turf.x, second_turf.y, first_turf.z)
|
||||
|
||||
///Returns all atoms present in a circle around the center
|
||||
/proc/circle_range(center = usr,radius = 3)
|
||||
|
||||
var/turf/center_turf = get_turf(center)
|
||||
var/list/atoms = new/list()
|
||||
var/rsq = radius * (radius + 0.5)
|
||||
|
||||
for(var/atom/checked_atom as anything in range(radius, center_turf))
|
||||
var/dx = checked_atom.x - center_turf.x
|
||||
var/dy = checked_atom.y - center_turf.y
|
||||
if(dx * dx + dy * dy <= rsq)
|
||||
atoms += checked_atom
|
||||
|
||||
return atoms
|
||||
|
||||
///Returns all atoms present in a circle around the center but uses view() instead of range() (Currently not used)
|
||||
/proc/circle_view(center=usr,radius=3)
|
||||
|
||||
var/turf/center_turf = get_turf(center)
|
||||
var/list/atoms = new/list()
|
||||
var/rsq = radius * (radius + 0.5)
|
||||
|
||||
for(var/atom/checked_atom as anything in view(radius, center_turf))
|
||||
var/dx = checked_atom.x - center_turf.x
|
||||
var/dy = checked_atom.y - center_turf.y
|
||||
if(dx * dx + dy * dy <= rsq)
|
||||
atoms += checked_atom
|
||||
|
||||
return atoms
|
||||
|
||||
///Returns the distance between two atoms
|
||||
/proc/get_dist_euclidean(atom/first_location, atom/second_location)
|
||||
var/dx = first_location.x - second_location.x
|
||||
var/dy = first_location.y - second_location.y
|
||||
|
||||
var/dist = sqrt(dx ** 2 + dy ** 2)
|
||||
|
||||
return dist
|
||||
|
||||
///Returns a list of turfs around a center based on RANGE_TURFS()
|
||||
/proc/circle_range_turfs(center = usr, radius = 3)
|
||||
|
||||
var/turf/center_turf = get_turf(center)
|
||||
var/list/turfs = new/list()
|
||||
var/rsq = radius * (radius + 0.5)
|
||||
|
||||
for(var/turf/checked_turf as anything in RANGE_TURFS(radius, center_turf))
|
||||
var/dx = checked_turf.x - center_turf.x
|
||||
var/dy = checked_turf.y - center_turf.y
|
||||
if(dx * dx + dy * dy <= rsq)
|
||||
turfs += checked_turf
|
||||
return turfs
|
||||
|
||||
///Returns a list of turfs around a center based on view()
|
||||
/proc/circle_view_turfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()? // Yes
|
||||
var/turf/center_turf = get_turf(center)
|
||||
var/list/turfs = new/list()
|
||||
var/rsq = radius * (radius + 0.5)
|
||||
|
||||
for(var/turf/checked_turf in view(radius, center_turf))
|
||||
var/dx = checked_turf.x - center_turf.x
|
||||
var/dy = checked_turf.y - center_turf.y
|
||||
if(dx * dx + dy * dy <= rsq)
|
||||
turfs += checked_turf
|
||||
return turfs
|
||||
|
||||
///Returns the list of turfs around the outside of a center based on RANGE_TURFS()
|
||||
/proc/border_diamond_range_turfs(atom/center = usr, radius = 3)
|
||||
var/turf/center_turf = get_turf(center)
|
||||
var/list/turfs = list()
|
||||
|
||||
for(var/turf/checked_turf as anything in RANGE_TURFS(radius, center_turf))
|
||||
var/dx = checked_turf.x - center_turf.x
|
||||
var/dy = checked_turf.y - center_turf.y
|
||||
var/abs_sum = abs(dx) + abs(dy)
|
||||
if(abs_sum == radius)
|
||||
turfs += checked_turf
|
||||
return turfs
|
||||
|
||||
///Returns a slice of a list of turfs, defined by the ones that are inside the inner/outer angle's bounds
|
||||
/proc/slice_off_turfs(atom/center, list/turf/turfs, inner_angle, outer_angle)
|
||||
var/turf/center_turf = get_turf(center)
|
||||
var/list/sliced_turfs = list()
|
||||
|
||||
for(var/turf/checked_turf as anything in turfs)
|
||||
var/angle_to = get_angle(center_turf, checked_turf)
|
||||
if(angle_to < inner_angle || angle_to > outer_angle)
|
||||
continue
|
||||
sliced_turfs += checked_turf
|
||||
return sliced_turfs
|
||||
|
||||
/// Like view but bypasses luminosity check
|
||||
/proc/get_hear(range, atom/source)
|
||||
var/lum = source.luminosity
|
||||
source.luminosity = 6
|
||||
|
||||
. = view(range, source)
|
||||
source.luminosity = lum
|
||||
|
||||
///Returns a list of all areas that are adjacent to the center atom's area, clear the list of nulls at the end.
|
||||
/proc/get_adjacent_areas(atom/center)
|
||||
. = list(
|
||||
get_area(get_ranged_target_turf(center, NORTH, 1)),
|
||||
get_area(get_ranged_target_turf(center, SOUTH, 1)),
|
||||
get_area(get_ranged_target_turf(center, EAST, 1)),
|
||||
get_area(get_ranged_target_turf(center, WEST, 1))
|
||||
)
|
||||
list_clear_nulls(.)
|
||||
|
||||
///Returns a list of all turfs that are adjacent to the center atom's turf, clear the list of nulls at the end.
|
||||
/proc/get_adjacent_turfs(atom/center)
|
||||
. = list(
|
||||
get_step(center, NORTH),
|
||||
get_step(center, SOUTH),
|
||||
get_step(center, EAST),
|
||||
get_step(center, WEST)
|
||||
)
|
||||
list_clear_nulls(.)
|
||||
|
||||
/**
|
||||
* Behaves like the orange() proc, but only looks in the outer range of the function (The "peel" of the orange).
|
||||
* This is useful for things like checking if a mob is in a certain range, but not within a smaller range.
|
||||
*
|
||||
* @params outer_range - The outer range of the cicle to pull from.
|
||||
* @params inner_range - The inner range of the circle to NOT pull from.
|
||||
* @params center - The center of the circle to pull from, can be an atom (we'll apply get_turf() to it within circle_x_turfs procs.)
|
||||
* @params view_based - If TRUE, we'll use circle_view_turfs instead of circle_range_turfs procs.
|
||||
*/
|
||||
/proc/turf_peel(outer_range, inner_range, center, view_based = FALSE)
|
||||
if(inner_range > outer_range) // If the inner range is larger than the outer range, you're using this wrong.
|
||||
CRASH("Turf peel inner range is larger than outer range!")
|
||||
var/list/peel = list()
|
||||
var/list/outer
|
||||
var/list/inner
|
||||
if(view_based)
|
||||
outer = circle_view_turfs(center, outer_range)
|
||||
inner = circle_view_turfs(center, inner_range)
|
||||
else
|
||||
outer = circle_range_turfs(center, outer_range)
|
||||
inner = circle_range_turfs(center, inner_range)
|
||||
for(var/turf/possible_spawn as anything in outer)
|
||||
if(possible_spawn in inner)
|
||||
continue
|
||||
peel += possible_spawn
|
||||
|
||||
if(!length(peel))
|
||||
return center //Offer the center only as a default case when we don't have a valid circle.
|
||||
return peel
|
||||
|
||||
///check if 2 diagonal turfs are blocked by dense objects
|
||||
/proc/diagonally_blocked(turf/our_turf, turf/dest_turf)
|
||||
if(get_dist(our_turf, dest_turf) != 1)
|
||||
return FALSE
|
||||
var/direction_to_turf = get_dir(dest_turf, our_turf)
|
||||
if(!IS_DIR_DIAGONAL(direction_to_turf))
|
||||
return FALSE
|
||||
for(var/direction_check in GLOB.cardinal)
|
||||
if(!(direction_check & direction_to_turf))
|
||||
continue
|
||||
var/turf/test_turf = get_step(dest_turf, direction_check)
|
||||
if(isnull(test_turf))
|
||||
continue
|
||||
if(!test_turf.is_blocked_turf(exclude_mobs = TRUE))
|
||||
return FALSE
|
||||
return TRUE
|
||||
@@ -558,6 +558,17 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
// Prevents the atom from being transitioned to another Z level when approaching the edge of the map.
|
||||
#define TRAIT_NO_EDGE_TRANSITIONS "trait_no_edge_transitions"
|
||||
|
||||
//important_recursive_contents traits
|
||||
/*
|
||||
* Used for movables that need to be updated, via COMSIG_ENTER_AREA and COMSIG_EXIT_AREA, when transitioning areas.
|
||||
* Use [/atom/movable/proc/become_area_sensitive(trait_source)] to properly enable it. How you remove it isn't as important.
|
||||
*/
|
||||
#define TRAIT_AREA_SENSITIVE "area-sensitive"
|
||||
///every hearing sensitive atom has this trait
|
||||
#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
|
||||
///every object that is currently the active storage of some client mob has this trait
|
||||
#define TRAIT_ACTIVE_STORAGE "active_storage"
|
||||
|
||||
//***** PROC WRAPPERS *****//
|
||||
/// Proc wrapper of add_trait. You should only use this for callback. Otherwise, use the macro.
|
||||
/proc/callback_add_trait(datum/target, trait, source)
|
||||
|
||||
@@ -0,0 +1,840 @@
|
||||
///the subsystem creates this many [/mob/oranges_ear] mob instances during init. allocations that require more than this create more.
|
||||
#define NUMBER_OF_PREGENERATED_ORANGES_EARS 2500
|
||||
|
||||
/**
|
||||
* # Spatial Grid Cell
|
||||
*
|
||||
* used by [/datum/controller/subsystem/spatial_grid] to cover every z level so that the coordinates of every turf in the world corresponds to one of these in
|
||||
* the subsystems list of grid cells by z level. each one of these contains content lists holding all atoms meeting a certain criteria that is in our borders.
|
||||
* these datums shouldnt have significant behavior, they should just hold data. the lists are filled and emptied by the subsystem.
|
||||
*/
|
||||
/datum/spatial_grid_cell
|
||||
///our x index in the list of cells. this is our index inside of our row list
|
||||
var/cell_x
|
||||
///our y index in the list of cells. this is the index of our row list inside of our z level grid
|
||||
var/cell_y
|
||||
///which z level we belong to, corresponding to the index of our gridmap in SSspatial_grid.grids_by_z_level
|
||||
var/cell_z
|
||||
//every data point in a grid cell is separated by usecase
|
||||
|
||||
//when empty, the contents lists of these grid cell datums are just references to a dummy list from SSspatial_grid
|
||||
//this is meant to allow a great compromise between memory usage and speed.
|
||||
//now orthogonal_range_search() doesnt need to check if the list is null and each empty list is taking 12 bytes instead of 24
|
||||
//the only downside is that it needs to be switched over to a new list when it goes from 0 contents to > 0 contents and switched back on the opposite case
|
||||
|
||||
///every hearing sensitive movable inside this cell
|
||||
var/list/hearing_contents
|
||||
///every client possessed mob inside this cell
|
||||
var/list/client_contents
|
||||
///every atmos machine inside this cell
|
||||
var/list/atmos_contents
|
||||
|
||||
/datum/spatial_grid_cell/New(cell_x, cell_y, cell_z)
|
||||
. = ..()
|
||||
src.cell_x = cell_x
|
||||
src.cell_y = cell_y
|
||||
src.cell_z = cell_z
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/dummy_list = SSspatial_grid.dummy_list
|
||||
|
||||
if(length(dummy_list))
|
||||
dummy_list.Cut()
|
||||
stack_trace("SSspatial_grid.dummy_list had something inserted into it at some point! this is a problem as it is supposed to stay empty")
|
||||
hearing_contents = dummy_list
|
||||
client_contents = dummy_list
|
||||
atmos_contents = dummy_list
|
||||
|
||||
/datum/spatial_grid_cell/Destroy(force)
|
||||
if(!force)//the response to someone trying to qdel this is a right proper fuck you
|
||||
stack_trace("dont try to destroy spatial grid cells without a good reason. if you need to do it use force")
|
||||
return QDEL_HINT_LETMELIVE
|
||||
|
||||
. = ..()
|
||||
|
||||
/**
|
||||
* # Spatial Grid
|
||||
*
|
||||
* a gamewide grid of spatial_grid_cell datums, each "covering" [SPATIAL_GRID_CELLSIZE] ^ 2 turfs.
|
||||
* each spatial_grid_cell datum stores information about what is inside its covered area, so that searches through that area dont have to literally search
|
||||
* through all turfs themselves to know what is within it since view() calls are expensive, and so is iterating through stuff you dont want.
|
||||
* this allows you to only go through lists of what you want very cheaply.
|
||||
*
|
||||
* you can also register to objects entering and leaving a spatial cell, this allows you to do things like stay idle until a player enters, so you wont
|
||||
* have to use expensive view() calls or iteratite over the global list of players and call get_dist() on every one. which is fineish for a few things, but is
|
||||
* k * n operations for k objects iterating through n players.
|
||||
*
|
||||
* currently this system is only designed for searching for relatively uncommon things, small subsets of /atom/movable.
|
||||
* dont add stupid shit to the cells please, keep the information that the cells store to things that need to be searched for often
|
||||
*
|
||||
* The system currently implements two different "classes" of spatial type
|
||||
*
|
||||
* The first exists to support important_recursive_contents.
|
||||
* So if a client is inside a locker and the locker crosses a boundary, you'll still get a signal from the spatial grid.
|
||||
* These types are [SPATIAL_GRID_CONTENTS_TYPE_HEARING] and [SPATIAL_GRID_CONTENTS_TYPE_CLIENTS]
|
||||
*
|
||||
* The second pattern is more paired down, and supports more wide use.
|
||||
* Rather then the object and anything the object is in being sensitive, it's limited to just the object itself
|
||||
* Currently only [SPATIAL_GRID_CONTENTS_TYPE_ATMOS] uses this pattern. This is because it's far more common, and so worth optimizing
|
||||
*
|
||||
*/
|
||||
SUBSYSTEM_DEF(spatial_grid)
|
||||
flags = SS_NO_FIRE
|
||||
name = "Spatial Grid"
|
||||
init_order = INIT_ORDER_SPATIAL_GRID
|
||||
|
||||
///list of the spatial_grid_cell datums per z level, arranged in the order of y index then x index
|
||||
var/list/grids_by_z_level = list()
|
||||
///everything that spawns before us is added to this list until we initialize
|
||||
var/list/waiting_to_add_by_type = list(SPATIAL_GRID_CONTENTS_TYPE_HEARING = list(), SPATIAL_GRID_CONTENTS_TYPE_CLIENTS = list(), SPATIAL_GRID_CONTENTS_TYPE_ATMOS = list())
|
||||
///associative list of the form: movable.spatial_grid_key (string) -> inner list of spatial grid types for that key.
|
||||
///inner lists contain contents channel types such as SPATIAL_GRID_CONTENTS_TYPE_HEARING etc.
|
||||
///we use this to make adding to a cell static cost, and to save on memory
|
||||
var/list/spatial_grid_categories = list()
|
||||
|
||||
var/cells_on_x_axis = 0
|
||||
var/cells_on_y_axis = 0
|
||||
|
||||
///empty spatial grid cell content lists are just a reference to this instead of a standalone list to save memory without needed to check if its null when iterating
|
||||
var/list/dummy_list = list()
|
||||
|
||||
///list of all of /mob/oranges_ear instances we have pregenerated for view() iteration speedup
|
||||
var/list/mob/oranges_ear/pregenerated_oranges_ears = list()
|
||||
///how many pregenerated /mob/oranges_ear instances currently exist. this should hopefully never exceed its starting value
|
||||
var/number_of_oranges_ears = NUMBER_OF_PREGENERATED_ORANGES_EARS
|
||||
|
||||
/datum/controller/subsystem/spatial_grid/Initialize()
|
||||
cells_on_x_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxx)
|
||||
cells_on_y_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxy)
|
||||
|
||||
// enter_cell only runs if 'initialized'
|
||||
initialized = TRUE
|
||||
|
||||
for(var/z in GLOB.space_manager.z_list)
|
||||
var/datum/space_level/space_level = GLOB.space_manager.get_zlev(z)
|
||||
propogate_spatial_grid_to_new_z(null, space_level)
|
||||
CHECK_TICK
|
||||
|
||||
//go through the pre init queue for anything waiting to be let in the grid
|
||||
for(var/channel_type in waiting_to_add_by_type)
|
||||
for(var/atom/movable/movable as anything in waiting_to_add_by_type[channel_type])
|
||||
var/turf/movable_turf = get_turf(movable)
|
||||
if(movable_turf)
|
||||
enter_cell(movable, movable_turf)
|
||||
|
||||
UnregisterSignal(movable, COMSIG_PARENT_QDELETING)
|
||||
waiting_to_add_by_type[channel_type] -= movable
|
||||
|
||||
pregenerate_more_oranges_ears(NUMBER_OF_PREGENERATED_ORANGES_EARS)
|
||||
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_NEW_Z, PROC_REF(propogate_spatial_grid_to_new_z))
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, PROC_REF(after_world_bounds_expanded))
|
||||
|
||||
///add a movable to the pre init queue for whichever type is specified so that when the subsystem initializes they get added to the grid
|
||||
/datum/controller/subsystem/spatial_grid/proc/enter_pre_init_queue(atom/movable/waiting_movable, type)
|
||||
RegisterSignal(waiting_movable, COMSIG_PARENT_QDELETING, PROC_REF(queued_item_deleted), override = TRUE)
|
||||
//override because something can enter the queue for two different types but that is done through unrelated procs that shouldnt know about eachother
|
||||
waiting_to_add_by_type[type] += waiting_movable
|
||||
|
||||
///removes an initialized and probably deleted movable from our pre init queue before we're initialized
|
||||
/datum/controller/subsystem/spatial_grid/proc/remove_from_pre_init_queue(atom/movable/movable_to_remove, exclusive_type)
|
||||
if(exclusive_type)
|
||||
waiting_to_add_by_type[exclusive_type] -= movable_to_remove
|
||||
|
||||
var/waiting_movable_is_in_other_queues = FALSE//we need to check if this movable is inside the other queues
|
||||
for(var/type in waiting_to_add_by_type)
|
||||
if(movable_to_remove in waiting_to_add_by_type[type])
|
||||
waiting_movable_is_in_other_queues = TRUE
|
||||
|
||||
if(!waiting_movable_is_in_other_queues)
|
||||
UnregisterSignal(movable_to_remove, COMSIG_PARENT_QDELETING)
|
||||
|
||||
return
|
||||
|
||||
UnregisterSignal(movable_to_remove, COMSIG_PARENT_QDELETING)
|
||||
for(var/type in waiting_to_add_by_type)
|
||||
waiting_to_add_by_type[type] -= movable_to_remove
|
||||
|
||||
///if a movable is inside our pre init queue before we're initialized and it gets deleted we need to remove that reference with this proc
|
||||
/datum/controller/subsystem/spatial_grid/proc/queued_item_deleted(atom/movable/movable_being_deleted)
|
||||
SIGNAL_HANDLER
|
||||
remove_from_pre_init_queue(movable_being_deleted, null)
|
||||
|
||||
///creates the spatial grid for a new z level
|
||||
/datum/controller/subsystem/spatial_grid/proc/propogate_spatial_grid_to_new_z(datum/controller/subsystem/processing/dcs/fucking_dcs, datum/space_level/z_level)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/list/new_cell_grid = list()
|
||||
|
||||
grids_by_z_level += list(new_cell_grid)
|
||||
|
||||
for(var/y in 1 to cells_on_y_axis)
|
||||
new_cell_grid += list(list())
|
||||
for(var/x in 1 to cells_on_x_axis)
|
||||
var/datum/spatial_grid_cell/cell = new(x, y, z_level.zpos)
|
||||
new_cell_grid[y] += cell
|
||||
|
||||
///adds cells to the grid for every z level when world.maxx or world.maxy is expanded after this subsystem is initialized. hopefully this is never needed.
|
||||
///because i never tested this.
|
||||
/datum/controller/subsystem/spatial_grid/proc/after_world_bounds_expanded(datum/controller/subsystem/processing/dcs/fucking_dcs, has_expanded_world_maxx, has_expanded_world_maxy)
|
||||
SIGNAL_HANDLER
|
||||
var/old_x_axis = cells_on_x_axis
|
||||
var/old_y_axis = cells_on_y_axis
|
||||
|
||||
cells_on_x_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxx)
|
||||
cells_on_y_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxy)
|
||||
|
||||
for(var/z_level in 1 to length(grids_by_z_level))
|
||||
var/list/z_level_gridmap = grids_by_z_level[z_level]
|
||||
|
||||
for(var/cell_row_for_expanded_y_axis in 1 to cells_on_y_axis)
|
||||
|
||||
if(cell_row_for_expanded_y_axis > old_y_axis)//we are past the old length of the number of rows, so add to the list
|
||||
z_level_gridmap += list(list())
|
||||
|
||||
//now we know theres a row at this position, so add cells to it that need to be added and update the ones that already exist
|
||||
var/list/cell_row = z_level_gridmap[cell_row_for_expanded_y_axis]
|
||||
|
||||
for(var/grid_cell_for_expanded_x_axis in 1 to cells_on_x_axis)
|
||||
|
||||
if(grid_cell_for_expanded_x_axis > old_x_axis)
|
||||
var/datum/spatial_grid_cell/new_cell_inserted = new(grid_cell_for_expanded_x_axis, cell_row_for_expanded_y_axis, z_level)
|
||||
cell_row += new_cell_inserted
|
||||
continue
|
||||
|
||||
//now we know the cell index we're at contains an already existing cell that needs its x and y values updated
|
||||
var/datum/spatial_grid_cell/old_cell_that_needs_updating = cell_row[grid_cell_for_expanded_x_axis]
|
||||
old_cell_that_needs_updating.cell_x = grid_cell_for_expanded_x_axis
|
||||
old_cell_that_needs_updating.cell_y = cell_row_for_expanded_y_axis
|
||||
|
||||
///the left or bottom side index of a box composed of spatial grid cells with the given actual center x or y coordinate
|
||||
#define BOUNDING_BOX_MIN(center_coord) max(GET_SPATIAL_INDEX(center_coord - range), 1)
|
||||
///the right or upper side index of a box composed of spatial grid cells with the given center x or y coordinate.
|
||||
///outputted value cant exceed the number of cells on that axis
|
||||
#define BOUNDING_BOX_MAX(center_coord, axis_size) min(GET_SPATIAL_INDEX(center_coord + range), axis_size)
|
||||
|
||||
/**
|
||||
* https://en.wikipedia.org/wiki/Range_searching#Orthogonal_range_searching
|
||||
*
|
||||
* searches through the grid cells intersecting a rectangular search space (with sides of length 2 * range) then returns all contents of type inside them.
|
||||
* much faster than iterating through view() to find all of what you want.
|
||||
*
|
||||
* this does NOT return things only in range distance from center! the search space is a square not a circle, if you want only things in a certain distance
|
||||
* then you need to filter that yourself
|
||||
*
|
||||
* * center - the atom that is the center of the searched circle
|
||||
* * type - the type of grid contents you are looking for, see __DEFINES/spatial_grid.dm
|
||||
* * range - the bigger this is, the more spatial grid cells the search space intersects
|
||||
*/
|
||||
/datum/controller/subsystem/spatial_grid/proc/orthogonal_range_search(atom/center, type, range)
|
||||
var/turf/center_turf = get_turf(center)
|
||||
|
||||
var/center_x = center_turf.x//used inside the macros
|
||||
var/center_y = center_turf.y
|
||||
|
||||
. = list()
|
||||
|
||||
//technically THIS list only contains lists, but inside those lists are grid cell datums and we can go without a SINGLE var init if we do this
|
||||
var/list/list/datum/spatial_grid_cell/grid_level = grids_by_z_level[center_turf.z]
|
||||
|
||||
switch(type)
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
for(var/row in BOUNDING_BOX_MIN(center_y) to BOUNDING_BOX_MAX(center_y, cells_on_y_axis))
|
||||
for(var/x_index in BOUNDING_BOX_MIN(center_x) to BOUNDING_BOX_MAX(center_x, cells_on_x_axis))
|
||||
|
||||
. += grid_level[row][x_index].client_contents
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
for(var/row in BOUNDING_BOX_MIN(center_y) to BOUNDING_BOX_MAX(center_y, cells_on_y_axis))
|
||||
for(var/x_index in BOUNDING_BOX_MIN(center_x) to BOUNDING_BOX_MAX(center_x, cells_on_x_axis))
|
||||
|
||||
. += grid_level[row][x_index].hearing_contents
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
for(var/row in BOUNDING_BOX_MIN(center_y) to BOUNDING_BOX_MAX(center_y, cells_on_y_axis))
|
||||
for(var/x_index in BOUNDING_BOX_MIN(center_x) to BOUNDING_BOX_MAX(center_x, cells_on_x_axis))
|
||||
. += grid_level[row][x_index].atmos_contents
|
||||
|
||||
return .
|
||||
|
||||
///get the grid cell encomapassing targets coordinates
|
||||
/datum/controller/subsystem/spatial_grid/proc/get_cell_of(atom/target)
|
||||
var/turf/target_turf = get_turf(target)
|
||||
if(!target_turf)
|
||||
return
|
||||
|
||||
return grids_by_z_level[target_turf.z][GET_SPATIAL_INDEX(target_turf.y)][GET_SPATIAL_INDEX(target_turf.x)]
|
||||
|
||||
///get all grid cells intersecting the bounding box around center with sides of length 2 * range
|
||||
/datum/controller/subsystem/spatial_grid/proc/get_cells_in_range(atom/center, range)
|
||||
return get_cells_in_bounds(center, range, range)
|
||||
|
||||
///get all grid cells intersecting the bounding box around center with sides of length (2 * range_x, 2 * range_y)
|
||||
/datum/controller/subsystem/spatial_grid/proc/get_cells_in_bounds(atom/center, range_x, range_y)
|
||||
var/turf/center_turf = get_turf(center)
|
||||
|
||||
var/center_x = center_turf.x
|
||||
var/center_y = center_turf.y
|
||||
|
||||
var/list/intersecting_grid_cells = list()
|
||||
|
||||
//the minimum x and y cell indexes to test
|
||||
var/min_x = max(GET_SPATIAL_INDEX(center_x - range_x), 1)
|
||||
var/min_y = max(GET_SPATIAL_INDEX(center_y - range_y), 1)//calculating these indices only takes around 2 microseconds
|
||||
|
||||
//the maximum x and y cell indexes to test
|
||||
var/max_x = min(GET_SPATIAL_INDEX(center_x + range_x), cells_on_x_axis)
|
||||
var/max_y = min(GET_SPATIAL_INDEX(center_y + range_y), cells_on_y_axis)
|
||||
|
||||
var/list/grid_level = grids_by_z_level[center_turf.z]
|
||||
|
||||
for(var/row in min_y to max_y)
|
||||
var/list/grid_row = grid_level[row]
|
||||
|
||||
for(var/x_index in min_x to max_x)
|
||||
intersecting_grid_cells += grid_row[x_index]
|
||||
|
||||
return intersecting_grid_cells
|
||||
|
||||
/// Adds grid awareness to the passed in atom, of the passed in type
|
||||
/// Basically, when this atom moves between grids, it wants to have enter/exit cell called on it
|
||||
/datum/controller/subsystem/spatial_grid/proc/add_grid_awareness(atom/movable/add_to, type)
|
||||
// We need to ensure we have a new list reference, to build our new key out of
|
||||
var/list/current_list = spatial_grid_categories[add_to.spatial_grid_key]
|
||||
if(current_list)
|
||||
current_list = current_list.Copy()
|
||||
else
|
||||
current_list = list()
|
||||
// Now we do a binary insert, to ensure it's sorted (don't wanna overcache)
|
||||
BINARY_INSERT_DEFINE(type, current_list, SORT_VAR_NO_TYPE, type, SORT_COMPARE_DIRECTLY, COMPARE_KEY)
|
||||
update_grid_awareness(add_to, current_list)
|
||||
|
||||
/// Removes grid awareness from the passed in atom, of the passed in type
|
||||
/datum/controller/subsystem/spatial_grid/proc/remove_grid_awareness(atom/movable/remove_from, type)
|
||||
// We need to ensure we have a new list reference, to build our new key out of
|
||||
var/list/current_list = spatial_grid_categories[remove_from.spatial_grid_key]
|
||||
if(current_list)
|
||||
current_list = current_list.Copy()
|
||||
else
|
||||
current_list = list()
|
||||
current_list -= type
|
||||
update_grid_awareness(remove_from, current_list)
|
||||
|
||||
/// Alerts the atom's current cell that it wishes to be treated as a member
|
||||
/// This functionally amounts to "hey, I was recently made aware by [add_grid_awareness], please insert me into my current cell"
|
||||
/datum/controller/subsystem/spatial_grid/proc/add_grid_membership(atom/movable/add_to, turf/target_turf, type)
|
||||
if(!target_turf)
|
||||
return
|
||||
if(initialized)
|
||||
add_single_type(add_to, target_turf, type)
|
||||
else //SSspatial_grid isnt init'd yet, add ourselves to the queue
|
||||
enter_pre_init_queue(add_to, type)
|
||||
|
||||
/// Removes grid membership from the passed in atom, of the passed in type
|
||||
/datum/controller/subsystem/spatial_grid/proc/remove_grid_membership(atom/movable/remove_from, turf/target_turf, type)
|
||||
if(!target_turf)
|
||||
return
|
||||
if(initialized)
|
||||
remove_single_type(remove_from, target_turf, type)
|
||||
else //SSspatial_grid isnt init'd yet, remove ourselves from the queue
|
||||
remove_from_pre_init_queue(remove_from, type)
|
||||
|
||||
/// Updates the string that atoms hold that stores their grid awareness
|
||||
/// We will use it to key into their spatial grid categories later
|
||||
/datum/controller/subsystem/spatial_grid/proc/update_grid_awareness(atom/movable/update, list/new_list)
|
||||
// We locally store a stringified version of the list, to prevent people trying to mutate it
|
||||
update.spatial_grid_key = new_list.Join("-")
|
||||
// Ensure the global representation is cached
|
||||
if(!spatial_grid_categories[update.spatial_grid_key])
|
||||
spatial_grid_categories[update.spatial_grid_key] = new_list
|
||||
|
||||
///find the spatial map cell that target belongs to, then add the target to it, as its type prefers.
|
||||
///make sure to provide the turf new_target is "in"
|
||||
/datum/controller/subsystem/spatial_grid/proc/enter_cell(atom/movable/new_target, turf/target_turf)
|
||||
if(!initialized)
|
||||
return
|
||||
if(QDELETED(new_target))
|
||||
CRASH("qdeleted or null target trying to enter the spatial grid!")
|
||||
|
||||
if(!target_turf || !new_target.spatial_grid_key)
|
||||
CRASH("null turf loc or a new_target that doesn't support it trying to enter the spatial grid!")
|
||||
|
||||
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
|
||||
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
|
||||
var/z_index = target_turf.z
|
||||
|
||||
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
|
||||
for(var/type in spatial_grid_categories[new_target.spatial_grid_key])
|
||||
switch(type)
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
var/list/new_target_contents = new_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
|
||||
GRID_CELL_SET(intersecting_cell.client_contents, new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
var/list/new_target_contents = new_target.important_recursive_contents
|
||||
GRID_CELL_SET(intersecting_cell.hearing_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
GRID_CELL_SET(intersecting_cell.atmos_contents, new_target)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_ATMOS), new_target)
|
||||
|
||||
///acts like enter_cell() but only adds the target to a specified type of grid cell contents list
|
||||
/datum/controller/subsystem/spatial_grid/proc/add_single_type(atom/movable/new_target, turf/target_turf, exclusive_type)
|
||||
if(!initialized)
|
||||
return
|
||||
if(QDELETED(new_target))
|
||||
CRASH("qdeleted or null target trying to enter the spatial grid!")
|
||||
|
||||
if(!target_turf || !(exclusive_type in spatial_grid_categories[new_target.spatial_grid_key]))
|
||||
CRASH("null turf loc or a new_target that doesn't support it trying to enter the spatial grid as a [exclusive_type]!")
|
||||
|
||||
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
|
||||
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
|
||||
var/z_index = target_turf.z
|
||||
|
||||
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
|
||||
switch(exclusive_type)
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
var/list/new_target_contents = new_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
|
||||
GRID_CELL_SET(intersecting_cell.client_contents, new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
var/list/new_target_contents = new_target.important_recursive_contents
|
||||
GRID_CELL_SET(intersecting_cell.hearing_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
GRID_CELL_SET(intersecting_cell.atmos_contents, new_target)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_ATMOS), new_target)
|
||||
|
||||
return intersecting_cell
|
||||
|
||||
/**
|
||||
* find the spatial map cell that target used to belong to, then remove the target (and sometimes its important_recusive_contents) from it.
|
||||
* make sure to provide the turf old_target used to be "in"
|
||||
*
|
||||
* * old_target - the thing we want to remove from the spatial grid cell
|
||||
* * target_turf - the turf we use to determine the cell we're removing from
|
||||
* * exclusive_type - either null or a valid contents channel. if you just want to remove a single type from the grid cell then use this
|
||||
*/
|
||||
/datum/controller/subsystem/spatial_grid/proc/exit_cell(atom/movable/old_target, turf/target_turf, exclusive_type)
|
||||
if(!initialized)
|
||||
return
|
||||
|
||||
if(!target_turf || !old_target.spatial_grid_key)
|
||||
stack_trace("/datum/controller/subsystem/spatial_grid/proc/exit_cell() was given null arguments or a old_target that doesn't use the spatial grid!")
|
||||
return FALSE
|
||||
|
||||
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
|
||||
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
|
||||
var/z_index = target_turf.z
|
||||
|
||||
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
|
||||
for(var/type in spatial_grid_categories[old_target.spatial_grid_key])
|
||||
switch(type)
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
var/list/old_target_contents = old_target.important_recursive_contents?[type] || old_target
|
||||
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target_contents)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(type), old_target_contents)
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
var/list/old_target_contents = old_target.important_recursive_contents?[type] || old_target
|
||||
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target_contents)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(type), old_target_contents)
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
GRID_CELL_REMOVE(intersecting_cell.atmos_contents, old_target)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(type), old_target)
|
||||
|
||||
return TRUE
|
||||
|
||||
///acts like exit_cell() but only removes the target from the specified type of grid cell contents list
|
||||
/datum/controller/subsystem/spatial_grid/proc/remove_single_type(atom/movable/old_target, turf/target_turf, exclusive_type)
|
||||
if(!target_turf || !exclusive_type || !old_target.spatial_grid_key)
|
||||
stack_trace("/datum/controller/subsystem/spatial_grid/proc/remove_single_type() was given null arguments or an old_target that doesn't use the spatial grid!")
|
||||
return FALSE
|
||||
|
||||
if(!(exclusive_type in spatial_grid_categories[old_target.spatial_grid_key]))
|
||||
return FALSE
|
||||
|
||||
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
|
||||
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
|
||||
var/z_index = target_turf.z
|
||||
|
||||
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
|
||||
|
||||
switch(exclusive_type)
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
var/list/old_target_contents = old_target.important_recursive_contents?[exclusive_type] || old_target //cache for sanic speeds (lists are references anyways)
|
||||
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target_contents)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target_contents)
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
var/list/old_target_contents = old_target.important_recursive_contents?[exclusive_type] || old_target
|
||||
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target_contents)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target_contents)
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
GRID_CELL_REMOVE(intersecting_cell.atmos_contents, old_target)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target)
|
||||
|
||||
return TRUE
|
||||
|
||||
/// if for whatever reason this movable is "untracked" e.g. it breaks the assumption that a movable is only inside the contents of any grid cell associated with its loc,
|
||||
/// this will error. this checks every grid cell in the world so dont call this on live unless you have to.
|
||||
/// returns TRUE if this movable is untracked, FALSE otherwise
|
||||
/datum/controller/subsystem/spatial_grid/proc/untracked_movable_error(atom/movable/movable_to_check)
|
||||
if(!movable_to_check?.spatial_grid_key)
|
||||
return FALSE
|
||||
|
||||
if(!initialized)
|
||||
return FALSE
|
||||
|
||||
var/datum/spatial_grid_cell/loc_cell = get_cell_of(movable_to_check)
|
||||
var/list/containing_cells = find_hanging_cell_refs_for_movable(movable_to_check, remove_from_cells=FALSE)
|
||||
//if we're in multiple cells, throw an error.
|
||||
//if we're in 1 cell but it cant be deduced by our location, throw an error.
|
||||
if(length(containing_cells) > 1 || (length(containing_cells) == 1 && loc_cell && containing_cells[1] != loc_cell && containing_cells[1] != null))
|
||||
var/error_data = ""
|
||||
|
||||
var/location_string = "which is in nullspace, and thus not be within the contents of any spatial grid cell"
|
||||
if(loc_cell)
|
||||
location_string = "which is supposed to only be in the contents of a spatial grid cell at coords: ([GRID_INDEX_TO_COORDS(loc_cell.cell_x)], [GRID_INDEX_TO_COORDS(loc_cell.cell_y)], [loc_cell.cell_z])"
|
||||
|
||||
var/error_explanation = "was in the contents of [length(containing_cells)] spatial grid cells when it was only supposed to be in one!"
|
||||
if(length(containing_cells) == 1)
|
||||
error_explanation = "was in the contents of 1 spatial grid cell but it was inside the area handled by another grid cell!"
|
||||
var/datum/spatial_grid_cell/bad_cell = containing_cells[1]
|
||||
|
||||
error_data = "within the contents of a cell at coords: ([GRID_INDEX_TO_COORDS(bad_cell.cell_x)], [GRID_INDEX_TO_COORDS(bad_cell.cell_y)], [bad_cell.cell_z])"
|
||||
|
||||
if(!error_data)
|
||||
for(var/datum/spatial_grid_cell/cell in containing_cells)
|
||||
var/coords = "([GRID_INDEX_TO_COORDS(cell.cell_x)], [GRID_INDEX_TO_COORDS(cell.cell_y)], [cell.cell_z])"
|
||||
var/contents = ""
|
||||
|
||||
if(movable_to_check in cell.hearing_contents)
|
||||
contents = "hearing"
|
||||
|
||||
if(movable_to_check in cell.client_contents)
|
||||
if(length(contents) > 0)
|
||||
contents = "[contents], client"
|
||||
else
|
||||
contents = "client"
|
||||
|
||||
if(movable_to_check in cell.atmos_contents)
|
||||
if(length(contents) > 0)
|
||||
contents = "[contents], atmos"
|
||||
else
|
||||
contents = "atmos"
|
||||
|
||||
if(length(error_data) > 0)
|
||||
error_data = "[error_data], {coords: [coords], within channels: [contents]}"
|
||||
else
|
||||
error_data = "within the contents of the following cells: {coords: [coords], within channels: [contents]}"
|
||||
|
||||
/**
|
||||
* example:
|
||||
*
|
||||
* /mob/living/trolls_the_maintainer instance, which is supposed to only be in the contents of a spatial grid cell at coords: (136, 136, 14),
|
||||
* was in the contents of 3 spatial grid cells when it was only supposed to be in one! within the contents of the following cells:
|
||||
* {(68, 153, 2), within channels: hearing},
|
||||
* {coords: (221, 170, 3), within channels: hearing},
|
||||
* {coords: (255, 153, 11), within channels: hearing},
|
||||
* {coords: (136, 136, 14), within channels: hearing}.
|
||||
*/
|
||||
stack_trace("[movable_to_check.type] instance, [location_string], [error_explanation] [error_data].")
|
||||
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* remove this movable from the grid by finding the grid cell its in and removing it from that.
|
||||
* if it cant infer a grid cell its located in (e.g. if its in nullspace but it can happen if the grid isnt expanded to a z level), search every grid cell.
|
||||
*/
|
||||
/datum/controller/subsystem/spatial_grid/proc/force_remove_from_grid(atom/movable/to_remove)
|
||||
if(!to_remove?.spatial_grid_key)
|
||||
return
|
||||
|
||||
if(!initialized)
|
||||
remove_from_pre_init_queue(to_remove)//the spatial grid doesnt exist yet, so just take it out of the queue
|
||||
return
|
||||
|
||||
#ifdef GAME_TESTS
|
||||
if(untracked_movable_error(to_remove))
|
||||
find_hanging_cell_refs_for_movable(to_remove, remove_from_cells=FALSE) //dont remove from cells because we should be able to see 2 errors
|
||||
return
|
||||
#endif
|
||||
|
||||
var/datum/spatial_grid_cell/loc_cell = get_cell_of(to_remove)
|
||||
|
||||
if(loc_cell)
|
||||
GRID_CELL_REMOVE_ALL(loc_cell, to_remove)
|
||||
else
|
||||
find_hanging_cell_refs_for_movable(to_remove, remove_from_cells=TRUE)
|
||||
|
||||
///remove this movable from the given spatial_grid_cell
|
||||
/datum/controller/subsystem/spatial_grid/proc/force_remove_from_cell(atom/movable/to_remove, datum/spatial_grid_cell/input_cell)
|
||||
if(!input_cell)
|
||||
return
|
||||
|
||||
GRID_CELL_REMOVE_ALL(input_cell, to_remove)
|
||||
|
||||
///if shit goes south, this will find hanging references for qdeleting movables inside the spatial grid
|
||||
/datum/controller/subsystem/spatial_grid/proc/find_hanging_cell_refs_for_movable(atom/movable/to_remove, remove_from_cells = TRUE)
|
||||
|
||||
var/list/queues_containing_movable = list()
|
||||
for(var/queue_channel in waiting_to_add_by_type)
|
||||
var/list/queue_list = waiting_to_add_by_type[queue_channel]
|
||||
if(to_remove in queue_list)
|
||||
queues_containing_movable += queue_channel//just add the associative key
|
||||
if(remove_from_cells)
|
||||
queue_list -= to_remove
|
||||
|
||||
if(!initialized)
|
||||
return queues_containing_movable
|
||||
|
||||
var/list/containing_cells = list()
|
||||
for(var/list/z_level_grid as anything in grids_by_z_level)
|
||||
for(var/list/cell_row as anything in z_level_grid)
|
||||
for(var/datum/spatial_grid_cell/cell as anything in cell_row)
|
||||
if(to_remove in (cell.hearing_contents | cell.client_contents | cell.atmos_contents))
|
||||
containing_cells += cell
|
||||
if(remove_from_cells)
|
||||
force_remove_from_cell(to_remove, cell)
|
||||
|
||||
return containing_cells
|
||||
|
||||
///debug proc for checking if a movable is in multiple cells when it shouldnt be (ie always unless multitile entering is implemented)
|
||||
/atom/proc/find_all_cells_containing(remove_from_cells = FALSE)
|
||||
var/datum/spatial_grid_cell/real_cell = SSspatial_grid.get_cell_of(src)
|
||||
var/list/containing_cells = SSspatial_grid.find_hanging_cell_refs_for_movable(src, remove_from_cells)
|
||||
|
||||
message_admins("[src] is located in the contents of [length(containing_cells)] spatial grid cells")
|
||||
|
||||
var/cell_coords = "the following cells contain [src]: "
|
||||
for(var/datum/spatial_grid_cell/cell as anything in containing_cells)
|
||||
cell_coords += "([cell.cell_x], [cell.cell_y], [cell.cell_z]), "
|
||||
|
||||
message_admins(cell_coords)
|
||||
message_admins("[src] is supposed to only be contained in the cell at indexes ([real_cell.cell_x], [real_cell.cell_y], [real_cell.cell_z]). but is contained at the cells at [cell_coords]")
|
||||
|
||||
///creates number_to_generate new oranges_ear's and adds them to the subsystems list of ears.
|
||||
///i really fucking hope this never gets called after init :clueless:
|
||||
/datum/controller/subsystem/spatial_grid/proc/pregenerate_more_oranges_ears(number_to_generate)
|
||||
for(var/new_ear in 1 to number_to_generate)
|
||||
pregenerated_oranges_ears += new/mob/oranges_ear(null)
|
||||
|
||||
number_of_oranges_ears = length(pregenerated_oranges_ears)
|
||||
|
||||
///allocate one [/mob/oranges_ear] mob per turf containing atoms_that_need_ears and give them a reference to every listed atom in their turf.
|
||||
///if an oranges_ear is allocated to a turf that already has an oranges_ear then the second one fails to allocate (and gives the existing one the atom it was assigned to)
|
||||
/datum/controller/subsystem/spatial_grid/proc/assign_oranges_ears(list/atoms_that_need_ears)
|
||||
var/input_length = length(atoms_that_need_ears)
|
||||
|
||||
if(input_length > number_of_oranges_ears)
|
||||
stack_trace("somehow, for some reason, more than the preset generated number of oranges ears was requested. thats fucking [number_of_oranges_ears]. this is not good that should literally never happen")
|
||||
pregenerate_more_oranges_ears(input_length - number_of_oranges_ears)//im still gonna DO IT but ill complain about it
|
||||
|
||||
. = list()
|
||||
|
||||
///the next unallocated /mob/oranges_ear that we try to allocate to assigned_atom's turf
|
||||
var/mob/oranges_ear/current_ear
|
||||
///the next atom in atoms_that_need_ears an ear assigned to it
|
||||
var/atom/assigned_atom
|
||||
///the turf loc of the current assigned_atom. turfs are used to track oranges_ears already assigned to one location so we dont allocate more than one
|
||||
///because allocating more than one oranges_ear to a given loc wastes view iterations
|
||||
var/turf/turf_loc
|
||||
|
||||
for(var/current_ear_index in 1 to input_length)
|
||||
assigned_atom = atoms_that_need_ears[current_ear_index]
|
||||
|
||||
turf_loc = get_turf(assigned_atom)
|
||||
if(!turf_loc)
|
||||
continue
|
||||
|
||||
current_ear = pregenerated_oranges_ears[current_ear_index]
|
||||
|
||||
if(turf_loc.assigned_oranges_ear)
|
||||
turf_loc.assigned_oranges_ear.references += assigned_atom
|
||||
continue //if theres already an oranges_ear mob at assigned_movable's turf we give assigned_movable to it instead and dont allocate ourselves
|
||||
|
||||
current_ear.references += assigned_atom
|
||||
|
||||
current_ear.loc = turf_loc //normally this is bad, but since this is meant to be as fast as possible we literally just need to exist there for view() to see us
|
||||
turf_loc.assigned_oranges_ear = current_ear
|
||||
|
||||
. += current_ear
|
||||
|
||||
///debug proc for finding how full the cells of src's z level are
|
||||
/atom/proc/find_grid_statistics_for_z_level(insert_clients = 0)
|
||||
var/raw_clients = 0
|
||||
var/raw_hearables = 0
|
||||
var/raw_atmos = 0
|
||||
|
||||
var/cells_with_clients = 0
|
||||
var/cells_with_hearables = 0
|
||||
var/cells_with_atmos = 0
|
||||
|
||||
var/list/client_list = list()
|
||||
var/list/hearable_list = list()
|
||||
var/list/atmos_list = list()
|
||||
|
||||
var/x_cell_count = world.maxx / SPATIAL_GRID_CELLSIZE
|
||||
var/y_cell_count = world.maxy / SPATIAL_GRID_CELLSIZE
|
||||
|
||||
var/total_cells = x_cell_count ** 2
|
||||
|
||||
var/average_clients_per_cell = 0
|
||||
var/average_hearables_per_cell = 0
|
||||
var/average_atmos_mech_per_call = 0
|
||||
|
||||
var/hearable_min_x = x_cell_count
|
||||
var/hearable_max_x = 1
|
||||
|
||||
var/hearable_min_y = y_cell_count
|
||||
var/hearable_max_y = 1
|
||||
|
||||
var/client_min_x = x_cell_count
|
||||
var/client_max_x = 1
|
||||
|
||||
var/client_min_y = y_cell_count
|
||||
var/client_max_y = 1
|
||||
|
||||
var/atmos_min_x = x_cell_count
|
||||
var/atmos_max_x = 1
|
||||
|
||||
var/atmos_min_y = y_cell_count
|
||||
var/atmos_max_y = 1
|
||||
|
||||
var/list/inserted_clients = list()
|
||||
|
||||
if(insert_clients)
|
||||
var/list/turfs = Z_TURFS(z)
|
||||
for(var/client_to_insert in 0 to insert_clients)
|
||||
var/turf/random_turf = pick(turfs)
|
||||
var/mob/fake_client = new()
|
||||
fake_client.important_recursive_contents = list(SPATIAL_GRID_CONTENTS_TYPE_HEARING = list(fake_client), SPATIAL_GRID_CONTENTS_TYPE_CLIENTS = list(fake_client))
|
||||
fake_client.forceMove(random_turf)
|
||||
inserted_clients += fake_client
|
||||
|
||||
var/list/all_z_level_cells = SSspatial_grid.get_cells_in_range(src, 1000)
|
||||
|
||||
for(var/datum/spatial_grid_cell/cell as anything in all_z_level_cells)
|
||||
var/client_length = length(cell.client_contents)
|
||||
var/hearable_length = length(cell.hearing_contents)
|
||||
var/atmos_length = length(cell.atmos_contents)
|
||||
|
||||
raw_clients += client_length
|
||||
raw_hearables += hearable_length
|
||||
raw_atmos += atmos_length
|
||||
|
||||
if(client_length)
|
||||
cells_with_clients++
|
||||
|
||||
client_list += cell.client_contents
|
||||
|
||||
if(cell.cell_x < client_min_x)
|
||||
client_min_x = cell.cell_x
|
||||
|
||||
if(cell.cell_x > client_max_x)
|
||||
client_max_x = cell.cell_x
|
||||
|
||||
if(cell.cell_y < client_min_y)
|
||||
client_min_y = cell.cell_y
|
||||
|
||||
if(cell.cell_y > client_max_y)
|
||||
client_max_y = cell.cell_y
|
||||
|
||||
if(hearable_length)
|
||||
cells_with_hearables++
|
||||
|
||||
hearable_list += cell.hearing_contents
|
||||
|
||||
if(cell.cell_x < hearable_min_x)
|
||||
hearable_min_x = cell.cell_x
|
||||
|
||||
if(cell.cell_x > hearable_max_x)
|
||||
hearable_max_x = cell.cell_x
|
||||
|
||||
if(cell.cell_y < hearable_min_y)
|
||||
hearable_min_y = cell.cell_y
|
||||
|
||||
if(cell.cell_y > hearable_max_y)
|
||||
hearable_max_y = cell.cell_y
|
||||
|
||||
if(raw_atmos)
|
||||
cells_with_atmos++
|
||||
|
||||
atmos_list += cell.atmos_contents
|
||||
|
||||
if(cell.cell_x < atmos_min_x)
|
||||
atmos_min_x = cell.cell_x
|
||||
|
||||
if(cell.cell_x > atmos_max_x)
|
||||
atmos_max_x = cell.cell_x
|
||||
|
||||
if(cell.cell_y < atmos_min_y)
|
||||
atmos_min_y = cell.cell_y
|
||||
|
||||
if(cell.cell_y > atmos_max_y)
|
||||
atmos_max_y = cell.cell_y
|
||||
|
||||
var/total_client_distance = 0
|
||||
var/total_hearable_distance = 0
|
||||
var/total_atmos_distance = 0
|
||||
|
||||
var/average_client_distance = 0
|
||||
var/average_hearable_distance = 0
|
||||
var/average_atmos_distance = 0
|
||||
|
||||
for(var/hearable in hearable_list)//n^2 btw
|
||||
for(var/other_hearable in hearable_list)
|
||||
if(hearable == other_hearable)
|
||||
continue
|
||||
total_hearable_distance += get_dist(hearable, other_hearable)
|
||||
|
||||
for(var/client in client_list)//n^2 btw
|
||||
for(var/other_client in client_list)
|
||||
if(client == other_client)
|
||||
continue
|
||||
total_client_distance += get_dist(client, other_client)
|
||||
|
||||
for(var/atmos in atmos_list)//n^2 btw
|
||||
for(var/other_atmos in atmos_list)
|
||||
if(atmos == other_atmos)
|
||||
continue
|
||||
total_atmos_distance += get_dist(atmos, other_atmos)
|
||||
|
||||
if(length(hearable_list))
|
||||
average_hearable_distance = total_hearable_distance / length(hearable_list)
|
||||
if(length(client_list))
|
||||
average_client_distance = total_client_distance / length(client_list)
|
||||
if(length(atmos_list))
|
||||
average_atmos_distance = total_atmos_distance / length(atmos_list)
|
||||
|
||||
average_clients_per_cell = raw_clients / total_cells
|
||||
average_hearables_per_cell = raw_hearables / total_cells
|
||||
average_atmos_mech_per_call = raw_atmos / total_cells
|
||||
|
||||
for(var/mob/inserted_client as anything in inserted_clients)
|
||||
qdel(inserted_client)
|
||||
|
||||
message_admins("on z level [z] there are [raw_clients] clients ([insert_clients] of whom are fakes inserted to random station turfs)\
|
||||
, [raw_hearables] hearables, and [raw_atmos] atmos machines. all of whom are inside the bounding box given by \
|
||||
clients: ([client_min_x], [client_min_y]) x ([client_max_x], [client_max_y]), \
|
||||
hearables: ([hearable_min_x], [hearable_min_y]) x ([hearable_max_x], [hearable_max_y]) \
|
||||
and atmos machines: ([atmos_min_x], [atmos_min_y]) x ([atmos_max_x], [atmos_max_y]), \
|
||||
on average there are [average_clients_per_cell] clients per cell, [average_hearables_per_cell] hearables per cell, \
|
||||
and [average_atmos_mech_per_call] per cell, \
|
||||
[cells_with_clients] cells have clients, [cells_with_hearables] have hearables, and [cells_with_atmos] have atmos machines \
|
||||
the average client distance is: [average_client_distance], the average hearable_distance is [average_hearable_distance], \
|
||||
and the average atmos distance is [average_atmos_distance] ")
|
||||
|
||||
#undef BOUNDING_BOX_MAX
|
||||
#undef BOUNDING_BOX_MIN
|
||||
|
||||
#undef NUMBER_OF_PREGENERATED_ORANGES_EARS
|
||||
@@ -82,6 +82,18 @@
|
||||
/// How far (in pixels) should this atom scatter when created/dropped/etc. Does not apply to mapped-in items.
|
||||
var/scatter_distance = 0
|
||||
|
||||
/**
|
||||
* an associative lazylist of relevant nested contents by "channel", the list is of the form: list(channel = list(important nested contents of that type))
|
||||
* each channel has a specific purpose and is meant to replace potentially expensive nested contents iteration.
|
||||
* do NOT add channels to this for little reason as it can add considerable memory usage.
|
||||
*/
|
||||
var/list/important_recursive_contents
|
||||
|
||||
/// String representing the spatial grid groups we want to be held in.
|
||||
/// acts as a key to the list of spatial grid contents types we exist in via SSspatial_grid.spatial_grid_categories.
|
||||
/// We do it like this to prevent people trying to mutate them and to save memory on holding the lists ourselves
|
||||
var/spatial_grid_key
|
||||
|
||||
/atom/movable/attempt_init(loc, ...)
|
||||
var/turf/T = get_turf(src)
|
||||
if(T && SSatoms.initialized != INITIALIZATION_INSSATOMS && GLOB.space_manager.is_zlevel_dirty(T.z))
|
||||
@@ -135,6 +147,13 @@
|
||||
if(opacity)
|
||||
RemoveElement(/datum/element/light_blocking)
|
||||
|
||||
moveToNullspace()
|
||||
|
||||
// This absolutely must be after moveToNullspace()
|
||||
// We rely on Entered and Exited to manage this list, and the copy of this list that is on any /atom/movable "Containers"
|
||||
// If we clear this before the nullspace move, a ref to this object will be hung in any of its movable containers
|
||||
LAZYNULL(important_recursive_contents)
|
||||
|
||||
//Returns an atom's power cell, if it has one. Overload for individual items.
|
||||
/atom/movable/proc/get_cell()
|
||||
return
|
||||
@@ -1181,3 +1200,101 @@
|
||||
|
||||
checking = next
|
||||
return FALSE
|
||||
|
||||
/atom/movable/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
|
||||
if(!LAZYLEN(gone.important_recursive_contents))
|
||||
return
|
||||
var/list/nested_locs = get_nested_locs(src) + src
|
||||
for(var/channel in gone.important_recursive_contents)
|
||||
for(var/atom/movable/location as anything in nested_locs)
|
||||
LAZYINITLIST(location.important_recursive_contents)
|
||||
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
||||
LAZYINITLIST(recursive_contents[channel])
|
||||
recursive_contents[channel] -= gone.important_recursive_contents[channel]
|
||||
switch(channel)
|
||||
if(RECURSIVE_CONTENTS_CLIENT_MOBS, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
if(!length(recursive_contents[channel]))
|
||||
// This relies on a nice property of the linked recursive and gridmap types
|
||||
// They're defined in relation to each other, so they have the same value
|
||||
SSspatial_grid.remove_grid_awareness(location, channel)
|
||||
ASSOC_UNSETEMPTY(recursive_contents, channel)
|
||||
UNSETEMPTY(location.important_recursive_contents)
|
||||
|
||||
/atom/movable/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
||||
. = ..()
|
||||
|
||||
if(!LAZYLEN(arrived.important_recursive_contents))
|
||||
return
|
||||
var/list/nested_locs = get_nested_locs(src) + src
|
||||
for(var/channel in arrived.important_recursive_contents)
|
||||
for(var/atom/movable/location as anything in nested_locs)
|
||||
LAZYINITLIST(location.important_recursive_contents)
|
||||
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
||||
LAZYINITLIST(recursive_contents[channel])
|
||||
switch(channel)
|
||||
if(RECURSIVE_CONTENTS_CLIENT_MOBS, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
if(!length(recursive_contents[channel]))
|
||||
SSspatial_grid.add_grid_awareness(location, channel)
|
||||
recursive_contents[channel] |= arrived.important_recursive_contents[channel]
|
||||
|
||||
///allows this movable to hear and adds itself to the important_recursive_contents list of itself and every movable loc its in
|
||||
/atom/movable/proc/become_hearing_sensitive(trait_source = TRAIT_GENERIC)
|
||||
var/already_hearing_sensitive = HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE)
|
||||
ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
|
||||
if(already_hearing_sensitive) // If we were already hearing sensitive, we don't wanna be in important_recursive_contents twice, else we'll have potential issues like one radio sending the same message multiple times
|
||||
return
|
||||
|
||||
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
||||
LAZYINITLIST(location.important_recursive_contents)
|
||||
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
||||
if(!length(recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
|
||||
SSspatial_grid.add_grid_awareness(location, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] += list(src)
|
||||
|
||||
var/turf/our_turf = get_turf(src)
|
||||
SSspatial_grid.add_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
|
||||
/**
|
||||
* removes the hearing sensitivity channel from the important_recursive_contents list of this and all nested locs containing us if there are no more sources of the trait left
|
||||
* since RECURSIVE_CONTENTS_HEARING_SENSITIVE is also a spatial grid content type, removes us from the spatial grid if the trait is removed
|
||||
*
|
||||
* * trait_source - trait source define or ALL, if ALL, force removes hearing sensitivity. if a trait source define, removes hearing sensitivity only if the trait is removed
|
||||
*/
|
||||
/atom/movable/proc/lose_hearing_sensitivity(trait_source = TRAIT_GENERIC)
|
||||
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
|
||||
return
|
||||
REMOVE_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
|
||||
if(HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
|
||||
return
|
||||
|
||||
var/turf/our_turf = get_turf(src)
|
||||
/// We get our awareness updated by the important recursive contents stuff, here we remove our membership
|
||||
SSspatial_grid.remove_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
|
||||
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
||||
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
||||
recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] -= src
|
||||
if(!length(recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
|
||||
SSspatial_grid.remove_grid_awareness(location, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
UNSETEMPTY(location.important_recursive_contents)
|
||||
|
||||
///allows this movable to know when it has "entered" another area no matter how many movable atoms its stuffed into, uses important_recursive_contents
|
||||
/atom/movable/proc/become_area_sensitive(trait_source = TRAIT_GENERIC)
|
||||
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
|
||||
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
||||
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
|
||||
ADD_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
|
||||
|
||||
///removes the area sensitive channel from the important_recursive_contents list of this and all nested locs containing us if there are no more source of the trait left
|
||||
/atom/movable/proc/lose_area_sensitivity(trait_source = TRAIT_GENERIC)
|
||||
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
|
||||
return
|
||||
REMOVE_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
|
||||
if(HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
|
||||
return
|
||||
|
||||
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
||||
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
|
||||
|
||||
@@ -100,6 +100,10 @@
|
||||
/// The destination z-level that atoms entering this turf will be automatically moved to.
|
||||
var/destination_z
|
||||
|
||||
///what /mob/oranges_ear instance is already assigned to us as there should only ever be one.
|
||||
///used for guaranteeing there is only one oranges_ear per turf when assigned, speeds up view() iteration
|
||||
var/mob/oranges_ear/assigned_oranges_ear
|
||||
|
||||
/turf/Initialize(mapload)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
if(initialized)
|
||||
|
||||
@@ -60,6 +60,8 @@ GLOBAL_DATUM_INIT(_preloader, /datum/dmm_suite/preloader, new())
|
||||
var/key_len = 0
|
||||
|
||||
var/datum/dmm_suite/loaded_map/LM = new
|
||||
var/expanded_x
|
||||
var/expanded_y
|
||||
// This try-catch is used as a budget "Finally" clause, as the dirt count
|
||||
// needs to be reset
|
||||
log_debug("[measureOnly ? "Measuring" : "Loading"] map: [fname]")
|
||||
@@ -122,6 +124,7 @@ GLOBAL_DATUM_INIT(_preloader, /datum/dmm_suite/preloader, new())
|
||||
if(!shouldCropMap && ycrd > world.maxy)
|
||||
if(!measureOnly)
|
||||
world.maxy = ycrd // Expand Y here. X is expanded in the loop below
|
||||
expanded_y = TRUE
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd)
|
||||
else
|
||||
bounds[MAP_MAXY] = max(bounds[MAP_MAXY], min(ycrd, world.maxy))
|
||||
@@ -140,6 +143,7 @@ GLOBAL_DATUM_INIT(_preloader, /datum/dmm_suite/preloader, new())
|
||||
break
|
||||
else
|
||||
world.maxx = xcrd
|
||||
expanded_x = TRUE
|
||||
|
||||
if(xcrd >= 1)
|
||||
var/model_key = copytext(line, tpos, tpos + key_len)
|
||||
@@ -166,6 +170,9 @@ GLOBAL_DATUM_INIT(_preloader, /datum/dmm_suite/preloader, new())
|
||||
CRASH("Bad Map bounds in [fname], Min x: [bounds[MAP_MINX]], Min y: [bounds[MAP_MINY]], Min z: [bounds[MAP_MINZ]], Max x: [bounds[MAP_MAXX]], Max y: [bounds[MAP_MAXY]], Max z: [bounds[MAP_MAXZ]]")
|
||||
else
|
||||
if(!measureOnly)
|
||||
if(expanded_x || expanded_y)
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, expanded_x, expanded_y)
|
||||
|
||||
for(var/t in block(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ], bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))
|
||||
var/turf/T = t
|
||||
// we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Spatial gridmap, cell tracking
|
||||
*
|
||||
* This datum exists to make the large, repeated "everything in some range" pattern faster
|
||||
* Rather then just refreshing against everything, we track all the cells in range of the passed in "window"
|
||||
* This lets us do entered/left logic, and make ordinarially quite expensive logic much cheaper
|
||||
*
|
||||
* Note: This system should not be used for things who have strict requirements about what is NOT in their processed entries
|
||||
* It should instead only be used for logic that only really cares about limiting how much gets "entered" in any one call
|
||||
* Because we apply this limitation, we can do things to make our code much less prone to unneeded work
|
||||
*/
|
||||
/datum/cell_tracker
|
||||
var/list/datum/spatial_grid_cell/member_cells = list()
|
||||
// Inner window
|
||||
// If a cell is inside this space, it will be entered into our membership list
|
||||
/// The height (y radius) of our inner window
|
||||
var/inner_window_x_radius
|
||||
/// The width (x radius) of our inner window
|
||||
var/inner_window_y_radius
|
||||
|
||||
// Outer window
|
||||
// If a cell is outside this space, it will be removed from our memebership list
|
||||
// This effectively applies a grace window, to prevent moving back and forth across a border line causing issues
|
||||
/// The height (y radius) of our outer window
|
||||
var/outer_window_x_radius
|
||||
/// The width (x radius) of our outer window
|
||||
var/outer_window_y_radius
|
||||
|
||||
/// Accepts a width and height to use for this tracker
|
||||
/// Also accepts the ratio to use between inner and outer window. Optional, defaults to 2
|
||||
/datum/cell_tracker/New(width, height, inner_outer_ratio)
|
||||
set_bounds(width, height, inner_outer_ratio)
|
||||
return ..()
|
||||
|
||||
/datum/cell_tracker/Destroy(force)
|
||||
stack_trace("Attempted to delete a cell tracker. They don't hold any refs outside of cells, what are you doing")
|
||||
if(!force)
|
||||
return QDEL_HINT_LETMELIVE
|
||||
member_cells.Cut()
|
||||
return ..()
|
||||
|
||||
/// Takes a width and height, and uses them to set the inner window, and interpolate the outer window
|
||||
/datum/cell_tracker/proc/set_bounds(width = 0, height = 0, ratio = 2)
|
||||
// We want to store these as radii, rather then width and height, since that's convineient for spatial grid code
|
||||
var/x_radius = CEILING(width, 2)
|
||||
var/y_radius = CEILING(height, 2)
|
||||
inner_window_x_radius = x_radius
|
||||
inner_window_y_radius = y_radius
|
||||
|
||||
outer_window_x_radius = x_radius * ratio
|
||||
outer_window_y_radius = y_radius * ratio
|
||||
|
||||
/// Returns a list of newly and formerly joined spatial grid managed objects of type [type] in the form list(new, old)
|
||||
/// Takes the center of our window as input
|
||||
/datum/cell_tracker/proc/recalculate_type_members(turf/center, type)
|
||||
var/list/new_and_old = recalculate_cells(center)
|
||||
|
||||
var/list/new_members = list()
|
||||
var/list/former_members = list()
|
||||
/// Pull out all the new and old memebers we want
|
||||
switch(type)
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1])
|
||||
new_members += cell.client_contents
|
||||
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2])
|
||||
former_members += cell.client_contents
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1])
|
||||
new_members += cell.hearing_contents
|
||||
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2])
|
||||
former_members += cell.hearing_contents
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1])
|
||||
new_members += cell.atmos_contents
|
||||
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2])
|
||||
former_members += cell.atmos_contents
|
||||
|
||||
return list(new_members, former_members)
|
||||
|
||||
/// Recalculates our member list, returns a list in the form list(new members, old members) for reaction
|
||||
/// Accepts the turf to use as our "center"
|
||||
/datum/cell_tracker/proc/recalculate_cells(turf/center)
|
||||
if(!center)
|
||||
CRASH("/datum/cell_tracker had an invalid location on refresh, ya done fucked")
|
||||
// This is a mild waste of cpu time. Consider optimizing by adding a new helper function to get just the space between two bounds
|
||||
// Assuming it ever becomes a real problem
|
||||
var/list/datum/spatial_grid_cell/inner_window = SSspatial_grid.get_cells_in_bounds(center, inner_window_x_radius, inner_window_y_radius)
|
||||
var/list/datum/spatial_grid_cell/outer_window = SSspatial_grid.get_cells_in_bounds(center, outer_window_x_radius, outer_window_y_radius)
|
||||
|
||||
var/list/datum/spatial_grid_cell/new_cells = inner_window - member_cells
|
||||
// The outer window may contain cells we don't actually have, so we do it like this
|
||||
var/list/datum/spatial_grid_cell/old_cells = member_cells - outer_window
|
||||
|
||||
// This whole thing is a naive implementation,
|
||||
// if it turns out to be expensive because of all the list operations I'll look closer at it
|
||||
member_cells -= old_cells
|
||||
member_cells += new_cells
|
||||
|
||||
return list(new_cells, old_cells)
|
||||
@@ -77,6 +77,7 @@
|
||||
#include "code\__DEFINES\hud_locations.dm"
|
||||
#include "code\__DEFINES\hydroponics_defines.dm"
|
||||
#include "code\__DEFINES\icon_smoothing_defines.dm"
|
||||
#include "code\__DEFINES\important_recursive_contents.dm"
|
||||
#include "code\__DEFINES\instrument_defines.dm"
|
||||
#include "code\__DEFINES\inventory_defines.dm"
|
||||
#include "code\__DEFINES\is_helpers.dm"
|
||||
@@ -127,6 +128,7 @@
|
||||
#include "code\__DEFINES\silicon_defines.dm"
|
||||
#include "code\__DEFINES\smith_defines.dm"
|
||||
#include "code\__DEFINES\sound_defines.dm"
|
||||
#include "code\__DEFINES\spatial_gridmap_defines.dm"
|
||||
#include "code\__DEFINES\speech_channels.dm"
|
||||
#include "code\__DEFINES\spell_defines.dm"
|
||||
#include "code\__DEFINES\stat.dm"
|
||||
@@ -204,6 +206,7 @@
|
||||
#include "code\__HELPERS\radiation_helpers.dm"
|
||||
#include "code\__HELPERS\sanitize_values.dm"
|
||||
#include "code\__HELPERS\screen_objs.dm"
|
||||
#include "code\__HELPERS\spatial_info.dm"
|
||||
#include "code\__HELPERS\stacktrace.dm"
|
||||
#include "code\__HELPERS\string_assoc_lists.dm"
|
||||
#include "code\__HELPERS\text.dm"
|
||||
@@ -383,6 +386,7 @@
|
||||
#include "code\controllers\subsystem\non_firing\SSradio.dm"
|
||||
#include "code\controllers\subsystem\non_firing\SSserver_queue.dm"
|
||||
#include "code\controllers\subsystem\non_firing\SSsounds.dm"
|
||||
#include "code\controllers\subsystem\non_firing\SSspatial_grid.dm"
|
||||
#include "code\controllers\subsystem\non_firing\SStitlescreen.dm"
|
||||
#include "code\controllers\subsystem\processing\SSai_behaviors.dm"
|
||||
#include "code\controllers\subsystem\processing\SSdcs.dm"
|
||||
@@ -3144,6 +3148,7 @@
|
||||
#include "code\modules\space_management\space_transition.dm"
|
||||
#include "code\modules\space_management\turf_reservation.dm"
|
||||
#include "code\modules\space_management\zlevel_manager.dm"
|
||||
#include "code\modules\spatial_grid\cell_tracker.dm"
|
||||
#include "code\modules\station_goals\bluespace_tap.dm"
|
||||
#include "code\modules\station_goals\bluespace_tap_events.dm"
|
||||
#include "code\modules\station_goals\bsa.dm"
|
||||
|
||||
Reference in New Issue
Block a user