mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 10:11:09 +00:00
[MIRROR] Ventcrawling improvements, performance and visual [MDB IGNORE] (#13461)
* Ventcrawling improvements, performance and visual (#66709) * Initial pipecrawl work Ok so pipecrawl images were updating EVERY TIME YOU MOVED This was not good mojo What I've done here is twofold First, I ensured pipecrawl updates only when the net changes. This breaks the current implementation, but I intend on fixing that Second, I moved our method of getting pipes to the spatial grid This isn't that great at the moment, but I intend on adding support for tracking entered/exited cells, which should make this much better * Much faster pipecrawling processing, niceties Adds a concept called a cell tracker datum. It manages a list of cells a passed in bound is "inside", and when queried will return a list of new cells, and old cells. Because we only really care about maintaining an absolute window of "CELLS WE ARE IN" and less about always removing cells we're not in, we can manage a second window to prevent moving up and down on a cell line causing a ton of updates. Uses this concept to optimize pipecrawling significantly, from 3ms per call before to roughly 0.03ms per call. Also moves pipecrawl images to their own plane, so they don't overlap ui elements * Pipecrawling effects niceties, direction help You can now move in more then one direction when pipecrawling This works as expected, if you hold up and left, move up for a while, and come to a fork, you'll go left Added some effects to pipecrawling. It'll darken the lighting plane slightly, so you get a nice effect instead of just fullbright. Also added a color matrix and drop shadow to the pipe images, this way they stand out a bit more. You now glide between pipe moves, rather then moving instantly. This doesn't effect your actual move rate, but it no longer feels jittery with say, 60fps * Bounds * Fixes runtimes, cache something somethign sonic speed * Reworks how being interested in the spatial grid is tracked Rather then checking multiple variables on the atom to consider, we instead check for the existence of a string key. This key is used by a list on the spatial grid subsystem to retrive a cached list of all of the atoms "types" Doing this requires doing a bit of extra work in important_recursive_contents code, but it allows us to separate being a part of the spatial grid from using important recursive contents, which is very nice. As a consequence, I've had to unroll some lazylist macros in important recursive contents logic. It's not ""that"" bad but it's not great either. Oh and this adds a slight cost to enter/exit cell, but it's minimal. Basically, rather then checking for different features of a grid member, we just iterate the list their string key points to. Very handy So there's an added cost of a list copy and all, but we save the headache of more types technically increasing the cost of addition/removal. I also made adding/removing from the grid into one "pulbic" proc rather then two different ones for each operation, because it was starting to get silly * waaa waa it doesn't compile * chord -> coord * Ensures important_recursive_contents is actually emptied on removal * Removes soul * Kyler's review Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com> * Kyler's review 2 Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com> * Kyler's review 3 Moves some procs around, improves some documentation, catches a few small issues Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com> * Ventcrawling improvements, performance and visual Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com>
This commit is contained in:
@@ -163,6 +163,9 @@
|
||||
|
||||
///---------------- MISC -----------------------
|
||||
|
||||
///Pipecrawling images
|
||||
#define PIPECRAWL_IMAGES_PLANE 180
|
||||
|
||||
///AI Camera Static
|
||||
#define CAMERA_STATIC_PLANE 200
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
///each cell in a spatial_grid is this many turfs in length and width
|
||||
#define SPATIAL_GRID_CELLSIZE 17
|
||||
|
||||
#define SPATIAL_GRID_CELLS_PER_SIDE(world_bounds) ROUND_UP((world_bounds) / SPATIAL_GRID_CELLSIZE)
|
||||
///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)
|
||||
#define SPATIAL_GRID_CELLS_PER_SIDE(world_bounds) GET_SPATIAL_INDEX(world_bounds)
|
||||
|
||||
#define SPATIAL_GRID_CHANNELS 2
|
||||
|
||||
@@ -11,11 +12,13 @@
|
||||
#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"
|
||||
|
||||
///whether movable is itself or containing something which should be in one of the spatial grid channels.
|
||||
#define HAS_SPATIAL_GRID_CONTENTS(movable) (movable.important_recursive_contents && (movable.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] || movable.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
|
||||
#define HAS_SPATIAL_GRID_CONTENTS(movable) (movable.spatial_grid_key)
|
||||
|
||||
// macros meant specifically to add/remove movables from lazy content lists in the spatial grid.
|
||||
// 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) \
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#define LAZYINITLIST(L) if (!L) { L = list(); }
|
||||
///If the provided list is empty, set it to null
|
||||
#define UNSETEMPTY(L) if (L && !length(L)) L = null
|
||||
///If the provided key -> list is empty, remove it from the list
|
||||
#define ASSOC_UNSETEMPTY(L, K) if (!length(L[K])) L -= K;
|
||||
///Like LAZYCOPY - copies an input list if the list has entries, If it doesn't the assigned list is nulled
|
||||
#define LAZYLISTDUPLICATE(L) (L ? L.Copy() : null )
|
||||
///Remove an item from the list, set the list to null if empty
|
||||
@@ -134,6 +136,7 @@
|
||||
} while(FALSE)
|
||||
|
||||
#define SORT_FIRST_INDEX(list) (list[1])
|
||||
#define SORT_COMPARE_DIRECTLY(thing) (thing)
|
||||
#define SORT_VAR_NO_TYPE(varname) var/varname
|
||||
/****
|
||||
* Even more custom binary search sorted insert, using defines instead of vars
|
||||
|
||||
@@ -200,6 +200,20 @@
|
||||
name = "parallax whitifier plane master"
|
||||
plane = PLANE_SPACE
|
||||
|
||||
/atom/movable/screen/plane_master/pipecrawl
|
||||
name = "pipecrawl plane master"
|
||||
plane = PIPECRAWL_IMAGES_PLANE
|
||||
appearance_flags = PLANE_MASTER
|
||||
blend_mode = BLEND_OVERLAY
|
||||
|
||||
/atom/movable/screen/plane_master/pipecrawl/Initialize(mapload)
|
||||
. = ..()
|
||||
// Makes everything on this plane slightly brighter
|
||||
// Has a nice effect, makes thing stand out
|
||||
color = list(1.2,0,0,0, 0,1.2,0,0, 0,0,1.2,0, 0,0,0,1, 0,0,0,0)
|
||||
// This serves a similar purpose, I want the pipes to pop
|
||||
add_filter("pipe_dropshadow", 1, drop_shadow_filter(x = -1, y= -1, size = 1, color = "#0000007A"))
|
||||
|
||||
/atom/movable/screen/plane_master/camera_static
|
||||
name = "camera static plane master"
|
||||
plane = CAMERA_STATIC_PLANE
|
||||
|
||||
@@ -115,6 +115,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
|
||||
O_LIGHTING_VISUAL_PLANE,
|
||||
ABOVE_LIGHTING_PLANE,
|
||||
CAMERA_STATIC_PLANE,
|
||||
PIPECRAWL_IMAGES_PLANE,
|
||||
ATMOS_GROUP_PLANE,
|
||||
FULLSCREEN_PLANE,
|
||||
RUNECHAT_PLANE,
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
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)
|
||||
. = ..()
|
||||
@@ -40,6 +42,7 @@
|
||||
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
|
||||
@@ -63,8 +66,16 @@
|
||||
* 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
|
||||
*
|
||||
* as of right now this system operates on a subset of the important_recursive_contents list for atom/movable, specifically
|
||||
* [RECURSIVE_CONTENTS_HEARING_SENSITIVE] and [RECURSIVE_CONTENTS_CLIENT_MOBS] because both are those are both 1. important and 2. commonly searched for
|
||||
* 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)
|
||||
can_fire = FALSE
|
||||
@@ -74,7 +85,11 @@ SUBSYSTEM_DEF(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(RECURSIVE_CONTENTS_HEARING_SENSITIVE = list(), RECURSIVE_CONTENTS_CLIENT_MOBS = list())
|
||||
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
|
||||
@@ -237,10 +252,10 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
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(ROUND_UP((center_coord - range) / SPATIAL_GRID_CELLSIZE), 1)
|
||||
#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(ROUND_UP((center_coord + range) / SPATIAL_GRID_CELLSIZE), axis_size)
|
||||
#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
|
||||
@@ -283,6 +298,11 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
|
||||
. += 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
|
||||
@@ -291,10 +311,14 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
if(!target_turf)
|
||||
return
|
||||
|
||||
return grids_by_z_level[target_turf.z][ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)][ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)]
|
||||
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
|
||||
@@ -303,12 +327,12 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
var/list/intersecting_grid_cells = list()
|
||||
|
||||
//the minimum x and y cell indexes to test
|
||||
var/min_x = max(ROUND_UP((center_x - range) / SPATIAL_GRID_CELLSIZE), 1)
|
||||
var/min_y = max(ROUND_UP((center_y - range) / SPATIAL_GRID_CELLSIZE), 1)//calculating these indices only takes around 2 microseconds
|
||||
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(ROUND_UP((center_x + range) / SPATIAL_GRID_CELLSIZE), cells_on_x_axis)
|
||||
var/max_y = min(ROUND_UP((center_y + range) / SPATIAL_GRID_CELLSIZE), cells_on_y_axis)
|
||||
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]
|
||||
|
||||
@@ -320,7 +344,59 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
|
||||
return intersecting_grid_cells
|
||||
|
||||
///find the spatial map cell that target belongs to, then add target's important_recusive_contents to it.
|
||||
/// 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)
|
||||
@@ -328,27 +404,62 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
if(QDELETED(new_target))
|
||||
CRASH("qdeleted or null target trying to enter the spatial grid!")
|
||||
|
||||
if(!target_turf || !new_target.important_recursive_contents)
|
||||
CRASH("null turf loc or a new_target without important_recursive_contents 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 = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
|
||||
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
|
||||
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(new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
GRID_CELL_SET(intersecting_cell.client_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
|
||||
if(new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
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])
|
||||
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), new_target.important_recursive_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)
|
||||
|
||||
/**
|
||||
* find the spatial map cell that target used to belong to, then subtract target's important_recusive_contents from it.
|
||||
* find the spatial map cell that target used to belong to, then remove the target (and sometimes it's 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
|
||||
@@ -358,56 +469,66 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
/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?.important_recursive_contents)
|
||||
stack_trace("/datum/controller/subsystem/spatial_grid/proc/exit_cell() was given null arguments or a new_target without important_recursive_contents!")
|
||||
|
||||
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 = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
|
||||
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
|
||||
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 //cache for sanic speeds (lists are references anyways)
|
||||
|
||||
if(old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
|
||||
if(old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
var/list/old_target_contents = old_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
|
||||
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
GRID_CELL_REMOVE(intersecting_cell.atmos_contents, old_target)
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_ATMOS), 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_contents_type(atom/movable/old_target, turf/target_turf, exclusive_type)
|
||||
if(!target_turf || !old_target?.important_recursive_contents || !exclusive_type)
|
||||
stack_trace("/datum/controller/subsystem/spatial_grid/proc/remove_single_contents_type() was given null arguments or a new_target without important_recursive_contents!")
|
||||
/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(!old_target.important_recursive_contents[exclusive_type])
|
||||
if(!(exclusive_type in spatial_grid_categories[old_target.spatial_grid_key]))
|
||||
return FALSE
|
||||
|
||||
var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
|
||||
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
|
||||
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)
|
||||
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
var/list/old_target_contents = old_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
|
||||
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
|
||||
|
||||
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
|
||||
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
var/list/old_target_contents = old_target.important_recursive_contents
|
||||
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
|
||||
|
||||
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
|
||||
|
||||
|
||||
///find the cell this movable is associated with and removes it from all lists
|
||||
/datum/controller/subsystem/spatial_grid/proc/force_remove_from_cell(atom/movable/to_remove, datum/spatial_grid_cell/input_cell)
|
||||
if(!initialized)
|
||||
@@ -422,6 +543,7 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
|
||||
GRID_CELL_REMOVE(input_cell.client_contents, to_remove)
|
||||
GRID_CELL_REMOVE(input_cell.hearing_contents, to_remove)
|
||||
GRID_CELL_REMOVE(input_cell.atmos_contents, 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)
|
||||
@@ -441,7 +563,7 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
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))
|
||||
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)
|
||||
@@ -466,30 +588,43 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
/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/total_cells = (world.maxx / SPATIAL_GRID_CELLSIZE) ** 2
|
||||
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 = (world.maxx / SPATIAL_GRID_CELLSIZE)
|
||||
var/hearable_min_x = x_cell_count
|
||||
var/hearable_max_x = 1
|
||||
|
||||
var/hearable_min_y = (world.maxy / SPATIAL_GRID_CELLSIZE)
|
||||
var/hearable_min_y = y_cell_count
|
||||
var/hearable_max_y = 1
|
||||
|
||||
var/client_min_x = (world.maxx / SPATIAL_GRID_CELLSIZE)
|
||||
var/client_min_x = x_cell_count
|
||||
var/client_max_x = 1
|
||||
|
||||
var/client_min_y = (world.maxy / SPATIAL_GRID_CELLSIZE)
|
||||
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)
|
||||
@@ -513,9 +648,11 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
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++
|
||||
@@ -551,11 +688,30 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
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)
|
||||
@@ -569,24 +725,36 @@ SUBSYSTEM_DEF(spatial_grid)
|
||||
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) \
|
||||
and [raw_hearables] hearables. all of whom are inside the bounding box given by \
|
||||
clients: ([client_min_x], [client_min_y]) x ([client_max_x], [client_max_y]) \
|
||||
and hearables: ([hearable_min_x], [hearable_min_y]) x ([hearable_max_x], [hearable_max_y]) \
|
||||
on average there are [average_clients_per_cell] clients per cell and [average_hearables_per_cell] hearables per cell. \
|
||||
[cells_with_clients] cells have clients and [cells_with_hearables] have hearables, \
|
||||
the average client distance is: [average_client_distance] and the average hearable_distance is [average_hearable_distance].")
|
||||
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 GRID_CELL_ADD
|
||||
#undef GRID_CELL_REMOVE
|
||||
|
||||
@@ -48,6 +48,11 @@
|
||||
///only the last container of a client eye has this list assuming no movement since SSparallax's last fire
|
||||
var/list/client_mobs_in_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
|
||||
|
||||
/**
|
||||
* In case you have multiple types, you automatically use the most useful one.
|
||||
* IE: Skating on ice, flippers on water, flying over chasm/space, etc.
|
||||
@@ -142,7 +147,7 @@
|
||||
qdel(move_packet)
|
||||
move_packet = null
|
||||
|
||||
if(important_recursive_contents && (important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] || important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
|
||||
if(spatial_grid_key)
|
||||
SSspatial_grid.force_remove_from_cell(src)
|
||||
|
||||
LAZYCLEARLIST(client_mobs_in_contents)
|
||||
@@ -684,8 +689,8 @@
|
||||
|
||||
if(HAS_SPATIAL_GRID_CONTENTS(src))
|
||||
if(old_turf && new_turf && (old_turf.z != new_turf.z \
|
||||
|| ROUND_UP(old_turf.x / SPATIAL_GRID_CELLSIZE) != ROUND_UP(new_turf.x / SPATIAL_GRID_CELLSIZE) \
|
||||
|| ROUND_UP(old_turf.y / SPATIAL_GRID_CELLSIZE) != ROUND_UP(new_turf.y / SPATIAL_GRID_CELLSIZE)))
|
||||
|| GET_SPATIAL_INDEX(old_turf.x) != GET_SPATIAL_INDEX(new_turf.x) \
|
||||
|| GET_SPATIAL_INDEX(old_turf.y) != GET_SPATIAL_INDEX(new_turf.y)))
|
||||
|
||||
SSspatial_grid.exit_cell(src, old_turf)
|
||||
SSspatial_grid.enter_cell(src, new_turf)
|
||||
@@ -760,36 +765,54 @@
|
||||
/atom/movable/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
|
||||
if(LAZYLEN(gone.important_recursive_contents))
|
||||
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)
|
||||
LAZYREMOVEASSOC(location.important_recursive_contents, channel, gone.important_recursive_contents[channel])
|
||||
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
|
||||
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))
|
||||
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)
|
||||
LAZYORASSOCLIST(location.important_recursive_contents, channel, arrived.important_recursive_contents[channel])
|
||||
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)
|
||||
ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
|
||||
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
|
||||
//RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_HEARING_SENSITIVE), .proc/on_hearing_sensitive_trait_loss)
|
||||
return
|
||||
|
||||
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
|
||||
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, 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)
|
||||
if(our_turf && SSspatial_grid.initialized)
|
||||
SSspatial_grid.enter_cell(src, our_turf)
|
||||
|
||||
else if(our_turf && !SSspatial_grid.initialized)//SSspatial_grid isnt init'd yet, add ourselves to the queue
|
||||
SSspatial_grid.enter_pre_init_queue(src, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
|
||||
ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
|
||||
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
|
||||
@@ -805,13 +828,16 @@
|
||||
return
|
||||
|
||||
var/turf/our_turf = get_turf(src)
|
||||
if(our_turf && SSspatial_grid.initialized)
|
||||
SSspatial_grid.exit_cell(src, our_turf)
|
||||
else if(our_turf && !SSspatial_grid.initialized)
|
||||
SSspatial_grid.remove_from_pre_init_queue(src, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
|
||||
/// 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)
|
||||
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, 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)
|
||||
@@ -835,27 +861,30 @@
|
||||
///propogates ourselves through our nested contents, similar to other important_recursive_contents procs
|
||||
///main difference is that client contents need to possibly duplicate recursive contents for the clients mob AND its eye
|
||||
/mob/proc/enable_client_mobs_in_contents()
|
||||
var/turf/our_turf = get_turf(src)
|
||||
|
||||
if(our_turf && SSspatial_grid.initialized)
|
||||
SSspatial_grid.enter_cell(src, our_turf, RECURSIVE_CONTENTS_CLIENT_MOBS)
|
||||
else if(our_turf && !SSspatial_grid.initialized)
|
||||
SSspatial_grid.enter_pre_init_queue(src, RECURSIVE_CONTENTS_CLIENT_MOBS)
|
||||
|
||||
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
|
||||
LAZYORASSOCLIST(movable_loc.important_recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS, src)
|
||||
LAZYINITLIST(movable_loc.important_recursive_contents)
|
||||
var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity
|
||||
if(!length(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
|
||||
SSspatial_grid.add_grid_awareness(movable_loc, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
|
||||
recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] |= src
|
||||
|
||||
var/turf/our_turf = get_turf(src)
|
||||
/// We got our awareness updated by the important recursive contents stuff, now we add our membership
|
||||
SSspatial_grid.add_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
|
||||
///Clears the clients channel of this mob
|
||||
/mob/proc/clear_important_client_contents()
|
||||
var/turf/our_turf = get_turf(src)
|
||||
|
||||
if(our_turf && SSspatial_grid.initialized)
|
||||
SSspatial_grid.remove_single_contents_type(src, our_turf, RECURSIVE_CONTENTS_CLIENT_MOBS)
|
||||
else if(our_turf && !SSspatial_grid.initialized)
|
||||
SSspatial_grid.remove_from_pre_init_queue(src, RECURSIVE_CONTENTS_CLIENT_MOBS)
|
||||
SSspatial_grid.remove_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
|
||||
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
|
||||
LAZYREMOVEASSOC(movable_loc.important_recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS, src)
|
||||
var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity
|
||||
recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] -= src
|
||||
if(!length(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
|
||||
SSspatial_grid.remove_grid_awareness(movable_loc, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
|
||||
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS)
|
||||
UNSETEMPTY(movable_loc.important_recursive_contents)
|
||||
|
||||
///Sets the anchored var and returns if it was sucessfully changed or not.
|
||||
/atom/movable/proc/set_anchored(anchorvalue)
|
||||
|
||||
@@ -89,6 +89,11 @@
|
||||
/obj/machinery/atmospherics/Initialize(mapload)
|
||||
if(mapload && name != initial(name))
|
||||
override_naming = TRUE
|
||||
var/turf/turf_loc = null
|
||||
if(isturf(loc))
|
||||
turf_loc = loc
|
||||
SSspatial_grid.add_grid_awareness(src, SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
SSspatial_grid.add_grid_membership(src, turf_loc, SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
return ..()
|
||||
|
||||
/obj/machinery/atmospherics/Destroy()
|
||||
@@ -497,21 +502,36 @@
|
||||
|
||||
// Handles mob movement inside a pipenet
|
||||
/obj/machinery/atmospherics/relaymove(mob/living/user, direction)
|
||||
|
||||
if(!direction || !(direction in GLOB.cardinals_multiz)) //cant go this way.
|
||||
if(!direction) //cant go this way.
|
||||
return
|
||||
if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug
|
||||
return
|
||||
var/obj/machinery/atmospherics/target_move = find_connecting(direction, user.ventcrawl_layer)
|
||||
|
||||
// We want to support holding two directions at once, so we do this
|
||||
var/obj/machinery/atmospherics/target_move
|
||||
for(var/canon_direction in GLOB.cardinals_multiz)
|
||||
if(!(direction & canon_direction))
|
||||
continue
|
||||
var/obj/machinery/atmospherics/temp_target = find_connecting(canon_direction, user.ventcrawl_layer)
|
||||
if(!temp_target)
|
||||
continue
|
||||
target_move = temp_target
|
||||
// If you're at a fork with two directions held, we will always prefer the direction you didn't last use
|
||||
// This way if you find a direction you've not used before, you take it, and if you don't, you take the other
|
||||
if(user.last_vent_dir == canon_direction)
|
||||
continue
|
||||
user.last_vent_dir = canon_direction
|
||||
break
|
||||
|
||||
if(!target_move)
|
||||
return
|
||||
if(target_move.vent_movement & VENTCRAWL_ALLOWED)
|
||||
|
||||
if(!(target_move.vent_movement & VENTCRAWL_ALLOWED))
|
||||
return
|
||||
user.forceMove(target_move)
|
||||
user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement
|
||||
var/list/pipenetdiff = return_pipenets() ^ target_move.return_pipenets()
|
||||
if(pipenetdiff.len)
|
||||
user.update_pipe_vision()
|
||||
user.update_pipe_vision(full_refresh = TRUE)
|
||||
if(world.time - user.last_played_vent > VENT_SOUND_DELAY)
|
||||
user.last_played_vent = world.time
|
||||
playsound(src, 'sound/machines/ventcrawl.ogg', 50, TRUE, -3)
|
||||
@@ -519,7 +539,18 @@
|
||||
//Would be great if this could be implemented when someone alt-clicks the image.
|
||||
if (target_move.vent_movement & VENTCRAWL_ENTRANCE_ALLOWED)
|
||||
user.handle_ventcrawl(target_move)
|
||||
//PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER?
|
||||
return
|
||||
|
||||
var/client/our_client = user.client
|
||||
if(!our_client)
|
||||
return
|
||||
our_client.eye = target_move
|
||||
// Let's smooth out that movement with an animate yeah?
|
||||
// If the new x is greater (move is left to right) we get a negative offset. vis versa
|
||||
our_client.pixel_x = (x - target_move.x) * world.icon_size
|
||||
our_client.pixel_y = (y - target_move.y) * world.icon_size
|
||||
animate(our_client, pixel_x = 0, pixel_y = 0, time = 0.05 SECONDS)
|
||||
our_client.move_delay = world.time + 0.05 SECONDS
|
||||
|
||||
/obj/machinery/atmospherics/AltClick(mob/living/L)
|
||||
if(vent_movement & VENTCRAWL_ALLOWED && istype(L))
|
||||
|
||||
@@ -1723,7 +1723,8 @@ GLOBAL_LIST_EMPTY(fire_appearances)
|
||||
return result
|
||||
|
||||
/mob/living/reset_perspective(atom/A)
|
||||
if(..())
|
||||
if(!..())
|
||||
return
|
||||
update_sight()
|
||||
update_fullscreen()
|
||||
update_pipe_vision()
|
||||
|
||||
@@ -102,7 +102,12 @@
|
||||
var/usable_hands = 2
|
||||
|
||||
var/list/pipes_shown = list()
|
||||
var/last_played_vent
|
||||
var/last_played_vent = 0
|
||||
/// The last direction we moved in a vent. Used to make holding two directions feel nice
|
||||
var/last_vent_dir = 0
|
||||
/// Cell tracker datum we use to manage the pipes around us, for faster ventcrawling
|
||||
/// Should only exist if you're in a pipe
|
||||
var/datum/cell_tracker/pipetracker
|
||||
|
||||
var/smoke_delay = 0 ///used to prevent spam with smoke reagent reaction on mob.
|
||||
|
||||
|
||||
@@ -83,34 +83,63 @@
|
||||
* One important thing to note however is that the movement of the client's eye is handled by the relaymove() proc in /obj/machinery/atmospherics.
|
||||
* We move first and then call update. Dont flip this around
|
||||
*/
|
||||
/mob/living/proc/update_pipe_vision()
|
||||
// Take the pipe images from the client
|
||||
if (!isnull(client))
|
||||
/mob/living/proc/update_pipe_vision(full_refresh = FALSE)
|
||||
// We're gonna color the lighting plane to make it darker while ventcrawling, so things look nicer
|
||||
var/atom/movable/screen/plane_master/lighting
|
||||
if(hud_used)
|
||||
lighting = hud_used?.plane_masters["[LIGHTING_PLANE]"]
|
||||
|
||||
// Take away all the pipe images if we're not doing anything with em
|
||||
if(isnull(client) || !HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) || !istype(loc, /obj/machinery/atmospherics) || !(movement_type & VENTCRAWLING))
|
||||
for(var/image/current_image in pipes_shown)
|
||||
client.images -= current_image
|
||||
pipes_shown.len = 0
|
||||
|
||||
// Give the pipe images to the client
|
||||
if(HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) && istype(loc, /obj/machinery/atmospherics) && movement_type & VENTCRAWLING)
|
||||
var/list/total_members = list()
|
||||
var/obj/machinery/atmospherics/current_location = loc
|
||||
for(var/datum/pipeline/location_pipeline in current_location.return_pipenets())
|
||||
total_members += location_pipeline.members
|
||||
total_members += location_pipeline.other_atmos_machines
|
||||
|
||||
if(!total_members.len)
|
||||
pipetracker = null
|
||||
lighting?.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#4d4d4d")
|
||||
return
|
||||
|
||||
if(client)
|
||||
for(var/obj/machinery/atmospherics/pipenet_part in total_members)
|
||||
// If the machinery is not in view or is not meant to be seen, continue
|
||||
if(!in_view_range(client.mob, pipenet_part))
|
||||
// This is a bit hacky but it makes the background darker, which has a nice effect
|
||||
lighting?.add_atom_colour("#4d4d4d", TEMPORARY_COLOUR_PRIORITY)
|
||||
|
||||
var/obj/machinery/atmospherics/current_location = loc
|
||||
var/list/our_pipenets = current_location.return_pipenets()
|
||||
|
||||
// We on occasion want to do a full rebuild. this lets us do that
|
||||
if(full_refresh)
|
||||
for(var/image/current_image in pipes_shown)
|
||||
client.images -= current_image
|
||||
pipes_shown.len = 0
|
||||
pipetracker = null
|
||||
|
||||
if(!pipetracker)
|
||||
pipetracker = new()
|
||||
|
||||
var/turf/our_turf = get_turf(src)
|
||||
// We're getting the smallest "range" arg we can pass to the spatial grid and still get all the stuff we need
|
||||
// We preload a bit more then we need so movement looks ok
|
||||
var/list/view_range = getviewsize(client.view)
|
||||
pipetracker.set_bounds(view_range[1] + 1, view_range[2] + 1)
|
||||
|
||||
var/list/entered_exited_pipes = pipetracker.recalculate_type_members(our_turf, SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
|
||||
var/list/pipes_gained = entered_exited_pipes[1]
|
||||
var/list/pipes_lost = entered_exited_pipes[2]
|
||||
|
||||
for(var/obj/machinery/atmospherics/pipenet_part as anything in pipes_lost)
|
||||
if(!pipenet_part.pipe_vision_img)
|
||||
continue
|
||||
client.images -= pipenet_part.pipe_vision_img
|
||||
pipes_shown -= pipenet_part.pipe_vision_img
|
||||
|
||||
for(var/obj/machinery/atmospherics/pipenet_part as anything in pipes_gained)
|
||||
// If the machinery is not part of our net or is not meant to be seen, continue
|
||||
var/list/thier_pipenets = pipenet_part.return_pipenets()
|
||||
if(!length(thier_pipenets & our_pipenets))
|
||||
continue
|
||||
if(!(pipenet_part.vent_movement & VENTCRAWL_CAN_SEE))
|
||||
continue
|
||||
|
||||
if(!pipenet_part.pipe_vision_img)
|
||||
pipenet_part.pipe_vision_img = image(pipenet_part, pipenet_part.loc, dir = pipenet_part.dir)
|
||||
pipenet_part.pipe_vision_img.plane = ABOVE_HUD_PLANE
|
||||
pipenet_part.pipe_vision_img.plane = PIPECRAWL_IMAGES_PLANE
|
||||
client.images += pipenet_part.pipe_vision_img
|
||||
pipes_shown += pipenet_part.pipe_vision_img
|
||||
|
||||
99
code/modules/spatial_grid/cell_tracker.dm
Normal file
99
code/modules/spatial_grid/cell_tracker.dm
Normal file
@@ -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)
|
||||
@@ -4128,6 +4128,7 @@
|
||||
#include "code\modules\shuttle\supply.dm"
|
||||
#include "code\modules\shuttle\syndicate.dm"
|
||||
#include "code\modules\shuttle\white_ship.dm"
|
||||
#include "code\modules\spatial_grid\cell_tracker.dm"
|
||||
#include "code\modules\spells\spell.dm"
|
||||
#include "code\modules\spells\spell_types\aimed.dm"
|
||||
#include "code\modules\spells\spell_types\area_teleport.dm"
|
||||
|
||||
Reference in New Issue
Block a user