Files
fulpstation/code/__HELPERS/atoms.dm
John Willard 7199947c08 [MDB IGNORE] [IDB IGNORE] WIP TGU (#1427)
Several months worth of updates.

---------

Co-authored-by: A miscellaneous Fern <80640114+FernandoJ8@users.noreply.github.com>
Co-authored-by: Pepsilawn <reisenrui@gmail.com>
Co-authored-by: Ray <64306407+OneAsianTortoise@users.noreply.github.com>
Co-authored-by: Cure221 <106662180+Cure221@users.noreply.github.com>
2025-11-06 08:20:20 -05:00

385 lines
12 KiB
Plaintext

///Returns the src and all recursive contents as a list.
/atom/proc/get_all_contents(ignore_flag_1)
. = list(src)
var/i = 0
while(i < length(.))
var/atom/checked_atom = .[++i]
if(checked_atom.flags_1 & ignore_flag_1)
continue
. += checked_atom.contents
///identical to get_all_contents but returns a list of atoms of the type passed in the argument.
/atom/proc/get_all_contents_type(type)
var/list/processing_list = list(src)
. = list()
while(length(processing_list))
var/atom/checked_atom = processing_list[1]
processing_list.Cut(1, 2)
processing_list += checked_atom.contents
if(istype(checked_atom, type))
. += checked_atom
///Like get_all_contents_type, but uses a typecache list as argument
/atom/proc/get_all_contents_ignoring(list/ignore_typecache)
if(!length(ignore_typecache))
return get_all_contents()
var/list/processing = list(src)
. = list()
var/i = 0
while(i < length(processing))
var/atom/checked_atom = processing[++i]
if(ignore_typecache[checked_atom.type])
continue
processing += checked_atom.contents
. += checked_atom
///Returns the src and all recursive contents, but skipping going any deeper if an atom has a specific trait.
/atom/proc/get_all_contents_skipping_traits(skipped_trait)
. = list(src)
if(!skipped_trait)
CRASH("get_all_contents_skipping_traits called without a skipped_trait")
var/i = 0
while(i < length(.))
var/atom/checked_atom = .[++i]
if(HAS_TRAIT(checked_atom, skipped_trait))
continue
. += checked_atom.contents
///Returns a list of all locations (except the area) the movable is within.
/proc/get_nested_locs(atom/movable/atom_on_location, include_turf = FALSE)
. = list()
var/atom/location = atom_on_location.loc
var/turf/our_turf = get_turf(atom_on_location)
while(location && location != our_turf)
. += location
location = location.loc
if(our_turf && include_turf) //At this point, only the turf is left, provided it exists.
. += our_turf
///Step-towards method of determining whether one atom can see another. Similar to viewers()
///note: this is a line of sight algorithm, view() does not do any sort of raycasting and cannot be emulated by it accurately
/proc/can_see(atom/source, atom/target, length=5) // I couldnt be arsed to do actual raycasting :I This is horribly inaccurate.
var/turf/current = get_turf(source)
var/turf/target_turf = get_turf(target)
if(get_dist(source, target) > length)
return FALSE
if(current == target_turf)
return TRUE
var/steps = 1
if(current == target_turf)//they are on the same turf, source can see the target
return TRUE
current = get_step_towards(current, target_turf)
while(current != target_turf)
if(steps > length)
return FALSE
if(IS_OPAQUE_TURF(current))
return FALSE
current = get_step_towards(current, target_turf)
steps++
return TRUE
///Get the cardinal direction between two atoms
/proc/get_cardinal_dir(atom/start, atom/end)
var/dx = abs(end.x - start.x)
var/dy = abs(end.y - start.y)
return get_dir(start, end) & (rand() * (dx+dy) < dy ? 3 : 12)
/**
* Finds the distance between two atoms, in pixels \
* centered = FALSE counts from turf edge to edge \
* centered = TRUE counts from turf center to turf center \
* of course mathematically this is just adding world.icon_size on again
**/
/proc/get_pixel_distance(atom/start, atom/end, centered = TRUE)
if(!istype(start) || !istype(end))
return 0
. = bounds_dist(start, end) + sqrt((((start.pixel_x + end.pixel_x) ** 2) + ((start.pixel_y + end.pixel_y) ** 2)))
if(centered)
. += ICON_SIZE_ALL
/**
* Check if there is already a wall item on the turf loc
* floor_loc = floor tile in front of the wall
* dir_toward_wall = direction from the floor tile in front of the wall towards the wall
* check_external = truthy if we should be checking against items coming out of the wall, rather than visually on top of the wall.
**/
/proc/check_wall_item(floor_loc, dir_toward_wall, check_external = 0)
var/wall_loc = get_step(floor_loc, dir_toward_wall)
for(var/obj/checked_object in floor_loc)
if(is_type_in_typecache(checked_object, GLOB.WALLITEMS_INTERIOR) && !check_external)
//Direction works sometimes
if(checked_object.dir == dir_toward_wall)
return TRUE
//Some stuff doesn't use dir properly, so we need to check pixel instead
//That's exactly what get_turf_pixel() does
if(get_turf_pixel(checked_object) == wall_loc)
return TRUE
if(is_type_in_typecache(checked_object, GLOB.WALLITEMS_EXTERIOR) && check_external)
if(checked_object.dir == dir_toward_wall)
return TRUE
//Some stuff is placed directly on the wallturf (signs).
//If we're only checking for external entities, we don't need to look though these.
if (check_external)
return FALSE
for(var/obj/checked_object in wall_loc)
if(is_type_in_typecache(checked_object, GLOB.WALLITEMS_INTERIOR))
if(checked_object.pixel_x == 0 && checked_object.pixel_y == 0)
return TRUE
return FALSE
///Forces the atom to take a step in a random direction
/proc/random_step(atom/movable/moving_atom, steps, chance)
var/initial_chance = chance
while(steps > 0)
if(prob(chance))
step(moving_atom, pick(GLOB.alldirs))
chance = max(chance - (initial_chance / steps), 0)
steps--
/**
* Compare source's dir, the clockwise dir of source and the anticlockwise dir of source
* To the opposite dir of the dir returned by get_dir(target,source)
* If one of them is a match, then source is facing target
**/
/proc/is_source_facing_target(atom/source,atom/target)
if(!istype(source) || !istype(target))
return FALSE
if(isliving(source))
var/mob/living/source_mob = source
if(source_mob.body_position == LYING_DOWN)
return FALSE
var/goal_dir = get_dir(source, target)
var/clockwise_source_dir = turn(source.dir, -45)
var/anticlockwise_source_dir = turn(source.dir, 45)
if(source.dir == goal_dir || clockwise_source_dir == goal_dir || anticlockwise_source_dir == goal_dir)
return TRUE
return FALSE
/*
rough example of the "cone" made by the 3 dirs checked
* \
* \
* >
* <
* \
* \
*B --><-- A
* /
* /
* <
* >
* /
* /
*/
///ultra range (no limitations on distance, faster than range for distances > 8); including areas drastically decreases performance
/proc/urange(dist = 0, atom/center = usr, orange = FALSE, areas = FALSE)
if(!dist)
if(!orange)
return list(center)
else
return list()
var/list/turfs = RANGE_TURFS(dist, center)
if(orange)
turfs -= get_turf(center)
. = list()
for(var/turf/checked_turf as anything in turfs)
. += checked_turf
. += checked_turf.contents
if(areas)
. |= checked_turf.loc
///similar function to range(), but with no limitations on the distance; will search spiralling outwards from the center
/proc/spiral_range(dist = 0, center = usr, orange = FALSE)
var/list/atom_list = list()
var/turf/t_center = get_turf(center)
if(!t_center)
return list()
if(!orange)
atom_list += t_center
atom_list += t_center.contents
if(!dist)
return atom_list
var/turf/checked_turf
var/y
var/x
var/c_dist = 1
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)
atom_list += checked_turf
atom_list += checked_turf.contents
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)
atom_list += checked_turf
atom_list += checked_turf.contents
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)
atom_list += checked_turf
atom_list += checked_turf.contents
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)
atom_list += checked_turf
atom_list += checked_turf.contents
c_dist++
return atom_list
///Returns the closest atom of a specific type in a list from a source
/proc/get_closest_atom(type, list/atom_list, source)
var/closest_atom
var/closest_distance
for(var/atom in atom_list)
if(!istype(atom, type))
continue
var/distance = get_dist(source, atom)
if(!closest_atom)
closest_distance = distance
closest_atom = atom
else
if(closest_distance > distance)
closest_distance = distance
closest_atom = atom
return closest_atom
///Returns a chosen path that is the closest to a list of matches
/proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types())
if (value == FALSE) //nothing should be calling us with a number, so this is safe
value = input("Enter type to find (blank for all, cancel to cancel)", "Search for type") as null|text
if (isnull(value))
return
value = trim(value)
var/random = FALSE
if(findtext(value, "?"))
value = replacetext(value, "?", "")
random = TRUE
if(!isnull(value) && value != "")
matches = filter_fancy_list(matches, value)
if(matches.len == 0)
return
var/chosen
if(matches.len == 1)
chosen = matches[1]
else if(random)
chosen = pick(matches) || null
else
chosen = input("Select a type", "Pick Type", matches[1]) as null|anything in sort_list(matches)
if(!chosen)
return
chosen = matches[chosen]
return chosen
///Creates new items inside an atom based on a list
/proc/generate_items_inside(list/items_list, where_to)
for(var/each_item in items_list)
for(var/i in 1 to items_list[each_item])
new each_item(where_to)
///Returns the atom type in the specified loc
/proc/get(atom/loc, type)
while(loc)
if(istype(loc, type))
return loc
loc = loc.loc
return null
/**
* Line of sight check!
* Spawns a dummy object and then iterates through each turf to see if it's blocked by something not handled by pass_args.
* Contains a mid_los_check, meant to be overriden by subtypes.
* args:
* * user = Origin to start at.
* * target = End point.
* * pass_args = pass_flags given to dummy object to allow it to ignore certain types of blockades.
*/
/proc/los_check(atom/movable/user, mob/target, pass_args = PASSTABLE|PASSGLASS|PASSGRILLE, datum/callback/mid_check)
var/turf/user_turf = user.loc
if(!istype(user_turf))
return FALSE
var/obj/dummy = new(user_turf)
dummy.pass_flags |= pass_args //Grille/Glass so it can be used through common windows
var/turf/previous_step = user_turf
var/first_step = TRUE
for(var/turf/next_step as anything in (get_line(user_turf, target) - user_turf))
if(first_step)
for(var/obj/blocker in user_turf)
if(!blocker.density || !(blocker.flags_1 & ON_BORDER_1))
continue
if(blocker.CanPass(dummy, get_dir(user_turf, next_step)))
continue
return FALSE // Could not leave the first turf.
first_step = FALSE
if(next_step.density)
qdel(dummy)
return FALSE
for(var/atom/movable/movable as anything in next_step)
if(!movable.CanPass(dummy, get_dir(next_step, previous_step)))
qdel(dummy)
return FALSE
if(mid_check?.Invoke(user, target, pass_args, next_step, dummy) == FALSE) // specify false as it may return null if there's no check
qdel(dummy)
return FALSE
previous_step = next_step
qdel(dummy)
return TRUE
///Returns true if the src countain the atom target
/atom/proc/contains(atom/target)
if(!target)
return FALSE
for(var/atom/location = target.loc, location, location = location.loc)
if(location == src)
return TRUE
///A do nothing proc
/proc/pass(...)
return
/// Returns an x and y value require to reverse the transformations made to center an oversized icon
/atom/proc/get_oversized_icon_offsets()
if (pixel_x == 0 && pixel_y == 0)
return list("x" = 0, "y" = 0)
var/list/icon_dimensions = get_icon_dimensions(icon)
var/icon_width = icon_dimensions["width"]
var/icon_height = icon_dimensions["height"]
return list(
"x" = icon_width > ICON_SIZE_X && pixel_x != 0 ? (icon_width - ICON_SIZE_X) * 0.5 : 0,
"y" = icon_height > ICON_SIZE_Y && pixel_y != 0 ? (icon_height - ICON_SIZE_Y) * 0.5 : 0,
)
/// Helper for easily adding blood from INSIDE a mob to an atom (NOT blood ON the mob)
#define add_mob_blood(from_who) add_blood_DNA(from_who.get_blood_dna_list(), from_who.get_static_viruses())