Files
Bubberstation/code/__HELPERS/turfs.dm
SkyratBot fe47d6ba35 [MIRROR] Refactors sheet crafting to better support directional construction [MDB IGNORE] (#20594)
* Refactors sheet crafting to better support directional construction (#74572)

## About The Pull Request

0426f7ddba/code/game/objects/items/stacks/stack.dm (L449)

Ok, but can we not?

This PR refactors sheet crafting to generalize all the cases that were
previously locked behind grille/window type checks and such. In their
stead there are bitflags that can be set to achieve certain behaviors.

All the behavior from before should be preserved, but now it can be
extended to other items. E.g. if you want a railing that can be crafted
underneath directional windows, or an item that behaves like a grille
does--it's just a matter of setting the right obj_flags for it now.

This makes it very simple and painless to add new recipes that use
directional crafting! It's all modular now.

<details><summary>Details</summary>

---

### What I've done:

-Eliminated all the type checks, instead it will now be handled by
object flags and recipe vars, making for a much more configurable
system.

-Added two new obj_flags: `BLOCKS_CONSTRUCTION_DIR` and
`IGNORE_DENSITY`.
-Additionally, I renamed the existing flag `NO_BUILD` to
`BLOCKS_CONSTRUCTION`.

-Changes the proc `valid_window_location` to `valid_build_direction`,
and makes it work for things other than windows.

-Removed a deprecated `window_checks` var from the stack_recipe datum.
-Added three more vars to the stack_recipe datum: `check_direction` and
`check_density`, `is_fulltile`

-Decoupled `on_solid_ground` from the object density check. Now you can
set those separately, allowing you to make recipes that forbid/allow
building things over other things while in space.

---

### What the new flags do:

`BLOCKS_CONSTRUCTION` works as before---prevents objects from being
built on the object. I felt that the previous name was not descriptive
enough, you should know exactly what it does just from looking at the
name.
_example: dna scanner_

`BLOCKS_CONSTRUCTION_DIR` -- setting this on an object will prevent
objects from being built on it when their directions are the same.
_example: directional windows, windoors, railings_

`IGNORE_DENSITY` -- setting this on an object will cause its density to
be ignored when performing the construction density check. This could
have other potential uses as well in the future.
_example: grilles, directional windows, tables_

These three flags cover all the bases for the types of items that are
currently craftable, so there is no more need for any type checking or
weird snowflake window checks. Simply set the appropriate flag and it'll
work as you would expect.

---

### What the recipe vars do:
`check_direction` tells the recipe to check if there's something in that
direction with the `BLOCKS_CONSTRUCTION_DIR` flag set.

`check_density` tells the recipe to run the density check when set. This
is true by default. There are very few items in the game that currently
have this set to false--namely grilles. Setting this to false will make
it so that the object can be constructed regardless of what is in that
tile (unless `one_per_turf` is also set, which will make it so that you
can't craft the same thing twice in the same turf).

`is_fulltile` is used for fulltile windows, but it doesn't necessarily
have to be--you can give this to any recipe and it will adopt the same
properties as that of the fulltile window. Basically they have a special
case where they shouldn't be able to be built over directional
constructions, where normally things would be able to be. Setting this
makes check_direction true as well.

---

### In summary:

Sheet crafting still works just as it did before. But the backend of it
has gotten a glow up and will be able to more easily support new
behaviors.

</details>

## Why It's Good For The Game

This makes the crafting system much more flexible to add recipes to, and
will prevent bad code practices of stacking more conditionals down the
line whenever someone wants to add an item that behaves like grilles or
directional windows in how they are constructed.

It had to be done. Those window checks were a mess.

## Changelog

🆑
qol: added fifty stack versions of remaining glass sheet stacks for ease
of debugging
refactor: refactored sheet crafting to better support directional
constructions that aren't windows
/🆑

---------

Co-authored-by: san7890 <the@ san7890.com>

* Refactors sheet crafting to better support directional construction

* fex

* https://github.com/Skyrat-SS13/Skyrat-tg/pull/20636

---------

Co-authored-by: Bloop <vinylspiders@gmail.com>
Co-authored-by: san7890 <the@ san7890.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
2023-04-21 02:14:10 +01:00

402 lines
15 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 rectengle.
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 - optional - stops looking if stop_type is found in the turf, returning that type (if found).
**/
/proc/get_atom_on_turf(atom/movable/something_in_turf, stop_type)
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))
break
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 atleast 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)
if(!istype(checked_atom))
return
//Find coordinates
var/turf/atom_turf = get_turf(checked_atom) //use checked_atom's turfs, as it's 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
//Find checked_atom's matrix so we can use it's X/Y pixel shifts
var/matrix/atom_matrix = matrix(checked_atom.transform)
var/pixel_x_offset = checked_atom.pixel_x + atom_matrix.get_x_shift()
var/pixel_y_offset = checked_atom.pixel_y + atom_matrix.get_y_shift()
//Irregular objects
var/icon/checked_atom_icon = icon(checked_atom.icon, checked_atom.icon_state)
var/checked_atom_icon_height = checked_atom_icon.Height()
var/checked_atom_icon_width = checked_atom_icon.Width()
if(checked_atom_icon_height != world.icon_size || checked_atom_icon_width != world.icon_size)
pixel_x_offset += ((checked_atom_icon_width / world.icon_size) - 1) * (world.icon_size * 0.5)
pixel_y_offset += ((checked_atom_icon_height / world.icon_size) - 1) * (world.icon_size * 0.5)
//DY and DX
var/rough_x = round(round(pixel_x_offset, world.icon_size) / world.icon_size)
var/rough_y = round(round(pixel_y_offset, world.icon_size) / world.icon_size)
var/final_x = clamp(atom_turf.x + rough_x, 1, world.maxx)
var/final_y = clamp(atom_turf.y + rough_y, 1, world.maxy)
if(final_x || final_y)
return locate(final_x, final_y, atom_turf.z)
///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) * world.icon_size)]")
LAZYSET(modifiers, ICON_Y, "[(click_turf_py - click_turf.pixel_y) + ((click_turf_y - click_turf.y) * world.icon_size)]")
return click_turf
///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
/**
* 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 TRUE if an instance of the desired type or a subtype of it is found
* Returns FALSE 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 FALSE
if(locate(type_to_find) in location)
return TRUE
return FALSE