Files
Bubberstation/code/__HELPERS/turfs.dm

554 lines
22 KiB
Plaintext

///Returns location. Returns null if no location was found.
/proc/get_teleport_loc(turf/location, mob/target, distance = 1, density_check = FALSE, closed_turf_check = FALSE, errorx = 0, errory = 0, eoffsetx = 0, eoffsety = 0)
/*
Location where the teleport begins, target that will teleport, distance to go, density checking 0/1(yes/no), closed turf checking.
Random error in tile placement x, error in tile placement y, and block offset.
Block offset tells the proc how to place the box. Behind teleport location, relative to starting location, forward, etc.
Negative values for offset are accepted, think of it in relation to North, -x is west, -y is south. Error defaults to positive.
Turf and target are separate in case you want to teleport some distance from a turf the target is not standing on or something.
*/
var/dirx = 0//Generic location finding variable.
var/diry = 0
var/xoffset = 0//Generic counter for offset location.
var/yoffset = 0
var/b1xerror = 0//Generic placing for point A in box. The lower left.
var/b1yerror = 0
var/b2xerror = 0//Generic placing for point B in box. The upper right.
var/b2yerror = 0
errorx = abs(errorx)//Error should never be negative.
errory = abs(errory)
switch(target.dir)//This can be done through equations but switch is the simpler method. And works fast to boot.
//Directs on what values need modifying.
if(1)//North
diry += distance
yoffset += eoffsety
xoffset += eoffsetx
b1xerror -= errorx
b1yerror -= errory
b2xerror += errorx
b2yerror += errory
if(2)//South
diry -= distance
yoffset -= eoffsety
xoffset += eoffsetx
b1xerror -= errorx
b1yerror -= errory
b2xerror += errorx
b2yerror += errory
if(4)//East
dirx += distance
yoffset += eoffsetx//Flipped.
xoffset += eoffsety
b1xerror -= errory//Flipped.
b1yerror -= errorx
b2xerror += errory
b2yerror += errorx
if(8)//West
dirx -= distance
yoffset -= eoffsetx//Flipped.
xoffset += eoffsety
b1xerror -= errory//Flipped.
b1yerror -= errorx
b2xerror += errory
b2yerror += errorx
var/turf/destination = locate(location.x+dirx,location.y+diry,location.z)
if(!destination)//If there isn't a destination.
return
if(!errorx && !errory)//If errorx or y were not specified.
if(density_check && destination.density)
return
if(closed_turf_check && isclosedturf(destination))
return//If closed was specified.
if(destination.x>world.maxx || destination.x<1)
return
if(destination.y>world.maxy || destination.y<1)
return
var/destination_list[] = list()//To add turfs to list.
//destination_list = new()
/*This will draw a block around the target turf, given what the error is.
Specifying the values above will basically draw a different sort of block.
If the values are the same, it will be a square. If they are different, it will be a rectangle.
In either case, it will center based on offset. Offset is position from center.
Offset always calculates in relation to direction faced. In other words, depending on the direction of the teleport,
the offset should remain positioned in relation to destination.*/
var/turf/center = locate((destination.x + xoffset), (destination.y + yoffset), location.z)//So now, find the new center.
//Now to find a box from center location and make that our destination.
var/width = (b2xerror - b1xerror) + 1
var/height = (b2yerror - b1yerror) + 1
for(var/turf/current_turf as anything in CORNER_BLOCK_OFFSET(center, width, height, b1xerror, b1yerror))
if(density_check && current_turf.density)
continue//If density was specified.
if(closed_turf_check && isclosedturf(current_turf))
continue//If closed was specified.
if(current_turf.x > world.maxx || current_turf.x < 1)
continue//Don't want them to teleport off the map.
if(current_turf.y > world.maxy || current_turf.y < 1)
continue
destination_list += current_turf
if(!destination_list.len)
return
destination = pick(destination_list)
return destination
/**
* Returns the top-most atom sitting on the turf.
* For example, using this on a disk, which is in a bag, on a mob,
* will return the mob because it's on the turf.
*
* Arguments
* * something_in_turf - a movable within the turf, somewhere.
* * stop_type - stops looking if stop_type is found in the turf, returning that type (if found).
* * return_any - if set to TRUE, will return last movable found even if its not of the passed type
**/
/proc/get_atom_on_turf(atom/movable/something_in_turf, stop_type = null, return_any = FALSE)
if(!istype(something_in_turf))
CRASH("get_atom_on_turf was not passed an /atom/movable! Got [isnull(something_in_turf) ? "null":"type: [something_in_turf.type]"]")
var/atom/movable/topmost_thing = something_in_turf
while(topmost_thing?.loc && !isturf(topmost_thing.loc))
topmost_thing = topmost_thing.loc
if(stop_type && istype(topmost_thing, stop_type))
return topmost_thing
if (!stop_type || return_any)
return topmost_thing
///Returns the turf located at the map edge in the specified direction relative to target_atom used for mass driver
/proc/get_edge_target_turf(atom/target_atom, direction)
var/turf/target = locate(target_atom.x, target_atom.y, target_atom.z)
if(!target_atom || !target)
return 0
//since NORTHEAST == NORTH|EAST, etc, doing it this way allows for diagonal mass drivers in the future
//and isn't really any more complicated
var/x = target_atom.x
var/y = target_atom.y
if(direction & NORTH)
y = world.maxy
else if(direction & SOUTH) //you should not have both NORTH and SOUTH in the provided direction
y = 1
if(direction & EAST)
x = world.maxx
else if(direction & WEST)
x = 1
if(ISDIAGONALDIR(direction)) //let's make sure it's accurately-placed for diagonals
var/lowest_distance_to_map_edge = min(abs(x - target_atom.x), abs(y - target_atom.y))
return get_ranged_target_turf(target_atom, direction, lowest_distance_to_map_edge)
return locate(x,y,target_atom.z)
// returns turf relative to target_atom in given direction at set range
// result is bounded to map size
// note range is non-pythagorean
// used for disposal system
/proc/get_ranged_target_turf(atom/target_atom, direction, range)
var/x = target_atom.x
var/y = target_atom.y
if(direction & NORTH)
y = min(world.maxy, y + range)
else if(direction & SOUTH)
y = max(1, y - range)
if(direction & EAST)
x = min(world.maxx, x + range)
else if(direction & WEST) //if you have both EAST and WEST in the provided direction, then you're gonna have issues
x = max(1, x - range)
return locate(x,y,target_atom.z)
/**
* Get ranged target turf, but with direct targets as opposed to directions
*
* Starts at atom starting_atom and gets the exact angle between starting_atom and target
* Moves from starting_atom with that angle, Range amount of times, until it stops, bound to map size
* Arguments:
* * starting_atom - Initial Firer / Position
* * target - Target to aim towards
* * range - Distance of returned target turf from starting_atom
* * offset - Angle offset, 180 input would make the returned target turf be in the opposite direction
*/
/proc/get_ranged_target_turf_direct(atom/starting_atom, atom/target, range, offset)
var/angle = ATAN2(target.x - starting_atom.x, target.y - starting_atom.y)
if(offset)
angle += offset
var/turf/starting_turf = get_turf(starting_atom)
for(var/i in 1 to range)
var/turf/check = locate(starting_atom.x + cos(angle) * i, starting_atom.y + sin(angle) * i, starting_atom.z)
if(!check)
break
starting_turf = check
return starting_turf
/// returns turf relative to target_atom offset in dx and dy tiles, bound to map limits
/proc/get_offset_target_turf(atom/target_atom, dx, dy)
var/x = min(world.maxx, max(1, target_atom.x + dx))
var/y = min(world.maxy, max(1, target_atom.y + dy))
return locate(x, y, target_atom.z)
/**
* Lets the turf this atom's *ICON* appears to inhabit
* it takes into account:
* Pixel_x/y
* Matrix x/y
* NOTE: if your atom has non-standard bounds then this proc
* will handle it, but:
* if the bounds are even, then there are an even amount of "middle" turfs, the one to the EAST, NORTH, or BOTH is picked
* this may seem bad, but you're at least as close to the center of the atom as possible, better than byond's default loc being all the way off)
* if the bounds are odd, the true middle turf of the atom is returned
**/
/proc/get_turf_pixel(atom/checked_atom)
var/turf/atom_turf = get_turf(checked_atom) //use checked_atom's turfs, as its coords are the same as checked_atom's AND checked_atom's coords are lost if it is inside another atom
if(!atom_turf)
return null
if(checked_atom.flags_1 & IGNORE_TURF_PIXEL_OFFSET_1)
return atom_turf
var/list/offsets = get_visual_offset(checked_atom)
return pixel_offset_turf(atom_turf, offsets)
/**
* Returns how visually "off" the atom is from its source turf as a list of x, y (in pixel steps)
* it takes into account:
* Pixel_x/y
* Matrix x/y
* Icon width/height
**/
/proc/get_visual_offset(atom/checked_atom)
//Find checked_atom's matrix so we can use its X/Y pixel shifts
var/matrix/atom_matrix = matrix(checked_atom.transform)
var/pixel_x_offset = checked_atom.pixel_x + checked_atom.pixel_w + atom_matrix.get_x_shift()
var/pixel_y_offset = checked_atom.pixel_y + checked_atom.pixel_z + atom_matrix.get_y_shift()
//Irregular objects
var/list/icon_dimensions = get_icon_dimensions_pure(checked_atom.icon)
var/checked_atom_icon_height = icon_dimensions["height"]
var/checked_atom_icon_width = icon_dimensions["width"]
if(checked_atom_icon_height != ICON_SIZE_Y || checked_atom_icon_width != ICON_SIZE_X)
pixel_x_offset += ((checked_atom_icon_width / ICON_SIZE_X) - 1) * (ICON_SIZE_X * 0.5)
pixel_y_offset += ((checked_atom_icon_height / ICON_SIZE_Y) - 1) * (ICON_SIZE_Y * 0.5)
return list(pixel_x_offset, pixel_y_offset)
/**
* Takes a turf, and a list of x and y pixel offsets and returns the turf that the offset position best lands in
**/
/proc/pixel_offset_turf(turf/offset_from, list/offsets)
//DY and DX
var/rough_x = round(round(offsets[1], ICON_SIZE_X) / ICON_SIZE_X)
var/rough_y = round(round(offsets[2], ICON_SIZE_Y) / ICON_SIZE_Y)
var/final_x = clamp(offset_from.x + rough_x, 1, world.maxx)
var/final_y = clamp(offset_from.y + rough_y, 1, world.maxy)
if(final_x || final_y)
return locate(final_x, final_y, offset_from.z)
return offset_from
///Returns a turf based on text inputs, original turf and viewing client
/proc/parse_caught_click_modifiers(list/modifiers, turf/origin, client/viewing_client)
if(!modifiers)
return null
var/screen_loc = splittext(LAZYACCESS(modifiers, SCREEN_LOC), ",")
var/list/actual_view = getviewsize(viewing_client ? viewing_client.view : world.view)
var/click_turf_x = splittext(screen_loc[1], ":")
var/click_turf_y = splittext(screen_loc[2], ":")
var/click_turf_z = origin.z
var/click_turf_px = text2num(click_turf_x[2])
var/click_turf_py = text2num(click_turf_y[2])
click_turf_x = origin.x + text2num(click_turf_x[1]) - round(actual_view[1] / 2) - 1
click_turf_y = origin.y + text2num(click_turf_y[1]) - round(actual_view[2] / 2) - 1
var/turf/click_turf = locate(clamp(click_turf_x, 1, world.maxx), clamp(click_turf_y, 1, world.maxy), click_turf_z)
LAZYSET(modifiers, ICON_X, "[(click_turf_px - click_turf.pixel_x) + ((click_turf_x - click_turf.x) * ICON_SIZE_X)]")
LAZYSET(modifiers, ICON_Y, "[(click_turf_py - click_turf.pixel_y) + ((click_turf_y - click_turf.y) * ICON_SIZE_Y)]")
return click_turf
/**
* Converts mouse-pos control coordinates to a specific turf location on the map.
*
* Handles the conversion between control-space mouse coordinates and screen-space map coordinates,
* accounting for various BYOND quirks and display scaling factors.
*
* @param mousepos_x The x-coordinate of the mouse click (in control pixels, top-left origin)
* @param mousepos_y The y-coordinate of the mouse click (in control pixels, top-left origin)
* @param sizex x control width of the map
* @param sizey y control width of the map
* @param viewing_client The client whose view perspective to use for the conversion
*
* @return The turf at the calculated map position, or the closest one if out of bounds, as well as the residual x and y map offsets
*
* Important Notes:
* - This WILL be incorrect when client pixel_wxyz is animating, and it WILL be incorrect if the user is gliding, because we don't have a good way to compensate this on the serverside. Yay!!!
* - Mouse coordinates originate from the top-left corner because we can't have consistency in this engine
* - Coordinate systems are inconsistent between control pixels and screen pixels
* - Something on the byond side (icon size likely? needs debugging) affects the control pixels
* - This uses the ratios between them rather than absolute values for reliable results
* - For absolute value comparisons, dividing by 2 may work as a temporary hack,
* but using ratios (as implemented in the proc) is the recommended approach
*/
/proc/get_loc_from_mousepos(mousepos_x, mousepos_y, sizex, sizey, client/viewing_client)
if(sizex == 0 || sizey == 0) //contexts where this information is not availible should return 0 in size, aka tgui passthrough
return list(null, 0, 0)
var/turf/baseloc = get_turf(viewing_client.eye)
var/list/actual_view = getviewsize(viewing_client ? viewing_client.view : world.view)
var/screen_width = actual_view[1] * ICON_SIZE_X
var/screen_height = actual_view[2] * ICON_SIZE_Y
//handle letterboxing to get the right sizes and mouseposes
var/size_ratio = sizex/sizey
var/screen_ratio = screen_width/screen_height
if(size_ratio < screen_ratio) //sizex too high, y has black banners
var/effective_height = sizex / screen_ratio
var/banner_height = (sizey - effective_height) / 2
mousepos_y -= banner_height
sizey -= (banner_height*2)
else if (size_ratio > screen_ratio) //sizey too high, x has black banners
var/effective_width = sizey * screen_ratio
var/banner_width = (sizex - effective_width) / 2
mousepos_x -= banner_width
sizex -= (banner_width*2)
// if its a black banner, just assume we clicked the turf
mousepos_x = max(mousepos_x, 0)
mousepos_y = max(mousepos_y, 0)
//fix ratios being off due to screen width/height
var/x_ratio = sizex/screen_width
var/y_ratio = sizey/screen_height
mousepos_x /= x_ratio
mousepos_y /= y_ratio
//relative to bottom left corner of turf in the middle of the screen
var/relative_x = mousepos_x - (screen_width / 2) + (ICON_SIZE_X/2) + viewing_client.pixel_x + viewing_client.pixel_w
var/relative_y = -(mousepos_y - (screen_height / 2))+ (ICON_SIZE_Y/2) - 1 + viewing_client.pixel_y + viewing_client.pixel_z
var/turf_x_diff = FLOOR(relative_x / ICON_SIZE_X, 1)
var/turf_y_diff = FLOOR(relative_y / ICON_SIZE_Y, 1)
var/click_turf_x = baseloc.x + turf_x_diff
var/click_turf_y = baseloc.y + turf_y_diff
var/click_turf_z = baseloc.z
var/turf/click_turf = locate(clamp(click_turf_x, 1, world.maxx), clamp(click_turf_y, 1, world.maxy), click_turf_z)
var/x_residual = relative_x % ICON_SIZE_X
var/y_residual = relative_y % ICON_SIZE_Y
return list(click_turf, x_residual, y_residual)
///Almost identical to the params_to_turf(), but unused (remove?)
/proc/screen_loc_to_turf(text, turf/origin, client/C)
if(!text)
return null
var/tZ = splittext(text, ",")
var/tX = splittext(tZ[1], "-")
var/tY = text2num(tX[2])
tX = splittext(tZ[2], "-")
tX = text2num(tX[2])
tZ = origin.z
var/list/actual_view = getviewsize(C ? C.view : world.view)
tX = clamp(origin.x + round(actual_view[1] / 2) - tX, 1, world.maxx)
tY = clamp(origin.y + round(actual_view[2] / 2) - tY, 1, world.maxy)
return locate(tX, tY, tZ)
///similar function to RANGE_TURFS(), but will search spiralling outwards from the center (like the above, but only turfs)
/proc/spiral_range_turfs(dist = 0, center = usr, orange = FALSE, list/outlist = list(), tick_checked)
outlist.Cut()
if(!dist)
outlist += center
return outlist
var/turf/t_center = get_turf(center)
if(!t_center)
return outlist
var/list/turf_list = outlist
var/turf/checked_turf
var/y
var/x
var/c_dist = 1
if(!orange)
turf_list += t_center
while( c_dist <= dist )
y = t_center.y + c_dist
x = t_center.x - c_dist + 1
for(x in x to t_center.x + c_dist)
checked_turf = locate(x, y, t_center.z)
if(checked_turf)
turf_list += checked_turf
y = t_center.y + c_dist - 1
x = t_center.x + c_dist
for(y in t_center.y - c_dist to y)
checked_turf = locate(x, y, t_center.z)
if(checked_turf)
turf_list += checked_turf
y = t_center.y - c_dist
x = t_center.x + c_dist - 1
for(x in t_center.x - c_dist to x)
checked_turf = locate(x, y, t_center.z)
if(checked_turf)
turf_list += checked_turf
y = t_center.y - c_dist + 1
x = t_center.x - c_dist
for(y in y to t_center.y + c_dist)
checked_turf = locate(x, y, t_center.z)
if(checked_turf)
turf_list += checked_turf
c_dist++
if(tick_checked)
CHECK_TICK
return turf_list
///Returns a random turf on the station
/proc/get_random_station_turf()
var/list/turfs = get_area_turfs(pick(GLOB.the_station_areas))
if (length(turfs))
return pick(turfs)
///Returns a random turf on the station, excludes dense turfs (like walls) and areas that have valid_territory set to FALSE
/proc/get_safe_random_station_turf(list/areas_to_pick_from = GLOB.the_station_areas)
for (var/i in 1 to 5)
var/list/turf_list = get_area_turfs(pick(areas_to_pick_from))
var/turf/target
while (turf_list.len && !target)
var/I = rand(1, turf_list.len)
var/turf/checked_turf = turf_list[I]
var/area/turf_area = get_area(checked_turf)
if(!checked_turf.density && (turf_area.area_flags & VALID_TERRITORY) && !isgroundlessturf(checked_turf))
var/clear = TRUE
for(var/obj/checked_object in checked_turf)
if(checked_object.density)
clear = FALSE
break
if(clear)
target = checked_turf
if (!target)
turf_list.Cut(I, I + 1)
if (target)
return target
///Returns a random department of areas to pass into get_safe_random_station_turf() for more equal spawning.
/proc/get_safe_random_station_turf_equal_weight()
// Big list of departments, each with lists of each area subtype.
var/static/list/department_areas
if(isnull(department_areas))
department_areas = list(
subtypesof(/area/station/engineering), \
subtypesof(/area/station/medical), \
subtypesof(/area/station/science), \
subtypesof(/area/station/security), \
subtypesof(/area/station/service), \
subtypesof(/area/station/command), \
subtypesof(/area/station/hallway), \
subtypesof(/area/station/ai_monitored), \
subtypesof(/area/station/cargo)
)
var/list/area/final_department = pick(department_areas) // Pick a department
var/list/area/final_area_list = list()
for(var/area/checked_area as anything in final_department) // Check each area to make sure it exists on the station
if(checked_area in GLOB.the_station_areas)
final_area_list += checked_area
if(!final_area_list.len) // Failsafe
return get_safe_random_station_turf()
return get_safe_random_station_turf(final_area_list)
/**
* Checks whether the target turf is in a valid state to accept a directional construction
* such as windows or railings.
*
* Returns FALSE if the target turf cannot accept a directional construction.
* Returns TRUE otherwise.
*
* Arguments:
* * dest_turf - The destination turf to check for existing directional constructions
* * test_dir - The prospective dir of some atom you'd like to put on this turf.
* * is_fulltile - Whether the thing you're attempting to move to this turf takes up the entire tile or whether it supports multiple movable atoms on its tile.
*/
/proc/valid_build_direction(turf/dest_turf, test_dir, is_fulltile = FALSE)
if(!dest_turf)
return FALSE
for(var/obj/turf_content in dest_turf)
if(turf_content.obj_flags & BLOCKS_CONSTRUCTION_DIR)
if(is_fulltile) // for making it so fulltile things can't be built over directional things--a special case
return FALSE
if(turf_content.dir == test_dir)
return FALSE
return TRUE
/**
* Checks whether or not a particular typepath or subtype of it is present on a turf
*
* Returns the first instance located if an instance of the desired type or a subtype of it is found
* Returns null if the type is not found, or if no turf is supplied
*
* Arguments:
* * location - The turf to be checked for the desired type
* * type_to_find - The typepath whose presence you are checking for
*/
/proc/is_type_on_turf(turf/location, type_to_find)
if(!location)
return
var/found_type = locate(type_to_find) in location
return found_type
/**
* get_blueprint_data
* Gets a list of turfs around a central turf and gets the blueprint data in a list
* Args:
* - central_turf: The center turf we're getting data from.
* - viewsize: The viewsize we're getting the turfs around central_turf of.
*/
/proc/get_blueprint_data(turf/central_turf, viewsize)
var/list/blueprint_data_returned = list()
var/list/dimensions = getviewsize(viewsize)
var/horizontal_radius = dimensions[1] / 2
var/vertical_radius = dimensions[2] / 2
for(var/turf/nearby_turf as anything in RECT_TURFS(horizontal_radius, vertical_radius, central_turf))
if(nearby_turf.blueprint_data)
blueprint_data_returned += nearby_turf.blueprint_data
return blueprint_data_returned
/// Returns the diffrence in pressure between a turf from surrounding turfs
/turf/proc/return_turf_delta_p()
var/pressure_greatest = 0
var/pressure_smallest = INFINITY //Freaking terrified to use INFINITY, man
for(var/turf/open/turf_adjacent in RANGE_TURFS(1, src)) //Begin processing the delta pressure across the wall.
pressure_greatest = max(pressure_greatest, turf_adjacent.air.return_pressure())
pressure_smallest = min(pressure_smallest, turf_adjacent.air.return_pressure())
return pressure_greatest - pressure_smallest
///Runs through all adjacent open turfs and checks if any are planetary_atmos returns true if even one passes.
/turf/proc/is_nearby_planetary_atmos()
for(var/turf/open/turf_adjacent in RANGE_TURFS(1, src))
if(turf_adjacent.planetary_atmos)
return TRUE
return FALSE