Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ventcrawl port #751

Merged
merged 17 commits into from
Jan 16, 2025
Next Next commit
Ventcrawl port
  • Loading branch information
MalorMorfin committed Dec 4, 2024
commit f97c48aa8e41e29168e7448a42b9fbcb7a8c2ea9
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ранний ретурн можно сделать

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..."))
MalorMorfin marked this conversation as resolved.
Show resolved Hide resolved
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()
MalorMorfin marked this conversation as resolved.
Show resolved Hide resolved
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()
MalorMorfin marked this conversation as resolved.
Show resolved Hide resolved

/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)
MalorMorfin marked this conversation as resolved.
Show resolved Hide resolved
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
Loading