Skip to content

Commit

Permalink
Ventcrawl port
Browse files Browse the repository at this point in the history
  • Loading branch information
MalorMorfin committed Dec 4, 2024
1 parent c88f376 commit f97c48a
Show file tree
Hide file tree
Showing 20 changed files with 719 additions and 308 deletions.
8 changes: 8 additions & 0 deletions code/__DEFINES/atmospherics.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)) { \
Expand Down
2 changes: 1 addition & 1 deletion code/__DEFINES/dcs/signals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
35 changes: 32 additions & 3 deletions code/__DEFINES/spatial_gridmap.dm
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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; \
};
40 changes: 40 additions & 0 deletions code/__HELPERS/_lists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions code/_onclick/hud/rendering/plane_master.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"))

201 changes: 110 additions & 91 deletions code/_onclick/ventcrawl.dm
Original file line number Diff line number Diff line change
@@ -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!")
Expand All @@ -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
Loading

0 comments on commit f97c48a

Please sign in to comment.