Files
Paradise/code/__HELPERS/unsorted.dm
GDN 82b863cefd Converts over the missing TGUI input lists (#23859)
* Converts over the missing TGUI input lists

* last fixes

* num revert
2024-02-14 21:17:29 +00:00

2063 lines
62 KiB
Plaintext

/*
* A large number of misc global procs.
*/
/* Get the direction of startObj relative to endObj.
* Return values: To the right, 1. Below, 2. To the left, 3. Above, 4. Not found adjacent in cardinal directions, 0.
*/
/proc/getRelativeDirection(atom/movable/startObj, atom/movable/endObj)
if(endObj.x == startObj.x + 1 && endObj.y == startObj.y)
return EAST
if(endObj.x == startObj.x - 1 && endObj.y == startObj.y)
return WEST
if(endObj.y == startObj.y + 1 && endObj.x == startObj.x)
return NORTH
if(endObj.y == startObj.y - 1 && endObj.x == startObj.x)
return SOUTH
return 0
//Returns the middle-most value
/proc/dd_range(low, high, num)
return max(low,min(high,num))
//Returns whether or not A is the middle most value
/proc/InRange(A, lower, upper)
if(A < lower) return 0
if(A > upper) return 0
return 1
/proc/get_angle(atom/movable/start, atom/movable/end)//For beams.
if(!start || !end)
return 0
var/dy
var/dx
dy = (32 * end.y + end.pixel_y) - (32 * start.y + start.pixel_y)
dx = (32 * end.x + end.pixel_x) - (32 * start.x + start.pixel_x)
if(!dy)
return (dx >= 0) ? 90 : 270
. = arctan(dx / dy)
if(dy < 0)
. += 180
else if(dx < 0)
. += 360
// Returns location. Returns null if no location was found.
/proc/get_teleport_loc(turf/location, mob/target, distance = 1, density = TRUE, 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).
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 seperate 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(NORTH)
diry += distance
yoffset += eoffsety
xoffset += eoffsetx
b1xerror -= errorx
b1yerror -= errory
b2xerror += errorx
b2yerror += errory
if(SOUTH)
diry -= distance
yoffset -= eoffsety
xoffset += eoffsetx
b1xerror -= errorx
b1yerror -= errory
b2xerror += errorx
b2yerror += errory
if(EAST)
dirx += distance
yoffset += eoffsetx // Flipped.
xoffset += eoffsety
b1xerror -= errory // Flipped.
b1yerror -= errorx
b2xerror += errory
b2yerror += errorx
if(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)
return
if(!errorx && !errory)
if(density && destination.density)
return
if(destination.x > world.maxx || destination.x < 1 || destination.y > world.maxy || destination.y < 1)
return
return destination
var/list/destination_list = list()
/*
This will draw a block around the target turf, given what the error is.
Specifying `errorx` and `errory` 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.
for(var/turf/T in block(locate(center.x + b1xerror, center.y + b1yerror, location.z), locate(center.x + b2xerror, center.y + b2yerror, location.z)))
if(density && T.density)
continue
if(T.x > world.maxx || T.x < 1 || T.y > world.maxy || T.y < 1)
continue // Don't want them to teleport off the map.
destination_list += T
if(!length(destination_list))
return
return pick(destination_list)
/proc/is_in_teleport_proof_area(atom/O)
if(!O)
return FALSE
var/area/A = get_area(O)
if(!A)
return FALSE
if(A.tele_proof)
return TRUE
if(!is_teleport_allowed(O.z))
return TRUE
else
return FALSE
// Returns true if direction is blocked from loc
// Checks if doors are open
/proc/DirBlocked(turf/loc, dir)
for(var/obj/structure/window/D in loc)
if(!D.density)
continue
if(D.fulltile)
return 1
if(D.dir == dir)
return 1
for(var/obj/machinery/door/D in loc)
if(!D.density)//if the door is open
continue
else return 1 // if closed, it's a real, air blocking door
return 0
/////////////////////////////////////////////////////////////////////////
/**
* Get a list of turfs in a line from `starting_atom` to `ending_atom`.
*
* Uses the ultra-fast [Bresenham Line-Drawing Algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm).
*/
/proc/get_line(atom/starting_atom, atom/ending_atom)
var/current_x_step = starting_atom.x//start at x and y, then add 1 or -1 to these to get every turf from starting_atom to ending_atom
var/current_y_step = starting_atom.y
var/starting_z = starting_atom.z
var/list/line = list(get_step(starting_atom, 0))//get_turf(atom) is faster than locate(x, y, z) //Get turf isn't defined yet so we use get step
var/x_distance = ending_atom.x - current_x_step //x distance
var/y_distance = ending_atom.y - current_y_step
var/abs_x_distance = abs(x_distance)//Absolute value of x distance
var/abs_y_distance = abs(y_distance)
var/x_distance_sign = SIGN(x_distance) //Sign of x distance (+ or -)
var/y_distance_sign = SIGN(y_distance)
var/x = abs_x_distance >> 1 //Counters for steps taken, setting to distance/2
var/y = abs_y_distance >> 1 //Bit-shifting makes me l33t. It also makes get_line() unnessecarrily fast.
if(abs_x_distance >= abs_y_distance) //x distance is greater than y
for(var/distance_counter in 0 to (abs_x_distance - 1))//It'll take abs_x_distance steps to get there
y += abs_y_distance
if(y >= abs_x_distance) //Every abs_y_distance steps, step once in y direction
y -= abs_x_distance
current_y_step += y_distance_sign
current_x_step += x_distance_sign //Step on in x direction
line += locate(current_x_step, current_y_step, starting_z)//Add the turf to the list
else
for(var/distance_counter in 0 to (abs_y_distance - 1))
x += abs_x_distance
if(x >= abs_y_distance)
x -= abs_y_distance
current_x_step += x_distance_sign
current_y_step += y_distance_sign
line += locate(current_x_step, current_y_step, starting_z)
return line
//Same as the thing below just for density and without support for atoms.
/proc/can_line(atom/source, atom/target, length = 5)
var/turf/current = get_turf(source)
var/turf/target_turf = get_turf(target)
var/steps = 0
while(current != target_turf)
if(steps > length)
return FALSE
if(!current)
return FALSE
if(current.density)
return FALSE
current = get_step_towards(current, target_turf)
steps++
return TRUE
//Returns whether or not a player is a guest using their ckey as an input
/proc/IsGuestKey(key)
if(findtext(key, "Guest-", 1, 7) != 1) //was findtextEx
return 0
var/i, ch, len = length(key)
for(i = 7, i <= len, ++i)
ch = text2ascii(key, i)
if(ch < 48 || ch > 57)
return 0
return 1
//Ensure the frequency is within bounds of what it should be sending/recieving at
/proc/sanitize_frequency(f, low = PUBLIC_LOW_FREQ, high = PUBLIC_HIGH_FREQ)
f = round(f)
f = max(low, f)
f = min(high, f)
if((f % 2) == 0) //Ensure the last digit is an odd number
f += 1
return f
//Turns 1479 into 147.9
/proc/format_frequency(f)
return "[round(f / 10)].[f % 10]"
/obj/proc/atmosanalyzer_scan(datum/gas_mixture/air_contents, mob/user, obj/target = src)
var/obj/icon = target
if(isliving(user))
user.visible_message("<span class='notice'>[user] uses the analyzer on [target].</span>", "<span class='notice'>You use the analyzer on [target].</span>")
var/pressure = air_contents.return_pressure()
var/total_moles = air_contents.total_moles()
var/volume = air_contents.return_volume()
user.show_message("<span class='notice'>Results of analysis of [bicon(icon)] [target].</span>", 1)
if(total_moles>0)
var/o2_concentration = air_contents.oxygen/total_moles
var/n2_concentration = air_contents.nitrogen/total_moles
var/co2_concentration = air_contents.carbon_dioxide/total_moles
var/plasma_concentration = air_contents.toxins/total_moles
var/n2o_concentration = air_contents.sleeping_agent/total_moles
var/unknown_concentration = 1-(o2_concentration+n2_concentration+co2_concentration+plasma_concentration+n2o_concentration)
user.show_message("<span class='notice'>Pressure: [round(pressure,0.1)] kPa</span>", 1)
user.show_message("<span class='notice'>Nitrogen: [round(n2_concentration*100)] % ([round(air_contents.nitrogen,0.01)] moles)</span>", 1)
user.show_message("<span class='notice'>Oxygen: [round(o2_concentration*100)] % ([round(air_contents.oxygen,0.01)] moles)</span>", 1)
user.show_message("<span class='notice'>CO2: [round(co2_concentration*100)] % ([round(air_contents.carbon_dioxide,0.01)] moles)</span>", 1)
user.show_message("<span class='notice'>Plasma: [round(plasma_concentration*100)] % ([round(air_contents.toxins,0.01)] moles)</span>", 1)
user.show_message("<span class='notice'>Nitrous Oxide: [round(n2o_concentration*100)] % ([round(air_contents.sleeping_agent,0.01)] moles)</span>", 1)
if(unknown_concentration>0.01)
user.show_message("<span class='danger'>Unknown: [round(unknown_concentration*100)] % ([round(unknown_concentration*total_moles,0.01)] moles)</span>", 1)
user.show_message("<span class='notice'>Total: [round(total_moles,0.01)] moles</span>", 1)
user.show_message("<span class='notice'>Temperature: [round(air_contents.temperature-T0C)] &deg;C</span>", 1)
user.show_message("<span class='notice'>Volume: [round(volume)] Liters</span>", 1)
else
user.show_message("<span class='notice'>[target] is empty!</span>", 1)
user.show_message("<span class='notice'>Volume: [round(volume)] Liters</span>", 1)
return
//Picks a string of symbols to display as the law number for hacked or ion laws
/proc/ionnum()
return "[pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")]"
// Selects an unlinked borg, used in the robot upload console
/proc/freeborg(mob/user)
var/select
var/list/borgs = list()
for(var/mob/living/silicon/robot/A in GLOB.player_list)
if(A.stat == DEAD || A.connected_ai || A.scrambledcodes || isdrone(A))
continue
var/name = "[A.real_name] ([A.modtype] [A.braintype])"
borgs[name] = A
if(length(borgs))
select = tgui_input_list(user, "Unshackled borg signals detected:", "Borg selection", borgs)
return borgs[select]
//When a borg is activated, it can choose which AI it wants to be slaved to
/proc/active_ais()
. = list()
for(var/mob/living/silicon/ai/A in GLOB.alive_mob_list)
if(A.stat == DEAD)
continue
if(A.control_disabled)
continue
. += A
return .
//Find an active ai with the least borgs. VERBOSE PROCNAME HUH!
/proc/select_active_ai_with_fewest_borgs()
var/mob/living/silicon/ai/selected
var/list/active = active_ais()
for(var/thing in active)
var/mob/living/silicon/ai/A = thing
if(!selected || (length(selected.connected_robots) > length(A.connected_robots)))
selected = A
return selected
/proc/select_active_ai(mob/user)
var/list/ais = active_ais()
if(!length(ais))
return
if(user)
return tgui_input_list(user, "AI signals detected:", "AI selection", ais)
else
return pick(ais)
/proc/get_sorted_mobs()
var/list/old_list = getmobs()
var/list/AI_list = list()
var/list/Dead_list = list()
var/list/keyclient_list = list()
var/list/key_list = list()
var/list/logged_list = list()
for(var/named in old_list)
var/mob/M = old_list[named]
if(issilicon(M))
AI_list |= M
else if(isobserver(M) || M.stat == DEAD)
Dead_list |= M
else if(M.key && M.client)
keyclient_list |= M
else if(M.key)
key_list |= M
else
logged_list |= M
old_list.Remove(named)
var/list/new_list = list()
new_list += AI_list
new_list += keyclient_list
new_list += key_list
new_list += logged_list
new_list += Dead_list
return new_list
//Returns a list of all mobs with their name
/proc/getmobs()
var/list/mobs = sortmobs()
var/list/names = list()
var/list/creatures = list()
var/list/namecounts = list()
for(var/mob/M in mobs)
var/name = M.name
if(name in names)
namecounts[name]++
name = "[name] ([namecounts[name]])"
else
names.Add(name)
namecounts[name] = 1
if(M.real_name && M.real_name != M.name)
name += " \[[M.real_name]\]"
if(M.stat == DEAD)
if(isobserver(M))
name += " \[ghost\]"
else
name += " \[dead\]"
creatures[name] = M
return creatures
//Orders mobs by type then by name
/proc/sortmobs()
var/list/moblist = list()
var/list/sortmob = sortAtom(GLOB.mob_list)
for(var/mob/living/silicon/ai/M in sortmob)
moblist.Add(M)
if(M.eyeobj)
moblist.Add(M.eyeobj)
for(var/mob/living/silicon/pai/M in sortmob)
moblist.Add(M)
for(var/mob/living/silicon/robot/M in sortmob)
moblist.Add(M)
for(var/mob/living/carbon/human/M in sortmob)
moblist.Add(M)
for(var/mob/living/brain/M in sortmob)
moblist.Add(M)
for(var/mob/living/carbon/alien/M in sortmob)
moblist.Add(M)
for(var/mob/dead/observer/M in sortmob)
moblist.Add(M)
for(var/mob/new_player/M in sortmob)
moblist.Add(M)
for(var/mob/living/simple_animal/slime/M in sortmob)
moblist.Add(M)
for(var/mob/living/simple_animal/M in sortmob)
moblist.Add(M)
return moblist
// Format a power value in W, kW, MW, or GW.
/proc/DisplayPower(powerused)
if(powerused < 1000) //Less than a kW
return "[powerused] W"
else if(powerused < 1000000) //Less than a MW
return "[round((powerused * 0.001), 0.01)] kW"
else if(powerused < 1000000000) //Less than a GW
return "[round((powerused * 0.000001), 0.001)] MW"
return "[round((powerused * 0.000000001), 0.0001)] GW"
// Format an energy value in J, kJ, MJ, or GJ. 1W = 1J/s.
/proc/DisplayJoules(units)
if(units < 1000) // Less than a kJ
return "[round(units, 0.1)] J"
else if(units < 1000000) // Less than a MJ
return "[round(units * 0.001, 0.01)] kJ"
else if(units < 1000000000) // Less than a GJ
return "[round(units * 0.000001, 0.001)] MJ"
return "[round(units * 0.000000001, 0.0001)] GJ"
// Format an energy value measured in Power Cell units.
/proc/DisplayEnergy(units)
// APCs process every (SSmachines.wait * 0.1) seconds, and turn 1 W of
// excess power into GLOB.CELLRATE energy units when charging cells.
// With the current configuration of wait=20 and CELLRATE=0.002, this
// means that one unit is 1 kJ.
return DisplayJoules(units * SSmachines.wait * 0.1 / GLOB.CELLRATE)
//Forces a variable to be posative
/proc/modulus(M)
if(M >= 0)
return M
if(M < 0)
return -M
/proc/get_mob_by_ckey(key)
if(!key)
return
for(var/mob/M in GLOB.mob_list)
if(M.ckey == key)
return M
/proc/get_client_by_ckey(ckey)
if(cmptext(copytext(ckey, 1, 2),"@"))
ckey = findStealthKey(ckey)
return GLOB.directory[ckey]
/proc/findStealthKey(txt)
if(txt)
for(var/P in GLOB.stealthminID)
if(GLOB.stealthminID[P] == txt)
return P
//Returns the 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.
//Optional arg 'type' to stop once it reaches a specific type instead of a turf.
/proc/get_atom_on_turf(atom/movable/M, stop_type)
var/atom/loc = M
while(loc?.loc && !isturf(loc.loc))
loc = loc.loc
if(stop_type && istype(loc, stop_type))
break
return loc
/*
Returns 1 if the chain up to the area contains the given typepath
0 otherwise
*/
/atom/proc/is_found_within(typepath)
var/atom/A = src
while(A.loc)
if(istype(A.loc, typepath))
return 1
A = A.loc
return 0
// the on-close client verb
// called when a browser popup window is closed after registering with proc/onclose()
// if a valid atom reference is supplied, call the atom's Topic() with "close=1"
// otherwise, just reset the client mob's machine var.
// returns the turf located at the map edge in the specified direction relative to A
// used for mass driver
/proc/get_edge_target_turf(atom/A, direction)
var/turf/target = locate(A.x, A.y, A.z)
if(!A || !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
// Note diagonal directions won't usually be accurate
if(direction & NORTH)
target = locate(target.x, world.maxy, target.z)
if(direction & SOUTH)
target = locate(target.x, 1, target.z)
if(direction & EAST)
target = locate(world.maxx, target.y, target.z)
if(direction & WEST)
target = locate(1, target.y, target.z)
return target
// returns turf relative to A 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/A, direction, range)
var/x = A.x
var/y = A.y
if(direction & NORTH)
y = min(world.maxy, y + range)
if(direction & SOUTH)
y = max(1, y - range)
if(direction & EAST)
x = min(world.maxx, x + range)
if(direction & WEST)
x = max(1, x - range)
return locate(x,y,A.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 A offset in dx and dy tiles
// bound to map limits
/proc/get_offset_target_turf(atom/A, dx, dy)
var/x = min(world.maxx, max(1, A.x + dx))
var/y = min(world.maxy, max(1, A.y + dy))
return locate(x,y,A.z)
//returns random gauss number
/proc/GaussRand(sigma)
var/x,y,rsq
do
x=2*rand()-1
y=2*rand()-1
rsq=x*x+y*y
while(rsq>1 || !rsq)
return sigma*y*sqrt(-2*log(rsq)/rsq)
//returns random gauss number, rounded to 'roundto'
/proc/GaussRandRound(sigma, roundto)
return round(GaussRand(sigma),roundto)
//Will return the contents of an atom recursivly to a depth of 'searchDepth'
/atom/proc/GetAllContents(searchDepth = 5)
var/list/toReturn = list()
for(var/atom/part in contents)
toReturn += part
if(length(part.contents) && searchDepth)
toReturn += part.GetAllContents(searchDepth - 1)
return toReturn
//Searches contents of the atom and returns the sum of all w_class of obj/item within
/atom/proc/GetTotalContentsWeight(searchDepth = 5)
var/weight = 0
var/list/content = GetAllContents(searchDepth)
for(var/obj/item/I in content)
weight += I.w_class
return weight
//Step-towards method of determining whether one atom can see another. Similar to viewers()
/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)
var/steps = 1
if(current != target_turf)
current = get_step_towards(current, target_turf)
while(current != target_turf)
if(steps > length)
return 0
if(current.opacity)
return 0
for(var/thing in current)
var/atom/A = thing
if(A.opacity)
return 0
current = get_step_towards(current, target_turf)
steps++
return 1
/proc/is_blocked_turf(turf/T, exclude_mobs, list/excluded_objs)
if(T.density)
return TRUE
if(locate(/mob/living/silicon/ai) in T) //Prevents jaunting onto the AI core cheese, AI should always block a turf due to being a dense mob even when unanchored
return TRUE
if(!exclude_mobs)
for(var/mob/living/L in T)
if(L.density)
return TRUE
var/any_excluded_objs = length(excluded_objs)
for(var/obj/O in T)
if(any_excluded_objs && (O in excluded_objs))
continue
if(O.density)
return TRUE
return FALSE
/proc/get_step_towards2(atom/ref , atom/trg)
var/base_dir = get_dir(ref, get_step_towards(ref,trg))
var/turf/temp = get_step_towards(ref,trg)
if(is_blocked_turf(temp))
var/dir_alt1 = turn(base_dir, 90)
var/dir_alt2 = turn(base_dir, -90)
var/turf/turf_last1 = temp
var/turf/turf_last2 = temp
var/free_tile
var/breakpoint = 0
while(!free_tile && breakpoint < 10)
if(!is_blocked_turf(turf_last1))
free_tile = turf_last1
break
if(!is_blocked_turf(turf_last2))
free_tile = turf_last2
break
turf_last1 = get_step(turf_last1,dir_alt1)
turf_last2 = get_step(turf_last2,dir_alt2)
breakpoint++
if(!free_tile) return get_step(ref, base_dir)
else return get_step_towards(ref,free_tile)
else return get_step(ref, base_dir)
//Takes: Anything that could possibly have variables and a varname to check.
//Returns: 1 if found, 0 if not.
/proc/hasvar(datum/A, varname)
if(A.vars.Find(lowertext(varname))) return 1
else return 0
//Returns: all the areas in the world
/proc/return_areas()
var/list/area/areas = list()
for(var/area/A in world)
areas += A
return areas
//Returns: all the areas in the world, sorted.
/proc/return_sorted_areas()
return sortAtom(return_areas())
//Takes: Area type as text string or as typepath OR an instance of the area.
//Returns: A list of all areas of that type in the world.
/proc/get_areas(areatype)
if(!areatype) return null
if(istext(areatype)) areatype = text2path(areatype)
if(isarea(areatype))
var/area/areatemp = areatype
areatype = areatemp.type
var/list/areas = list()
for(var/area/N in world)
if(istype(N, areatype)) areas += N
return areas
//Takes: Area type as text string or as typepath OR an instance of the area.
//Returns: A list of all turfs in areas of that type of that type in the world.
/proc/get_area_turfs(areatype)
if(!areatype) return null
if(istext(areatype)) areatype = text2path(areatype)
if(isarea(areatype))
var/area/areatemp = areatype
areatype = areatemp.type
var/list/turfs = list()
for(var/area/N in world)
if(istype(N, areatype))
for(var/turf/T in N) turfs += T
return turfs
//Takes: Area type as text string or as typepath OR an instance of the area.
//Returns: A list of all atoms (objs, turfs, mobs) in areas of that type of that type in the world.
/proc/get_area_all_atoms(areatype)
if(!areatype) return null
if(istext(areatype)) areatype = text2path(areatype)
if(isarea(areatype))
var/area/areatemp = areatype
areatype = areatemp.type
var/list/atoms = list()
for(var/area/N in world)
if(istype(N, areatype))
for(var/atom/A in N)
atoms += A
return atoms
/datum/coords //Simple datum for storing coordinates.
var/x_pos
var/y_pos
var/z_pos
/area/proc/move_contents_to(area/A, turf_to_leave, direction)
//Takes: Area. Optional: turf type to leave behind.
//Returns: Nothing.
//Notes: Attempts to move the contents of one area to another area.
// Movement based on lower left corner. Tiles that do not fit
// into the new area will not be moved.
if(!A || !src) return 0
var/list/turfs_src = get_area_turfs(src.type)
var/list/turfs_trg = get_area_turfs(A.type)
var/src_min_x = 0
var/src_min_y = 0
for(var/turf/T in turfs_src)
if(T.x < src_min_x || !src_min_x) src_min_x = T.x
if(T.y < src_min_y || !src_min_y) src_min_y = T.y
var/trg_min_x = 0
var/trg_min_y = 0
for(var/turf/T in turfs_trg)
if(T.x < trg_min_x || !trg_min_x) trg_min_x = T.x
if(T.y < trg_min_y || !trg_min_y) trg_min_y = T.y
var/list/refined_src = list()
for(var/turf/T in turfs_src)
refined_src += T
refined_src[T] = new/datum/coords
var/datum/coords/C = refined_src[T]
C.x_pos = (T.x - src_min_x)
C.y_pos = (T.y - src_min_y)
var/list/refined_trg = list()
for(var/turf/T in turfs_trg)
refined_trg += T
refined_trg[T] = new/datum/coords
var/datum/coords/C = refined_trg[T]
C.x_pos = (T.x - trg_min_x)
C.y_pos = (T.y - trg_min_y)
var/list/from_update = list()
var/list/to_update = list()
moving:
for(var/turf/T in refined_src)
var/datum/coords/C_src = refined_src[T]
for(var/turf/B in refined_trg)
var/datum/coords/C_trg = refined_trg[B]
if(C_src.x_pos == C_trg.x_pos && C_src.y_pos == C_trg.y_pos)
var/old_dir1 = T.dir
var/old_icon_state1 = T.icon_state
var/old_icon1 = T.icon
var/turf/X = B.ChangeTurf(T.type)
X.dir = old_dir1
X.icon_state = old_icon_state1
X.icon = old_icon1 // Shuttle floors are in shuttle.dmi while the defaults are floors.dmi
// Give the new turf our air, if simulated
if(issimulatedturf(X) && issimulatedturf(T))
var/turf/simulated/sim = X
sim.copy_air_with_tile(T)
// Quick visual fix for some weird shuttle corner artefacts when on transit space tiles
if(direction && findtext(X.icon_state, "swall_s"))
// Spawn a new shuttle corner object
var/obj/corner = new
corner.loc = X
corner.density = TRUE
corner.anchored = TRUE
corner.icon = X.icon
corner.icon_state = replacetext(X.icon_state, "_s", "_f")
corner.tag = "delete me"
corner.name = "wall"
// Find a new turf to take on the property of
var/turf/nextturf = get_step(corner, direction)
if(!nextturf || !isspaceturf(nextturf))
nextturf = get_step(corner, turn(direction, 180))
// Take on the icon of a neighboring scrolling space icon
X.icon = nextturf.icon
X.icon_state = nextturf.icon_state
for(var/obj/O in T)
// Reset the shuttle corners
if(O.tag == "delete me")
X.icon = 'icons/turf/shuttle.dmi'
X.icon_state = replacetext(O.icon_state, "_f", "_s") // Revert the turf to the old icon_state
X.name = "wall"
qdel(O) // Prevents multiple shuttle corners from stacking
continue
if(QDELETED(O))
continue
O.loc.Exited(O)
O.setLoc(X)
O.loc.Entered(O)
for(var/mob/M in T)
if(!M.move_on_shuttle)
continue
M.loc = X
to_update += X
if(turf_to_leave)
from_update += T.ChangeTurf(turf_to_leave)
else
T.ChangeTurf(T.baseturf)
refined_src -= T
refined_trg -= B
continue moving
if(length(to_update))
for(var/turf/simulated/T1 in to_update)
SSair.remove_from_active(T1)
T1.CalculateAdjacentTurfs()
SSair.add_to_active(T1, TRUE)
if(length(from_update))
for(var/turf/simulated/T2 in from_update)
SSair.remove_from_active(T2)
T2.CalculateAdjacentTurfs()
SSair.add_to_active(T2, TRUE)
/proc/DuplicateObject(obj/original, perfectcopy = 0, sameloc = 0, atom/newloc)
if(!original)
return
var/obj/O
if(sameloc)
O = new original.type(original.loc)
else
O = new original.type(newloc)
if(perfectcopy)
if(O && original)
var/static/list/forbidden_vars = list("type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key", "power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "comp_lookup", "datum_components")
for(var/V in original.vars - forbidden_vars)
if(islist(original.vars[V]))
var/list/L = original.vars[V]
O.vars[V] = L.Copy()
else if(istype(original.vars[V], /datum))
continue // This would reference the original's object, that will break when it is used or deleted.
else
O.vars[V] = original.vars[V]
if(istype(O))
O.update_icon()
return O
/area/proc/copy_contents_to(area/A, platingRequired = FALSE, perfect_copy = TRUE)
//Takes: Area. Optional: If it should copy to areas that don't have plating
//Returns: List containing copied objects or `FALSE` if source/target area are null.
//Notes: Attempts to move the contents of one area to another area.
// Movement based on lower left corner. Tiles that do not fit
// into the new area will not be moved.
if(!A || !src)
return FALSE
var/list/turfs_src = get_area_turfs(src.type)
var/list/turfs_trg = get_area_turfs(A.type)
var/src_min_x = 0
var/src_min_y = 0
for(var/turf/T in turfs_src)
if(T.x < src_min_x || !src_min_x)
src_min_x = T.x
if(T.y < src_min_y || !src_min_y)
src_min_y = T.y
var/trg_min_x = 0
var/trg_min_y = 0
for(var/turf/T in turfs_trg)
if(T.x < trg_min_x || !trg_min_x)
trg_min_x = T.x
if(T.y < trg_min_y || !trg_min_y)
trg_min_y = T.y
var/list/refined_src = list()
for(var/turf/T in turfs_src)
refined_src += T
refined_src[T] = new/datum/coords
var/datum/coords/C = refined_src[T]
C.x_pos = (T.x - src_min_x)
C.y_pos = (T.y - src_min_y)
var/list/refined_trg = list()
for(var/turf/T in turfs_trg)
refined_trg += T
refined_trg[T] = new/datum/coords
var/datum/coords/C = refined_trg[T]
C.x_pos = (T.x - trg_min_x)
C.y_pos = (T.y - trg_min_y)
var/list/to_update = list()
var/list/copied_objects = list()
moving:
for(var/turf/T in refined_src)
var/datum/coords/C_src = refined_src[T]
for(var/turf/B in refined_trg)
var/datum/coords/C_trg = refined_trg[B]
if(C_src.x_pos == C_trg.x_pos && C_src.y_pos == C_trg.y_pos)
if(platingRequired && isspaceturf(B))
continue moving
var/old_dir1 = T.dir
var/old_icon_state1 = T.icon_state
var/old_icon1 = T.icon
var/turf/X = new T.type(B)
X.dir = old_dir1
X.icon_state = old_icon_state1
X.icon = old_icon1 // Shuttle floors are in shuttle.dmi while the defaults are floors.dmi
var/list/newobjs = list()
var/list/newmobs = list()
for(var/obj/O in T)
newobjs += DuplicateObject(O, perfect_copy, FALSE, X)
for(var/mob/M in T)
if(!M.move_on_shuttle)
continue
newmobs += DuplicateObject(M, TRUE, FALSE, X)
copied_objects += newobjs
copied_objects += newmobs
for(var/V in T.vars)
if(!(V in list("type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key", "x", "y", "z", "destination_z", "destination_x", "destination_y", "contents", "luminosity", "group")))
X.vars[V] = T.vars[V]
to_update += X
refined_src -= T
refined_trg -= B
continue moving
if(length(to_update))
for(var/turf/simulated/T1 in to_update)
T1.CalculateAdjacentTurfs()
SSair.add_to_active(T1,1)
return copied_objects
/proc/get_cardinal_dir(atom/A, atom/B)
var/dx = abs(B.x - A.x)
var/dy = abs(B.y - A.y)
return get_dir(A, B) & (rand() * (dx+dy) < dy ? 3 : 12)
//chances are 1:value. anyprob(1) will always return true
/proc/anyprob(value)
return (rand(1,value)==value)
/proc/view_or_range(distance = world.view , center = usr , type)
switch(type)
if("view")
. = view(distance,center)
if("range")
. = range(distance,center)
return
/proc/oview_or_orange(distance = world.view , center = usr , type)
switch(type)
if("view")
. = oview(distance,center)
if("range")
. = orange(distance,center)
return
/proc/get_mob_with_client_list()
var/list/mobs = list()
for(var/mob/M in GLOB.mob_list)
if(M.client)
mobs += M
return mobs
/proc/parse_zone(zone)
if(zone == "r_hand") return "right hand"
else if(zone == "l_hand") return "left hand"
else if(zone == "l_arm") return "left arm"
else if(zone == "r_arm") return "right arm"
else if(zone == "l_leg") return "left leg"
else if(zone == "r_leg") return "right leg"
else if(zone == "l_foot") return "left foot"
else if(zone == "r_foot") return "right foot"
else if(zone == "l_hand") return "left hand"
else if(zone == "r_hand") return "right hand"
else if(zone == "l_foot") return "left foot"
else if(zone == "r_foot") return "right foot"
else return zone
/*
Gets 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/movable/AM)
if(!istype(AM))
return
//Find AM's matrix so we can use it's X/Y pixel shifts
var/matrix/M = matrix(AM.transform)
var/pixel_x_offset = AM.pixel_x + M.get_x_shift()
var/pixel_y_offset = AM.pixel_y + M.get_y_shift()
//Irregular objects
if(AM.bound_height != world.icon_size || AM.bound_width != world.icon_size)
var/icon/AMicon = icon(AM.icon, AM.icon_state)
pixel_x_offset += ((AMicon.Width()/world.icon_size)-1)*(world.icon_size*0.5)
pixel_y_offset += ((AMicon.Height()/world.icon_size)-1)*(world.icon_size*0.5)
qdel(AMicon)
//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)
//Find coordinates
var/turf/T = get_turf(AM) //use AM's turfs, as it's coords are the same as AM's AND AM's coords are lost if it is inside another atom
if(!T)
return null
var/final_x = T.x + rough_x
var/final_y = T.y + rough_y
if(final_x || final_y)
return locate(final_x, final_y, T.z)
//Finds the distance between two atoms, in pixels
//centered = 0 counts from turf edge to edge
//centered = 1 counts from turf center to turf center
//of course mathematically this is just adding world.icon_size on again
/proc/getPixelDistance(atom/A, atom/B, centered = 1)
if(!istype(A)||!istype(B))
return 0
. = bounds_dist(A, B) + sqrt((((A.pixel_x+B.pixel_x)**2) + ((A.pixel_y+B.pixel_y)**2)))
if(centered)
. += world.icon_size
/proc/get(atom/loc, type)
while(loc)
if(istype(loc, type))
return loc
loc = loc.loc
return null
/proc/get_turf_or_move(turf/location)
return get_turf(location)
//For objects that should embed, but make no sense being is_sharp or is_pointed()
//e.g: rods
GLOBAL_LIST_INIT(can_embed_types, typecacheof(list(
/obj/item/stack/rods,
/obj/item/pipe)))
/proc/can_embed(obj/item/W)
if(is_sharp(W))
return 1
if(is_pointed(W))
return 1
if(is_type_in_typecache(W, GLOB.can_embed_types))
return 1
//Whether or not the given item counts as sharp in terms of dealing damage
/proc/is_sharp(obj/O)
if(!O)
return 0
if(O.sharp)
return 1
return 0
/proc/reverse_direction(dir)
switch(dir)
if(NORTH)
return SOUTH
if(NORTHEAST)
return SOUTHWEST
if(EAST)
return WEST
if(SOUTHEAST)
return NORTHWEST
if(SOUTH)
return NORTH
if(SOUTHWEST)
return NORTHEAST
if(WEST)
return EAST
if(NORTHWEST)
return SOUTHEAST
/*
Checks if that loc and dir has a item on the wall
*/
GLOBAL_LIST_INIT(wall_items, typecacheof(list(/obj/machinery/power/apc, /obj/machinery/alarm,
/obj/item/radio/intercom, /obj/structure/extinguisher_cabinet, /obj/structure/reagent_dispensers/peppertank,
/obj/machinery/status_display, /obj/machinery/requests_console, /obj/machinery/light_switch, /obj/structure/sign,
/obj/machinery/newscaster, /obj/machinery/firealarm, /obj/structure/noticeboard, /obj/machinery/door_control,
/obj/machinery/computer/security/telescreen, /obj/machinery/airlock_controller,
/obj/item/storage/secure/safe, /obj/machinery/door_timer, /obj/machinery/flasher, /obj/machinery/keycard_auth,
/obj/structure/mirror, /obj/structure/closet/fireaxecabinet, /obj/machinery/computer/security/telescreen/entertainment,
/obj/structure/sign)))
/proc/gotwallitem(loc, dir)
for(var/obj/O in loc)
if(is_type_in_typecache(O, GLOB.wall_items))
//Direction works sometimes
if(O.dir == dir)
return 1
//Some stuff doesn't use dir properly, so we need to check pixel instead
switch(dir)
if(SOUTH)
if(O.pixel_y > 10)
return 1
if(NORTH)
if(O.pixel_y < -10)
return 1
if(WEST)
if(O.pixel_x > 10)
return 1
if(EAST)
if(O.pixel_x < -10)
return 1
//Some stuff is placed directly on the wallturf (signs)
for(var/obj/O in get_step(loc, dir))
if(is_type_in_typecache(O, GLOB.wall_items))
if(abs(O.pixel_x) <= 10 && abs(O.pixel_y) <= 10)
return 1
return 0
/proc/atan2(x, y)
if(!x && !y) return 0
return y >= 0 ? arccos(x / sqrt(x * x + y * y)) : -arccos(x / sqrt(x * x + y * y))
/proc/format_text(text)
return replacetext(replacetext(text,"\proper ",""),"\improper ","")
/*
Standard way to write links -Sayu
*/
/proc/topic_link(datum/D, arglist, content)
if(islist(arglist))
arglist = list2params(arglist)
return "<a href='?src=[D.UID()];[arglist]'>[content]</a>"
/proc/get_location_accessible(mob/M, location)
var/covered_locations = 0 //based on body_parts_covered
var/face_covered = 0 //based on flags_inv
var/eyesmouth_covered = 0 //based on flags_cover
if(iscarbon(M))
var/mob/living/carbon/C = M
for(var/obj/item/clothing/I in list(C.back, C.wear_mask))
covered_locations |= I.body_parts_covered
face_covered |= I.flags_inv
eyesmouth_covered |= I.flags_cover
if(ishuman(C))
var/mob/living/carbon/human/H = C
for(var/obj/item/I in list(H.wear_suit, H.w_uniform, H.shoes, H.belt, H.gloves, H.glasses, H.head, H.r_ear, H.l_ear))
covered_locations |= I.body_parts_covered
face_covered |= I.flags_inv
eyesmouth_covered |= I.flags_cover
switch(location)
if("head")
if(covered_locations & HEAD)
return 0
if("eyes")
if(face_covered & HIDEEYES || eyesmouth_covered & GLASSESCOVERSEYES || eyesmouth_covered & HEADCOVERSEYES)
return 0
if("mouth")
if(covered_locations & HEAD || face_covered & HIDEFACE || eyesmouth_covered & MASKCOVERSMOUTH)
return 0
if("chest")
if(covered_locations & UPPER_TORSO)
return 0
if("groin")
if(covered_locations & LOWER_TORSO)
return 0
if("l_arm")
if(covered_locations & ARM_LEFT)
return 0
if("r_arm")
if(covered_locations & ARM_RIGHT)
return 0
if("l_leg")
if(covered_locations & LEG_LEFT)
return 0
if("r_leg")
if(covered_locations & LEG_RIGHT)
return 0
if("l_hand")
if(covered_locations & HAND_LEFT)
return 0
if("r_hand")
if(covered_locations & HAND_RIGHT)
return 0
if("l_foot")
if(covered_locations & FOOT_LEFT)
return 0
if("r_foot")
if(covered_locations & FOOT_RIGHT)
return 0
return 1
/proc/check_target_facings(mob/living/initator, mob/living/target)
/*This can be used to add additional effects on interactions between mobs depending on how the mobs are facing each other, such as adding a crit damage to blows to the back of a guy's head.
Given how click code currently works (Nov '13), the initiating mob will be facing the target mob most of the time
That said, this proc should not be used if the change facing proc of the click code is overriden at the same time*/
if(!ismob(target) || IS_HORIZONTAL(target))
//Make sure we are not doing this for things that can't have a logical direction to the players given that the target would be on their side
return FACING_FAILED
if(initator.dir == target.dir) //mobs are facing the same direction
return FACING_SAME_DIR
if(is_A_facing_B(initator, target) && is_A_facing_B(target, initator)) //mobs are facing each other
return FACING_EACHOTHER
if(initator.dir + 2 == target.dir || initator.dir - 2 == target.dir || initator.dir + 6 == target.dir || initator.dir - 6 == target.dir) //Initating mob is looking at the target, while the target mob is looking in a direction perpendicular to the 1st
return FACING_INIT_FACING_TARGET_TARGET_FACING_PERPENDICULAR
/atom/proc/GetTypeInAllContents(typepath)
var/list/processing_list = list(src)
var/list/processed = list()
var/atom/found
while(length(processing_list) && isnull(found))
var/atom/A = processing_list[1]
if(istype(A, typepath))
found = A
processing_list -= A
for(var/atom/a in A)
if(!(a in processed))
processing_list |= a
processed |= A
return found
/proc/random_step(atom/movable/AM, steps, chance)
var/initial_chance = chance
while(steps > 0)
if(prob(chance))
step(AM, pick(GLOB.alldirs))
chance = max(chance - (initial_chance / steps), 0)
steps--
/proc/get_random_colour(simple, lower, upper)
var/colour
if(simple)
colour = pick(list("FF0000","FF7F00","FFFF00","00FF00","0000FF","4B0082","8F00FF"))
else
for(var/i=1;i<=3;i++)
var/temp_col = "[num2hex(rand(lower,upper), 2)]"
colour += temp_col
return colour
/proc/get_distant_turf(turf/T, direction, distance)
if(!T || !direction || !distance) return
var/dest_x = T.x
var/dest_y = T.y
var/dest_z = T.z
if(direction & NORTH)
dest_y = min(world.maxy, dest_y+distance)
if(direction & SOUTH)
dest_y = max(0, dest_y-distance)
if(direction & EAST)
dest_x = min(world.maxy, dest_x+distance)
if(direction & WEST)
dest_x = max(0, dest_x-distance)
return locate(dest_x,dest_y,dest_z)
GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
//Version of view() which ignores darkness, because BYOND doesn't have it.
/proc/dview(range = world.view, center, invis_flags = 0)
if(!center)
return
GLOB.dview_mob.loc = center
GLOB.dview_mob.see_invisible = invis_flags
. = view(range, GLOB.dview_mob)
GLOB.dview_mob.loc = null
/mob/dview
invisibility = 101
density = FALSE
move_force = 0
pull_force = 0
move_resist = INFINITY
simulated = 0
see_in_dark = 1e6
/mob/dview/New() //For whatever reason, if this isn't called, then BYOND will throw a type mismatch runtime when attempting to add this to the mobs list. -Fox
SHOULD_CALL_PARENT(FALSE)
/mob/dview/Destroy()
SHOULD_CALL_PARENT(FALSE)
// should never be deleted
return QDEL_HINT_LETMELIVE
/proc/IsValidSrc(A)
if(istype(A, /datum))
var/datum/D = A
return !QDELETED(D)
if(isclient(A))
return TRUE
return FALSE
//can a window be here, or is there a window blocking it?
/proc/valid_window_location(turf/T, dir_to_check)
if(!T)
return FALSE
for(var/obj/O in T)
if(istype(O, /obj/machinery/door/window) && (O.dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR))
return FALSE
if(istype(O, /obj/structure/windoor_assembly))
var/obj/structure/windoor_assembly/W = O
if(W.ini_dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR)
return FALSE
if(istype(O, /obj/structure/window))
var/obj/structure/window/W = O
if(W.ini_dir == dir_to_check || W.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR)
return FALSE
return TRUE
//datum may be null, but it does need to be a typed var
#define NAMEOF(datum, X) (#X || ##datum.##X)
#define VARSET_LIST_CALLBACK(target, var_name, var_value) CALLBACK(GLOBAL_PROC, PROC_REF(___callbackvarset), ##target, ##var_name, ##var_value)
//dupe code because dm can't handle 3 level deep macros
#define VARSET_CALLBACK(datum, var, var_value) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___callbackvarset), ##datum, NAMEOF(##datum, ##var), ##var_value)
/proc/___callbackvarset(list_or_datum, var_name, var_value)
if(length(list_or_datum))
list_or_datum[var_name] = var_value
return
var/datum/D = list_or_datum
if(IsAdminAdvancedProcCall())
D.vv_edit_var(var_name, var_value) //same result generally, unless badmemes
else
D.vars[var_name] = var_value
//Get the dir to the RIGHT of dir if they were on a clock
//NORTH --> NORTHEAST
/proc/get_clockwise_dir(dir)
. = angle2dir(dir2angle(dir)+45)
//Get the dir to the LEFT of dir if they were on a clock
//NORTH --> NORTHWEST
/proc/get_anticlockwise_dir(dir)
. = angle2dir(dir2angle(dir)-45)
//Compare A's dir, the clockwise dir of A and the anticlockwise dir of A
//To the opposite dir of the dir returned by get_dir(B,A)
//If one of them is a match, then A is facing B
/proc/is_A_facing_B(atom/A, atom/B)
if(!istype(A) || !istype(B))
return 0
if(isliving(A))
var/mob/living/LA = A
if(IS_HORIZONTAL(LA))
return 0
var/goal_dir = angle2dir(dir2angle(get_dir(B, A)+180))
var/clockwise_A_dir = get_clockwise_dir(A.dir)
var/anticlockwise_A_dir = get_anticlockwise_dir(B.dir)
if(A.dir == goal_dir || clockwise_A_dir == goal_dir || anticlockwise_A_dir == goal_dir)
return 1
return 0
//Centers an image.
//Requires:
//The Image
//The x dimension of the icon file used in the image
//The y dimension of the icon file used in the image
// eg: center_image(I, 32,32)
// eg2: center_image(I, 96,96)
/proc/center_image(image/I, x_dimension = 0, y_dimension = 0)
if(!I)
return
if(!x_dimension || !y_dimension)
return
//Get out of here, punk ass kids calling procs needlessly
if((x_dimension == world.icon_size) && (y_dimension == world.icon_size))
return I
//Offset the image so that it's bottom left corner is shifted this many pixels
//This makes it infinitely easier to draw larger inhands/images larger than world.iconsize
//but still use them in game
var/x_offset = -((x_dimension/world.icon_size)-1)*(world.icon_size*0.5)
var/y_offset = -((y_dimension/world.icon_size)-1)*(world.icon_size*0.5)
//Correct values under world.icon_size
if(x_dimension < world.icon_size)
x_offset *= -1
if(y_dimension < world.icon_size)
y_offset *= -1
I.pixel_x = x_offset
I.pixel_y = y_offset
return I
//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=0)
if(!dist)
if(!orange)
return list(center)
else
return list()
var/turf/t_center = get_turf(center)
if(!t_center)
return list()
var/list/L = list()
var/turf/T
var/y
var/x
var/c_dist = 1
if(!orange)
L += 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)
T = locate(x,y,t_center.z)
if(T)
L += T
y = t_center.y + c_dist - 1
x = t_center.x + c_dist
var/list/temp_list_one = list()
for(y in t_center.y-c_dist to y)
T = locate(x,y,t_center.z)
if(T)
temp_list_one += T
L += reverselist(temp_list_one)
y = t_center.y - c_dist
x = t_center.x + c_dist - 1
var/list/temp_list_two = list()
for(x in t_center.x-c_dist to x)
T = locate(x,y,t_center.z)
if(T)
temp_list_two += T
L += reverselist(temp_list_two)
y = t_center.y - c_dist + 1
x = t_center.x - c_dist
for(y in y to t_center.y+c_dist)
T = locate(x,y,t_center.z)
if(T)
L += T
c_dist++
return L
//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=0, areas=0)
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/V in turfs)
var/turf/T = V
. += T
. += T.contents
if(areas)
. |= T.loc
/proc/turf_clear(turf/T)
for(var/atom/A in T)
if(A.simulated)
return FALSE
return TRUE
/proc/screen_loc2turf(scr_loc, turf/origin)
var/tX = splittext(scr_loc, ",")
var/tY = splittext(tX[2], ":")
var/tZ = origin.z
tY = tY[1]
tX = splittext(tX[1], ":")
tX = tX[1]
tX = max(1, min(world.maxx, origin.x + (text2num(tX) - (world.view + 1))))
tY = max(1, min(world.maxy, origin.y + (text2num(tY) - (world.view + 1))))
return locate(tX, tY, tZ)
/proc/get_closest_atom(type, list, source)
var/closest_atom
var/closest_distance
for(var/A in list)
if(!istype(A, type))
continue
var/distance = get_dist(source, A)
if(!closest_distance)
closest_distance = distance
closest_atom = A
else
if(closest_distance > distance)
closest_distance = distance
closest_atom = A
return closest_atom
/proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types())
if(!value) //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)
if(!isnull(value) && value != "")
matches = filter_fancy_list(matches, value)
if(!length(matches))
return
var/chosen
if(length(matches) == 1)
chosen = matches[1]
else
chosen = input("Select a type", "Pick Type", matches[1]) as null|anything in matches
if(!chosen)
return
chosen = matches[chosen]
return chosen
/proc/make_types_fancy(list/types)
if(ispath(types))
types = list(types)
. = list()
for(var/type in types)
var/typename = "[type]"
var/static/list/TYPES_SHORTCUTS = list(
//longest paths comes first - otherwise they get shadowed by the more generic ones
/obj/effect/decal/cleanable = "CLEANABLE",
/obj/effect = "EFFECT",
/obj/item/ammo_casing = "AMMO",
/obj/item/book/manual = "MANUAL",
/obj/item/borg/upgrade = "BORG_UPGRADE",
/obj/item/cartridge = "PDA_CART",
/obj/item/clothing/head/helmet/space = "SPESSHELMET",
/obj/item/clothing/head = "HEAD",
/obj/item/clothing/under = "UNIFORM",
/obj/item/clothing/shoes = "SHOES",
/obj/item/clothing/suit = "SUIT",
/obj/item/clothing/gloves = "GLOVES",
/obj/item/clothing/mask/cigarette = "CIGARRETE", // oof
/obj/item/clothing/mask = "MASK",
/obj/item/clothing/glasses = "GLASSES",
/obj/item/clothing = "CLOTHING",
/obj/item/grenade/clusterbuster = "CLUSTERBUSTER",
/obj/item/grenade = "GRENADE",
/obj/item/gun = "GUN",
/obj/item/bio_chip = "BIO_CHIP",
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack = "MECHA_MISSILE_RACK",
/obj/item/mecha_parts/mecha_equipment/weapon = "MECHA_WEAPON",
/obj/item/mecha_parts/mecha_equipment = "MECHA_EQUIP",
/obj/item/melee = "MELEE",
/obj/item/mmi = "MMI",
/obj/item/nullrod = "NULLROD",
/obj/item/organ/external = "EXT_ORG",
/obj/item/organ/internal/cyberimp = "CYBERIMP",
/obj/item/organ/internal = "INT_ORG",
/obj/item/organ = "ORGAN",
/obj/item/pda = "PDA",
/obj/item/projectile = "PROJ",
/obj/item/radio/headset = "HEADSET",
/obj/item/reagent_containers/glass/beaker = "BEAKER",
/obj/item/reagent_containers/glass/bottle = "BOTTLE",
/obj/item/reagent_containers/patch = "PATCH",
/obj/item/reagent_containers/pill = "PILL",
/obj/item/reagent_containers/drinks = "DRINK",
/obj/item/food = "FOOD",
/obj/item/reagent_containers/syringe = "SYRINGE",
/obj/item/reagent_containers = "REAGENT_CONTAINERS",
/obj/item/robot_parts = "ROBOT_PARTS",
/obj/item/seeds = "SEED",
/obj/item/slime_extract = "SLIME_CORE",
/obj/item/stack/sheet/mineral = "MINERAL",
/obj/item/stack/sheet = "SHEET",
/obj/item/stack/tile = "TILE",
/obj/item/stack = "STACK",
/obj/item/stock_parts/cell = "POWERCELL",
/obj/item/stock_parts = "STOCK_PARTS",
/obj/item/storage/firstaid = "FIRSTAID",
/obj/item/storage = "STORAGE",
/obj/item/tank = "GAS_TANK",
/obj/item/toy/crayon = "CRAYON",
/obj/item/toy = "TOY",
/obj/item = "ITEM",
/obj/machinery/atmospherics = "ATMOS_MACH",
/obj/machinery/computer = "CONSOLE",
/obj/machinery/door/airlock = "AIRLOCK",
/obj/machinery/door = "DOOR",
/obj/machinery/kitchen_machine = "KITCHEN",
/obj/machinery/atmospherics/portable/canister = "CANISTER",
/obj/machinery/atmospherics/portable = "PORT_ATMOS",
/obj/machinery/power = "POWER",
/obj/machinery = "MACHINERY",
/obj/mecha = "MECHA",
/obj/structure/closet/crate = "CRATE",
/obj/structure/closet = "CLOSET",
/obj/structure/statue = "STATUE",
/obj/structure/chair = "CHAIR", // oh no
/obj/structure/bed = "BED",
/obj/structure/chair/stool = "STOOL",
/obj/structure/table = "TABLE",
/obj/structure = "STRUCTURE",
/obj/vehicle = "VEHICLE",
/obj = "O",
/datum = "D",
/turf/simulated/floor = "SIM_FLOOR",
/turf/simulated/wall = "SIM_WALL",
/turf = "T",
/mob/living/carbon/alien = "XENO",
/mob/living/carbon/human = "HUMAN",
/mob/living/carbon = "CARBON",
/mob/living/silicon/robot = "CYBORG",
/mob/living/silicon/ai = "AI",
/mob/living/silicon = "SILICON",
/mob/living/simple_animal/bot = "BOT",
/mob/living/simple_animal = "SIMPLE",
/mob/living = "LIVING",
/mob = "M"
)
for(var/tn in TYPES_SHORTCUTS)
if(copytext(typename, 1, length("[tn]/") + 1) == "[tn]/")
typename = TYPES_SHORTCUTS[tn]+copytext(typename,length("[tn]/"))
break
.[typename] = type
/proc/get_fancy_list_of_atom_types()
var/static/list/pre_generated_list
if(!pre_generated_list) //init
pre_generated_list = make_types_fancy(typesof(/atom))
return pre_generated_list
/proc/get_fancy_list_of_datum_types()
var/static/list/pre_generated_list
if(!pre_generated_list) //init
pre_generated_list = make_types_fancy(sortList(typesof(/datum) - typesof(/atom)))
return pre_generated_list
/proc/filter_fancy_list(list/L, filter as text)
var/list/matches = list()
for(var/key in L)
var/value = L[key]
if(findtext("[key]", filter) || findtext("[value]", filter))
matches[key] = value
return matches
//Key thing that stops lag. Cornerstone of performance in ss13, Just sitting here, in unsorted.dm.
//Increases delay as the server gets more overloaded,
//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful
#define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1)
//returns the number of ticks slept
/proc/stoplag(initial_delay)
if(!Master || !(Master.current_runlevel & RUNLEVELS_DEFAULT))
sleep(world.tick_lag)
return 1
if(!initial_delay)
initial_delay = world.tick_lag
. = 0
var/i = DS2TICKS(initial_delay)
do
. += CEILING(i*DELTA_CALC, 1)
sleep(i*world.tick_lag*DELTA_CALC)
i *= 2
while(TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit))
#undef DELTA_CALC
/*
* This proc gets a list of all "points of interest" (poi's) that can be used by admins to track valuable mobs or atoms (such as the nuke disk).
* @param mobs_only if set to TRUE it won't include locations to the returned list
* @param skip_mindless if set to TRUE it will skip mindless mobs
* @param force_include_bots if set to TRUE it will include bots even if skip_mindless is set to TRUE
* @param force_include_cameras if set to TRUE it will include camera eyes even if skip_mindless is set to TRUE
* @return returns a list with the found points of interest
*/
/proc/getpois(mobs_only = FALSE, skip_mindless = FALSE, force_include_bots = FALSE, force_include_cameras = FALSE)
var/list/mobs = sortmobs()
var/list/names = list()
var/list/pois = list()
var/list/namecounts = list()
for(var/mob/M in mobs)
if(skip_mindless && (!M.mind && !M.ckey))
if(!(force_include_bots && isbot(M)) && !(force_include_cameras && istype(M, /mob/camera)))
continue
if(M.client && M.client.holder && M.client.holder.fakekey) //stealthmins
continue
var/name = M.name
if(name in names)
namecounts[name]++
name = "[name] ([namecounts[name]])"
else
names.Add(name)
namecounts[name] = 1
if(M.real_name && M.real_name != M.name)
name += " \[[M.real_name]\]"
if(M.stat == DEAD)
if(isobserver(M))
name += " \[ghost\]"
else
name += " \[dead\]"
pois[name] = M
if(!mobs_only)
for(var/atom/A in GLOB.poi_list)
if(!A || !A.loc)
continue
var/name = A.name
if(names.Find(name))
namecounts[name]++
name = "[name] ([namecounts[name]])"
else
names.Add(name)
namecounts[name] = 1
pois[name] = A
return pois
/proc/get_observers()
var/list/ghosts = list()
for(var/mob/dead/observer/M in GLOB.player_list) // for every observer with a client
ghosts += M
return ghosts
#define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255)))
/// This proc returns every player with a client who is not a ghost or a new_player
/proc/get_living_players(exclude_nonhuman = FALSE, exclude_offstation = FALSE)
var/list/living_players = list()
for(var/mob/living/M in GLOB.player_list)
if(exclude_nonhuman && !ishuman(M))
continue
if(exclude_offstation && M.mind?.offstation_role)
continue
living_players += M
return living_players
/proc/make_bit_triplet()
var/list/num_sample = list(1, 2, 3, 4, 5, 6, 7, 8, 9)
var/result = 0
for(var/i = 0, i < 3, i++)
var/num = pick(num_sample)
num_sample -= num
result += (1 << num)
return result
/proc/pixel_shift_dir(dir, amount_x = 32, amount_y = 32) //Returns a list with pixel_shift values that will shift an object's icon one tile in the direction passed.
amount_x = min(max(0, amount_x), 32) //No less than 0, no greater than 32.
amount_y = min(max(0, amount_x), 32)
var/list/shift = list("x" = 0, "y" = 0)
switch(dir)
if(NORTH)
shift["y"] = amount_y
if(SOUTH)
shift["y"] = -amount_y
if(EAST)
shift["x"] = amount_x
if(WEST)
shift["x"] = -amount_x
if(NORTHEAST)
shift = list("x" = amount_x, "y" = amount_y)
if(NORTHWEST)
shift = list("x" = -amount_x, "y" = amount_y)
if(SOUTHEAST)
shift = list("x" = amount_x, "y" = -amount_y)
if(SOUTHWEST)
shift = list("x" = -amount_x, "y" = -amount_y)
return shift
/**
* Returns a list of atoms in a location of a given type. Can be refined to look for pixel-shift.
*
* Arguments:
* * loc - The atom to look in.
* * type - The type to look for.
* * check_shift - If true, will exclude atoms whose pixel_x/pixel_y do not match shift_x/shift_y.
* * shift_x - If check_shift is true, atoms whose pixel_x is different to this will be excluded.
* * shift_y - If check_shift is true, atoms whose pixel_y is different to this will be excluded.
*/
/proc/get_atoms_of_type(atom/loc, type, check_shift = FALSE, shift_x = 0, shift_y = 0)
. = list()
if(!loc)
return
for(var/a in loc)
var/atom/A = a
if(!istype(A, type))
continue
if(check_shift && !(A.pixel_x == shift_x && A.pixel_y == shift_y))
continue
. += A
/proc/pass()
return
/atom/proc/Shake(pixelshiftx = 15, pixelshifty = 15, duration = 250)
var/initialpixelx = pixel_x
var/initialpixely = pixel_y
var/shiftx = rand(-pixelshiftx,pixelshiftx)
var/shifty = rand(-pixelshifty,pixelshifty)
animate(src, pixel_x = pixel_x + shiftx, pixel_y = pixel_y + shifty, time = 0.2, loop = duration)
pixel_x = initialpixelx
pixel_y = initialpixely
/proc/params2turf(scr_loc, turf/origin, client/C)
if(!scr_loc)
return null
var/tX = splittext(scr_loc, ",")
var/tY = splittext(tX[2], ":")
var/tZ = origin.z
tY = tY[1]
tX = splittext(tX[1], ":")
tX = tX[1]
var/list/actual_view = getviewsize(C ? C.view : world.view)
tX = clamp(origin.x + text2num(tX) - round(actual_view[1] / 2) - 1, 1, world.maxx)
tY = clamp(origin.y + text2num(tY) - round(actual_view[2] / 2) - 1, 1, world.maxy)
return locate(tX, tY, tZ)
/proc/CallAsync(datum/source, proctype, list/arguments)
set waitfor = FALSE
return call(source, proctype)(arglist(arguments))
/proc/IsFrozen(atom/A)
if(A in GLOB.frozen_atom_list)
return TRUE
return FALSE
// Check if the source atom contains another atom
/atom/proc/contains(atom/location)
if(!location)
return FALSE
if(location == src)
return TRUE
return contains(location.loc)
/proc/log_connection(ckey, ip, cid, connection_type)
ASSERT(connection_type in list(CONNECTION_TYPE_ESTABLISHED, CONNECTION_TYPE_DROPPED_IPINTEL, CONNECTION_TYPE_DROPPED_BANNED, CONNECTION_TYPE_DROPPED_INVALID))
var/datum/db_query/query_accesslog = SSdbcore.NewQuery("INSERT INTO connection_log (`datetime`, `ckey`, `ip`, `computerid`, `result`, `server_id`) VALUES(Now(), :ckey, :ip, :cid, :result, :server_id)", list(
"ckey" = ckey,
"ip" = "[ip ? ip : ""]", // This is important. NULL is not the same as "", and if you directly open the `.dmb` file, you get a NULL IP.
"cid" = cid,
"result" = connection_type,
"server_id" = GLOB.configuration.system.instance_id
))
query_accesslog.warn_execute()
qdel(query_accesslog)
/**
* Returns the clean name of an audio channel.
*
* Arguments:
* * channel - The channel number.
*/
/proc/get_channel_name(channel)
switch(channel)
if(CHANNEL_GENERAL)
return "General Sounds"
if(CHANNEL_LOBBYMUSIC)
return "Lobby Music"
if(CHANNEL_ADMIN)
return "Admin MIDIs"
if(CHANNEL_VOX)
return "AI Announcements"
if(CHANNEL_JUKEBOX)
return "Dance Machines"
if(CHANNEL_HEARTBEAT)
return "Heartbeat"
if(CHANNEL_BUZZ)
return "White Noise"
if(CHANNEL_AMBIENCE)
return "Ambience"
if(CHANNEL_ENGINE)
return "Engine Ambience"
if(CHANNEL_FIREALARM)
return "Fire Alarms"
if(CHANNEL_ASH_STORM)
return "Ash Storms"
if(CHANNEL_RADIO_NOISE)
return "Radio Noise"
if(CHANNEL_BOSS_MUSIC)
return "Boss Music"
/proc/slot_bitfield_to_slot(input_slot_flags) // Kill off this garbage ASAP; slot flags and clothing flags should be IDENTICAL. GOSH DARN IT. Doesn't work with ears or pockets, either.
switch(input_slot_flags)
if(SLOT_FLAG_OCLOTHING)
return SLOT_HUD_OUTER_SUIT
if(SLOT_FLAG_ICLOTHING)
return SLOT_HUD_JUMPSUIT
if(SLOT_FLAG_GLOVES)
return SLOT_HUD_GLOVES
if(SLOT_FLAG_EYES)
return SLOT_HUD_GLASSES
if(SLOT_FLAG_MASK)
return SLOT_HUD_WEAR_MASK
if(SLOT_FLAG_HEAD)
return SLOT_HUD_HEAD
if(SLOT_FLAG_FEET)
return SLOT_HUD_SHOES
if(SLOT_FLAG_ID)
return SLOT_HUD_WEAR_ID
if(SLOT_FLAG_BELT)
return SLOT_HUD_BELT
if(SLOT_FLAG_BACK)
return SLOT_HUD_BACK
if(SLOT_FLAG_PDA)
return SLOT_HUD_WEAR_PDA
if(SLOT_FLAG_TIE)
return SLOT_HUD_TIE
/**
* HTTP Get (Powered by RUSTG)
*
* This proc should be used as a replacement for [world.Export()] due to an underlying issue with it.
* See: https://www.byond.com/forum/post/2772166
* The one thing you will need to be aware of is that this no longer wraps the response inside a "file", so anything that relies on a file2text() unwrap will need tweaking.
* RUST HTTP also has better support for HTTPS as well as weird quirks with modern webservers.
* Returns an assoc list that follows the standard [world.Export()] format (https://secure.byond.com/docs/ref/index.html#/world/proc/Export), with the above exception
*
* Arguments:
* * url - URL to GET
*/
/proc/HTTPGet(url)
var/datum/http_request/req = new
req.prepare(RUSTG_HTTP_METHOD_GET, url)
req.begin_async()
// Check if we are complete
UNTIL(req.is_complete())
var/datum/http_response/res = req.into_response()
if(res.errored)
. = list() // Return an empty list
CRASH("Internal error during HTTP get: [res.error]")
var/list/output = list()
output["STATUS"] = res.status_code
// Handle changes of line format. ASCII 13 = CR
var/content = replacetext(res.body, "[ascii2text(13)]\n", "\n")
output["CONTENT"] = content
return output
/// Given a color in the format of "#RRGGBB", will return if the color is dark.
/proc/is_color_dark(color, threshold = 25)
var/hsl = rgb2num(color, COLORSPACE_HSL)
return hsl[3] < threshold