diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index ae218f750ec..daee66b1063 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
/*
@@ -1587,3 +1588,1588 @@ var/mob/dview/dview_mob = new
// Third one is the text that will be clickable.
/proc/href(href_src, list/href_params, href_text)
return "[href_text]"
+=======
+//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
+
+/*
+ * A large number of misc global procs.
+ */
+
+//Checks if all high bits in req_mask are set in bitfield
+#define BIT_TEST_ALL(bitfield, req_mask) ((~(bitfield) & (req_mask)) == 0)
+
+//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a
+#define RANGE_TURFS(RADIUS, CENTER) \
+ block( \
+ locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \
+ locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \
+ )
+
+//Inverts the colour of an HTML string
+/proc/invertHTML(HTMLstring)
+
+ if (!( istext(HTMLstring) ))
+ CRASH("Given non-text argument!")
+ return
+ else
+ if (length(HTMLstring) != 7)
+ CRASH("Given non-HTML argument!")
+ return
+ var/textr = copytext(HTMLstring, 2, 4)
+ var/textg = copytext(HTMLstring, 4, 6)
+ var/textb = copytext(HTMLstring, 6, 8)
+ var/r = hex2num(textr)
+ var/g = hex2num(textg)
+ var/b = hex2num(textb)
+ textr = num2hex(255 - r)
+ textg = num2hex(255 - g)
+ textb = num2hex(255 - b)
+ if (length(textr) < 2)
+ textr = text("0[]", textr)
+ if (length(textg) < 2)
+ textr = text("0[]", textg)
+ if (length(textb) < 2)
+ textr = text("0[]", textb)
+ return text("#[][][]", textr, textg, textb)
+ return
+
+//Returns the middle-most value
+/proc/dd_range(var/low, var/high, var/num)
+ return max(low,min(high,num))
+
+//Returns whether or not A is the middle most value
+/proc/InRange(var/A, var/lower, var/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 = 0, 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)
+ //var/errorxy = round((errorx+errory)/2)//Used for diagonal boxes.
+
+ 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 is a destination.
+ if(errorx||errory)//If errorx or y were specified.
+ 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.
+ 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||T.contains_dense_objects())) continue//If density was specified.
+ if(T.x>world.maxx || T.x<1) continue//Don't want them to teleport off the map.
+ if(T.y>world.maxy || T.y<1) continue
+ destination_list += T
+ if(destination_list.len)
+ destination = pick(destination_list)
+ else return
+
+ else//Same deal here.
+ if(density&&(destination.density||destination.contains_dense_objects())) return
+ if(destination.x>world.maxx || destination.x<1) return
+ if(destination.y>world.maxy || destination.y<1) return
+ else return
+
+ return destination
+
+
+
+/proc/LinkBlocked(turf/A, turf/B)
+ if(A == null || B == null) return 1
+ var/adir = get_dir(A,B)
+ var/rdir = get_dir(B,A)
+ if((adir & (NORTH|SOUTH)) && (adir & (EAST|WEST))) // diagonal
+ var/iStep = get_step(A,adir&(NORTH|SOUTH))
+ if(!LinkBlocked(A,iStep) && !LinkBlocked(iStep,B)) return 0
+
+ var/pStep = get_step(A,adir&(EAST|WEST))
+ if(!LinkBlocked(A,pStep) && !LinkBlocked(pStep,B)) return 0
+ return 1
+
+ if(DirBlocked(A,adir)) return 1
+ if(DirBlocked(B,rdir)) return 1
+ return 0
+
+
+/proc/DirBlocked(turf/loc,var/dir)
+ for(var/obj/structure/window/D in loc)
+ if(!D.density) continue
+ if(D.dir == SOUTHWEST) return 1
+ if(D.dir == dir) return 1
+
+ for(var/obj/machinery/door/D in loc)
+ if(!D.density) continue
+ if(istype(D, /obj/machinery/door/window))
+ if((dir & SOUTH) && (D.dir & (EAST|WEST))) return 1
+ if((dir & EAST ) && (D.dir & (NORTH|SOUTH))) return 1
+ else return 1 // it's a real, air blocking door
+ return 0
+
+/proc/TurfBlockedNonWindow(turf/loc)
+ for(var/obj/O in loc)
+ if(O.density && !istype(O, /obj/structure/window))
+ return 1
+ return 0
+
+/proc/sign(x)
+ return x!=0?x/abs(x):0
+
+/proc/getline(atom/M,atom/N)//Ultra-Fast Bresenham Line-Drawing Algorithm
+ var/px=M.x //starting x
+ var/py=M.y
+ var/line[] = list(locate(px,py,M.z))
+ var/dx=N.x-px //x distance
+ var/dy=N.y-py
+ var/dxabs=abs(dx)//Absolute value of x distance
+ var/dyabs=abs(dy)
+ var/sdx=sign(dx) //Sign of x distance (+ or -)
+ var/sdy=sign(dy)
+ var/x=dxabs>>1 //Counters for steps taken, setting to distance/2
+ var/y=dyabs>>1 //Bit-shifting makes me l33t. It also makes getline() unnessecarrily fast.
+ var/j //Generic integer for counting
+ if(dxabs>=dyabs) //x distance is greater than y
+ for(j=0;j=dxabs) //Every dyabs steps, step once in y direction
+ y-=dxabs
+ py+=sdy
+ px+=sdx //Step on in x direction
+ line+=locate(px,py,M.z)//Add the turf to the list
+ else
+ for(j=0;j=dyabs)
+ x-=dyabs
+ px+=sdx
+ py+=sdy
+ line+=locate(px,py,M.z)
+ return line
+
+#define LOCATE_COORDS(X, Y, Z) locate(between(1, X, world.maxx), between(1, Y, world.maxy), Z)
+/proc/getcircle(turf/center, var/radius) //Uses a fast Bresenham rasterization algorithm to return the turfs in a thin circle.
+ if(!radius) return list(center)
+
+ var/x = 0
+ var/y = radius
+ var/p = 3 - 2 * radius
+
+ . = list()
+ while(y >= x) // only formulate 1/8 of circle
+
+ . += LOCATE_COORDS(center.x - x, center.y - y, center.z) //upper left left
+ . += LOCATE_COORDS(center.x - y, center.y - x, center.z) //upper upper left
+ . += LOCATE_COORDS(center.x + y, center.y - x, center.z) //upper upper right
+ . += LOCATE_COORDS(center.x + x, center.y - y, center.z) //upper right right
+ . += LOCATE_COORDS(center.x - x, center.y + y, center.z) //lower left left
+ . += LOCATE_COORDS(center.x - y, center.y + x, center.z) //lower lower left
+ . += LOCATE_COORDS(center.x + y, center.y + x, center.z) //lower lower right
+ . += LOCATE_COORDS(center.x + x, center.y + y, center.z) //lower right right
+
+ if(p < 0)
+ p += 4*x++ + 6;
+ else
+ p += 4*(x++ - y--) + 10;
+
+#undef LOCATE_COORDS
+
+//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 = 7, ch, len = length(key)
+
+ if(copytext(key, 7, 8) == "W") //webclient
+ i++
+
+ for (, 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(var/f, var/low = PUBLIC_LOW_FREQ, var/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(var/f)
+ return "[round(f / 10)].[f % 10]"
+
+
+
+//This will update a mob's name, real_name, mind.name, data_core records, pda and id
+//Calling this proc without an oldname will only update the mob and skip updating the pda, id and records ~Carn
+/mob/proc/fully_replace_character_name(var/oldname,var/newname)
+ if(!newname) return 0
+ real_name = newname
+ name = newname
+ if(mind)
+ mind.name = newname
+ if(dna)
+ dna.real_name = real_name
+
+ if(oldname)
+ //update the datacore records! This is goig to be a bit costly.
+ for(var/list/L in list(data_core.general,data_core.medical,data_core.security,data_core.locked))
+ for(var/datum/data/record/R in L)
+ if(R.fields["name"] == oldname)
+ R.fields["name"] = newname
+ break
+
+ //update our pda and id if we have them on our person
+ var/list/searching = GetAllContents(searchDepth = 3)
+ var/search_id = 1
+ var/search_pda = 1
+
+ for(var/A in searching)
+ if( search_id && istype(A,/obj/item/weapon/card/id) )
+ var/obj/item/weapon/card/id/ID = A
+ if(ID.registered_name == oldname)
+ ID.registered_name = newname
+ ID.name = "[newname]'s ID Card ([ID.assignment])"
+ if(!search_pda) break
+ search_id = 0
+
+ else if( search_pda && istype(A,/obj/item/device/pda) )
+ var/obj/item/device/pda/PDA = A
+ if(PDA.owner == oldname)
+ PDA.owner = newname
+ PDA.name = "PDA-[newname] ([PDA.ownjob])"
+ if(!search_id) break
+ search_pda = 0
+ return 1
+
+
+
+//Generalised helper proc for letting mobs rename themselves. Used to be clname() and ainame()
+//Last modified by Carn
+/mob/proc/rename_self(var/role, var/allow_numbers=0)
+ spawn(0)
+ var/oldname = real_name
+
+ var/time_passed = world.time
+ var/newname
+
+ for(var/i=1,i<=3,i++) //we get 3 attempts to pick a suitable name.
+ newname = input(src,"You are \a [role]. Would you like to change your name to something else?", "Name change",oldname) as text
+ if((world.time-time_passed)>3000)
+ return //took too long
+ newname = sanitizeName(newname, ,allow_numbers) //returns null if the name doesn't meet some basic requirements. Tidies up a few other things like bad-characters.
+
+ for(var/mob/living/M in player_list)
+ if(M == src)
+ continue
+ if(!newname || M.real_name == newname)
+ newname = null
+ break
+ if(newname)
+ break //That's a suitable name!
+ to_chat(src, "Sorry, that [role]-name wasn't appropriate, please try another. It's possibly too long/short, has bad characters or is already taken.")
+
+ if(!newname) //we'll stick with the oldname then
+ return
+
+ if(cmptext("ai",role))
+ if(isAI(src))
+ var/mob/living/silicon/ai/A = src
+ oldname = null//don't bother with the records update crap
+ //to_world("[newname] is the AI!")
+ //world << sound('sound/AI/newAI.ogg')
+ // Set eyeobj name
+ A.SetName(newname)
+
+
+ fully_replace_character_name(oldname,newname)
+
+
+
+//Picks a string of symbols to display as the law number for hacked or ion laws
+/proc/ionnum()
+ return "[pick("1","2","3","4","5","6","7","8","9","0")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")]"
+
+//When an AI is activated, it can choose from a list of non-slaved borgs to have as a slave.
+/proc/freeborg()
+ var/select = null
+ var/list/borgs = list()
+ for (var/mob/living/silicon/robot/A in player_list)
+ if (A.stat == 2 || A.connected_ai || A.scrambledcodes || istype(A,/mob/living/silicon/robot/drone))
+ continue
+ var/name = "[A.real_name] ([A.modtype] [A.braintype])"
+ borgs[name] = A
+
+ if (borgs.len)
+ select = input("Unshackled borg signals detected:", "Borg selection", null, null) as null|anything in 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 living_mob_list)
+ if(A.stat == DEAD)
+ continue
+ if(A.control_disabled == 1)
+ 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/mob/living/silicon/ai/A in active)
+ if(!selected || (selected.connected_robots.len > A.connected_robots.len))
+ selected = A
+
+ return selected
+
+/proc/select_active_ai(var/mob/user)
+ var/list/ais = active_ais()
+ if(ais.len)
+ if(user) . = input(usr,"AI signals detected:", "AI selection") in ais
+ else . = pick(ais)
+ return .
+
+/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 == 2)
+ 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 == 2)
+ if(istype(M, /mob/observer/dead/))
+ 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(mob_list)
+ for(var/mob/observer/eye/M in sortmob)
+ moblist.Add(M)
+ for(var/mob/observer/blob/M in sortmob)
+ moblist.Add(M)
+ for(var/mob/living/silicon/ai/M in sortmob)
+ moblist.Add(M)
+ 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/carbon/brain/M in sortmob)
+ moblist.Add(M)
+ for(var/mob/living/carbon/alien/M in sortmob)
+ moblist.Add(M)
+ for(var/mob/observer/dead/M in sortmob)
+ moblist.Add(M)
+ for(var/mob/new_player/M in sortmob)
+ moblist.Add(M)
+ for(var/mob/living/simple_mob/M in sortmob)
+ moblist.Add(M)
+// for(var/mob/living/silicon/hivebot/M in sortmob)
+// mob_list.Add(M)
+// for(var/mob/living/silicon/hive_mainframe/M in sortmob)
+// mob_list.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"
+
+/proc/get_mob_by_ckey(key)
+ if(!key)
+ return
+ var/list/mobs = sortmobs()
+ for(var/mob/M in mobs)
+ if(M.ckey == key)
+ return M
+
+//Forces a variable to be posative
+/proc/modulus(var/M)
+ if(M >= 0)
+ return M
+ if(M < 0)
+ return -M
+
+// returns the turf located at the map edge in the specified direction relative to A
+// used for mass driver
+/proc/get_edge_target_turf(var/atom/A, var/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(var/atom/A, var/direction, var/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)
+
+
+// returns turf relative to A offset in dx and dy tiles
+// bound to map limits
+/proc/get_offset_target_turf(var/atom/A, var/dx, var/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)
+
+//Makes sure MIDDLE is between LOW and HIGH. If not, it adjusts it. Returns the adjusted value.
+/proc/between(var/low, var/middle, var/high)
+ return max(min(middle, high), low)
+
+//returns random gauss number
+proc/GaussRand(var/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(var/sigma,var/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(part.contents.len && searchDepth)
+ toReturn += part.GetAllContents(searchDepth - 1)
+
+ return toReturn
+
+//Step-towards method of determining whether one atom can see another. Similar to viewers()
+/proc/can_see(var/atom/source, var/atom/target, var/length=5) // I couldn't 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 = 0
+
+ if(!current || !target_turf)
+ return 0
+
+ while(current != target_turf)
+ if(steps > length) return 0
+ if(current.opacity) return 0
+ for(var/atom/A in current)
+ if(A.opacity) return 0
+ current = get_step_towards(current, target_turf)
+ steps++
+
+ return 1
+
+/proc/is_blocked_turf(var/turf/T)
+ var/cant_pass = 0
+ if(T.density) cant_pass = 1
+ for(var/atom/A in T)
+ if(A.density)//&&A.anchored
+ cant_pass = 1
+ return cant_pass
+
+/proc/get_step_towards2(var/atom/ref , var/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 = null
+ 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(var/datum/A, var/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 all_areas)
+ 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(var/areatype)
+ if(!areatype) return null
+ if(istext(areatype)) areatype = text2path(areatype)
+ if(isarea(areatype))
+ var/area/areatemp = areatype
+ areatype = areatemp.type
+
+ var/list/areas = new/list()
+ for(var/area/N in all_areas)
+ 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(var/areatype)
+ if(!areatype) return null
+ if(istext(areatype)) areatype = text2path(areatype)
+ if(isarea(areatype))
+ var/area/areatemp = areatype
+ areatype = areatemp.type
+
+ var/list/turfs = new/list()
+ for(var/area/N in all_areas)
+ 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(var/areatype)
+ if(!areatype) return null
+ if(istext(areatype)) areatype = text2path(areatype)
+ if(isarea(areatype))
+ var/area/areatemp = areatype
+ areatype = areatemp.type
+
+ var/list/atoms = new/list()
+ for(var/area/N in all_areas)
+ if(istype(N, areatype))
+ for(var/atom/A in N)
+ atoms += A
+ return atoms
+
+/datum/coords //Simple datum for storing coordinates.
+ var/x_pos = null
+ var/y_pos = null
+ var/z_pos = null
+
+/area/proc/move_contents_to(var/area/A, var/turftoleave=null, var/direction = null)
+ //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 = new/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 = new/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)
+
+ 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)
+
+ //You can stay, though.
+ if(istype(T,/turf/space))
+ refined_src -= T
+ refined_trg -= B
+ continue moving
+
+ var/turf/X //New Destination Turf
+
+ //Are we doing shuttlework? Just to save another type check later.
+ var/shuttlework = 0
+
+ //Shuttle turfs handle their own fancy moving.
+ if(istype(T,/turf/simulated/shuttle))
+ shuttlework = 1
+ var/turf/simulated/shuttle/SS = T
+ if(!SS.landed_holder) SS.landed_holder = new(turf = SS)
+ X = SS.landed_holder.land_on(B)
+
+ //Generic non-shuttle turf move.
+ else
+ var/old_dir1 = T.dir
+ var/old_icon_state1 = T.icon_state
+ var/old_icon1 = T.icon
+ var/old_underlays = T.underlays.Copy()
+ var/old_decals = T.decals ? T.decals.Copy() : null
+
+ X = B.ChangeTurf(T.type)
+ X.set_dir(old_dir1)
+ X.icon_state = old_icon_state1
+ X.icon = old_icon1
+ X.copy_overlays(T, TRUE)
+ X.underlays = old_underlays
+ X.decals = old_decals
+
+ //Move the air from source to dest
+ var/turf/simulated/ST = T
+ if(istype(ST) && ST.zone)
+ var/turf/simulated/SX = X
+ if(!SX.air)
+ SX.make_air()
+ SX.air.copy_from(ST.zone.air)
+ ST.zone.remove(ST)
+
+ var/z_level_change = FALSE
+ if(T.z != X.z)
+ z_level_change = TRUE
+
+ //Move the objects. Not forceMove because the object isn't "moving" really, it's supposed to be on the "same" turf.
+ for(var/obj/O in T)
+ O.loc = X
+ O.update_light()
+ if(z_level_change) // The objects still need to know if their z-level changed.
+ O.onTransitZ(T.z, X.z)
+
+ //Move the mobs unless it's an AI eye or other eye type.
+ for(var/mob/M in T)
+ if(istype(M, /mob/observer/eye)) continue // If we need to check for more mobs, I'll add a variable
+ M.loc = X
+
+ if(z_level_change) // Same goes for mobs.
+ M.onTransitZ(T.z, X.z)
+
+ if(istype(M, /mob/living))
+ var/mob/living/LM = M
+ LM.check_shadow() // Need to check their Z-shadow, which is normally done in forceMove().
+
+ if(shuttlework)
+ var/turf/simulated/shuttle/SS = T
+ SS.landed_holder.leave_turf()
+ else if(turftoleave)
+ T.ChangeTurf(turftoleave)
+ else
+ T.ChangeTurf(get_base_turf_by_area(T))
+
+ refined_src -= T
+ refined_trg -= B
+ continue moving
+
+proc/DuplicateObject(obj/original, var/perfectcopy = 0 , var/sameloc = 0)
+ if(!original)
+ return null
+
+ var/obj/O = null
+
+ if(sameloc)
+ O=new original.type(original.loc)
+ else
+ O=new original.type(locate(0,0,0))
+
+ if(perfectcopy)
+ if((O) && (original))
+ for(var/V in original.vars)
+ if(!(V in list("type","loc","locs","vars", "parent", "parent_type","verbs","ckey","key")))
+ O.vars[V] = original.vars[V]
+ return O
+
+
+/area/proc/copy_contents_to(var/area/A , var/platingRequired = 0 )
+ //Takes: Area. Optional: If it should copy to areas that don't have plating
+ //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.
+
+ // Does *not* affect gases etc; copied turfs will be changed via ChangeTurf, and the dir, icon, and icon_state copied. All other vars will remain default.
+
+ 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 = new/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 = new/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/toupdate = new/list()
+
+ var/copiedobjs = 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/old_overlays = T.overlays.Copy()
+ var/old_underlays = T.underlays.Copy()
+
+ if(platingRequired)
+ if(istype(B, get_base_turf_by_area(B)))
+ continue moving
+
+ var/turf/X = B
+ X.ChangeTurf(T.type)
+ X.set_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
+ X.overlays = old_overlays
+ X.underlays = old_underlays
+
+ var/list/objs = new/list()
+ var/list/newobjs = new/list()
+ var/list/mobs = new/list()
+ var/list/newmobs = new/list()
+
+ for(var/obj/O in T)
+
+ if(!istype(O,/obj))
+ continue
+
+ objs += O
+
+
+ for(var/obj/O in objs)
+ newobjs += DuplicateObject(O , 1)
+
+
+ for(var/obj/O in newobjs)
+ O.loc = X
+
+ for(var/mob/M in T)
+
+ if(!istype(M,/mob) || istype(M, /mob/observer/eye)) continue // If we need to check for more mobs, I'll add a variable
+ mobs += M
+
+ for(var/mob/M in mobs)
+ newmobs += DuplicateObject(M , 1)
+
+ for(var/mob/M in newmobs)
+ M.loc = X
+
+ copiedobjs += newobjs
+ copiedobjs += newmobs
+
+// var/area/AR = X.loc
+
+// if(AR.dynamic_lighting)
+// X.opacity = !X.opacity
+// X.sd_SetOpacity(!X.opacity) //TODO: rewrite this code so it's not messed by lighting ~Carn
+
+ toupdate += X
+
+ refined_src -= T
+ refined_trg -= B
+ continue moving
+
+
+
+
+ if(toupdate.len)
+ for(var/turf/simulated/T1 in toupdate)
+ air_master.mark_for_update(T1)
+
+ return copiedobjs
+
+
+
+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 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
+
+/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)
+
+
+//Quick type checks for some tools
+var/global/list/common_tools = list(
+/obj/item/stack/cable_coil,
+/obj/item/weapon/tool/wrench,
+/obj/item/weapon/weldingtool,
+/obj/item/weapon/tool/screwdriver,
+/obj/item/weapon/tool/wirecutters,
+/obj/item/device/multitool,
+/obj/item/weapon/tool/crowbar)
+
+/proc/istool(O)
+ if(O && is_type_in_list(O, common_tools))
+ return 1
+ return 0
+
+
+/proc/is_wire_tool(obj/item/I)
+ if(istype(I, /obj/item/device/multitool) || I.is_wirecutter())
+ return TRUE
+ if(istype(I, /obj/item/device/assembly/signaler))
+ return TRUE
+ return
+
+proc/is_hot(obj/item/W as obj)
+ switch(W.type)
+ if(/obj/item/weapon/weldingtool)
+ var/obj/item/weapon/weldingtool/WT = W
+ if(WT.isOn())
+ return 3800
+ else
+ return 0
+ if(/obj/item/weapon/flame/lighter)
+ if(W:lit)
+ return 1500
+ else
+ return 0
+ if(/obj/item/weapon/flame/match)
+ if(W:lit)
+ return 1000
+ else
+ return 0
+ if(/obj/item/clothing/mask/smokable/cigarette)
+ if(W:lit)
+ return 1000
+ else
+ return 0
+ if(/obj/item/weapon/pickaxe/plasmacutter)
+ return 3800
+ if(/obj/item/weapon/melee/energy)
+ return 3500
+ else
+ return 0
+
+ return 0
+
+//Whether or not the given item counts as sharp in terms of dealing damage
+/proc/is_sharp(obj/O as obj)
+ if(!O)
+ return FALSE
+ if(O.sharp)
+ return TRUE
+ if(O.edge)
+ return TRUE
+ return FALSE
+
+//Whether or not the given item counts as cutting with an edge in terms of removing limbs
+/proc/has_edge(obj/O as obj)
+ if(!O)
+ return FALSE
+ if(O.edge)
+ return TRUE
+ return FALSE
+
+//Returns 1 if the given item is capable of popping things like balloons, inflatable barriers, or cutting police tape.
+/proc/can_puncture(obj/item/W as obj) // For the record, WHAT THE HELL IS THIS METHOD OF DOING IT?
+ if(!W)
+ return FALSE
+ if(W.sharp)
+ return TRUE
+ return ( \
+ W.is_screwdriver() || \
+ istype(W, /obj/item/weapon/pen) || \
+ istype(W, /obj/item/weapon/weldingtool) || \
+ istype(W, /obj/item/weapon/flame/lighter/zippo) || \
+ istype(W, /obj/item/weapon/flame/match) || \
+ istype(W, /obj/item/clothing/mask/smokable/cigarette) || \
+ istype(W, /obj/item/weapon/shovel) \
+ )
+
+/proc/is_surgery_tool(obj/item/W as obj)
+ return ( \
+ istype(W, /obj/item/weapon/surgical/scalpel) || \
+ istype(W, /obj/item/weapon/surgical/hemostat) || \
+ istype(W, /obj/item/weapon/surgical/retractor) || \
+ istype(W, /obj/item/weapon/surgical/cautery) || \
+ istype(W, /obj/item/weapon/surgical/bonegel) || \
+ istype(W, /obj/item/weapon/surgical/bonesetter)
+ )
+
+// check if mob is lying down on something we can operate him on.
+// The RNG with table/rollerbeds comes into play in do_surgery() so that fail_step() can be used instead.
+/proc/can_operate(mob/living/carbon/M)
+ return M.lying
+
+// Returns an instance of a valid surgery surface.
+/mob/living/proc/get_surgery_surface()
+ if(!lying)
+ return null // Not lying down means no surface.
+ var/obj/surface = null
+ for(var/obj/O in loc) // Looks for the best surface.
+ if(O.surgery_odds)
+ if(!surface || surface.surgery_odds < O)
+ surface = O
+ if(surface)
+ return surface
+
+/proc/reverse_direction(var/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
+TODO - Fix this ancient list of wall items. Preferably make it dynamically populated. ~Leshana
+*/
+var/list/WALLITEMS = list(
+ /obj/machinery/power/apc, /obj/machinery/alarm, /obj/item/device/radio/intercom, /obj/structure/frame,
+ /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/button/remote,
+ /obj/machinery/computer/security/telescreen, /obj/machinery/embedded_controller/radio,
+ /obj/item/weapon/storage/secure/safe, /obj/machinery/door_timer, /obj/machinery/flasher, /obj/machinery/keycard_auth,
+ /obj/structure/mirror, /obj/structure/fireaxecabinet, /obj/machinery/computer/security/telescreen/entertainment
+ )
+/proc/gotwallitem(loc, dir)
+ for(var/obj/O in loc)
+ for(var/item in WALLITEMS)
+ if(istype(O, item))
+ //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))
+ for(var/item in WALLITEMS)
+ if(istype(O, item))
+ if(O.pixel_x == 0 && O.pixel_y == 0)
+ return 1
+ return 0
+
+/proc/format_text(text)
+ return replacetext(replacetext(text,"\proper ",""),"\improper ","")
+
+/proc/topic_link(var/datum/D, var/arglist, var/content)
+ if(istype(arglist,/list))
+ arglist = list2params(arglist)
+ return "[content]"
+
+/proc/get_random_colour(var/simple, var/lower=0, var/upper=255)
+ 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))]"
+ if(length(temp_col )<2)
+ temp_col = "0[temp_col]"
+ colour += temp_col
+ return colour
+
+var/mob/dview/dview_mob = new
+
+//Version of view() which ignores darkness, because BYOND doesn't have it.
+/proc/dview(var/range = world.view, var/center, var/invis_flags = 0)
+ if(!center)
+ return
+
+ dview_mob.loc = center
+
+ dview_mob.see_invisible = invis_flags
+
+ . = view(range, dview_mob)
+ dview_mob.loc = null
+
+/mob/dview
+ invisibility = 101
+ density = 0
+
+ anchored = 1
+ simulated = 0
+
+ see_in_dark = 1e6
+
+/atom/proc/get_light_and_color(var/atom/origin)
+ if(origin)
+ color = origin.color
+ set_light(origin.light_range, origin.light_power, origin.light_color)
+
+/mob/dview/New()
+ ..()
+ // We don't want to be in any mob lists; we're a dummy not a mob.
+ mob_list -= src
+ if(stat == DEAD)
+ dead_mob_list -= src
+ else
+ living_mob_list -= src
+
+/mob/dview/Destroy(var/force)
+ crash_with("Attempt to delete the dview_mob: [log_info_line(src)]")
+ if (!force)
+ return QDEL_HINT_LETMELIVE
+ global.dview_mob = new
+ return ..()
+
+// call to generate a stack trace and print to runtime logs
+/proc/crash_with(msg)
+ CRASH(msg)
+
+/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)
+
+// Displays something as commonly used (non-submultiples) SI units.
+/proc/format_SI(var/number, var/symbol)
+ switch(round(abs(number)))
+ if(0 to 1000-1)
+ return "[number] [symbol]"
+ if(1e3 to 1e6-1)
+ return "[round(number / 1000, 0.1)] k[symbol]" // kilo
+ if(1e6 to 1e9-1)
+ return "[round(number / 1e6, 0.1)] M[symbol]" // mega
+ if(1e9 to 1e12-1) // Probably not needed but why not be complete?
+ return "[round(number / 1e9, 0.1)] G[symbol]" // giga
+ if(1e12 to 1e15-1)
+ return "[round(number / 1e12, 0.1)] T[symbol]" // tera
+
+
+
+//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
+
+#define NOT_FLAG(flag) (!(flag & use_flags))
+#define HAS_FLAG(flag) (flag & use_flags)
+
+// Checks if user can use this object. Set use_flags to customize what checks are done.
+// Returns 0 if they can use it, a value representing why they can't if not.
+// Flags are in `code/__defines/misc.dm`
+/atom/proc/use_check(mob/user, use_flags = 0, show_messages = FALSE)
+ . = 0
+ if (NOT_FLAG(USE_ALLOW_NONLIVING) && !isliving(user))
+ // No message for ghosts.
+ return USE_FAIL_NONLIVING
+
+ if (NOT_FLAG(USE_ALLOW_NON_ADJACENT) && !Adjacent(user))
+ if (show_messages)
+ to_chat(user, span("notice","You're too far away from [src] to do that."))
+ return USE_FAIL_NON_ADJACENT
+
+ if (NOT_FLAG(USE_ALLOW_DEAD) && user.stat == DEAD)
+ if (show_messages)
+ to_chat(user, span("notice","You can't do that when you're dead."))
+ return USE_FAIL_DEAD
+
+ if (NOT_FLAG(USE_ALLOW_INCAPACITATED) && (user.incapacitated()))
+ if (show_messages)
+ to_chat(user, span("notice","You cannot do that in your current state."))
+ return USE_FAIL_INCAPACITATED
+
+ if (NOT_FLAG(USE_ALLOW_NON_ADV_TOOL_USR) && !user.IsAdvancedToolUser())
+ if (show_messages)
+ to_chat(user, span("notice","You don't know how to operate [src]."))
+ return USE_FAIL_NON_ADV_TOOL_USR
+
+ if (HAS_FLAG(USE_DISALLOW_SILICONS) && issilicon(user))
+ if (show_messages)
+ to_chat(user, span("notice","You need hands for that."))
+ return USE_FAIL_IS_SILICON
+
+ if (HAS_FLAG(USE_FORCE_SRC_IN_USER) && !(src in user))
+ if (show_messages)
+ to_chat(user, span("notice","You need to be holding [src] to do that."))
+ return USE_FAIL_NOT_IN_USER
+
+#undef NOT_FLAG
+#undef HAS_FLAG
+
+//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/___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, /proc/___callbackvarset, ##datum, NAMEOF(##datum, ##var), ##var_value)
+//we'll see about those 3-level deep macros
+#define VARSET_IN(datum, var, var_value, time) addtimer(VARSET_CALLBACK(datum, var, var_value), time)
+
+/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
+ D.vars[var_name] = var_value
+
+// Returns direction-string, rounded to multiples of 22.5, from the first parameter to the second
+// N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW
+/proc/get_adir(var/turf/A, var/turf/B)
+ var/degree = Get_Angle(A, B)
+ switch(round(degree%360, 22.5))
+ if(0)
+ return "North"
+ if(22.5)
+ return "North-Northeast"
+ if(45)
+ return "Northeast"
+ if(67.5)
+ return "East-Northeast"
+ if(90)
+ return "East"
+ if(112.5)
+ return "East-Southeast"
+ if(135)
+ return "Southeast"
+ if(157.5)
+ return "South-Southeast"
+ if(180)
+ return "South"
+ if(202.5)
+ return "South-Southwest"
+ if(225)
+ return "Southwest"
+ if(247.5)
+ return "West-Southwest"
+ if(270)
+ return "West"
+ if(292.5)
+ return "West-Northwest"
+ if(315)
+ return "Northwest"
+ if(337.5)
+ return "North-Northwest"
+
+/proc/pass()
+ return
+
+#define NAMEOF(datum, X) (#X || ##datum.##X)
+
+/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)
+ 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
+ chosen = input("Select a type", "Pick Type", matches[1]) as null|anything in matches
+ if(!chosen)
+ return
+ chosen = matches[chosen]
+ return chosen
+
+/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 = new
+ for(var/key in L)
+ var/value = L[key]
+ if(findtext("[key]", filter) || findtext("[value]", filter))
+ matches[key] = value
+ return matches
+
+/proc/make_types_fancy(var/list/types)
+ if (ispath(types))
+ types = list(types)
+ . = list()
+ for(var/type in types)
+ var/typename = "[type]"
+ var/static/list/TYPES_SHORTCUTS = list(
+ /obj/effect/decal/cleanable = "CLEANABLE",
+ /obj/item/device/radio/headset = "HEADSET",
+ /obj/item/clothing/head/helmet/space = "SPESSHELMET",
+ /obj/item/weapon/book/manual = "MANUAL",
+ /obj/item/weapon/reagent_containers/food/drinks = "DRINK",
+ /obj/item/weapon/reagent_containers/food = "FOOD",
+ /obj/item/weapon/reagent_containers = "REAGENT_CONTAINERS",
+ /obj/machinery/atmospherics = "ATMOS_MECH",
+ /obj/machinery/portable_atmospherics = "PORT_ATMOS",
+ /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack = "MECHA_MISSILE_RACK",
+ /obj/item/mecha_parts/mecha_equipment = "MECHA_EQUIP",
+ /obj/item/organ = "ORGAN",
+ /obj/item = "ITEM",
+ /obj/machinery = "MACHINERY",
+ /obj/effect = "EFFECT",
+ /obj = "O",
+ /datum = "D",
+ /turf/simulated/wall = "S-WALL",
+ /turf/simulated/floor = "S-FLOOR",
+ /turf/simulated = "SIMULATED",
+ /turf/unsimulated/wall = "US-WALL",
+ /turf/unsimulated/floor = "US-FLOOR",
+ /turf/unsimulated = "UNSIMULATED",
+ /turf = "T",
+ /mob/living/carbon = "CARBON",
+ /mob/living/simple_mob = "SIMPLE",
+ /mob/living = "LIVING",
+ /mob = "M"
+ )
+ for (var/tn in TYPES_SHORTCUTS)
+ if (copytext(typename,1, length("[tn]/")+1)=="[tn]/" /*findtextEx(typename,"[tn]/",1,2)*/ )
+ typename = TYPES_SHORTCUTS[tn]+copytext(typename,length("[tn]/"))
+ break
+ .[typename] = type
+
+/proc/IsValidSrc(datum/D)
+ if(istype(D))
+ return !QDELETED(D)
+ return FALSE
+
+//gives us the stack trace from CRASH() without ending the current proc.
+/proc/stack_trace(msg)
+ CRASH(msg)
+
+/datum/proc/stack_trace(msg)
+ CRASH(msg)
+
+// \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
+// If it ever becomes necesary to get a more performant REF(), this lies here in wait
+// #define REF(thing) (thing && istype(thing, /datum) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : "\ref[thing]")
+/proc/REF(input)
+ if(istype(input, /datum))
+ var/datum/thing = input
+ if(thing.datum_flags & DF_USE_TAG)
+ if(!thing.tag)
+ thing.datum_flags &= ~DF_USE_TAG
+ stack_trace("A ref was requested of an object with DF_USE_TAG set but no tag: [thing]")
+ else
+ return "\[[url_encode(thing.tag)]\]"
+ return "\ref[input]"
+
+// Painlessly creates an element.
+// First argument is where to send the Topic call to when clicked. Should be a reference to an object. This is generally src, but not always.
+// Second one is for all the params that will be sent. Uses an assoc list (e.g. "value" = "5").
+// Note that object refs will be converted to text, as if \ref[thing] was done. To get the ref back on Topic() side, you will need to use locate().
+// Third one is the text that will be clickable.
+/proc/href(href_src, list/href_params, href_text)
+ return "[href_text]"
+>>>>>>> 6d8cd17... Merge pull request #6939 from VOREStation/pol-assemblies
diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm
index 0f981e29f9f..1bc653662bf 100644
--- a/code/modules/assembly/timer.dm
+++ b/code/modules/assembly/timer.dm
@@ -17,7 +17,7 @@
if(!..())
return FALSE
- timing = !timing
+ set_state(!timing)
update_icon()
return 0