mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2025-12-21 15:42:35 +00:00
1113 lines
32 KiB
Plaintext
1113 lines
32 KiB
Plaintext
/*
|
|
* 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)
|
|
|
|
//Inverts the colour of an HTML string
|
|
/proc/invertHTML(HTMLstring)
|
|
|
|
if (!( istext(HTMLstring) ))
|
|
CRASH("Given non-text argument!")
|
|
else
|
|
if (length(HTMLstring) != 7)
|
|
CRASH("Given non-HTML argument!")
|
|
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, 0)
|
|
textg = num2hex(255 - g, 0)
|
|
textb = num2hex(255 - b, 0)
|
|
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)
|
|
|
|
//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 FALSE
|
|
var/dy = (32 * end.y + end.pixel_y) - (32 * start.y + start.pixel_y)
|
|
var/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
|
|
|
|
/proc/get_projectile_angle(atom/source, atom/target)
|
|
var/sx = source.x * world.icon_size
|
|
var/sy = source.y * world.icon_size
|
|
var/tx = target.x * world.icon_size
|
|
var/ty = target.y * world.icon_size
|
|
var/atom/movable/AM
|
|
if(ismovable(source))
|
|
AM = source
|
|
sx += AM.step_x
|
|
sy += AM.step_y
|
|
if(ismovable(target))
|
|
AM = target
|
|
tx += AM.step_x
|
|
ty += AM.step_y
|
|
return SIMPLIFY_DEGREES(arctan(ty - sy, tx - sx))
|
|
|
|
//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) 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) 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;j++)//It'll take dxabs steps to get there
|
|
y+=dyabs
|
|
if(y>=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;j++)
|
|
x+=dxabs
|
|
if(x>=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]"
|
|
|
|
//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.scrambled_codes || istype(A,/mob/living/silicon/robot/drone))
|
|
continue
|
|
var/name = "[A.real_name] ([A.mod_type] [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/abstract/observer/))
|
|
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/abstract/eye/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/abstract/observer/M in sortmob)
|
|
moblist.Add(M)
|
|
for(var/mob/abstract/new_player/M in sortmob)
|
|
moblist.Add(M)
|
|
for(var/mob/living/carbon/slime/M in sortmob)
|
|
moblist.Add(M)
|
|
for(var/mob/living/simple_animal/M in sortmob)
|
|
moblist.Add(M)
|
|
// for(var/mob/living/silicon/hivebot/M in world)
|
|
// mob_list.Add(M)
|
|
// for(var/mob/living/silicon/hive_mainframe/M in world)
|
|
// mob_list.Add(M)
|
|
return moblist
|
|
|
|
//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)
|
|
|
|
//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)
|
|
|
|
/proc/do_mob(mob/user, mob/target, delay = 30, needhand = TRUE, display_progress = TRUE, datum/callback/extra_checks) //This is quite an ugly solution but i refuse to use the old request system.
|
|
if(!user || !target)
|
|
return 0
|
|
|
|
var/user_loc = user.loc
|
|
var/target_loc = target.loc
|
|
var/holding = user.get_active_hand()
|
|
|
|
var/datum/progressbar/progbar
|
|
if (display_progress && user.client && (user.client.prefs.toggles_secondary & PROGRESS_BARS))
|
|
var/atom/loc_check = target
|
|
for(var/i = 0; !isturf(loc_check.loc) && i < 5; i++)
|
|
loc_check = target.loc
|
|
progbar = new(user, delay, loc_check)
|
|
|
|
var/endtime = world.time + delay
|
|
var/starttime = world.time
|
|
|
|
. = 1
|
|
|
|
while (world.time < endtime)
|
|
stoplag(1)
|
|
if (progbar)
|
|
progbar.update(world.time - starttime)
|
|
|
|
if(QDELETED(user) || QDELETED(target))
|
|
. = 0
|
|
break
|
|
|
|
if (user.loc != user_loc || target.loc != target_loc || (needhand && user.get_active_hand() != holding) || user.stat || user.weakened || user.stunned || (extra_checks && !extra_checks.Invoke()))
|
|
. = 0
|
|
break
|
|
|
|
if (progbar)
|
|
qdel(progbar)
|
|
|
|
/proc/do_after(mob/user as mob, delay as num, needhand = TRUE, atom/movable/act_target = null, use_user_turf = FALSE, display_progress = TRUE, datum/callback/extra_checks)
|
|
if(!user || isnull(user))
|
|
return 0
|
|
|
|
var/Location
|
|
var/act_location
|
|
if(use_user_turf) //When this is true, do_after() will check whether the user's turf has changed, rather than the user's loc.
|
|
Location = get_turf(user)
|
|
else
|
|
Location = user.loc
|
|
|
|
if (!act_target)
|
|
act_target = user
|
|
else
|
|
act_location = get_turf(act_target)
|
|
|
|
var/holding = user.get_active_hand()
|
|
|
|
var/datum/progressbar/progbar
|
|
if (display_progress && user.client && (user.client.prefs.toggles_secondary & PROGRESS_BARS))
|
|
progbar = new(user, delay, act_target)
|
|
|
|
var/endtime = world.time + delay
|
|
var/starttime = world.time
|
|
|
|
. = 1
|
|
|
|
while (world.time < endtime)
|
|
stoplag(1)
|
|
if (progbar)
|
|
progbar.update(world.time - starttime)
|
|
|
|
var/user_loc_to_check
|
|
if(use_user_turf)
|
|
user_loc_to_check = get_turf(user)
|
|
else
|
|
user_loc_to_check = user.loc
|
|
|
|
if (!user || user.stat || user.weakened || user.stunned)
|
|
. = 0
|
|
break
|
|
|
|
if ((use_user_turf >= 0 && user_loc_to_check != Location) || (act_location && (get_turf(act_target) != act_location)))
|
|
. = 0
|
|
break
|
|
|
|
if(needhand && !(user.get_active_hand() == holding)) //Sometimes you don't want the user to have to keep their active hand
|
|
. = 0
|
|
break
|
|
|
|
if (extra_checks && !extra_checks.Invoke())
|
|
. = 0
|
|
break
|
|
|
|
if (progbar)
|
|
qdel(progbar)
|
|
|
|
/proc/atom_maintain_position(var/atom/A, var/atom/location)
|
|
if(QDELETED(A) || QDELETED(location))
|
|
return FALSE
|
|
if(A.loc != location)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
//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
|
|
|
|
/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
|
|
|
|
/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 == BP_R_HAND) return "right hand"
|
|
else if (zone == BP_L_HAND) return "left hand"
|
|
else if (zone == BP_L_ARM) return "left arm"
|
|
else if (zone == BP_R_ARM) return "right arm"
|
|
else if (zone == BP_L_LEG) return "left leg"
|
|
else if (zone == BP_R_LEG) return "right leg"
|
|
else if (zone == BP_L_FOOT) return "left foot"
|
|
else if (zone == BP_R_FOOT) return "right foot"
|
|
else if (zone == BP_L_HAND) return "left hand"
|
|
else if (zone == BP_R_HAND) return "right hand"
|
|
else if (zone == BP_L_FOOT) return "left foot"
|
|
else if (zone == BP_R_FOOT) return "right foot"
|
|
else return zone
|
|
|
|
/proc/reverse_parse_zone(zone)
|
|
if(zone == "right hand") return BP_R_HAND
|
|
else if (zone == "left hand") return BP_L_HAND
|
|
else if (zone == "left arm") return BP_L_ARM
|
|
else if (zone == "right arm") return BP_R_ARM
|
|
else if (zone == "left leg") return BP_L_LEG
|
|
else if (zone == "right leg") return BP_R_LEG
|
|
else if (zone == "left foot") return BP_L_FOOT
|
|
else if (zone == "right foot") return BP_R_FOOT
|
|
else if (zone == "left hand") return BP_L_HAND
|
|
else if (zone == "right hand") return BP_R_HAND
|
|
else if (zone == "left foot") return BP_L_FOOT
|
|
else if (zone == "right foot") return BP_R_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/wrench,
|
|
/obj/item/pipewrench,
|
|
/obj/item/weldingtool,
|
|
/obj/item/screwdriver,
|
|
/obj/item/wirecutters,
|
|
/obj/item/powerdrill,
|
|
/obj/item/combitool,
|
|
/obj/item/device/multitool,
|
|
/obj/item/crowbar)
|
|
|
|
/proc/istool(O)
|
|
if(O && is_type_in_list(O, common_tools))
|
|
return 1
|
|
return 0
|
|
|
|
/proc/is_hot(obj/item/W as obj)
|
|
switch(W.type)
|
|
if(/obj/item/weldingtool)
|
|
var/obj/item/weldingtool/WT = W
|
|
if(WT.isOn())
|
|
return 3800
|
|
else
|
|
return 0
|
|
if(/obj/item/flame/lighter)
|
|
if(W:lit)
|
|
return 1500
|
|
else
|
|
return 0
|
|
if(/obj/item/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/gun/energy/plasmacutter)
|
|
return 3800
|
|
if(/obj/item/melee/energy)
|
|
return 3500
|
|
else
|
|
return 0
|
|
|
|
//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
|
|
if (O.edge)
|
|
return 1
|
|
return 0
|
|
|
|
//Whether or not the given item counts as cutting with an edge in terms of removing limbs
|
|
/proc/has_edge(obj/O)
|
|
if (!O)
|
|
return 0
|
|
if (O.edge)
|
|
return 1
|
|
return 0
|
|
|
|
/proc/is_surgery_tool(obj/item/W)
|
|
return istype(W, /obj/item/surgery)
|
|
|
|
/proc/is_borg_item(obj/item/W)
|
|
return W && W.loc && isrobot(W.loc)
|
|
|
|
//check if mob is lying down on something we can operate him on.
|
|
/proc/can_operate(mob/living/carbon/M) //If it's 2, commence surgery, if it's 1, fail surgery, if it's 0, attack
|
|
var/surgery_attempt = SURGERY_IGNORE
|
|
var/located = FALSE
|
|
if(locate(/obj/machinery/optable, M.loc))
|
|
located = TRUE
|
|
surgery_attempt = SURGERY_SUCCESS
|
|
else if(locate(/obj/structure/bed/roller, M.loc))
|
|
located = TRUE
|
|
if(prob(80))
|
|
surgery_attempt = SURGERY_SUCCESS
|
|
else
|
|
surgery_attempt = SURGERY_FAIL
|
|
else if(locate(/obj/structure/table, M.loc))
|
|
located = TRUE
|
|
if(prob(66))
|
|
surgery_attempt = SURGERY_SUCCESS
|
|
else
|
|
surgery_attempt = SURGERY_FAIL
|
|
if(!M.lying && surgery_attempt != SURGERY_SUCCESS && located)
|
|
surgery_attempt = SURGERY_IGNORE //hit yourself if you're not lying
|
|
return surgery_attempt
|
|
|
|
/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
|
|
*/
|
|
var/list/wall_items = typecacheof(list(
|
|
/obj/machinery/power/apc,
|
|
/obj/machinery/alarm,
|
|
/obj/item/device/radio/intercom,
|
|
/obj/structure/extinguisher_cabinet,
|
|
/obj/structure/reagent_dispensers/peppertank,
|
|
/obj/machinery/status_display,
|
|
/obj/machinery/requests_console,
|
|
/obj/machinery/light_switch,
|
|
/obj/machinery/newscaster,
|
|
/obj/machinery/firealarm,
|
|
/obj/structure/noticeboard,
|
|
/obj/machinery/computer/security/telescreen,
|
|
/obj/machinery/embedded_controller/radio/airlock,
|
|
/obj/item/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,
|
|
/obj/machinery/station_map,
|
|
/obj/structure/sign
|
|
))
|
|
|
|
/proc/gotwallitem(loc, dir)
|
|
for(var/obj/O in loc)
|
|
if (is_type_in_typecache(O, global.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, global.wall_items) && O.pixel_x == 0 && O.pixel_y == 0)
|
|
return 1
|
|
return 0
|
|
|
|
// Returns a variable type as string, optionally with some details:
|
|
// Objects (datums) get their type, paths get the type name, scalars show length (text) and value (numbers), lists show length.
|
|
// Also attempts some detection of otherwise undetectable types using ref IDs
|
|
var/global/known_proc = new /proc/get_type_ref_bytes
|
|
/proc/get_debug_type(var/V, var/details = TRUE, var/print_numbers = TRUE, var/path_names = TRUE, var/text_lengths = TRUE, var/list_lengths = TRUE, var/show_useless_subtypes = TRUE)
|
|
// scalars / basic types
|
|
if(isnull(V))
|
|
return "null"
|
|
if(ispath(V))
|
|
return details && path_names ? "path([V])" : "path"
|
|
if(istext(V))
|
|
return details && text_lengths ? "text([length(V) ])" : "text"
|
|
if(isnum(V)) // Byond doesn't really differentiate between floats and ints, but we can sort of guess here
|
|
// also technically we could also say that 0 and 1 are boolean but that'd be quite silly
|
|
if(IsInteger(V) && V < 16777216 && V > -16777216)
|
|
return details && print_numbers ? "int([V])" : "int"
|
|
if(V >= INFINITY)
|
|
return details ? "float(+INF)" : "float"
|
|
if(V <= -INFINITY)
|
|
return details ? "float(-INF)" : "float"
|
|
return details && print_numbers ? "float([V])" : "float"
|
|
// Resource types
|
|
if(isicon(V))
|
|
return "icon"
|
|
if(isfile(V))
|
|
return "file"
|
|
// Types that don't inherit from /datum (note that /world is not here because you can't hold a reference to it)
|
|
if(islist(V))
|
|
return details && list_lengths ? "list([length(V)])" : "list"
|
|
if(isclient(V))
|
|
return "client"
|
|
if(istype(V, /savefile))
|
|
return "savefile"
|
|
// Finally actual objects that inherit from /datum
|
|
// We want to differentiate at least the basic "special" Byond types
|
|
var/datum/D = V
|
|
if(isarea(D))
|
|
return details ? "area([D.type])" : "area"
|
|
if(isturf(D))
|
|
return details ? "turf([D.type])" : "turf"
|
|
if(ismob(D))
|
|
return details ? "mob([D.type])" : "mob"
|
|
if(isobj(D))
|
|
return details ? "obj([D.type])" : "obj"
|
|
if(istype(D, /atom/movable)) // according to DM docs there should be no defined types under this but there certainly are some
|
|
return details ? "movable([D.type])" : "movable"
|
|
if(isatom(D))
|
|
return details ? "atom([D.type])" : "atom"
|
|
if(istype(D, /database))
|
|
return details && show_useless_subtypes ? "database([D.type])" : "database"
|
|
if(istype(D, /exception))
|
|
return details && show_useless_subtypes ? "exception([D.type])" : "exception"
|
|
if(istype(D, /mutable_appearance)) // must come before /image
|
|
return details && show_useless_subtypes ? "mutable_appearance([D.type])" : "mutable_appearance"
|
|
if(istype(D, /image))
|
|
return details ? "image([D.type])" : "image"
|
|
if(istype(D, /matrix))
|
|
return details && show_useless_subtypes ? "matrix([D.type])" : "matrix"
|
|
if(istype(D, /regex))
|
|
return details && show_useless_subtypes ? "regex([D.type])" : "regex"
|
|
if(istype(D, /sound))
|
|
return details ? "sound([D.type])" : "sound"
|
|
if(istype(D, /singleton))
|
|
return details ? "singleton([D.type])" : "singleton"
|
|
if(isdatum(D))
|
|
return details ? "datum([D.type])" : "datum"
|
|
if(istype(D)) // let's future proof ourselves
|
|
return details ? "unknown-object([D.type])" : "unknown-object"
|
|
// some undetectable types
|
|
var/refType = get_type_ref_bytes(V)
|
|
if(refType == "")
|
|
return "unknown"
|
|
if(refType == get_type_ref_bytes(known_proc)) // it's a proc of some kind
|
|
if(istext(V?:name) && V:name != "") // procs with names are generally verbs
|
|
return "verb"
|
|
return "proc"
|
|
if(refType == "53")
|
|
return "filters"
|
|
if(refType == "3a")
|
|
return "appearance"
|
|
return "unknown-object([refType])" // If you see this you found a new undetectable type. Feel free to add it here.
|
|
|
|
/proc/get_type_ref_bytes(var/V) // returns first 4 bytes from \ref which denote the object type (for objects that is)
|
|
return lowertext(copytext(ref(V), 4, 6))
|
|
|
|
/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 "<a href='?src=\ref[D];[arglist]'>[content]</a>"
|
|
|
|
/proc/get_random_colour(var/simple, var/lower, var/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), 0)]"
|
|
if(length(temp_col )<2)
|
|
temp_col = "0[temp_col]"
|
|
colour += temp_col
|
|
return "#[colour]"
|
|
|
|
/proc/color_square(red, green, blue, hex)
|
|
var/color = hex ? hex : "#[num2hex(red, 2)][num2hex(green, 2)][num2hex(blue, 2)]"
|
|
return "<span style='font-face: fixedsys; font-size: 14px; background-color: [color]; color: [color]'>___</span>"
|
|
|
|
// call to generate a stack trace and print to runtime logs
|
|
/proc/crash_with(msg)
|
|
CRASH(msg)
|
|
|
|
//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
|
|
for(y in t_center.y-c_dist to y)
|
|
T = locate(x,y,t_center.z)
|
|
if(T)
|
|
L += T
|
|
|
|
y = t_center.y - c_dist
|
|
x = t_center.x + c_dist - 1
|
|
for(x in t_center.x-c_dist to x)
|
|
T = locate(x,y,t_center.z)
|
|
if(T)
|
|
L += T
|
|
|
|
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
|
|
|
|
//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(world.tick_usage, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1)
|
|
|
|
/proc/stoplag(initial_delay)
|
|
// If we're initializing, our tick limit might be over 100 (testing config), but stoplag() penalizes procs that go over.
|
|
// Unfortunately, this penalty slows down init a *lot*. So, we disable it during boot and lobby, when relatively few things should be calling this.
|
|
if (!Master || GAME_STATE < RUNLEVEL_SETUP)
|
|
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)
|
|
sleep(i*world.tick_lag*DELTA_CALC)
|
|
i *= 2
|
|
while (world.tick_usage > min(TICK_LIMIT_TO_RUN, CURRENT_TICKLIMIT))
|
|
|
|
#undef DELTA_CALC
|