From f97c48aa8e41e29168e7448a42b9fbcb7a8c2ea9 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Wed, 4 Dec 2024 22:36:23 +1000 Subject: [PATCH 01/14] Ventcrawl port --- code/__DEFINES/atmospherics.dm | 8 + code/__DEFINES/dcs/signals.dm | 2 +- code/__DEFINES/spatial_gridmap.dm | 35 +- code/__HELPERS/_lists.dm | 40 ++ code/_onclick/hud/rendering/plane_master.dm | 15 + code/_onclick/ventcrawl.dm | 201 +++++----- code/controllers/subsystem/spatial_gridmap.dm | 345 +++++++++++++----- code/game/atoms/atom_movable.dm | 123 ++++--- .../atmospherics/machinery/atmosmachinery.dm | 97 ++--- .../components/unary_devices/cryo.dm | 4 +- .../components/unary_devices/vent_pump.dm | 10 +- .../components/unary_devices/vent_scrubber.dm | 10 +- .../atmospherics/machinery/pipes/pipes.dm | 2 +- .../castes/hunter/abilities_hunter.dm | 4 +- .../castes/panther/abilities_panther.dm | 4 +- code/modules/mob/living/living.dm | 18 +- code/modules/mob/living/living_defines.dm | 6 + code/modules/mob/living/login.dm | 3 +- code/modules/spatial_grid/cell_tracker.dm | 99 +++++ tgmc.dme | 1 + 20 files changed, 719 insertions(+), 308 deletions(-) create mode 100644 code/modules/spatial_grid/cell_tracker.dm diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 8d6fa9723ed..bd256b29ac2 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -41,6 +41,14 @@ #define PIPING_DEFAULT_LAYER_ONLY (1<<2) //can only exist at PIPING_LAYER_DEFAULT #define PIPING_CARDINAL_AUTONORMALIZE (1<<3) //north/south east/west doesn't matter, auto normalize on build. +// Ventcrawling bitflags, handled in var/vent_movement +///Allows for ventcrawling to occur. All atmospheric machines have this flag on by default. Cryo is the exception +#define VENTCRAWL_ALLOWED (1<<0) +///Allows mobs to enter or leave from atmospheric machines. On for passive, unary, and scrubber vents. +#define VENTCRAWL_ENTRANCE_ALLOWED (1<<1) +///Used to check if a machinery is visible. Called by update_pipe_vision(). On by default for all except cryo. +#define VENTCRAWL_CAN_SEE (1<<2) + //HELPERS #define PIPING_LAYER_SHIFT(T, PipingLayer) \ if(T.dir & (NORTH|SOUTH)) { \ diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index ec035c4e288..4d2939c2aa4 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -539,7 +539,7 @@ #define COMSIG_LIVING_STATUS_MUTE "living_mute" //from base of mob/living/Mute() #define COMPONENT_NO_MUTE (1<<0) -#define COMSIG_LIVING_ADD_VENTCRAWL "living_add_ventcrawl" +#define COMSIG_LIVING_HANDLE_VENTCRAWL "living_handle_ventcrawl" #define COMSIG_LIVING_WEEDS_AT_LOC_CREATED "living_weeds_at_loc_created" ///from obj/alien/weeds/Initialize() #define COMSIG_LIVING_WEEDS_ADJACENT_REMOVED "living_weeds_adjacent_removed" ///from obj/alien/weeds/Destroy() diff --git a/code/__DEFINES/spatial_gridmap.dm b/code/__DEFINES/spatial_gridmap.dm index 858abc9d55b..7981b74378c 100644 --- a/code/__DEFINES/spatial_gridmap.dm +++ b/code/__DEFINES/spatial_gridmap.dm @@ -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(chord) ROUND_UP((chord) / SPATIAL_GRID_CELLSIZE) +#define SPATIAL_GRID_CELLS_PER_SIDE(world_bounds) GET_SPATIAL_INDEX(world_bounds) #define SPATIAL_GRID_CHANNELS 2 @@ -11,6 +12,34 @@ #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 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; \ + }; diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 27f9a24d0bf..65df0cc7cc6 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -13,6 +13,8 @@ #define reverseList(L) reverseRange(L.Copy()) #define LAZYADDASSOCSIMPLE(L, K, V) if(!L) { L = list(); } L[K] += V; #define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V); +///If the provided key -> list is empty, remove it from the list +#define ASSOC_UNSETEMPTY(L, K) if (!length(L[K])) L -= K; ///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects) #define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V); #define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } @@ -94,6 +96,44 @@ LIST.Insert(__BIN_MID, IN);\ } +#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 + * INPUT: Item to be inserted + * LIST: List to insert INPUT into + * TYPECONT: A define setting the var to the typepath of the contents of the list + * COMPARE: The item to compare against, usualy the same as INPUT + * COMPARISON: A define that takes an item to compare as input, and returns their comparable value + * COMPTYPE: How should the list be compared? Either COMPARE_KEY or COMPARE_VALUE. + */ +#define BINARY_INSERT_DEFINE(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ + do {\ + var/list/__BIN_LIST = LIST;\ + var/__BIN_CTTL = length(__BIN_LIST);\ + if(!__BIN_CTTL) {\ + __BIN_LIST += INPUT;\ + } else {\ + var/__BIN_LEFT = 1;\ + var/__BIN_RIGHT = __BIN_CTTL;\ + var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + ##TYPECONT(__BIN_ITEM);\ + while(__BIN_LEFT < __BIN_RIGHT) {\ + __BIN_ITEM = COMPTYPE;\ + if(##COMPARISON(__BIN_ITEM) <= ##COMPARISON(COMPARE)) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + };\ + __BIN_ITEM = COMPTYPE;\ + __BIN_MID = ##COMPARISON(__BIN_ITEM) > ##COMPARISON(COMPARE) ? __BIN_MID : __BIN_MID + 1;\ + __BIN_LIST.Insert(__BIN_MID, INPUT);\ + };\ + } while(FALSE) + //Returns a list in plain english as a string /proc/english_list(list/L, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) var/total = length(L) diff --git a/code/_onclick/hud/rendering/plane_master.dm b/code/_onclick/hud/rendering/plane_master.dm index 1c85b345fa5..531393073f0 100644 --- a/code/_onclick/hud/rendering/plane_master.dm +++ b/code/_onclick/hud/rendering/plane_master.dm @@ -188,3 +188,18 @@ name = "balloon alert plane" plane = BALLOON_CHAT_PLANE render_relay_plane = RENDER_PLANE_NON_GAME + +/atom/movable/screen/plane_master/pipecrawl + name = "pipecrawl plane master" + plane = ABOVE_HUD_LAYER + 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")) + diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index f9bfdfff686..3b438bf07f7 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -1,12 +1,9 @@ -GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list( - /obj/machinery/atmospherics/components/unary/vent_pump, - /obj/machinery/atmospherics/components/unary/vent_scrubber))) /mob/proc/start_ventcrawl() var/atom/pipe var/list/pipes = list() for(var/obj/machinery/atmospherics/components/unary/U in range(1)) - if(is_type_in_list(U, GLOB.ventcrawl_machinery) && Adjacent(U)) + if(U.vent_movement & VENTCRAWL_ENTRANCE_ALLOWED && Adjacent(U)) pipes |= U if(!pipes || !length(pipes)) balloon_alert(src, "No pipes in range!") @@ -18,104 +15,126 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list( if(!incapacitated() && pipe) return pipe -//VENTCRAWLING -/mob/living/proc/handle_ventcrawl(atom/A, crawl_time = 4.5 SECONDS, stealthy = FALSE) - if(!HAS_TRAIT(src, TRAIT_CAN_VENTCRAWL) || !Adjacent(A) || !canmove) +// VENTCRAWLING +// Handles the entrance and exit on ventcrawling +/mob/living/proc/handle_ventcrawl(obj/machinery/atmospherics/components/ventcrawl_target, crawl_time = 4.5 SECONDS, stealthy = FALSE) + + // Cache the vent_movement bitflag var from atmos machineries + var/vent_movement = ventcrawl_target.vent_movement + if(!HAS_TRAIT(src, TRAIT_CAN_VENTCRAWL)) + return + if(!Adjacent(ventcrawl_target)) return if(stat) - to_chat(src, "You must be conscious to do this!") + to_chat(src, span_warning("You must be conscious to do this!")) + return + if(buckled) + to_chat(src, span_warning("You can't vent crawl while buckled!")) + return + if(ventcrawl_target.welded) + to_chat(src, span_warning("You can't crawl around a welded vent!")) return - var/obj/machinery/atmospherics/components/unary/vent_found - - if(A) - vent_found = A - if(!istype(vent_found) || !vent_found.can_crawl_through()) - vent_found = null - - if(!vent_found) - for(var/obj/machinery/atmospherics/machine in range(1,src)) - if(!is_type_in_typecache(machine, GLOB.ventcrawl_machinery)) - continue - vent_found = machine - - if(!vent_found.can_crawl_through()) - vent_found = null - - if(vent_found) - break - - if(vent_found) - var/datum/pipeline/vent_found_parent = vent_found.parents[1] - if(vent_found_parent && (length(vent_found_parent.members) || vent_found_parent.other_atmosmch)) - visible_message(span_notice("[stealthy ? "[src] begins climbing into the ventilation system..." : ""]"),span_notice("You begin climbing into the ventilation system...")) - - if(!do_after(src, crawl_time, IGNORE_HELD_ITEM, vent_found, BUSY_ICON_GENERIC) || !client || !canmove) + if(vent_movement & VENTCRAWL_ENTRANCE_ALLOWED) + //Handle the exit here + if(src.is_ventcrawling && istype(loc, /obj/machinery/atmospherics)) + visible_message(span_notice("[src] begins climbing out from the ventilation system...") ,span_notice("You begin climbing out from the ventilation system...")) + if(!do_after(src, crawl_time, target = ventcrawl_target))\ + return + if(!client) return - - /// TODO istype(src) stupidity - if(iscarbon(src))//It must have atleast been 1 to get this far - var/failed = FALSE - var/list/items_list = get_equipped_items() //include_pockets = TRUE) - if(length(items_list)) - failed = TRUE - if(failed) - to_chat(src, span_warning("You can't crawl around in the ventilation ducts with items!")) - return - - - visible_message(span_notice("[stealthy ? "[src] scrambles into the ventilation ducts!" : ""]"),span_notice("You climb into the ventilation ducts.")) - if(!stealthy) //Xenos with stealth vent crawling can silently enter/exit vents. playsound(src, get_sfx("alien_ventpass"), 35, TRUE) - - forceMove(vent_found) + visible_message(span_notice("[src] scrambles out from the ventilation ducts!"),span_notice("You scramble out from the ventilation ducts.")) + forceMove(ventcrawl_target.loc) + src.is_ventcrawling = FALSE update_pipe_vision() - else - to_chat(src, span_warning("This ventilation duct is not connected to anything!")) - - -/mob/living/proc/add_ventcrawl(obj/machinery/atmospherics/starting_machine) - if(!istype(starting_machine) || !starting_machine.can_see_pipes) - return - var/list/totalMembers = list() - for(var/datum/pipeline/P in starting_machine.returnPipenets()) - totalMembers += P.members - totalMembers += P.other_atmosmch - if(!length(totalMembers)) - return - if(client) - for(var/X in totalMembers) - var/obj/machinery/atmospherics/A = X //all elements in totalMembers are necessarily of this type. - if(!in_view_range(client.mob, A)) - continue - if(!A.pipe_vision_img) - A.pipe_vision_img = image(A, A.loc, layer = ABOVE_HUD_LAYER, dir = A.dir) - A.pipe_vision_img.plane = ABOVE_HUD_PLANE - A.pipe_vision_img.alpha = 200 - client.images += A.pipe_vision_img - pipes_shown += A.pipe_vision_img - is_ventcrawling = TRUE - SEND_SIGNAL(src, COMSIG_LIVING_ADD_VENTCRAWL) - return TRUE - -/mob/living/proc/remove_ventcrawl() - is_ventcrawling = FALSE - if(client) + //Entrance here + else + var/datum/pipeline/vent_parent = ventcrawl_target.parents[1] + if(vent_parent && (vent_parent.members.len || vent_parent.other_atmosmch)) + visible_message(span_notice("[src] begins climbing into the ventilation system...") ,span_notice("You begin climbing into the ventilation system...")) + if(!do_after(src, crawl_time, target = ventcrawl_target)) + return + if(!client) + return + if(!stealthy) //Xenos with stealth vent crawling can silently enter/exit vents. + playsound(src, get_sfx("alien_ventpass"), 35, TRUE) + visible_message(span_notice("[src] scrambles into the ventilation ducts!"),span_notice("You climb into the ventilation ducts.")) + move_into_vent(ventcrawl_target) + else + to_chat(src, span_warning("This ventilation duct is not connected to anything!")) + + +/** + * Moves living mob directly into the vent as a ventcrawler + * + * Arguments: + * * ventcrawl_target - The vent into which we are moving the mob + */ +/mob/living/proc/move_into_vent(obj/machinery/atmospherics/components/ventcrawl_target) + forceMove(ventcrawl_target) + src.is_ventcrawling = TRUE + update_pipe_vision() + +/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) || !src.is_ventcrawling || !istype(loc, /obj/machinery/atmospherics)) for(var/image/current_image in pipes_shown) client.images -= current_image + pipes_shown.len = 0 + pipetracker = null + lighting?.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#4d4d4d") + return - pipes_shown.len = 0 - - -/atom/proc/update_pipe_vision(atom/new_loc = null) - return + // 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.returnPipenets() -/mob/living/update_pipe_vision(atom/new_loc = null) - . = loc - if(new_loc) - . = new_loc - remove_ventcrawl() - add_ventcrawl(.) + // 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.returnPipenets() + 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_LAYER + client.images += pipenet_part.pipe_vision_img + pipes_shown += pipenet_part.pipe_vision_img diff --git a/code/controllers/subsystem/spatial_gridmap.dm b/code/controllers/subsystem/spatial_gridmap.dm index d013295d3a3..6558d5f464d 100644 --- a/code/controllers/subsystem/spatial_gridmap.dm +++ b/code/controllers/subsystem/spatial_gridmap.dm @@ -1,32 +1,6 @@ ///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 -// macros meant specifically to add/remove movables from the hearing_contents and client_contents 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; \ - }; - /** * # Spatial Grid Cell * @@ -52,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) . = ..() @@ -67,8 +43,9 @@ hearing_contents = dummy_list client_contents = dummy_list + atmos_contents = dummy_list -/datum/spatial_grid_cell/Destroy(force) +/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 @@ -90,8 +67,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 @@ -101,7 +86,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 @@ -114,14 +103,12 @@ SUBSYSTEM_DEF(spatial_grid) ///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() +/datum/controller/subsystem/spatial_grid/Initialize(start_timeofday) + . = ..() 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/datum/space_level/z_level as anything in SSmapping.z_list) propogate_spatial_grid_to_new_z(null, z_level) CHECK_TICK @@ -138,13 +125,12 @@ SUBSYSTEM_DEF(spatial_grid) 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)) - return SS_INIT_SUCCESS + RegisterSignal(SSdcs, COMSIG_GLOB_NEW_Z, .proc/propogate_spatial_grid_to_new_z) + RegisterSignal(SSdcs, COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, .proc/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_PREQDELETED, PROC_REF(queued_item_deleted), override = TRUE) + RegisterSignal(waiting_movable, COMSIG_PREQDELETED, .proc/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 @@ -267,10 +253,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 @@ -299,6 +285,7 @@ SUBSYSTEM_DEF(spatial_grid) //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/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)) @@ -312,6 +299,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 @@ -320,10 +312,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 @@ -332,12 +328,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] @@ -349,34 +345,122 @@ 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) return 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!") - var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE) - var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE) + 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(new_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]) - GRID_CELL_SET(intersecting_cell.client_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS]) + 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]!") - SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(RECURSIVE_CONTENTS_CLIENT_MOBS), new_target) + var/x_index = GET_SPATIAL_INDEX(target_turf.x) + var/y_index = GET_SPATIAL_INDEX(target_turf.y) + var/z_index = target_turf.z - if(new_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]) - GRID_CELL_SET(intersecting_cell.hearing_contents, new_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]) + 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]) - SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(RECURSIVE_CONTENTS_HEARING_SENSITIVE), new_target) + 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 @@ -386,36 +470,65 @@ 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) - CRASH("/datum/controller/subsystem/spatial_grid/proc/exit_cell() was given null arguments or a new_target without important_recursive_contents!") - - var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE) - var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE) - var/z_index = target_turf.z - var/list/grid = grids_by_z_level[z_index] - var/datum/spatial_grid_cell/intersecting_cell = grid[y_index][x_index] + 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 - if(exclusive_type && old_target.important_recursive_contents[exclusive_type]) - switch(exclusive_type) - if(RECURSIVE_CONTENTS_CLIENT_MOBS) - GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]) + var/x_index = GET_SPATIAL_INDEX(target_turf.x) + var/y_index = GET_SPATIAL_INDEX(target_turf.y) + var/z_index = target_turf.z - if(RECURSIVE_CONTENTS_HEARING_SENSITIVE) - GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]) + 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) + 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(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_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 - SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target) - return + var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index] - if(old_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]) - GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]) + switch(exclusive_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) + 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]) - SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), old_target) + if(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(old_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]) - GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]) + 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) - SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(RECURSIVE_CONTENTS_HEARING_SENSITIVE), 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) @@ -431,6 +544,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) @@ -450,7 +564,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) @@ -475,35 +589,47 @@ 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/x_cell_count = world.maxx / SPATIAL_GRID_CELLSIZE + var/y_cell_count = world.maxy / SPATIAL_GRID_CELLSIZE - var/total_cells = (world.maxx / SPATIAL_GRID_CELLSIZE) ** 2 + 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) var/list/turfs - turfs = block(locate(1,1,z), locate(world.maxx, world.maxy, z)) for(var/client_to_insert in 0 to insert_clients) @@ -518,9 +644,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++ @@ -556,11 +684,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) @@ -574,24 +721,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 diff --git a/code/game/atoms/atom_movable.dm b/code/game/atoms/atom_movable.dm index d421e50688b..7c4d67fdb3c 100644 --- a/code/game/atoms/atom_movable.dm +++ b/code/game/atoms/atom_movable.dm @@ -57,6 +57,11 @@ RU TGMC EDIT */ ///Internal holder for emissive blocker object, do not use directly use blocks_emissive var/atom/movable/emissive_blocker/em_block + /// 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 + ///Lazylist to keep track on the sources of illumination. var/list/affected_movable_lights ///Highest-intensity light affecting us, which determines our visibility. @@ -119,6 +124,10 @@ RU TGMC EDIT */ if(thrower) thrower = null + if(spatial_grid_key) + SSspatial_grid.force_remove_from_cell(src) + + LAZYCLEARLIST(client_mobs_in_contents) . = ..() @@ -128,8 +137,6 @@ RU TGMC EDIT */ moveToNullspace() - if(important_recursive_contents && (important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] || important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE])) - SSspatial_grid.force_remove_from_cell(src) //This absolutely must be after moveToNullspace() @@ -384,8 +391,8 @@ RU TGMC EDIT */ 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) @@ -456,19 +463,41 @@ RU TGMC EDIT */ /atom/movable/Exited(atom/movable/gone, direction) . = ..() - if(LAZYLEN(gone.important_recursive_contents)) - var/list/nested_locs = get_nested_locs(src) + src - for(var/channel in gone.important_recursive_contents) - for(var/atom/movable/location AS in nested_locs) - LAZYREMOVEASSOC(location.important_recursive_contents, channel, gone.important_recursive_contents[channel]) + + 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) . = ..() - if(LAZYLEN(arrived.important_recursive_contents)) - var/list/nested_locs = get_nested_locs(src) + src - for(var/channel in arrived.important_recursive_contents) - for(var/atom/movable/location AS in nested_locs) - LAZYORASSOCLIST(location.important_recursive_contents, channel, arrived.important_recursive_contents[channel]) + + 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] ///called when src is thrown into hit_atom /atom/movable/proc/throw_impact(atom/hit_atom, speed, bounce = TRUE) @@ -1125,19 +1154,19 @@ RU TGMC EDIT */ ///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_REF(on_hearing_sensitive_trait_loss)) - for(var/atom/movable/location AS in get_nested_locs(src) + src) - LAZYADDASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, src) - - var/turf/our_turf = get_turf(src) - if(our_turf && SSspatial_grid.initialized) - SSspatial_grid.enter_cell(src, our_turf) + return - 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) + 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) - ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source) + 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 @@ -1153,38 +1182,46 @@ RU TGMC EDIT */ 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) ///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.exit_cell(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) + LAZYINITLIST(movable_loc.important_recursive_contents) + var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity + LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]) + 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) ///Checks the gravity the atom is subjected to /atom/movable/proc/get_gravity() diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 8a4a1c2081f..bbfa7c251c9 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -38,19 +38,27 @@ var/pipe_state //icon_state as a pipe item var/on = FALSE + ///The bitflag that's being checked on ventcrawling. Default is to allow ventcrawling and seeing pipes. + var/vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE + ///Whether we get pipenet vision while inside or just see normally. var/can_see_pipes = TRUE /obj/machinery/atmospherics/Initialize(mapload) - . = ..() RegisterSignal(src, COMSIG_MOVABLE_SHUTTLE_CRUSH, PROC_REF(shuttle_crush)) + 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/proc/shuttle_crush() covered_by_shuttle = TRUE /obj/machinery/atmospherics/examine(mob/user) . = ..() - if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) + if((vent_movement & VENTCRAWL_ENTRANCE_ALLOWED) && isliving(user)) var/mob/living/L = user if(HAS_TRAIT(L, TRAIT_CAN_VENTCRAWL)) . += span_notice("Alt-click to crawl through it.") @@ -132,9 +140,10 @@ //Find a connecting /obj/machinery/atmospherics in specified direction /obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) for(var/obj/machinery/atmospherics/target in get_step(src, direction)) - if(target.initialize_directions & get_dir(target,src)) - if(connection_check(target, prompted_layer)) - return target + if(!(target.initialize_directions & get_dir(target,src)) && !istype(target, /obj/machinery/atmospherics/pipe)) + continue + if(connection_check(target, prompted_layer)) + return target /obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) @@ -255,50 +264,50 @@ L.ventcrawl_layer = piping_layer return ..() - -/obj/machinery/atmospherics/proc/climb_out(mob/living/user, turf/T) - if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_VENTCRAWL)) - return FALSE - var/vent_crawl_exit_time = 2 SECONDS - TIMER_COOLDOWN_START(user, COOLDOWN_VENTCRAWL, vent_crawl_exit_time) - - if(T.density || covered_by_shuttle) - to_chat(user, span_notice("You cannot climb out, the exit is blocked!")) - return - - var/silent_crawl = FALSE - if(isxeno(user)) - var/mob/living/carbon/xenomorph/X = user - silent_crawl = X.xeno_caste.silent_vent_crawl - vent_crawl_exit_time = X.xeno_caste.vent_exit_speed - if(!silent_crawl) //Xenos with silent crawl can silently enter/exit/move through vents. - visible_message(span_warning("You hear something squeezing through the ducts.")) - to_chat(user, span_notice("You begin to climb out of the ventilation system.")) - if(!do_after(user, vent_crawl_exit_time, IGNORE_HELD_ITEM, user.loc)) - return FALSE - user.remove_ventcrawl() - user.forceMove(T) - user.visible_message(span_warning("[user] climbs out of the ventilation ducts."), \ - span_notice("You climb out of the ventilation ducts.")) - if(!silent_crawl) - playsound(src, get_sfx("alien_ventpass"), 35, TRUE) - /obj/machinery/atmospherics/relaymove(mob/living/user, direction) direction &= initialize_directions - if(!direction || !(direction in GLOB.cardinals)) - if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) // If we try to move somewhere besides existing pipes while in the vent, we try to leave - climb_out(user, loc) + if(!direction) return - var/obj/machinery/atmospherics/target_move = findConnecting(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) + if(!(direction & canon_direction)) + continue + var/obj/machinery/atmospherics/temp_target = findConnecting(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) - if(direction & initialize_directions) - climb_out(user, loc) return + if(!(target_move.vent_movement & VENTCRAWL_ALLOWED)) + return user.forceMove(target_move) - user.update_pipe_vision() - user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement + user.update_pipe_vision(full_refresh = TRUE) + + //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) + 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 var/silent_crawl = FALSE //Some creatures can move through the vents silently if(isxeno(user)) @@ -307,13 +316,9 @@ if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_VENTSOUND) || silent_crawl) return TIMER_COOLDOWN_START(user, COOLDOWN_VENTSOUND, 3 SECONDS) - playsound(src, pick('sound/effects/alien/ventcrawl1.ogg','sound/effects/alien/ventcrawl2.ogg'), 50, TRUE, -3) - + playsound(src,'sound/effects/alien/ventcrawl2.ogg', 50, TRUE, -3) -/obj/machinery/atmospherics/proc/can_crawl_through() - return TRUE - /obj/machinery/atmospherics/proc/returnPipenets() return list() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index fa571c809ad..b4a44ab221a 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -32,6 +32,7 @@ var/message_cooldown var/breakout_time = 300 var/mob/living/carbon/occupant + vent_movement = NONE /obj/machinery/atmospherics/components/unary/cryo_cell/Initialize(mapload) . = ..() @@ -426,9 +427,6 @@ start_processing() update_icon() -/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through() - return // can't ventcrawl in or out of cryo. - /obj/machinery/atmospherics/components/unary/cryo_cell/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount, damage_type, damage_flag, effects, armor_penetration, isrightclick) if(!occupant) to_chat(xeno_attacker, span_xenowarning("There is nothing of interest in there.")) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm index a03641e2aa1..d86ace39f5a 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm @@ -20,6 +20,7 @@ var/pressure_checks = EXT_BOUND var/radio_filter_out var/radio_filter_in + vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED pipe_state = "uvent" @@ -94,7 +95,7 @@ cut_overlay(GLOB.welding_sparks) welded = FALSE update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img = image(src, loc, dir = dir) pipe_vision_img.plane = ABOVE_HUD_PLANE return TRUE else @@ -121,7 +122,7 @@ F.visible_message("[F] furiously claws at [src]!", "We manage to clear away the stuff blocking the vent", "You hear loud scraping noises.") welded = FALSE update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img = image(src, loc, dir = dir) pipe_vision_img.plane = ABOVE_HUD_PLANE playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) @@ -134,9 +135,6 @@ ..() update_icon_nopipes() -/obj/machinery/atmospherics/components/unary/vent_pump/can_crawl_through() - return !welded - /obj/machinery/atmospherics/components/unary/vent_pump/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE) if(xeno_attacker.status_flags & INCORPOREAL) return @@ -145,7 +143,7 @@ xeno_attacker.visible_message("[xeno_attacker] furiously claws at [src]!", "We manage to clear away the stuff blocking the vent", "You hear loud scraping noises.") welded = FALSE update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img = image(src, loc, dir = dir) pipe_vision_img.plane = ABOVE_HUD_PLANE playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index b872831b846..80b5025ec67 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -19,6 +19,7 @@ var/volume_rate = 200 var/widenet = 0 //is this scrubber acting on the 3x3 area around it. var/list/turf/adjacent_turfs = list() + vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED pipe_state = "scrubber" @@ -101,7 +102,7 @@ cut_overlay(GLOB.welding_sparks) welded = FALSE update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img = image(src, loc, dir = dir) pipe_vision_img.plane = ABOVE_HUD_PLANE cut_overlay(GLOB.welding_sparks) return TRUE @@ -126,7 +127,7 @@ F.visible_message("[F] furiously claws at [src]!", "We manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.") welded = FALSE update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img = image(src, loc, dir = dir) pipe_vision_img.plane = ABOVE_HUD_PLANE playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) @@ -135,9 +136,6 @@ if(welded) . += span_notice("It seems welded shut.") -/obj/machinery/atmospherics/components/unary/vent_scrubber/can_crawl_through() - return !welded - /obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE) if(xeno_attacker.status_flags & INCORPOREAL) return @@ -146,7 +144,7 @@ xeno_attacker.visible_message("[xeno_attacker] furiously claws at [src]!", "We manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.") welded = FALSE update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img = image(src, loc, dir = dir) pipe_vision_img.plane = ABOVE_HUD_PLANE playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm index 55bc0f72296..786dae60531 100644 --- a/code/modules/atmospherics/machinery/pipes/pipes.dm +++ b/code/modules/atmospherics/machinery/pipes/pipes.dm @@ -68,7 +68,7 @@ update_alpha() /obj/machinery/atmospherics/pipe/proc/update_alpha() - alpha = invisibility ? 64 : 255 + alpha = 255 /obj/machinery/atmospherics/pipe/proc/update_node_icon() for(var/i in 1 to device_type) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/hunter/abilities_hunter.dm b/code/modules/mob/living/carbon/xenomorph/castes/hunter/abilities_hunter.dm index be8f5635701..1a7c9396324 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/hunter/abilities_hunter.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/hunter/abilities_hunter.dm @@ -60,7 +60,7 @@ RegisterSignals(owner, list( COMSIG_XENOMORPH_GRAB, COMSIG_LIVING_IGNITED, - COMSIG_LIVING_ADD_VENTCRAWL), PROC_REF(cancel_stealth)) + COMSIG_LIVING_HANDLE_VENTCRAWL), PROC_REF(cancel_stealth)) RegisterSignal(owner, COMSIG_XENOMORPH_ATTACK_OBJ, PROC_REF(on_obj_attack)) @@ -90,7 +90,7 @@ COMSIG_XENOMORPH_ATTACK_OBJ, COMSIG_XENOMORPH_THROW_HIT, COMSIG_LIVING_IGNITED, - COMSIG_LIVING_ADD_VENTCRAWL, + COMSIG_LIVING_HANDLE_VENTCRAWL, SIGNAL_ADDTRAIT(TRAIT_KNOCKEDOUT), SIGNAL_ADDTRAIT(TRAIT_FLOORED), COMSIG_XENOMORPH_ZONE_SELECT, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/panther/abilities_panther.dm b/code/modules/mob/living/carbon/xenomorph/castes/panther/abilities_panther.dm index 464ae4e8f0b..9bae196e314 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/panther/abilities_panther.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/panther/abilities_panther.dm @@ -324,7 +324,7 @@ RegisterSignal(R, COMSIG_XENO_PROJECTILE_HIT, PROC_REF(evasion_dodge)) //This is where we actually check to see if we dodge the projectile. RegisterSignal(R, COMSIG_ATOM_BULLET_ACT, PROC_REF(evasion_flamer_hit)) //Register status effects and fire which impact evasion. RegisterSignal(R, COMSIG_LIVING_PRE_THROW_IMPACT, PROC_REF(evasion_throw_dodge)) //Register status effects and fire which impact evasion. - RegisterSignal(R, COMSIG_LIVING_ADD_VENTCRAWL, PROC_REF(evasion_deactivate)) + RegisterSignal(R, COMSIG_LIVING_HANDLE_VENTCRAWL, PROC_REF(evasion_deactivate)) set_toggle(TRUE) evade_active = TRUE //evasion is currently active @@ -384,7 +384,7 @@ COMSIG_XENO_PROJECTILE_HIT, COMSIG_LIVING_IGNITED, COMSIG_LIVING_PRE_THROW_IMPACT, - COMSIG_LIVING_ADD_VENTCRAWL, + COMSIG_LIVING_HANDLE_VENTCRAWL, COMSIG_ATOM_BULLET_ACT,)) set_toggle(FALSE) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 7fd5a5060d6..906bf2a34fc 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -645,19 +645,19 @@ below 100 is not dizzy return /mob/living/reset_perspective(atom/A) - . = ..() - if(!.) + if(!..()) return - update_sight() - if (stat == DEAD) - animate(client, pixel_x = 0, pixel_y = 0) + update_fullscreen() + update_pipe_vision() + +/// Proc used to handle the fullscreen overlay updates, realistically meant for the reset_perspective() proc. +/mob/living/proc/update_fullscreen() if(client.eye && client.eye != src) - var/atom/AT = client.eye - AT.get_remote_view_fullscreens(src) + var/atom/client_eye = client.eye + client_eye.get_remote_view_fullscreens(src) else clear_fullscreen("remote_view", 0) - update_pipe_vision() /mob/living/update_sight() if(SSticker.current_state == GAME_STATE_FINISHED && !is_centcom_level(z)) //Reveal ghosts to remaining survivors @@ -916,7 +916,7 @@ below 100 is not dizzy /mob/living/carbon/xenomorph/transfer_mob(mob/candidate) . = ..() if(is_ventcrawling) //If we are in a vent, fetch a fresh vent map - add_ventcrawl(loc) + handle_ventcrawl(loc) get_up() ///Sets up the jump component for the mob. Proc args can be altered so different mobs have different 'default' jump settings diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index f0b6497f001..009eeb3ee69 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -101,6 +101,12 @@ var/resting = FALSE var/list/icon/pipes_shown = list() + 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 /// TODO MAKE ME A TRAIT var/is_ventcrawling diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index 6d1cace159c..1ac1a9107bf 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -14,7 +14,6 @@ if(length(pipes_shown)) //ventcrawling, need to reapply pipe vision var/obj/machinery/atmospherics/A = loc if(istype(A)) //a sanity check just to be safe - remove_ventcrawl() - add_ventcrawl(A) + handle_ventcrawl(A) LAZYREMOVE(GLOB.ssd_living_mobs, src) set_afk_status(MOB_CONNECTED) diff --git a/code/modules/spatial_grid/cell_tracker.dm b/code/modules/spatial_grid/cell_tracker.dm new file mode 100644 index 00000000000..fb0d2e0bcf1 --- /dev/null +++ b/code/modules/spatial_grid/cell_tracker.dm @@ -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) diff --git a/tgmc.dme b/tgmc.dme index db9a18d5670..049d32dc6ba 100644 --- a/tgmc.dme +++ b/tgmc.dme @@ -1993,6 +1993,7 @@ #include "code\modules\shuttle\shuttle.dm" #include "code\modules\shuttle\shuttle_rotate.dm" #include "code\modules\shuttle\gamemodes\crash.dm" +#include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\surgery\amputation.dm" #include "code\modules\surgery\bones.dm" #include "code\modules\surgery\brainrepair.dm" From 1fdd7e20fb9c1ef0f66037294bb0c47f3f444b9e Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Wed, 4 Dec 2024 23:05:20 +1000 Subject: [PATCH 02/14] =?UTF-8?q?=D1=83=D0=B1=D0=B8=D1=80=D0=B0=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=BF=D1=80=D0=BE=D0=B1=D0=B5=D0=BB=D1=8B..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/_onclick/ventcrawl.dm | 1 - code/game/atoms/atom_movable.dm | 1 - code/modules/atmospherics/machinery/atmosmachinery.dm | 5 +---- .../atmospherics/machinery/components/unary_devices/cryo.dm | 1 - 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index 3b438bf07f7..48542641270 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -66,7 +66,6 @@ else to_chat(src, span_warning("This ventilation duct is not connected to anything!")) - /** * Moves living mob directly into the vent as a ventcrawler * diff --git a/code/game/atoms/atom_movable.dm b/code/game/atoms/atom_movable.dm index 7c4d67fdb3c..b6a89383f0f 100644 --- a/code/game/atoms/atom_movable.dm +++ b/code/game/atoms/atom_movable.dm @@ -127,7 +127,6 @@ RU TGMC EDIT */ if(spatial_grid_key) SSspatial_grid.force_remove_from_cell(src) - LAZYCLEARLIST(client_mobs_in_contents) . = ..() diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index bbfa7c251c9..11f3d4950bf 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -41,9 +41,6 @@ ///The bitflag that's being checked on ventcrawling. Default is to allow ventcrawling and seeing pipes. var/vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE - ///Whether we get pipenet vision while inside or just see normally. - var/can_see_pipes = TRUE - /obj/machinery/atmospherics/Initialize(mapload) RegisterSignal(src, COMSIG_MOVABLE_SHUTTLE_CRUSH, PROC_REF(shuttle_crush)) var/turf/turf_loc = null @@ -323,7 +320,7 @@ return list() /obj/machinery/atmospherics/update_remote_sight(mob/user) - if(!can_see_pipes) + if(!(vent_movement & VENTCRAWL_CAN_SEE)) return user.sight |= (SEE_TURFS|BLIND) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index b4a44ab221a..5d5397f9b75 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -10,7 +10,6 @@ layer = ABOVE_MOB_LAYER pipe_flags = PIPING_ONE_PER_TURF|PIPING_DEFAULT_LAYER_ONLY interaction_flags = INTERACT_MACHINE_TGUI - can_see_pipes = FALSE light_range = 2 light_power = 0.5 light_color = LIGHT_COLOR_EMISSIVE_GREEN From a4efed3ddf7d8c7054e5ef7940dedcf16e96939a Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Wed, 4 Dec 2024 23:33:14 +1000 Subject: [PATCH 03/14] Fix init eror --- code/_onclick/ventcrawl.dm | 2 +- code/controllers/subsystem/spatial_gridmap.dm | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index 48542641270..9393176ae79 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -1,4 +1,4 @@ - +// TODO transfer this code into handle_ventcrawl /mob/proc/start_ventcrawl() var/atom/pipe var/list/pipes = list() diff --git a/code/controllers/subsystem/spatial_gridmap.dm b/code/controllers/subsystem/spatial_gridmap.dm index 6558d5f464d..2070c9cfaec 100644 --- a/code/controllers/subsystem/spatial_gridmap.dm +++ b/code/controllers/subsystem/spatial_gridmap.dm @@ -125,12 +125,13 @@ SUBSYSTEM_DEF(spatial_grid) pregenerate_more_oranges_ears(NUMBER_OF_PREGENERATED_ORANGES_EARS) - RegisterSignal(SSdcs, COMSIG_GLOB_NEW_Z, .proc/propogate_spatial_grid_to_new_z) - RegisterSignal(SSdcs, COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, .proc/after_world_bounds_expanded) + 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)) + return SS_INIT_SUCCESS ///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_PREQDELETED, .proc/queued_item_deleted, override = TRUE) + RegisterSignal(waiting_movable, COMSIG_PREQDELETED, 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 From 0856d387595d5c1315bfd5bb9eb41d2fe9cbbbdb Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Thu, 5 Dec 2024 02:02:22 +1000 Subject: [PATCH 04/14] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B2=D1=83=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/modules/atmospherics/machinery/atmosmachinery.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 11f3d4950bf..bfeedcc1a9a 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -313,7 +313,7 @@ if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_VENTSOUND) || silent_crawl) return TIMER_COOLDOWN_START(user, COOLDOWN_VENTSOUND, 3 SECONDS) - playsound(src,'sound/effects/alien/ventcrawl2.ogg', 50, TRUE, -3) + playsound(src, pick('sound/effects/alien/ventcrawl1.ogg','sound/effects/alien/ventcrawl2.ogg'), 50, TRUE, -3) /obj/machinery/atmospherics/proc/returnPipenets() From ff442baaeca3878123bebdc2c3b68f5422318c59 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Sat, 7 Dec 2024 01:11:00 +1000 Subject: [PATCH 05/14] =?UTF-8?q?=D0=A2=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=BF=D0=BE=D1=8F=D0=B2=D0=B8=D1=82=D1=81=D1=8F?= =?UTF-8?q?=20=D0=B2=20=D1=81=D1=82=D0=B5=D0=BD=D0=B5/=D1=88=D0=B0=D1=82?= =?UTF-8?q?=D1=82=D0=BB=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/_onclick/ventcrawl.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index 9393176ae79..f71b51acd29 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -34,6 +34,9 @@ if(ventcrawl_target.welded) to_chat(src, span_warning("You can't crawl around a welded vent!")) return + if(loc.density || ventcrawl_target.covered_by_shuttle) + to_chat(src, span_notice("You cannot climb out, the exit is blocked!")) + return if(vent_movement & VENTCRAWL_ENTRANCE_ALLOWED) //Handle the exit here From 57dfdb6be0d8d9eeaf757e478c66dac5f18a4199 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Wed, 11 Dec 2024 01:51:33 +1000 Subject: [PATCH 06/14] Update ventcrawl.dm --- code/_onclick/ventcrawl.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index f71b51acd29..f3ed732babf 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -47,7 +47,7 @@ if(!client) return if(!stealthy) //Xenos with stealth vent crawling can silently enter/exit vents. - playsound(src, get_sfx("alien_ventpass"), 35, TRUE) + playsound(src, get_sfx(SFX_ALIEN_VENTPASS), 35, TRUE) visible_message(span_notice("[src] scrambles out from the ventilation ducts!"),span_notice("You scramble out from the ventilation ducts.")) forceMove(ventcrawl_target.loc) src.is_ventcrawling = FALSE @@ -63,7 +63,7 @@ if(!client) return if(!stealthy) //Xenos with stealth vent crawling can silently enter/exit vents. - playsound(src, get_sfx("alien_ventpass"), 35, TRUE) + playsound(src, get_sfx(SFX_ALIEN_VENTPASS), 35, TRUE) visible_message(span_notice("[src] scrambles into the ventilation ducts!"),span_notice("You climb into the ventilation ducts.")) move_into_vent(ventcrawl_target) else From b7f57852f83f223b3f836e43de99ffa6ca8b5a90 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Fri, 13 Dec 2024 03:17:16 +1000 Subject: [PATCH 07/14] fix review day2 --- code/modules/atmospherics/machinery/atmosmachinery.dm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index bfeedcc1a9a..0ac67cca35f 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -291,7 +291,11 @@ user.update_pipe_vision(full_refresh = TRUE) //Would be great if this could be implemented when someone alt-clicks the image. - if (target_move.vent_movement & VENTCRAWL_ENTRANCE_ALLOWED) + if(target_move.vent_movement & VENTCRAWL_ENTRANCE_ALLOWED) + if(isxeno(user)) + var/mob/living/carbon/xenomorph/xeno_user = user + xeno_user.handle_ventcrawl(target_move, xeno_user.xeno_caste.vent_enter_speed, xeno_user.xeno_caste.silent_vent_crawl) + return user.handle_ventcrawl(target_move) return From df7af6e01751c2c9a68b8a773f3421bf263426af Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Fri, 13 Dec 2024 04:29:23 +1000 Subject: [PATCH 08/14] review final --- .../atmospherics/machinery/components/unary_devices/cryo.dm | 2 +- .../machinery/components/unary_devices/vent_pump.dm | 2 +- .../machinery/components/unary_devices/vent_scrubber.dm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 5d5397f9b75..3abbd6cb45a 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -13,6 +13,7 @@ light_range = 2 light_power = 0.5 light_color = LIGHT_COLOR_EMISSIVE_GREEN + vent_movement = NONE var/autoeject = FALSE var/release_notice = FALSE var/temperature = 100 @@ -31,7 +32,6 @@ var/message_cooldown var/breakout_time = 300 var/mob/living/carbon/occupant - vent_movement = NONE /obj/machinery/atmospherics/components/unary/cryo_cell/Initialize(mapload) . = ..() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm index d86ace39f5a..b5afe431339 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm @@ -16,11 +16,11 @@ level = 1 layer = ATMOS_DEVICE_LAYER flags_atom = SHUTTLE_IMMUNE + vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED var/pump_direction = RELEASING var/pressure_checks = EXT_BOUND var/radio_filter_out var/radio_filter_in - vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED pipe_state = "uvent" diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index 80b5025ec67..ebe106426df 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -14,12 +14,12 @@ level = 1 layer = ATMOS_DEVICE_LAYER flags_atom = SHUTTLE_IMMUNE + vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing var/filter_types = list()///datum/gas/carbon_dioxide) var/volume_rate = 200 var/widenet = 0 //is this scrubber acting on the 3x3 area around it. var/list/turf/adjacent_turfs = list() - vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED pipe_state = "scrubber" From 2f8908760c317757fb834e39dc461927a029adb5 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Sat, 14 Dec 2024 20:58:06 +1000 Subject: [PATCH 09/14] Fix --- code/_onclick/ventcrawl.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index f3ed732babf..8ba074dd113 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -34,7 +34,7 @@ if(ventcrawl_target.welded) to_chat(src, span_warning("You can't crawl around a welded vent!")) return - if(loc.density || ventcrawl_target.covered_by_shuttle) + if(ventcrawl_target.loc.density || ventcrawl_target.covered_by_shuttle) to_chat(src, span_notice("You cannot climb out, the exit is blocked!")) return From 16117d3b6701c0ae1872d61535b67c116a7f9e82 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Sun, 12 Jan 2025 18:46:51 +1000 Subject: [PATCH 10/14] exit --- code/_onclick/ventcrawl.dm | 64 ++++++++++++++----- .../atmospherics/machinery/atmosmachinery.dm | 7 ++ 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index 8ba074dd113..5a694eff395 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -17,40 +17,70 @@ // VENTCRAWLING // Handles the entrance and exit on ventcrawling -/mob/living/proc/handle_ventcrawl(obj/machinery/atmospherics/components/ventcrawl_target, crawl_time = 4.5 SECONDS, stealthy = FALSE) - - // Cache the vent_movement bitflag var from atmos machineries - var/vent_movement = ventcrawl_target.vent_movement +/mob/living/proc/ventcrawl_checks(obj/machinery/atmospherics/ventcrawl_target) if(!HAS_TRAIT(src, TRAIT_CAN_VENTCRAWL)) - return + return FALSE if(!Adjacent(ventcrawl_target)) - return + return FALSE if(stat) to_chat(src, span_warning("You must be conscious to do this!")) - return + return FALSE if(buckled) to_chat(src, span_warning("You can't vent crawl while buckled!")) - return - if(ventcrawl_target.welded) - to_chat(src, span_warning("You can't crawl around a welded vent!")) - return + return FALSE + if(istype(ventcrawl_target, /obj/machinery/atmospherics/components)) + var/obj/machinery/atmospherics/components/ventcrawl_component = ventcrawl_target + if(ventcrawl_component.welded) + to_chat(src, span_warning("You can't crawl around a welded vent!")) + return FALSE if(ventcrawl_target.loc.density || ventcrawl_target.covered_by_shuttle) to_chat(src, span_notice("You cannot climb out, the exit is blocked!")) + return FALSE + return TRUE + +/mob/living/proc/handle_pipe_exit(obj/machinery/atmospherics/components/ventcrawl_target, crawl_time = 4.5 SECONDS, stealthy = FALSE) + if(!ventcrawl_checks(ventcrawl_target)) + return + + if(!is_ventcrawling) + return + if(!istype(loc,/obj/machinery/atmospherics)) + return + visible_message(span_notice("[src] begins climbing out from the ventilation system..."), span_notice("You begin climbing out from the ventilation system...")) + if(!do_after(src, crawl_time, target = ventcrawl_target))\ + return + if(!client) + return + if(!stealthy) //Xenos with stealth vent crawling can silently enter/exit vents. + playsound(src, get_sfx(SFX_ALIEN_VENTPASS), 35, TRUE) + visible_message(span_notice("[src] scrambles out from the ventilation ducts!"), span_notice("You scramble out from the ventilation ducts.")) + forceMove(ventcrawl_target.loc) + is_ventcrawling = FALSE + update_pipe_vision() + +// VENTCRAWLING +// Handles the entrance and exit on ventcrawling +/mob/living/proc/handle_ventcrawl(obj/machinery/atmospherics/components/ventcrawl_target, crawl_time = 4.5 SECONDS, stealthy = FALSE) + + // Cache the vent_movement bitflag var from atmos machineries + var/vent_movement = ventcrawl_target.vent_movement + + if(!ventcrawl_checks(ventcrawl_target)) return if(vent_movement & VENTCRAWL_ENTRANCE_ALLOWED) //Handle the exit here - if(src.is_ventcrawling && istype(loc, /obj/machinery/atmospherics)) - visible_message(span_notice("[src] begins climbing out from the ventilation system...") ,span_notice("You begin climbing out from the ventilation system...")) + if(is_ventcrawling && istype(loc, /obj/machinery/atmospherics)) + visible_message(span_notice("[src] begins climbing out from the ventilation system..."), span_notice("You begin climbing out from the ventilation system...")) if(!do_after(src, crawl_time, target = ventcrawl_target))\ return if(!client) return if(!stealthy) //Xenos with stealth vent crawling can silently enter/exit vents. playsound(src, get_sfx(SFX_ALIEN_VENTPASS), 35, TRUE) - visible_message(span_notice("[src] scrambles out from the ventilation ducts!"),span_notice("You scramble out from the ventilation ducts.")) + visible_message(span_notice("[src] scrambles out from the ventilation ducts!"), span_notice("You scramble out from the ventilation ducts.")) forceMove(ventcrawl_target.loc) - src.is_ventcrawling = FALSE + is_ventcrawling = FALSE update_pipe_vision() //Entrance here @@ -77,7 +107,7 @@ */ /mob/living/proc/move_into_vent(obj/machinery/atmospherics/components/ventcrawl_target) forceMove(ventcrawl_target) - src.is_ventcrawling = TRUE + is_ventcrawling = TRUE update_pipe_vision() /mob/living/proc/update_pipe_vision(full_refresh = FALSE) @@ -87,7 +117,7 @@ lighting = hud_used?.plane_masters["[LIGHTING_PLANE]"] // Take away all the pipe images if we're not doing anything with em - if(isnull(client) || !src.is_ventcrawling || !istype(loc, /obj/machinery/atmospherics)) + if(isnull(client) || !is_ventcrawling || !istype(loc, /obj/machinery/atmospherics)) for(var/image/current_image in pipes_shown) client.images -= current_image pipes_shown.len = 0 diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 0ac67cca35f..e8e0730133e 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -283,6 +283,13 @@ break if(!target_move) + if(direction & initialize_directions) + if(isxeno(user)) + var/mob/living/carbon/xenomorph/xeno_user = user + xeno_user.handle_pipe_exit(src, xeno_user.xeno_caste.vent_enter_speed, xeno_user.xeno_caste.silent_vent_crawl) + return + user.handle_pipe_exit(src) + return return if(!(target_move.vent_movement & VENTCRAWL_ALLOWED)) From d957fb3b4de67c38a334a11eb23ca1a48c19a425 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Sun, 12 Jan 2025 18:54:27 +1000 Subject: [PATCH 11/14] cooldown smileface --- code/modules/atmospherics/machinery/atmosmachinery.dm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index e8e0730133e..baf0255d89b 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -284,6 +284,10 @@ if(!target_move) if(direction & initialize_directions) + if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_VENTCRAWL)) + return FALSE + //var/vent_crawl_exit_time = + TIMER_COOLDOWN_START(user, COOLDOWN_VENTCRAWL, 2 SECONDS) if(isxeno(user)) var/mob/living/carbon/xenomorph/xeno_user = user xeno_user.handle_pipe_exit(src, xeno_user.xeno_caste.vent_enter_speed, xeno_user.xeno_caste.silent_vent_crawl) From 695ab474d30d67d8789e6959085877e010bea850 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Sun, 12 Jan 2025 18:57:35 +1000 Subject: [PATCH 12/14] uh --- code/modules/atmospherics/machinery/atmosmachinery.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index baf0255d89b..804136404ec 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -286,7 +286,6 @@ if(direction & initialize_directions) if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_VENTCRAWL)) return FALSE - //var/vent_crawl_exit_time = TIMER_COOLDOWN_START(user, COOLDOWN_VENTCRAWL, 2 SECONDS) if(isxeno(user)) var/mob/living/carbon/xenomorph/xeno_user = user From 4b920deec2fb0aa6db934d94835acdf944fe33ae Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Thu, 16 Jan 2025 14:35:19 +1000 Subject: [PATCH 13/14] review f --- code/_onclick/ventcrawl.dm | 5 +++-- code/modules/atmospherics/machinery/atmosmachinery.dm | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index 5a694eff395..59209e0db82 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -47,7 +47,8 @@ if(!istype(loc,/obj/machinery/atmospherics)) return visible_message(span_notice("[src] begins climbing out from the ventilation system..."), span_notice("You begin climbing out from the ventilation system...")) - if(!do_after(src, crawl_time, target = ventcrawl_target))\ + if(!do_after(src, crawl_time, target = ventcrawl_target)) + TIMER_COOLDOWN_END(src, COOLDOWN_VENTCRAWL) return if(!client) return @@ -72,7 +73,7 @@ //Handle the exit here if(is_ventcrawling && istype(loc, /obj/machinery/atmospherics)) visible_message(span_notice("[src] begins climbing out from the ventilation system..."), span_notice("You begin climbing out from the ventilation system...")) - if(!do_after(src, crawl_time, target = ventcrawl_target))\ + if(!do_after(src, crawl_time, target = ventcrawl_target)) return if(!client) return diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 804136404ec..868398b9e2f 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -286,7 +286,7 @@ if(direction & initialize_directions) if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_VENTCRAWL)) return FALSE - TIMER_COOLDOWN_START(user, COOLDOWN_VENTCRAWL, 2 SECONDS) + TIMER_COOLDOWN_START(user, COOLDOWN_VENTCRAWL, 4 SECONDS) if(isxeno(user)) var/mob/living/carbon/xenomorph/xeno_user = user xeno_user.handle_pipe_exit(src, xeno_user.xeno_caste.vent_enter_speed, xeno_user.xeno_caste.silent_vent_crawl) From 3c683e8ac51553051b2c664dbaa51099fbfcd175 Mon Sep 17 00:00:00 2001 From: MalorMorfin Date: Fri, 17 Jan 2025 02:20:25 +1000 Subject: [PATCH 14/14] review --- code/_onclick/ventcrawl.dm | 3 +++ code/modules/atmospherics/machinery/atmosmachinery.dm | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/_onclick/ventcrawl.dm b/code/_onclick/ventcrawl.dm index 59209e0db82..14e57b96f54 100644 --- a/code/_onclick/ventcrawl.dm +++ b/code/_onclick/ventcrawl.dm @@ -46,6 +46,9 @@ return if(!istype(loc,/obj/machinery/atmospherics)) return + if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_VENTCRAWL)) + return + TIMER_COOLDOWN_START(src, COOLDOWN_VENTCRAWL, crawl_time) visible_message(span_notice("[src] begins climbing out from the ventilation system..."), span_notice("You begin climbing out from the ventilation system...")) if(!do_after(src, crawl_time, target = ventcrawl_target)) TIMER_COOLDOWN_END(src, COOLDOWN_VENTCRAWL) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 868398b9e2f..e8e0730133e 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -284,9 +284,6 @@ if(!target_move) if(direction & initialize_directions) - if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_VENTCRAWL)) - return FALSE - TIMER_COOLDOWN_START(user, COOLDOWN_VENTCRAWL, 4 SECONDS) if(isxeno(user)) var/mob/living/carbon/xenomorph/xeno_user = user xeno_user.handle_pipe_exit(src, xeno_user.xeno_caste.vent_enter_speed, xeno_user.xeno_caste.silent_vent_crawl)