mirror of
https://github.com/goonstation/goonstation-2020.git
synced 2026-07-10 14:42:20 +01:00
2489 lines
71 KiB
Plaintext
2489 lines
71 KiB
Plaintext
/**
|
|
* Returns the plural based on a number provided.
|
|
*
|
|
* @param number the number to base the judgement off of
|
|
* @param es set this to true if your item's plural ends in "es"
|
|
* @return the plural suffix based on numbers
|
|
*/
|
|
/proc/s_es(var/number as num, var/es = 0)
|
|
if (isnull(number))
|
|
return
|
|
if (number == 1)
|
|
return null
|
|
else
|
|
if (es)
|
|
return "es"
|
|
else
|
|
return "s"
|
|
|
|
/**
|
|
* Returns true if the char you feed it is uppercase.
|
|
*/
|
|
/proc/isUpper(var/test)
|
|
if(length(test) > 1)
|
|
test = copytext(test, 1, 2)
|
|
return test == uppertext(test)
|
|
|
|
/**
|
|
* Shows the calling client admins.txt
|
|
*/
|
|
/proc/showadminlist()
|
|
usr.client.Export("##action=browse_file","config/admins.txt")
|
|
|
|
/**
|
|
* Returns the line matrix from a start atom to an end atom, used in creating line objects
|
|
*/
|
|
/proc/getLineMatrix(var/atom/start, var/atom/end)
|
|
var/angle = get_angle(start, end)
|
|
|
|
var/lx = abs(start.x - end.x)
|
|
var/ly = abs(start.y - end.y)
|
|
var/beamlen = sqrt((lx * lx) + (ly * ly))
|
|
|
|
var/matrix/retMatrix = matrix()
|
|
retMatrix.Scale(1, beamlen / 2)
|
|
retMatrix.Translate(1, (((beamlen/2) * 64) / 2))
|
|
retMatrix.Turn(angle)
|
|
|
|
return retMatrix
|
|
|
|
/**
|
|
* Creates and shows a line object. Line object has an "affected" var that contains the cross tiles.
|
|
*/
|
|
/proc/showLine(var/atom/start, var/atom/end, s_icon_state = "lght", var/animate = 0, var/get_turfs=1)
|
|
var/angle = get_angle(start, end)
|
|
var/anglemod = (-(angle < 180 ? angle : angle - 360) + 90) //What the fuck am i looking at
|
|
var/lx = abs(start.x - end.x)
|
|
var/ly = abs(start.y - end.y)
|
|
var/beamlen = sqrt((lx * lx) + (ly * ly))
|
|
var/obj/beam_dummy/B = new/obj/beam_dummy(get_turf(start))
|
|
|
|
if(get_turfs) B.affected = castRay(start, anglemod, beamlen)
|
|
|
|
B.origin_angle = angle
|
|
B.icon_state = s_icon_state
|
|
B.origin = start
|
|
B.target = end
|
|
|
|
var/matrix/M
|
|
if(animate)
|
|
B.transform = matrix(turn(B.transform, angle), 1, 0.1, MATRIX_SCALE)
|
|
var/matrix/second = matrix(1, beamlen / 2, MATRIX_SCALE)
|
|
second.Translate(1, (((beamlen / 2) * 64) / 2))
|
|
second.Turn(angle)
|
|
animate(B, transform = second, time = animate, loop = 1, easing = LINEAR_EASING)
|
|
else
|
|
M = B.transform
|
|
M.Scale(1, beamlen / 2)
|
|
M.Translate(1, (((beamlen/2) * 64) / 2))
|
|
M.Turn(angle)
|
|
B.transform = M
|
|
|
|
return B
|
|
|
|
var/global/obj/flashDummy
|
|
|
|
/proc/getFlashDummy()
|
|
if (!flashDummy)
|
|
flashDummy = new /obj(null)
|
|
flashDummy.set_density(0)
|
|
flashDummy.opacity = 0
|
|
flashDummy.anchored = 1
|
|
flashDummy.mouse_opacity = 0
|
|
return flashDummy
|
|
|
|
/proc/arcFlashTurf(var/atom/from, var/turf/target, var/wattage)
|
|
var/obj/O = getFlashDummy()
|
|
O.loc = target
|
|
playsound(target, "sound/effects/elec_bigzap.ogg", 30, 1)
|
|
|
|
var/list/affected = DrawLine(from, O, /obj/line_obj/elec ,'icons/obj/projectiles.dmi',"WholeLghtn",1,1,"HalfStartLghtn","HalfEndLghtn",OBJ_LAYER,1,PreloadedIcon='icons/effects/LghtLine.dmi')
|
|
|
|
for(var/obj/Q in affected)
|
|
SPAWN_DBG(6 DECI SECONDS) pool(Q)
|
|
|
|
for(var/mob/living/M in get_turf(target))
|
|
M.shock(from, wattage, "chest", 1, 1)
|
|
O.loc = null
|
|
|
|
/proc/arcFlash(var/atom/from, var/atom/target, var/wattage)
|
|
playsound(target, "sound/effects/elec_bigzap.ogg", 30, 1)
|
|
|
|
var/list/affected = DrawLine(from, target, /obj/line_obj/elec ,'icons/obj/projectiles.dmi',"WholeLghtn",1,1,"HalfStartLghtn","HalfEndLghtn",OBJ_LAYER,1,PreloadedIcon='icons/effects/LghtLine.dmi')
|
|
|
|
for(var/obj/O in affected)
|
|
SPAWN_DBG(6 DECI SECONDS) pool(O)
|
|
|
|
if(wattage && isliving(target)) //Probably unsafe.
|
|
target:shock(from, wattage, "chest", 1, 1)
|
|
|
|
proc/castRay(var/atom/A, var/Angle, var/Distance) //Adapted from some forum stuff. Takes some sort of bizzaro angles ?! Aahhhhh
|
|
var/list/crossed = list()
|
|
var/xPlus=cos(Angle)
|
|
var/yPlus=sin(Angle)
|
|
var/Runs=round(Distance+0.5)
|
|
if(!isturf(A))
|
|
if(isturf(A.loc))
|
|
A=A.loc
|
|
else
|
|
return 0
|
|
for(var/v=1 to Runs)
|
|
set background = 1
|
|
var/X=A.x+round((xPlus*v)+0.5)
|
|
var/Y=A.y+round((yPlus*v)+0.5)
|
|
var/turf/T=locate(X,Y,A.z)
|
|
if(T)
|
|
if(!(T in crossed)) crossed.Add(T)
|
|
return crossed
|
|
|
|
proc/get_angle(atom/a, atom/b)
|
|
.= atan2(b.y - a.y, b.x - a.x)
|
|
|
|
//list2params without the dumb encoding
|
|
/proc/list2params_noencode(var/list/L)
|
|
var/strbuild = ""
|
|
var/first = 1
|
|
for(var/x in L)
|
|
strbuild += "[first?"":"&"][x]=[L[x]]"
|
|
first = 0
|
|
return strbuild
|
|
|
|
/turf/var/movable_area_next_type = null
|
|
/turf/var/movable_area_prev_type = null
|
|
|
|
/proc/get_steps(var/atom/ref, var/dir, var/numsteps)
|
|
var/atom/res = null
|
|
switch(dir)
|
|
if(NORTH)
|
|
res = locate(ref.x, ref.y+numsteps, ref.z)
|
|
if(NORTHEAST)
|
|
res = locate(ref.x+numsteps, ref.y+numsteps, ref.z)
|
|
if(EAST)
|
|
res = locate(ref.x+numsteps, ref.y, ref.z)
|
|
if(SOUTHEAST)
|
|
res = locate(ref.x+numsteps, ref.y-numsteps, ref.z)
|
|
if(SOUTH)
|
|
res = locate(ref.x, ref.y-numsteps, ref.z)
|
|
if(SOUTHWEST)
|
|
res = locate(ref.x-numsteps, ref.y-numsteps, ref.z)
|
|
if(WEST)
|
|
res = locate(ref.x-numsteps, ref.y, ref.z)
|
|
if(NORTHWEST)
|
|
res = locate(ref.x-numsteps, ref.y+numsteps, ref.z)
|
|
return res
|
|
/*
|
|
/proc/get_steps(var/atom/ref, var/direction, var/numsteps)
|
|
var/atom/curr = ref
|
|
for(var/num=0, num<numsteps, num++)
|
|
curr = get_step(curr, direction)
|
|
return curr
|
|
*/
|
|
|
|
/proc/movable_area_check(var/atom/A)
|
|
if(!A.loc) return 0
|
|
if(!A) return 0
|
|
if(A.x > world.maxx) return 0
|
|
if(A.x < 1) return 0
|
|
if(A.y > world.maxy) return 0
|
|
if(A.y < 1) return 0
|
|
if(A.density) return 0
|
|
for(var/atom/curr in A)
|
|
if(curr.density) return 0
|
|
return 1
|
|
|
|
/proc/do_mob(var/mob/user , var/atom/target as turf|obj|mob, var/time = 30) //This is quite an ugly solution but i refuse to use the old request system.
|
|
if(!user || !target) return 0
|
|
. = 0
|
|
var/user_loc = user.loc
|
|
var/target_loc = target.loc
|
|
var/holding = user.equipped()
|
|
sleep(time)
|
|
if (!user || !target)
|
|
return 0
|
|
if ( user.loc == user_loc && target.loc == target_loc && user.equipped() == holding && !( user.stat ) && ( !user.getStatusDuration("stunned") && !user.getStatusDuration("weakened") && !user.getStatusDuration("paralysis") && !user.lying ) )
|
|
return 1
|
|
|
|
/proc/do_after(mob/M as mob, time as num)
|
|
if (!ismob(M))
|
|
return 0
|
|
. = 0
|
|
var/turf/T = M.loc
|
|
var/holding = M.equipped()
|
|
sleep(time)
|
|
if ((M.loc == T && M.equipped() == holding && !( M.stat )))
|
|
return 1
|
|
|
|
/proc/is_blocked_turf(var/turf/T)
|
|
// drsingh for cannot read null.density
|
|
if (!T) return 0
|
|
if(T.density) return 1
|
|
for(var/atom/A in T)
|
|
if(A && A.density)//&&A.anchored
|
|
return 1
|
|
return 0
|
|
|
|
/proc/get_edge_cheap(var/atom/A, var/direction)
|
|
. = A.loc
|
|
switch(direction)
|
|
if(NORTH)
|
|
. = locate(A.x, world.maxy, A.z)
|
|
if(NORTHEAST)
|
|
. = locate(world.maxx, world.maxy, A.z)
|
|
if(EAST)
|
|
. = locate(world.maxx, A.y, A.z)
|
|
if(SOUTHEAST)
|
|
. = locate(world.maxx, 1, A.z)
|
|
if(SOUTH)
|
|
. = locate(A.x, 1, A.z)
|
|
if(SOUTHWEST)
|
|
. = locate(1, 1, A.z)
|
|
if(WEST)
|
|
. = locate(1, A.y, A.z)
|
|
if(NORTHWEST)
|
|
. = locate(1, world.maxy, A.z)
|
|
|
|
|
|
//Returns an integer given a hex input, supports negative values "-ff"
|
|
//skips preceding invalid characters
|
|
//breaks when hittin invalid characters thereafter
|
|
/proc/hex2num(hex)
|
|
. = 0
|
|
var/place = 1
|
|
for(var/i in length(hex) to 1 step -1)
|
|
var/num = text2ascii(hex, i)
|
|
switch(num)
|
|
if(48 to 57)
|
|
num -= 48 //0-9
|
|
if(97 to 102)
|
|
num -= 87 //a-f
|
|
if(65 to 70)
|
|
num -= 55 //A-F
|
|
if(45)
|
|
return . * -1 // -
|
|
else
|
|
CRASH("Malformed hex number")
|
|
|
|
. += num * place
|
|
place *= 16
|
|
|
|
|
|
//Returns the hex value of a decimal number
|
|
//len == length of returned string
|
|
//if len < 0 then the returned string will be as long as it needs to be to contain the data
|
|
//Only supports positive numbers
|
|
//if an invalid number is provided, it assumes num==0
|
|
//Note, unlike previous versions, this one works from low to high <-- that way
|
|
/proc/num2hex(num, len=2)
|
|
if(!isnum(num))
|
|
num = 0
|
|
num = round(abs(num))
|
|
. = ""
|
|
var/i=0
|
|
while(1)
|
|
if(len<=0)
|
|
if(!num)
|
|
break
|
|
else
|
|
if(i>=len)
|
|
break
|
|
var/remainder = num/16
|
|
num = round(remainder)
|
|
remainder = (remainder - num) * 16
|
|
switch(remainder)
|
|
if(9,8,7,6,5,4,3,2,1)
|
|
. = "[remainder]" + .
|
|
if(10,11,12,13,14,15)
|
|
. = ascii2text(remainder+87) + .
|
|
else
|
|
. = "0" + .
|
|
i++
|
|
return .
|
|
|
|
/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)
|
|
. = text("#[][][]", textr, textg, textb)
|
|
|
|
/proc/sanitize(var/t)
|
|
var/index = findtext(t, "\n")
|
|
while(index)
|
|
t = copytext(t, 1, index) + "#" + copytext(t, index+1)
|
|
index = findtext(t, "\n")
|
|
|
|
index = findtext(t, "\t")
|
|
while(index)
|
|
t = copytext(t, 1, index) + "#" + copytext(t, index+1)
|
|
index = findtext(t, "\t")
|
|
return t // fuk.
|
|
|
|
/proc/sanitize_noencode(var/t)
|
|
var/index = findtext(t, "\n")
|
|
while(index)
|
|
t = copytext(t, 1, index) + "#" + copytext(t, index+1)
|
|
index = findtext(t, "\n")
|
|
|
|
index = findtext(t, "\t")
|
|
while(index)
|
|
t = copytext(t, 1, index) + "#" + copytext(t, index+1)
|
|
index = findtext(t, "\t")
|
|
return t
|
|
|
|
/proc/strip_html(var/t,var/limit=MAX_MESSAGE_LEN, var/no_fucking_autoparse = 0)
|
|
t = html_decode(copytext(t,1,limit))
|
|
if (no_fucking_autoparse == 1)
|
|
var/list/bad_characters = list("_", "'", "\"", "<", ">", ";", "[", "]", "{", "}", "|", "\\", "/")
|
|
for(var/c in bad_characters)
|
|
t = replacetext(t, c, "")
|
|
var/index = findtext(t, "<")
|
|
while(index)
|
|
t = copytext(t, 1, index) + copytext(t, index+1)
|
|
index = findtext(t, "<")
|
|
index = findtext(t, ">")
|
|
while(index)
|
|
t = copytext(t, 1, index) + copytext(t, index+1)
|
|
index = findtext(t, ">")
|
|
. = sanitize(t)
|
|
|
|
/proc/adminscrub(var/t,var/limit=MAX_MESSAGE_LEN)
|
|
t = html_decode(copytext(t,1,limit))
|
|
var/index = findtext(t, "<")
|
|
while(index)
|
|
t = copytext(t, 1, index) + copytext(t, index+1)
|
|
index = findtext(t, "<")
|
|
index = findtext(t, ">")
|
|
while(index)
|
|
t = copytext(t, 1, index) + copytext(t, index+1)
|
|
index = findtext(t, ">")
|
|
. = html_encode(t)
|
|
|
|
/proc/map_numbers(var/x, var/in_min, var/in_max, var/out_min, var/out_max)
|
|
. = ((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
|
|
|
|
/proc/add_zero(t, u)
|
|
t = "[t]"
|
|
while (length(t) < u)
|
|
t = "0[t]"
|
|
. = t
|
|
|
|
/proc/add_lspace(t, u)
|
|
// why????? because if you pass this a number,
|
|
// then -- surprise -- length(t) is ZERO. because it's not text.
|
|
// so step one is to just. do that.
|
|
// *scream *scream *scream *scream *scream *scream *scream *scream *scream
|
|
// *scream *scream *scream *scream *scream *scream *scream *scream *scream
|
|
// *scream *scream *scream *scream *scream *scream *scream *scream *scream
|
|
t = "[t]"
|
|
while(length(t) < u)
|
|
t = " [t]"
|
|
. = t
|
|
|
|
/proc/add_tspace(t, u)
|
|
t = "[t]"
|
|
while(length(t) < u)
|
|
t = "[t] "
|
|
. = t
|
|
|
|
/proc/sortList(var/list/L)
|
|
if(L.len < 2)
|
|
return L
|
|
var/middle = L.len / 2 + 1 // Copy is first,second-1
|
|
. = mergeLists(sortList(L.Copy(0,middle)), sortList(L.Copy(middle))) //second parameter null = to end of list
|
|
|
|
/proc/sortNames(var/list/L)
|
|
var/list/Q = new()
|
|
for(var/atom/x in L)
|
|
Q[x.name] = x
|
|
. = sortList(Q)
|
|
|
|
/proc/mergeLists(var/list/L, var/list/R)
|
|
var/Li=1
|
|
var/Ri=1
|
|
. = list()
|
|
while(Li <= L.len && Ri <= R.len)
|
|
if(sorttext(L[Li], R[Ri]) < 1)
|
|
var/key = R[Ri]
|
|
var/ass = !isnum(key) ? R[key] : null //Associative lists. (also hurf durf)
|
|
. += R[Ri++]
|
|
if(ass) .[key] = ass
|
|
else
|
|
var/key = L[Li]
|
|
var/ass = !isnum(key) ? L[key] : null //Associative lists. (also hurf durf)
|
|
. += L[Li++]
|
|
if(ass) .[key] = ass
|
|
|
|
if(Li <= L.len)
|
|
. += L.Copy(Li, 0)
|
|
else
|
|
. += R.Copy(Ri, 0)
|
|
|
|
/proc/dd_file2list(file_path, separator)
|
|
if(separator == null)
|
|
separator = "\n"
|
|
if(isfile(file_path))
|
|
. = file_path
|
|
else
|
|
. = file(file_path)
|
|
. = splittext(file2text(.), separator)
|
|
|
|
/proc/dd_hasprefix(text, prefix)
|
|
var/start = 1
|
|
var/end = length(prefix) + 1
|
|
. = findtext(text, prefix, start, end)
|
|
|
|
/proc/dd_hasPrefix(text, prefix)
|
|
var/start = 1
|
|
var/end = length(prefix) + 1
|
|
. = findtext(text, prefix, start, end) //was findtextEx
|
|
|
|
/proc/dd_hassuffix(text, suffix)
|
|
var/start = length(text) - length(suffix)
|
|
if(start)
|
|
. = findtext(text, suffix, start, null)
|
|
|
|
/proc/dd_hasSuffix(text, suffix)
|
|
var/start = length(text) - length(suffix)
|
|
if(start)
|
|
. = findtext(text, suffix, start, null) //was findtextEx
|
|
|
|
/proc/english_list(var/list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "," )
|
|
var/total = input.len
|
|
if (!total)
|
|
return "[nothing_text]"
|
|
else if (total == 1)
|
|
return "[input[1]]"
|
|
else if (total == 2)
|
|
return "[input[1]][and_text][input[2]]"
|
|
else
|
|
var/output = ""
|
|
var/index = 1
|
|
while (index < total)
|
|
if (index == total - 1)
|
|
comma_text = final_comma_text
|
|
|
|
output += "[input[index]][comma_text]"
|
|
index++
|
|
|
|
return "[output][and_text][input[index]]"
|
|
|
|
/proc/dd_centertext(message, length)
|
|
. = length(message)
|
|
if(. == length)
|
|
.= message
|
|
else if(. > length)
|
|
.= copytext(message, 1, length + 1)
|
|
else
|
|
var/delta = length - .
|
|
if(delta == 1)
|
|
. = message + " "
|
|
else if(delta % 2)
|
|
. = " " + message
|
|
delta--
|
|
var/spaces = add_lspace("",delta/2-1)
|
|
. = spaces + . + spaces
|
|
|
|
/proc/dd_limittext(message, length)
|
|
var/size = length(message)
|
|
if(size <= length)
|
|
. = message
|
|
else
|
|
.= copytext(message, 1, length + 1)
|
|
|
|
/proc/angle2dir(var/degree)
|
|
. = NORTH|WEST
|
|
if (degree < 0)
|
|
degree = 360 + (degree % 360)
|
|
degree = ((degree+22.5)%360)
|
|
if(degree < 45) . = NORTH
|
|
else if(degree < 90) . = NORTH|EAST
|
|
else if(degree < 135) . = EAST
|
|
else if(degree < 180) . = SOUTH|EAST
|
|
else if(degree < 225) . = SOUTH
|
|
else if(degree < 270) . = SOUTH|WEST
|
|
else if(degree < 315) . = WEST
|
|
|
|
/proc/angle2text(var/degree)
|
|
. = dir2text(angle2dir(degree))
|
|
|
|
/proc/text_input(var/Message, var/Title, var/Default, var/length=MAX_MESSAGE_LEN)
|
|
. = sanitize(input(Message, Title, Default) as text, length)
|
|
|
|
/proc/scrubbed_input(var/user, var/Message, var/Title, var/Default, var/length=MAX_MESSAGE_LEN)
|
|
. = strip_html(input(user, Message, Title, Default) as null|text, length)
|
|
|
|
/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/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/window))
|
|
return 1
|
|
return 0
|
|
|
|
/proc/getline(atom/M,atom/N)//Ultra-Fast Bresenham Line-Drawing Algorithm
|
|
var/px=M.x //starting x
|
|
var/py=M.y
|
|
. = 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
|
|
. += 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
|
|
. += locate(px,py,M.z)
|
|
|
|
/proc/IsGuestKey(key)
|
|
//Wire note: This seems like it would work just fine and is a whole bunch shorter
|
|
return copytext(key, 1, 7) == "Guest-"
|
|
|
|
/*
|
|
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 != 87 && (ch < 48 || ch > 57)) // 87 is W for webclients... spec changed.
|
|
return 0
|
|
|
|
return 1
|
|
*/
|
|
|
|
/proc/sanitize_frequency(var/f)
|
|
. = round(f)
|
|
. = max(R_FREQ_MINIMUM, .) // 144.1
|
|
. = min(R_FREQ_MAXIMUM, .) // 148.9
|
|
. |= 1
|
|
|
|
/proc/format_frequency(var/f)
|
|
. = "[round(f / 10)].[f % 10]"
|
|
|
|
/proc/sortmobs()
|
|
|
|
var/list/mob_list = list()
|
|
for(var/mob/living/silicon/ai/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/dead/aieye/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/silicon/robot/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/silicon/hivebot/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/silicon/hive_mainframe/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/carbon/human/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/critter/C in mobs)
|
|
if(C.client)
|
|
mob_list.Add(C)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/wraith/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/intangible/blob_overmind/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
// for(var/mob/living/carbon/alien/M in mobs)
|
|
// mob_list.Add(M)
|
|
for(var/mob/dead/observer/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/dead/target_observer/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/new_player/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/carbon/wall/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
for(var/mob/living/silicon/ghostdrone/M in mobs)
|
|
mob_list.Add(M)
|
|
LAGCHECK(LAG_REALTIME)
|
|
|
|
return mob_list
|
|
|
|
//Include details shows traitor status etc
|
|
//Admins replaces the src ref for links with a placeholder for message_admins
|
|
//Mentor just changes the private message link
|
|
/proc/key_name(var/whom, var/include_details = 1, var/admins = 1, var/mentor = 0)
|
|
var/mob/the_mob = null
|
|
var/client/the_client = null
|
|
var/the_key = ""
|
|
|
|
if (isnull(whom))
|
|
return "*null*"
|
|
else if (isclient(whom))
|
|
the_client = whom
|
|
the_mob = the_client.mob
|
|
the_key = html_encode(the_client.key)
|
|
else if (ismob(whom))
|
|
the_mob = whom
|
|
the_client = the_mob.client
|
|
the_key = html_encode(the_mob.key)
|
|
else if (istype(whom, /datum))
|
|
if (ismind(whom))
|
|
var/datum/mind/the_mind = whom
|
|
the_mob = the_mind.current
|
|
the_key = html_encode(the_mind.key)
|
|
the_client = the_mind.current ? the_mind.current.client : null
|
|
if (!the_client && the_key)
|
|
for (var/client/C in clients)
|
|
if (C.key == the_key || C.ckey == the_key)
|
|
the_client = C
|
|
break
|
|
else
|
|
var/datum/the_datum = whom
|
|
return "*invalid:[the_datum.type]*"
|
|
else //It's probably just a text string. We're ok with that.
|
|
for (var/client/C in clients)
|
|
if (C.key == whom || C.ckey == whom)
|
|
the_client = C
|
|
the_key = html_encode(C.key)
|
|
if (C.mob)
|
|
the_mob = C.mob
|
|
break
|
|
if (C.mob && C.mob.real_name == whom)
|
|
the_client = C
|
|
the_key = html_encode(C.key)
|
|
the_mob = C.mob
|
|
break
|
|
|
|
var/text = ""
|
|
|
|
if (!the_key)
|
|
text += "*no client*"
|
|
else
|
|
if (!isnull(the_mob))
|
|
if(!mentor) text += "<a href=\"byond://?action=priv_msg&target=[the_mob.ckey]\">"
|
|
else text += "<a href=\"byond://?action=mentor_msg&target=[the_mob.ckey]\">"
|
|
|
|
if (the_client)
|
|
if (the_client.holder && the_client.stealth && !include_details)
|
|
text += "Administrator"
|
|
else if (the_client.holder && the_client.alt_key && !include_details)
|
|
text += "[the_client.fakekey]"
|
|
else
|
|
text += "[the_key]"
|
|
else
|
|
text += "[the_key] *no client*"
|
|
|
|
if (!isnull(the_mob))
|
|
text += "</a>"
|
|
|
|
//Show details for players
|
|
if (include_details)
|
|
if (!isnull(the_mob))
|
|
text += "/"
|
|
if (the_mob.real_name)
|
|
text += html_encode(the_mob.real_name)
|
|
else if (the_mob.name)
|
|
text += html_encode(the_mob.name)
|
|
text += " "
|
|
if (the_client && !the_client.holder) //only show this stuff for non-admins because admins do a lot of shit while dead and it is unnecessary to show it
|
|
if (checktraitor(the_mob))
|
|
text += "\[<font color='red'>T</font>\] "
|
|
if (isdead(the_mob))
|
|
text += "\[DEAD\] "
|
|
|
|
var/linkSrc
|
|
if (admins)
|
|
linkSrc = "%admin_ref%"
|
|
else
|
|
linkSrc = "\ref[usr.client.holder]"
|
|
text += "<a href='byond://?src=[linkSrc]&action=adminplayeropts&targetckey=[the_mob.ckey]' class='popt'><i class='icon-info-sign'></i></a>"
|
|
|
|
return text
|
|
|
|
// Registers the on-close verb for a browse window (client/verb/.windowclose)
|
|
// this will be called when the close-button of a window is pressed.
|
|
//
|
|
// This is usually only needed for devices that regularly update the browse window,
|
|
// e.g. canisters, timers, etc.
|
|
//
|
|
// windowid should be the specified window name
|
|
// e.g. code is : user.Browse(text, "window=fred")
|
|
// then use : onclose(user, "fred")
|
|
//
|
|
// Optionally, specify the "ref" parameter as the controlled atom (usually src)
|
|
// to pass a "close=1" parameter to the atom's Topic() proc for special handling.
|
|
// Otherwise, the user mob's machine var will be reset directly.
|
|
//
|
|
/proc/onclose(mob/user, windowid, var/datum/ref=null)
|
|
|
|
var/param = "null"
|
|
if(ref)
|
|
param = "\ref[ref]"
|
|
|
|
if (user && user.client) winset(user, windowid, "on-close=\".windowclose [param]\"")
|
|
|
|
//boutput(world, "OnClose [user]: [windowid] : ["on-close=\".windowclose [param]\""]")
|
|
|
|
|
|
// 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.
|
|
//
|
|
/client/verb/windowclose(var/atomref as text)
|
|
set hidden = 1 // hide this verb from the user's panel
|
|
set name = ".windowclose" // no autocomplete on cmd line
|
|
|
|
//boutput(world, "windowclose: [atomref]")
|
|
if(atomref!="null") // if passed a real atomref
|
|
var/hsrc = locate(atomref) // find the reffed atom
|
|
var/href = "close=1"
|
|
if(hsrc)
|
|
//boutput(world, "[src] Topic [href] [hsrc]")
|
|
usr = src.mob
|
|
src.Topic(href, params2list(href), hsrc) // this will direct to the atom's
|
|
return // Topic() proc via client.Topic()
|
|
|
|
// no atomref specified (or not found)
|
|
// so just reset the user mob's machine var
|
|
if(src && src.mob)
|
|
//boutput(world, "[src] was [src.mob.machine], setting to null")
|
|
if(src.mob.machine && istype(src.mob.machine, /obj/machinery))
|
|
src.mob.machine.current_user = null
|
|
src.mob.machine = null
|
|
return
|
|
|
|
/proc/get_turf_loc(var/atom/movable/M) //gets the location of the turf that the mob is on, or what the mob is in is on, etc
|
|
//in case they're in a closet or sleeper or something
|
|
if (!M) return null
|
|
var/atom/loc = M.loc
|
|
while(!istype(loc, /turf/))
|
|
if (!loc)
|
|
break
|
|
loc = loc.loc
|
|
return loc
|
|
|
|
// 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-1, target.z)
|
|
if(direction & SOUTH)
|
|
target = locate(target.x, 1, target.z)
|
|
if(direction & EAST)
|
|
target = locate(world.maxx-1, 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)
|
|
|
|
// extends pick() to associated lists
|
|
/proc/alist_pick(var/list/L)
|
|
if(!L || !L.len)
|
|
return null
|
|
return L[pick(L)]
|
|
|
|
/proc/ran_zone(zone, probability)
|
|
|
|
if (probability == null)
|
|
probability = 90
|
|
if (probability == 100)
|
|
return zone
|
|
switch(zone)
|
|
if("chest")
|
|
if (prob(probability))
|
|
return "chest"
|
|
else
|
|
var/t = rand(1, 9)
|
|
if (t < 3)
|
|
return "head"
|
|
else if (t < 6)
|
|
return "l_arm"
|
|
else if (t < 9)
|
|
return "r_arm"
|
|
else
|
|
return "chest"
|
|
|
|
if("head")
|
|
if (prob(probability * 0.75))
|
|
return "head"
|
|
else
|
|
if (prob(60))
|
|
return "chest"
|
|
else
|
|
return "head"
|
|
if("l_arm")
|
|
if (prob(probability * 0.75))
|
|
return "l_arm"
|
|
else
|
|
if (prob(60))
|
|
return "chest"
|
|
else
|
|
return "l_arm"
|
|
if("r_arm")
|
|
if (prob(probability * 0.75))
|
|
return "r_arm"
|
|
else
|
|
if (prob(60))
|
|
return "chest"
|
|
else
|
|
return "r_arm"
|
|
if("r_leg")
|
|
if (prob(probability * 0.75))
|
|
return "r_leg"
|
|
else
|
|
if (prob(60))
|
|
return "chest"
|
|
else
|
|
return "r_leg"
|
|
if("l_leg")
|
|
if (prob(probability * 0.75))
|
|
return "l_leg"
|
|
else
|
|
if (prob(60))
|
|
return "chest"
|
|
else
|
|
return "l_leg"
|
|
return
|
|
|
|
|
|
// Jesus fucking christ people has nobody heard of giving your variables useful names
|
|
// I mean seriously it doesnt take that much time and no you arent saving any space
|
|
// in the game by doing it. Christ on a stick this is awful. Who the hell uses n
|
|
// for a message in the first place I mean come on thats supposed to be the size of input
|
|
// you couldnt have gone for "msg" or something that makes 10 times more sense?!?
|
|
// In summary: aaaaaaaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
// <3 Fire
|
|
/proc/stars(n, pr)
|
|
|
|
if (pr == null)
|
|
pr = 25
|
|
if (pr <= 0)
|
|
return null
|
|
else
|
|
if (pr >= 100)
|
|
return n
|
|
var/te = n
|
|
var/t = ""
|
|
n = length(n)
|
|
var/p = null
|
|
p = 1
|
|
while(p <= n)
|
|
if ((copytext(te, p, p + 1) == " " || prob(pr)))
|
|
t = text("[][]", t, copytext(te, p, p + 1))
|
|
else
|
|
t = text("[]*", t)
|
|
p++
|
|
return t
|
|
|
|
/proc/stutter(n)
|
|
var/te = html_decode(n)
|
|
var/t = ""
|
|
n = length(n)
|
|
var/p = null
|
|
p = 1
|
|
while(p <= n)
|
|
var/n_letter = copytext(te, p, p + 1)
|
|
if (prob(80))
|
|
if (prob(10))
|
|
n_letter = text("[n_letter][n_letter][n_letter][n_letter]")
|
|
else
|
|
if (prob(20))
|
|
n_letter = text("[n_letter][n_letter][n_letter]")
|
|
else
|
|
if (prob(5))
|
|
n_letter = null
|
|
else
|
|
n_letter = text("[n_letter][n_letter]")
|
|
t = text("[t][n_letter]")
|
|
p++
|
|
return copytext(sanitize(t),1,MAX_MESSAGE_LEN)
|
|
|
|
/proc/shake_camera(mob/M, duration, strength=1, delay=0.2)
|
|
SPAWN_DBG(1 DECI SECOND)
|
|
if(!M || !M.client || M.shakecamera)
|
|
return
|
|
M.shakecamera = 1
|
|
|
|
for(var/i=0, i<duration, i++)
|
|
var/off_x = (rand(0, strength*32) * (prob(50) ? -1:1))
|
|
var/off_y = (rand(0, strength*32) * (prob(50) ? -1:1))
|
|
animate(M.client, pixel_x = off_x, pixel_y = off_y, easing = LINEAR_EASING, time = 1, flags = ANIMATION_RELATIVE)
|
|
animate(pixel_x = off_x*-1, pixel_y = off_y*-1, easing = LINEAR_EASING, time = 1, flags = ANIMATION_RELATIVE)
|
|
sleep(delay)
|
|
|
|
if (M.client)
|
|
M.client.pixel_x = 0
|
|
M.client.pixel_y = 0
|
|
M.shakecamera = 0
|
|
|
|
/proc/findname(msg)
|
|
for(var/mob/M in mobs)
|
|
if (M.real_name == text("[msg]"))
|
|
return 1
|
|
return 0
|
|
|
|
/proc/get_cardinal_step_away(atom/start, atom/finish) //returns the position of a step from start away from finish, in one of the cardinal directions
|
|
//returns only NORTH, SOUTH, EAST, or WEST
|
|
var/dx = finish.x - start.x
|
|
var/dy = finish.y - start.y
|
|
if(abs(dy) > abs (dx)) //slope is above 1:1 (move horizontally in a tie)
|
|
if(dy > 0)
|
|
return get_step(start, SOUTH)
|
|
else
|
|
return get_step(start, NORTH)
|
|
else
|
|
if(dx > 0)
|
|
return get_step(start, WEST)
|
|
else
|
|
return get_step(start, EAST)
|
|
|
|
|
|
/proc/parse_zone(zone)
|
|
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 return zone
|
|
|
|
/proc/text2dir(direction)
|
|
switch(uppertext(direction))
|
|
if("NORTH")
|
|
return NORTH
|
|
if("N")
|
|
return NORTH
|
|
if("SOUTH")
|
|
return SOUTH
|
|
if("S")
|
|
return SOUTH
|
|
if("EAST")
|
|
return EAST
|
|
if("E")
|
|
return EAST
|
|
if("WEST")
|
|
return WEST
|
|
if("W")
|
|
return WEST
|
|
if("NORTHEAST")
|
|
return NORTHEAST
|
|
if("NE")
|
|
return NORTHEAST
|
|
if("NORTHWEST")
|
|
return NORTHWEST
|
|
if("NW")
|
|
return NORTHWEST
|
|
if("SOUTHEAST")
|
|
return SOUTHEAST
|
|
if("SE")
|
|
return SOUTHEAST
|
|
if("SOUTHWEST")
|
|
return SOUTHWEST
|
|
if("SW")
|
|
return SOUTHWEST
|
|
else
|
|
return
|
|
|
|
/proc/dir2text(direction)
|
|
switch(direction)
|
|
if(0)
|
|
return "center"
|
|
if(NORTH)
|
|
return "north"
|
|
if(SOUTH)
|
|
return "south"
|
|
if(EAST)
|
|
return "east"
|
|
if(WEST)
|
|
return "west"
|
|
if(NORTHEAST)
|
|
return "northeast"
|
|
if(SOUTHEAST)
|
|
return "southeast"
|
|
if(NORTHWEST)
|
|
return "northwest"
|
|
if(SOUTHWEST)
|
|
return "southwest"
|
|
else
|
|
return
|
|
|
|
// Marquesas: added an extra parameter to fix issue with changeling.
|
|
// Unfortunately, it has to be this extra parameter, otherwise the spawn(0) in the mob say will
|
|
// cause the mob's name to revert from the one it acquired for mimic voice.
|
|
/atom/proc/hear_talk(mob/M as mob, text, real_name)
|
|
if (src.open_to_sound)
|
|
for(var/obj/O in src)
|
|
O.hear_talk(M,text,real_name)
|
|
|
|
/*var/mob/mo = locate(/mob) in src
|
|
if(mo)
|
|
var/heardname = M.name
|
|
if (real_name)
|
|
heardname = real_name
|
|
var/rendered = "<span class='game say'><span class='name'>[heardname]: </span> <span class='message'>[text]</span></span>"
|
|
mo.show_message(rendered, 2)*/
|
|
return
|
|
|
|
/**
|
|
* Returns true if given value is a hex value
|
|
*/
|
|
|
|
/proc/is_hex(hex)
|
|
if (!( istext(hex) ))
|
|
return FALSE
|
|
return (findtext(hex, hex_regex) == 1)
|
|
|
|
/proc/format_username(var/playerName)
|
|
if (!playerName)
|
|
return "Unknown"
|
|
|
|
var/list/name_temp = splittext(playerName, " ")
|
|
if (!name_temp.len)
|
|
playerName = "Unknown"
|
|
else if (name_temp.len == 1)
|
|
playerName = name_temp[1]
|
|
else //Ex: John Smith becomes JSmith
|
|
playerName = copytext( ( copytext(name_temp[1],1, 2) + name_temp[name_temp.len] ), 1, 16)
|
|
|
|
return lowertext(replacetext(playerName, "/", null))
|
|
|
|
/proc/engineering_notation(var/value=0 as num)
|
|
if (!value)
|
|
return "0 "
|
|
|
|
var/suffix = ""
|
|
var/power = round( log(10, value) / 3)
|
|
switch (power)
|
|
if (-8)
|
|
suffix = "y"
|
|
if (-7)
|
|
suffix = "z"
|
|
if (-6)
|
|
suffix = "a"
|
|
if (-5)
|
|
suffix = "f"
|
|
if (-4)
|
|
suffix = "p"
|
|
if (-3)
|
|
suffix = "n"
|
|
if (-2)
|
|
suffix = "μ"
|
|
if (-1)
|
|
suffix = "m"
|
|
if (1)
|
|
suffix = "k"
|
|
if (2)
|
|
suffix = "M"
|
|
if (3)
|
|
suffix = "G"
|
|
if (4)
|
|
suffix = "T"
|
|
if (5)
|
|
suffix = "P"
|
|
if (6)
|
|
suffix = "E"
|
|
if (7)
|
|
suffix = "Z"
|
|
if (8)
|
|
suffix = "Y"
|
|
|
|
value = round( (value / (10 ** (3 * power))), 0.001 )
|
|
return "[value] [suffix]"
|
|
|
|
/proc/obj_loc_chain(var/atom/movable/whose)
|
|
if (isturf(whose) || isarea(whose))
|
|
return list()
|
|
if (isturf(whose.loc))
|
|
return list()
|
|
var/list/chain = list()
|
|
var/atom/movable/M = whose
|
|
while (ismob(M.loc) || isobj(M.loc))
|
|
chain += M.loc
|
|
M = M.loc
|
|
return chain
|
|
|
|
/proc/all_hearers(var/range,var/centre)
|
|
. = list()
|
|
for(var/thing in (view(range,centre) | hearers(range, centre))) //Why was this view(). Oh no, the invisible man hears naught 'cause the sound can't find his ears.
|
|
var/atom/A = thing
|
|
if (ismob(A))
|
|
. += A
|
|
if (isobj(A) || ismob(A))
|
|
for(var/mob/M in A.contents)
|
|
var/can_hear = 0 //this check prevents observers from hearing their target's messages twice
|
|
|
|
if (istype(M,/mob/dead/target_observer))
|
|
var/mob/dead/target_observer/O = M
|
|
if (A != O.target)
|
|
can_hear = 1
|
|
else
|
|
can_hear = 1
|
|
|
|
if (can_hear)
|
|
. += M
|
|
|
|
|
|
|
|
/proc/all_viewers(var/range,var/centre)
|
|
. = list()
|
|
for(var/thing in viewers(range,centre))
|
|
var/atom/A = thing
|
|
if (ismob(A))
|
|
. += A
|
|
else if (isobj(A))
|
|
for(var/mob/M in A.contents)
|
|
. += M
|
|
|
|
/proc/all_range(var/range,var/centre) //above two are blocked by opaque objects
|
|
. = list()
|
|
for(var/thing in range(range,centre))
|
|
var/atom/A = thing
|
|
if (ismob(A))
|
|
. += A
|
|
else if (isobj(A))
|
|
for(var/mob/M in A.contents)
|
|
. += M
|
|
|
|
/proc/all_view(var/range,var/centre)
|
|
. = view(range,centre)
|
|
for(var/obj/O in .)
|
|
for(var/mob/M in O.contents)
|
|
. += M
|
|
|
|
/proc/weightedprob(choices[], weights[])
|
|
if(!choices || !weights) return null
|
|
|
|
//Build a range of weights
|
|
var/max_num = 0
|
|
for(var/X in weights) if(isnum(X)) max_num += X
|
|
|
|
//Now roll in the range.
|
|
var/weighted_num = rand(1,max_num)
|
|
|
|
var/running_total, i
|
|
|
|
//Loop through all possible choices
|
|
for(i = 1; i <= choices.len; i++)
|
|
if(i > weights.len) return null
|
|
|
|
running_total += weights[i]
|
|
|
|
//Once the current step is less than the roll,
|
|
// we have our winner.
|
|
if(weighted_num <= running_total)
|
|
return choices[i]
|
|
return
|
|
|
|
/* Get the highest ancestor of this object in the tree that is an immediate child of
|
|
a given ancestor.
|
|
Usage:
|
|
var/datum/fart/sassy/F = new
|
|
get_top_parent(F, /datum) //returns a path to /datum/fart
|
|
*/
|
|
/proc/get_top_ancestor(var/datum/object, var/ancestor_of_ancestor=/datum)
|
|
if(!object || !ancestor_of_ancestor)
|
|
CRASH("Null value parameters in get top ancestor.")
|
|
if(!ispath(ancestor_of_ancestor))
|
|
CRASH("Non-Path ancestor of ancestor parameter supplied.")
|
|
var/stringancestor = "[ancestor_of_ancestor]"
|
|
var/stringtype = "[object.type]"
|
|
var/ancestorposition = findtextEx(stringtype, stringancestor)
|
|
if(!ancestorposition)
|
|
return null
|
|
var/parentstart = ancestorposition + length(stringancestor) + 1
|
|
var/parentend = findtextEx(stringtype, "/", parentstart)
|
|
var/stringtarget = copytext(stringtype, 1, parentend ? parentend : 0)
|
|
return text2path(stringtarget)
|
|
|
|
//Returns a list of minds that are some type of antagonist role
|
|
//This may be a stop gap until a better solution can be figured out
|
|
/proc/get_all_enemies()
|
|
if(ticker && ticker.mode && current_state >= GAME_STATE_PLAYING)
|
|
var/datum/mind/enemies[] = new()
|
|
var/datum/mind/someEnemies[] = new()
|
|
|
|
//We gotta loop through the modes because someone thought it was a good idea to create new lists for all of them
|
|
if (istype(ticker.mode, /datum/game_mode/revolution))
|
|
someEnemies = ticker.mode:head_revolutionaries
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
someEnemies = ticker.mode:revolutionaries
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
someEnemies = ticker.mode:get_all_heads()
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
else if (istype(ticker.mode, /datum/game_mode/nuclear))
|
|
someEnemies = ticker.mode:syndicates
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
else if (istype(ticker.mode, /datum/game_mode/spy))
|
|
someEnemies = ticker.mode:spies
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
someEnemies = ticker.mode:leaders
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
else if (istype(ticker.mode, /datum/game_mode/gang))
|
|
someEnemies = ticker.mode:leaders
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
for(var/datum/mind/G in M.gang.members) //This may be fucked. Dunno how these are stored.
|
|
enemies += G
|
|
|
|
//Lists we grab regardless of game type
|
|
//Traitors list is populated during traitor or mixed rounds, however it is created along with the game_mode datum unlike the rest of the lists
|
|
someEnemies = ticker.mode.traitors
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
|
|
//Sometimes admins assign traitors, this contains those dudes
|
|
someEnemies = ticker.mode.Agimmicks
|
|
for(var/datum/mind/M in someEnemies)
|
|
if (M.current)
|
|
enemies += M
|
|
|
|
return enemies
|
|
|
|
else
|
|
return 0
|
|
|
|
/proc/GetRedPart(hex)
|
|
hex = uppertext(hex)
|
|
var/hi = text2ascii(hex, 2)
|
|
var/lo = text2ascii(hex, 3)
|
|
return ( ((hi >= 65 ? hi-55 : hi-48)<<4) | (lo >= 65 ? lo-55 : lo-48) )
|
|
|
|
/proc/GetGreenPart(hex)
|
|
hex = uppertext(hex)
|
|
var/hi = text2ascii(hex, 4)
|
|
var/lo = text2ascii(hex, 5)
|
|
return ( ((hi >= 65 ? hi-55 : hi-48)<<4) | (lo >= 65 ? lo-55 : lo-48) )
|
|
|
|
/proc/GetBluePart(hex)
|
|
hex = uppertext(hex)
|
|
var/hi = text2ascii(hex, 6)
|
|
var/lo = text2ascii(hex, 7)
|
|
return ( ((hi >= 65 ? hi-55 : hi-48)<<4) | (lo >= 65 ? lo-55 : lo-48) )
|
|
|
|
/proc/GetColors(hex)
|
|
hex = uppertext(hex)
|
|
var/hi1 = text2ascii(hex, 2)
|
|
var/lo1 = text2ascii(hex, 3)
|
|
var/hi2 = text2ascii(hex, 4)
|
|
var/lo2 = text2ascii(hex, 5)
|
|
var/hi3 = text2ascii(hex, 6)
|
|
var/lo3 = text2ascii(hex, 7)
|
|
return list(((hi1>= 65 ? hi1-55 : hi1-48)<<4) | (lo1 >= 65 ? lo1-55 : lo1-48),
|
|
((hi2 >= 65 ? hi2-55 : hi2-48)<<4) | (lo2 >= 65 ? lo2-55 : lo2-48),
|
|
((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48))
|
|
|
|
//Shoves a jump to link or whatever in the thing :effort:
|
|
/proc/showCoords(x, y, z, plaintext, holder)
|
|
var text
|
|
if (plaintext)
|
|
text += "[x], [y], [z]"
|
|
else
|
|
text += "<a href='?src=[holder ? "\ref[holder]" : "%admin_ref%"];action=jumptocoords;target=[x],[y],[z]' title='Jump to Coords'>[x],[y],[z]</a>"
|
|
|
|
return text
|
|
|
|
// hi I'm haine -throws more crap onto the pile-
|
|
/proc/rand_deci(var/num1 = 1, var/num2 = 0, var/num3 = 2, var/num4 = 0)
|
|
// input num1.num2 and num3.num4 returns a random number between them
|
|
var/output = text2num("[rand(num1, num2)].[rand(num3, num4)]")
|
|
return output
|
|
|
|
var/list/easing_types = list(
|
|
"Linear/0" = LINEAR_EASING,
|
|
"Sine/1" = SINE_EASING,
|
|
"Circular/2" = CIRCULAR_EASING,
|
|
"Cubic/3" = CUBIC_EASING,
|
|
"Bounce/4" = BOUNCE_EASING,
|
|
"Elastic/5" = ELASTIC_EASING,
|
|
"Back/6" = BACK_EASING)
|
|
|
|
var/list/blend_types = list(
|
|
"Default/0" = BLEND_DEFAULT,
|
|
"Overlay/1" = BLEND_OVERLAY,
|
|
"Add/2" = BLEND_ADD,
|
|
"Subtract/3" = BLEND_SUBTRACT,
|
|
"Multipy/4" = BLEND_MULTIPLY)
|
|
|
|
var/hex_regex = regex(@"^[0-9a-f]+$", "i")
|
|
var/color_regex = regex(@"^#[0-9a-f]{6}$", "i")
|
|
|
|
var/list/hex_chars = list("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F")
|
|
|
|
var/list/all_functional_reagent_ids = list()
|
|
|
|
proc/get_all_functional_reagent_ids()
|
|
for (var/X in childrentypesof(/datum/reagent) )
|
|
all_functional_reagent_ids += initial(X:id)
|
|
|
|
proc/reagent_id_to_name(var/reagent_id)
|
|
if (!reagent_id || !reagents_cache.len)
|
|
return
|
|
var/datum/reagent/R = reagents_cache[reagent_id]
|
|
if (!R)
|
|
return "nothing"
|
|
else
|
|
return R.name
|
|
|
|
proc/RarityClassRoll(var/scalemax = 100, var/mod = 0, var/list/category_boundaries)
|
|
if (!isnum(scalemax) || scalemax <= 0)
|
|
return 0
|
|
if (!isnum(mod))
|
|
return 0
|
|
if (category_boundaries.len <= 0)
|
|
return 0
|
|
|
|
var/picker = rand(1,scalemax)
|
|
picker += mod
|
|
var/list_counter = category_boundaries.len
|
|
|
|
for (var/X in category_boundaries)
|
|
if (!isnum(X))
|
|
return 1
|
|
if (picker >= X)
|
|
return list_counter + 1
|
|
list_counter--
|
|
|
|
return 1
|
|
|
|
/proc/circular_range(var/atom/A,var/size)
|
|
if (!A || !isnum(size) || size <= 0)
|
|
return list()
|
|
|
|
var/list/turfs = list()
|
|
var/turf/center = get_turf(A)
|
|
|
|
var/corner_range = round(size * 1.5)
|
|
var/total_distance = 0
|
|
var/current_range = 0
|
|
|
|
while (current_range < size - 1)
|
|
current_range++
|
|
total_distance = 0
|
|
for (var/turf/T in range(size,center))
|
|
if (get_dist(T,center) == current_range)
|
|
total_distance = abs(center.x - T.x) + abs(center.y - T.y) + (current_range / 2)
|
|
if (total_distance > corner_range)
|
|
continue
|
|
turfs += T
|
|
|
|
return turfs
|
|
|
|
/proc/get_fraction_of_percentage_and_whole(var/perc,var/whole)
|
|
if (!isnum(perc) || !isnum(whole) || perc == 0 || whole == 0)
|
|
return 0
|
|
return (perc / whole) * 100
|
|
|
|
/proc/get_percentage_of_fraction_and_whole(var/fraction,var/whole)
|
|
if (!isnum(fraction) || !isnum(whole) || fraction == 0 || whole == 0)
|
|
return 0
|
|
return (fraction * 100) / whole
|
|
|
|
/proc/get_whole_of_percentage_and_fraction(var/fraction,var/perc)
|
|
if (!isnum(fraction) || !isnum(perc) || fraction == 0 || perc == 0)
|
|
return 0
|
|
return (100 * fraction) / perc
|
|
|
|
/proc/get_damage_after_percentage_based_armor_reduction(var/armor,var/damage)
|
|
if (!isnum(armor) || !isnum(damage) || damage <= 0)
|
|
return 0
|
|
// [13:22] <volundr> it would be ( (100 - armorpercentage) / 100 ) * damageamount
|
|
armor = max(0,min(armor,100))
|
|
return ((100 - armor) / 100) * damage
|
|
|
|
/proc/get_filtered_atoms_in_touch_range(var/atom/center,var/filter)
|
|
if (!center)
|
|
return list()
|
|
|
|
var/list/list_to_return = list()
|
|
var/target_loc = get_turf(center)
|
|
|
|
for(var/atom/A in range(1,target_loc))
|
|
if (ispath(filter))
|
|
if (istype(A,filter))
|
|
list_to_return += A
|
|
else
|
|
list_to_return += A
|
|
|
|
for(var/atom/B in center.contents)
|
|
if (ispath(filter))
|
|
if (istype(B,filter))
|
|
list_to_return += B
|
|
else
|
|
list_to_return += B
|
|
|
|
return list_to_return
|
|
|
|
/proc/is_valid_color_string(var/string)
|
|
if (!istext(string))
|
|
return 0
|
|
return (findtext(string, color_regex) == 1)
|
|
|
|
/proc/get_digit_from_number(var/number,var/slot)
|
|
// note this works "backwards", so slot 1 of 52964 would be 4, not 5
|
|
if(!isnum(number))
|
|
return 0
|
|
var/string = num2text(number)
|
|
string = reverse_text(string)
|
|
return text2num(copytext(string,slot,slot+1))
|
|
|
|
/**
|
|
* Returns the current timeofday in o'clock format
|
|
*/
|
|
/proc/o_clock_time()
|
|
var/get_hour = text2num(time2text(world.timeofday, "hh"))
|
|
var/final_hour = get_hour
|
|
if (get_hour > 12)
|
|
final_hour = (get_hour - 12)
|
|
|
|
var/get_minutes = text2num(time2text(world.timeofday, "mm"))
|
|
var/final_minutes = "[get_english_num(get_minutes)] minutes past "
|
|
switch (get_minutes)
|
|
if (0)
|
|
final_minutes = ""
|
|
if (1)
|
|
final_minutes = "[get_english_num(get_minutes)] minute past "
|
|
if (15)
|
|
final_minutes = "quarter past "
|
|
if (30)
|
|
final_minutes = "half past "
|
|
if (45)
|
|
if (get_hour > 12)
|
|
final_hour = (get_hour - 11)
|
|
else
|
|
final_hour = (get_hour + 1)
|
|
final_minutes = "quarter 'til "
|
|
|
|
var/the_time = "[final_minutes][get_english_num(final_hour)] o'clock"
|
|
return the_time
|
|
|
|
/proc/antag_token_list() //List of all players redeeming antagonist tokens
|
|
var/list/token_list = list()
|
|
for(var/mob/new_player/player in mobs)
|
|
if((player.client) && (player.ready) && ((player.client.using_antag_token)))
|
|
token_list += player.mind
|
|
if (!token_list.len)
|
|
return 0
|
|
else
|
|
return token_list
|
|
|
|
/proc/strip_bad_characters(var/text)
|
|
var/list/bad_characters = list("_", "'", "\"", "<", ">", ";", "[", "]", "{", "}", "|", "\\", "/")
|
|
for(var/c in bad_characters)
|
|
text = replacetext(text, c, " ")
|
|
return text
|
|
|
|
var/list/english_num = list("0" = "zero", "1" = "one", "2" = "two", "3" = "three", "4" = "four", "5" = "five", "6" = "six", "7" = "seven", "8" = "eight", "9" = "nine",\
|
|
"10" = "ten", "11" = "eleven", "12" = "twelve", "13" = "thirteen", "14" = "fourteen", "15" = "fifteen", "16" = "sixteen", "17" = "seventeen", "18" = "eighteen", "19" = "nineteen",\
|
|
"20" = "twenty", "30" = "thirty", "40" = "forty", "50" = "fifty", "60" = "sixty", "70" = "seventy", "80" = "eighty", "90" = "ninety")
|
|
|
|
/proc/get_english_num(var/num, var/sep) // can only do up to 999,999 because of scientific notation kicking in after 6 digits
|
|
if (!num || !english_num.len)
|
|
return
|
|
|
|
DEBUG_MESSAGE("<b>get_english_num recieves num \"[num]\"</b>")
|
|
|
|
if (istext(num))
|
|
num = text2num(num)
|
|
|
|
var/num_return = null
|
|
|
|
if (num == 0) // 0
|
|
num_return = "[english_num["[num]"]]"
|
|
|
|
else if ((num >= 1) && (num <= 20)) // 1 to 20
|
|
num_return = "[english_num["[num]"]]"
|
|
|
|
else if ((num > 20) && (num < 100)) // 21 to 99
|
|
var/tens = text2num(copytext("[num]", 1, 2)) * 10
|
|
var/ones = text2num(copytext("[num]", 2))
|
|
if (ones <= 0)
|
|
num_return = "[english_num["[tens]"]]"
|
|
else
|
|
num_return = "[english_num["[tens]"]][sep ? sep : " "][english_num["[ones]"]]"
|
|
|
|
else if ((num >= 100) && (num < 1000)) // 100 to 999
|
|
var/hundreds = text2num(copytext("[num]", 1, 2))
|
|
var/tens = text2num(copytext("[num]", 2))
|
|
if (tens <= 0)
|
|
num_return = "[english_num["[hundreds]"]] hundred"
|
|
else
|
|
num_return = "[english_num["[hundreds]"]] hundred and [get_english_num(tens)]"
|
|
|
|
else if ((num >= 1000) && (num < 1000000)) // 1,000 to 999,999
|
|
var/thousands = null
|
|
var/hundreds = null
|
|
|
|
switch (num)
|
|
if (1000 to 9999)
|
|
thousands = text2num(copytext("[num]", 1, 2))
|
|
hundreds = text2num(copytext("[num]", 2))
|
|
if (10000 to 999999)
|
|
thousands = text2num(copytext("[num]", 1, 3))
|
|
hundreds = text2num(copytext("[num]", 3))
|
|
if (100000 to 999999)
|
|
thousands = text2num(copytext("[num]", 1, 4))
|
|
hundreds = text2num(copytext("[num]", 4))
|
|
|
|
if (hundreds <= 0)
|
|
num_return = "[get_english_num(thousands)] thousand"
|
|
else if (hundreds < 100)
|
|
num_return = "[get_english_num(thousands)] thousand and [get_english_num(hundreds)]"
|
|
else
|
|
num_return = "[get_english_num(thousands)] thousand, [get_english_num(hundreds)]"
|
|
|
|
if (num_return)
|
|
DEBUG_MESSAGE("<b>get_english_num returns num \"[num_return]\"</b>")
|
|
return num_return
|
|
|
|
/proc/mutual_attach(var/atom/movable/A as obj|mob, var/atom/movable/B as obj|mob)
|
|
if (!istype(A) || !istype(B))
|
|
return
|
|
if (A.anchored || B.anchored)
|
|
A.anchored = 1
|
|
B.anchored = 1
|
|
|
|
if (!islist(A.attached_objs))
|
|
A.attached_objs = list()
|
|
if (!islist(B.attached_objs))
|
|
B.attached_objs = list()
|
|
|
|
if (!A.attached_objs.Find(B))
|
|
A.attached_objs.Add(B)
|
|
if (!B.attached_objs.Find(A))
|
|
B.attached_objs.Add(A)
|
|
|
|
/proc/mutual_detach(var/atom/movable/A as obj|mob, var/atom/movable/B as obj|mob)
|
|
if (!istype(A) || !istype(B))
|
|
return
|
|
A.anchored = initial(A.anchored)
|
|
B.anchored = initial(B.anchored)
|
|
if (islist(A.attached_objs) && A.attached_objs.Find(B))
|
|
A.attached_objs.Remove(B)
|
|
if (islist(B.attached_objs) && B.attached_objs.Find(A))
|
|
B.attached_objs.Remove(A)
|
|
|
|
// This function counts a passed job.
|
|
proc/countJob(rank)
|
|
var/jobCount = 0
|
|
for(var/mob/H in mobs)
|
|
if(H.mind && H.mind.assigned_role == rank)
|
|
jobCount++
|
|
LAGCHECK(LAG_REALTIME)
|
|
return jobCount
|
|
|
|
//alldirs = list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)
|
|
/atom/proc/letter_overlay(var/letter as text, var/lcolor, var/dir)
|
|
if (!letter) // you get something random you shithead
|
|
letter = pick("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z")
|
|
if (!dir)
|
|
dir = NORTHEAST
|
|
if (!lcolor)
|
|
lcolor = rgb(rand(0,255),rand(0,255),rand(0,255))
|
|
var/image/B = image('icons/effects/letter_overlay.dmi', loc = src, icon_state = "[letter]2")
|
|
var/image/L = image('icons/effects/letter_overlay.dmi', loc = src, icon_state = letter)
|
|
B.color = lcolor
|
|
var/px = 0
|
|
var/py = 0
|
|
|
|
if (dir & (EAST | WEST))
|
|
px = 11
|
|
if (dir & WEST)
|
|
px *= -1
|
|
|
|
if (dir & (NORTH | SOUTH))
|
|
py = 11
|
|
if (dir & SOUTH)
|
|
py *= -1
|
|
|
|
B.pixel_x = px
|
|
L.pixel_x = px
|
|
|
|
B.pixel_y = py
|
|
L.pixel_y = py
|
|
|
|
src.overlays += B
|
|
src.overlays += L
|
|
return
|
|
|
|
/atom/proc/debug_loverlay()
|
|
var/letter = input(usr, "Please select a letter icon to display.", "Select Letter", "A") as null|anything in list("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z")
|
|
if (!letter)
|
|
return
|
|
var/lcolor = input(usr, "Please enter a color for the icon.", "Input Color", "#FFFFFF") as null|text
|
|
if (!lcolor)
|
|
return
|
|
var/dir = input(usr, "Please select a direction for the icon to display.", "Select Direction", "NORTHEAST") as null|anything in list("NORTH", "SOUTH", "EAST", "WEST", "NORTHEAST", "NORTHWEST", "SOUTHEAST", "SOUTHWEST")
|
|
if (!dir)
|
|
return
|
|
src.letter_overlay(letter, lcolor, text2dir(dir))
|
|
|
|
// Returns a list of eligible dead players to be respawned as an antagonist or whatever (Convair880).
|
|
// Text messages: 1: alert | 2: alert (chatbox) | 3: alert acknowledged (chatbox) | 4: no longer eligible (chatbox) | 5: waited too long (chatbox)
|
|
/proc/dead_player_list(var/return_minds = 0, var/confirmation_spawn = 0, var/list/text_messages = list())
|
|
var/list/candidates = list()
|
|
|
|
// Confirmation delay specified, so prompt eligible dead mobs and wait for response.
|
|
if (confirmation_spawn > 0)
|
|
var/ghost_timestamp = world.time
|
|
|
|
// Preliminary work.
|
|
var/text_alert = "Would you like to be respawned? Your name will be added to the list of eligible candidates and may be selected at random by the game."
|
|
var/text_chat_alert = "You are eligible to be respawned. You have [confirmation_spawn / 10] seconds to respond to the offer."
|
|
var/text_chat_added = "You have been added to the list of eligible candidates. The game will pick a player soon. Good luck!"
|
|
var/text_chat_failed = "You are no longer eligible for the offer."
|
|
var/text_chat_toolate = "You have waited too long to respond to the offer."
|
|
|
|
if (text_messages.len)
|
|
if (text_messages.len >= 1) text_alert = text_messages[1]
|
|
if (text_messages.len >= 2) text_chat_alert = text_messages[2]
|
|
if (text_messages.len >= 3) text_chat_added = text_messages[3]
|
|
if (text_messages.len >= 4) text_chat_failed = text_messages[4]
|
|
if (text_messages.len >= 5) text_chat_toolate = text_messages[5]
|
|
|
|
text_alert = strip_html(text_alert, MAX_MESSAGE_LEN, 1)
|
|
text_chat_alert = "<span style=\"color:blue\"><h3>[strip_html(text_chat_alert, MAX_MESSAGE_LEN)]</h3></span>"
|
|
text_chat_added = "<span style=\"color:blue\"><h3>[strip_html(text_chat_added, MAX_MESSAGE_LEN)]</h3></span>"
|
|
text_chat_failed = "<span style=\"color:red\"><b>[strip_html(text_chat_failed, MAX_MESSAGE_LEN)]</b></span>"
|
|
text_chat_toolate = "<span style=\"color:red\"><b>[strip_html(text_chat_toolate, MAX_MESSAGE_LEN)]</b></span>"
|
|
|
|
// Run prompts. Minds are preferable to mob references because of the confirmation delay.
|
|
for (var/datum/mind/M in ticker.minds)
|
|
if (M.current)
|
|
if (dead_player_list_helper(M.current) != 1)
|
|
continue
|
|
|
|
SPAWN_DBG (0) // Don't lock up the entire proc.
|
|
M.current << csound("sound/misc/lawnotify.ogg")
|
|
boutput(M.current, text_chat_alert)
|
|
|
|
if (alert(M.current, text_alert, "Respawn", "Yes", "No") == "Yes")
|
|
if (ghost_timestamp && world.time > ghost_timestamp + confirmation_spawn)
|
|
if (M.current) boutput(M.current, text_chat_toolate)
|
|
return
|
|
if (dead_player_list_helper(M.current) != 1)
|
|
if (M.current) boutput(M.current, text_chat_failed)
|
|
return
|
|
|
|
if (M.current && !(M in candidates))
|
|
boutput(M.current, text_chat_added)
|
|
candidates.Add(M)
|
|
else
|
|
return
|
|
|
|
while (ghost_timestamp && world.time < ghost_timestamp + confirmation_spawn)
|
|
sleep (300)
|
|
|
|
// Filter list again.
|
|
if (candidates.len)
|
|
for (var/datum/mind/M2 in candidates)
|
|
if (!M2.current || !ismob(M2.current) || dead_player_list_helper(M2.current) != 1)
|
|
candidates.Remove(M2)
|
|
continue
|
|
|
|
if (candidates.len)
|
|
if (return_minds == 1)
|
|
return candidates
|
|
else
|
|
var/list/mob/mobs = list()
|
|
for (var/datum/mind/M3 in candidates)
|
|
if (M3.current && ismob(M3.current))
|
|
if (!(M3.current in mobs))
|
|
mobs.Add(M3.current)
|
|
|
|
return mobs
|
|
else
|
|
return list()
|
|
else
|
|
return list()
|
|
|
|
// Confirmationd delay not specified, return list right away.
|
|
candidates = list()
|
|
|
|
for (var/mob/O in mobs)
|
|
if (dead_player_list_helper(O) != 1)
|
|
continue
|
|
if (!(O in candidates))
|
|
candidates.Add(O)
|
|
|
|
if (return_minds == 1)
|
|
var/list/datum/mind/minds = list()
|
|
for (var/mob/M2 in candidates)
|
|
if (M2.mind && !(M2.mind in minds))
|
|
minds.Add(M2.mind)
|
|
|
|
return minds
|
|
|
|
return candidates
|
|
|
|
// So there aren't multiple instances of C&P code (Convair880).
|
|
/proc/dead_player_list_helper(var/mob/G, var/allow_dead_antags = 0)
|
|
if (!G || !ismob(G))
|
|
return 0
|
|
if (!isobserver(G) && !(isliving(G) && isdead(G))) // if (NOT /mob/dead) AND NOT (/mob/living AND dead)
|
|
return 0
|
|
if (istype(G, /mob/new_player) || G.respawning)
|
|
return 0
|
|
if (jobban_isbanned(G, "Syndicate"))
|
|
return 0
|
|
if (jobban_isbanned(G, "Special Respawn"))
|
|
return 0
|
|
|
|
if (isobserver(G))
|
|
var/mob/dead/observer/the_ghost = null
|
|
|
|
if (istype(G, /mob/dead/observer))
|
|
the_ghost = G
|
|
|
|
if (istype(G, /mob/dead/target_observer))
|
|
var/mob/dead/target_observer/TO = G
|
|
if (TO.my_ghost && istype(TO.my_ghost, /mob/dead/observer))
|
|
the_ghost = TO.my_ghost
|
|
|
|
if (!the_ghost || !isobserver(the_ghost) || !isdead(the_ghost) || the_ghost.observe_round)
|
|
return 0
|
|
|
|
if (!G.client || G.client && G.client.suicide) // Suicided? Tough luck.
|
|
return 0
|
|
if (!allow_dead_antags && (!G.mind || G.mind && (G.mind.dnr || !isnull(G.mind.special_role) || G.mind.former_antagonist_roles.len))) // Dead antagonists have had their chance.
|
|
return 0
|
|
|
|
return 1
|
|
|
|
/proc/check_target_immunity(var/atom/target, var/ignore_everything_but_nodamage = 0, var/atom/source = 0)
|
|
var/is_immune = 0
|
|
|
|
var/area/a = get_area( target )
|
|
if( a && a.sanctuary )
|
|
return 1
|
|
|
|
if (isliving(target))
|
|
var/mob/living/L = target
|
|
|
|
if (!isdead(L))
|
|
if (ignore_everything_but_nodamage == 1)
|
|
if (L.nodamage)
|
|
is_immune = 1
|
|
else
|
|
if (L.nodamage || L.spellshield)
|
|
is_immune = 1
|
|
|
|
if (source && istype(source,/obj/projectile) && ishuman(target))
|
|
var/mob/living/carbon/human/H = target
|
|
if(H.stance == "dodge") //matrix dodge flip
|
|
is_immune = 1
|
|
|
|
//if (is_immune == 1)
|
|
// DEBUG_MESSAGE("[L] is immune to damage, aborting.")
|
|
|
|
return is_immune
|
|
|
|
// Their antag status is revoked on death/implant removal/expiration, but we still want them to show up in the game over stats (Convair880).
|
|
/proc/remove_mindslave_status(var/mob/M, var/slave_type ="", var/removal_type ="")
|
|
if (!M || !M.mind || !slave_type || !removal_type)
|
|
return
|
|
|
|
// Find our master's mob reference (if any).
|
|
var/mob/mymaster = whois_ckey_to_mob_reference(M.mind.master)
|
|
|
|
switch (slave_type)
|
|
if ("mslave")
|
|
switch (removal_type)
|
|
if ("expired")
|
|
logTheThing("combat", M, mymaster, "'s mindslave implant (implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has worn off.")
|
|
if ("surgery")
|
|
logTheThing("combat", M, mymaster, "'s mindslave implant (implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) was removed surgically.")
|
|
if ("override")
|
|
logTheThing("combat", M, mymaster, "'s mindslave implant (implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) was overridden by a different implant.")
|
|
if ("death")
|
|
logTheThing("combat", M, mymaster, "(implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has died, removing mindslave status.")
|
|
else
|
|
logTheThing("combat", M, mymaster, "'s mindslave implant (implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has vanished mysteriously.")
|
|
|
|
remove_antag(M, null, 1, 0)
|
|
if (M.mind && ticker.mode && !(M.mind in ticker.mode.former_antagonists))
|
|
if (!M.mind.former_antagonist_roles.Find("mindslave"))
|
|
M.mind.former_antagonist_roles.Add("mindslave")
|
|
ticker.mode.former_antagonists += M.mind
|
|
|
|
if ("vthrall")
|
|
switch (removal_type)
|
|
if ("death")
|
|
logTheThing("combat", M, mymaster, "(enthralled by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has died, removing vampire thrall status.")
|
|
else
|
|
logTheThing("combat", M, mymaster, "(enthralled by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has been freed mysteriously, removing vampire thrall status.")
|
|
|
|
remove_antag(M, null, 1, 0)
|
|
if (M.mind && ticker.mode && !(M.mind in ticker.mode.former_antagonists))
|
|
if (!M.mind.former_antagonist_roles.Find("vampthrall"))
|
|
M.mind.former_antagonist_roles.Add("vampthrall")
|
|
ticker.mode.former_antagonists += M.mind
|
|
|
|
// This is only used for spy slaves and mindslaved antagonists at the moment.
|
|
if ("otherslave")
|
|
switch (removal_type)
|
|
if ("expired")
|
|
logTheThing("combat", M, mymaster, "'s mindslave implant (implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has worn off.")
|
|
if ("surgery")
|
|
logTheThing("combat", M, mymaster, "'s mindslave implant (implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) was removed surgically.")
|
|
if ("override")
|
|
logTheThing("combat", M, mymaster, "'s mindslave implant (implanted by [mymaster ? "%target%" : "*NOKEYFOUND*"]) was overridden by a different implant.")
|
|
if ("death")
|
|
logTheThing("combat", M, mymaster, "(enslaved by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has died, removing mindslave status.")
|
|
else
|
|
logTheThing("combat", M, mymaster, "(enslaved by [mymaster ? "%target%" : "*NOKEYFOUND*"]) has been freed mysteriously, removing mindslave status.")
|
|
|
|
// Fix for mindslaved traitors etc losing their antagonist status.
|
|
if (M.mind && (M.mind.special_role == "spyslave"))
|
|
remove_antag(M, null, 1, 0)
|
|
else
|
|
M.mind.master = null
|
|
if (M.mind && ticker.mode && !(M.mind in ticker.mode.former_antagonists))
|
|
if (!M.mind.former_antagonist_roles.Find("mindslave"))
|
|
M.mind.former_antagonist_roles.Add("mindslave")
|
|
ticker.mode.former_antagonists += M.mind
|
|
|
|
else
|
|
logTheThing("debug", M, null, "<b>Convair880</b>: [M] isn't a mindslave or vampire thrall, can't remove mindslave status.")
|
|
return
|
|
|
|
if (removal_type == "death")
|
|
boutput(M, "<h2><span style=\"color:red\">Since you have died, you are no longer a mindslave! Do not obey your former master's orders even if you've been brought back to life somehow.</span></h2>")
|
|
SHOW_MINDSLAVE_DEATH_TIPS(M)
|
|
else if (removal_type == "override")
|
|
boutput(M, "<h2><span style=\"color:red\">Your mindslave implant has been overridden by a new one, cancelling out your former allegiances!</span></h2>")
|
|
SHOW_MINDSLAVE_OVERRIDE_TIPS(M)
|
|
else
|
|
boutput(M, "<h2><span style=\"color:red\">Your mind is your own again! You no longer feel the need to obey your former master's orders.</span></h2>")
|
|
SHOW_MINDSLAVE_EXPIRED_TIPS(M)
|
|
|
|
return
|
|
|
|
/**
|
|
* Looks up a player based on a string. Searches a shit load of things ~whoa~. Returns a list of mob refs.
|
|
*/
|
|
/proc/whois(target, limit = 0, admin)
|
|
target = trim(lowertext(target))
|
|
if (!target) return 0
|
|
var/list/found = list()
|
|
for (var/mob/M in mobs)
|
|
if (M.ckey && (limit == 0 || found.len < limit))
|
|
if (findtext(M.real_name, target))
|
|
found += M
|
|
else if (findtext(M.ckey, target))
|
|
found += M
|
|
else if (findtext(M.key, target))
|
|
found += M
|
|
else if (M.mind)
|
|
if (findtext(M.mind.assigned_role, target))
|
|
if (M.mind.assigned_role == "MODE") //We matched on the internal MODE job this doesn't fuckin' count
|
|
continue
|
|
else
|
|
found += M
|
|
else if (findtext(M.mind.special_role, target))
|
|
found += M
|
|
|
|
if (found.len > 0)
|
|
return found
|
|
else
|
|
return 0
|
|
|
|
/**
|
|
* A universal ckey -> mob reference lookup proc, adapted from whois() (Convair880).
|
|
*/
|
|
/proc/whois_ckey_to_mob_reference(target as text)
|
|
if (!target || isnull(target))
|
|
return 0
|
|
target = lowertext(target)
|
|
var/mob/our_mob
|
|
for (var/mob/M in mobs)
|
|
if ((!isnull(M.ckey) && !isnull(target)) && findtext(M.ckey, target))
|
|
//DEBUG_MESSAGE("Whois: match found for [target], it's [M].")
|
|
our_mob = M
|
|
break
|
|
if (our_mob) return our_mob
|
|
else return 0
|
|
|
|
/**
|
|
* Returns random hex value of length given
|
|
*/
|
|
/proc/random_hex(var/digits as num)
|
|
if (!digits)
|
|
digits = 6
|
|
var/return_hex = ""
|
|
for (var/i = 0, i < digits, i++)
|
|
return_hex += pick(hex_chars)
|
|
return return_hex
|
|
|
|
//A global cooldown on this so it doesnt destroy the external server
|
|
var/global/nextDectalkDelay = 5 //seconds
|
|
var/global/lastDectalkUse = 0
|
|
/proc/dectalk(msg)
|
|
if (!msg) return 0
|
|
if (world.timeofday > (lastDectalkUse + (nextDectalkDelay * 10)))
|
|
lastDectalkUse = world.timeofday
|
|
msg = copytext(msg, 1, 2000)
|
|
var/res[] = world.Export("http://spacebee.goonhub.com/api/tts?dectalk=[url_encode(msg)]&api_key=[url_encode(ircbot.apikey)]")
|
|
if (!res || !res["CONTENT"])
|
|
return 0
|
|
|
|
var/audio = file2text(res["CONTENT"])
|
|
return list("audio" = audio, "message" = msg)
|
|
else
|
|
return list("cooldown" = 1)
|
|
|
|
proc/copy_datum_vars(var/atom/from, var/atom/target)
|
|
if (!target || !from) return
|
|
for(var/V in from.vars)
|
|
if (!issaved(from.vars[V]))
|
|
continue
|
|
|
|
if(V == "type") continue
|
|
if(V == "parent_type") continue
|
|
if(V == "vars") continue
|
|
target.vars[V] = from.vars[V]
|
|
|
|
/**
|
|
* Given hex color, returns string name of nearest named color
|
|
*/
|
|
/proc/hex2color_name(var/hex)
|
|
if (!hex)
|
|
return
|
|
var/adj = 0
|
|
if (copytext(hex, 1, 2) == "#")
|
|
adj = 1
|
|
|
|
var/hR = hex2num(copytext(hex, 1 + adj, 3 + adj))
|
|
var/hG = hex2num(copytext(hex, 3 + adj, 5 + adj))
|
|
var/hB = hex2num(copytext(hex, 5 + adj, 7 + adj))
|
|
|
|
var/datum/color/C = new(hR, hG, hB, 0)
|
|
var/name = get_nearest_color(C)
|
|
if (name)
|
|
return name
|
|
|
|
var/list/uppercase_letters = list("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z")
|
|
var/list/lowercase_letters = list("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z")
|
|
|
|
// Helper for blob, wraiths and whoever else might need them (Convair880).
|
|
/proc/restricted_z_allowed(var/mob/M, var/T)
|
|
var/list/allowed = list()
|
|
for (var/areas in typesof(/area/shuttle/escape))
|
|
if (!allowed.Find(areas))
|
|
allowed.Add(areas)
|
|
for (var/areas2 in typesof(/area/shuttle_transit_space))
|
|
if (!allowed.Find(areas2))
|
|
allowed.Add(areas2)
|
|
|
|
if (M && isblob(M))
|
|
var/mob/living/intangible/blob_overmind/B = M
|
|
if (B.tutorial)
|
|
return 1
|
|
|
|
var/area/A
|
|
if (T && istype(T, /area))
|
|
A = T
|
|
else if (T && isturf(T))
|
|
A = get_area(T)
|
|
|
|
if (A && istype(A) && (A.type in allowed))
|
|
return 1
|
|
return 0
|
|
|
|
/**
|
|
* Given center turf/atom, range, and list of things to smash, will damage said objects within range of center.
|
|
* Used for sonic grenades and similar. Avoiding C&P Code.
|
|
*/
|
|
/proc/sonic_attack_environmental_effect(var/center, var/range, var/list/smash)
|
|
if (!center || !isnum(range) || range <= 0)
|
|
return 0
|
|
|
|
if (!islist(smash) || !smash.len)
|
|
return 0
|
|
|
|
var/turf/CT
|
|
if (isturf(center))
|
|
CT = center
|
|
else if (istype(center, /atom))
|
|
CT = get_turf(center)
|
|
|
|
if (!(CT && isturf(CT)))
|
|
return 0
|
|
|
|
// No visible_messages here because of text spam. The station has a lot of windows and light fixtures.
|
|
// And once again, view() proved quite unreliable.
|
|
for (var/S in smash)
|
|
if (S == "window" || S == "r_window")
|
|
for (var/obj/window/W in view(CT, range))
|
|
if (prob(get_dist(W, CT) * 6))
|
|
continue
|
|
//W.health = 0
|
|
//W.smash()
|
|
W.damage_blunt(125,1)
|
|
|
|
if (S == "light")
|
|
for (var/obj/machinery/light/L in view(CT, range))
|
|
L.broken()
|
|
|
|
if (S == "displaycase")
|
|
for (var/obj/displaycase/D in view(CT, range))
|
|
D.ex_act(1)
|
|
|
|
if (S == "glassware")
|
|
for (var/obj/item/reagent_containers/glass/G in view(CT, range))
|
|
G.smash()
|
|
for (var/obj/item/reagent_containers/food/drinks/drinkingglass/G2 in range(CT, range))
|
|
G2.smash()
|
|
|
|
return 1
|
|
|
|
/**
|
|
* Returns hud style preferences of given client/mob
|
|
*/
|
|
/proc/get_hud_style(var/someone)
|
|
if (!someone)
|
|
return
|
|
var/client/C = null
|
|
if (isclient(someone))
|
|
C = someone
|
|
else if (ismob(someone))
|
|
var/mob/M = someone
|
|
if (M.client)
|
|
C = M.client
|
|
if (!C || !C.preferences)
|
|
return
|
|
return C.preferences.hud_style
|
|
|
|
/**
|
|
* Returns list of all mobs within an atom. Not cheap! (unlike ur mum)
|
|
*/
|
|
/proc/get_all_mobs_in(var/atom/what)
|
|
var/list/ret = list()
|
|
if(ismob( what ))
|
|
ret[++ret.len] = what
|
|
for(var/atom/thing in what)
|
|
var/list/outp = get_all_mobs_in(thing)
|
|
ret.len += outp.len
|
|
ret += outp
|
|
return ret
|
|
|
|
/**
|
|
* Given user, will proompt user to select skin color from list (or custom) and returns skin tone after blending
|
|
*/
|
|
/proc/get_standard_skintone(var/mob/user)
|
|
var/new_tone = input(user, "Please select skin color.", "Character Generation") as null|anything in standard_skintones + list("Custom...")
|
|
if (new_tone == "Custom...")
|
|
var/tone = input(user, "Please select skin tone level: 1-220 (1=albino, 35=caucasian, 150=black, 220='very' black)", "Skin tone picker") as null|num
|
|
if (!isnull(tone))
|
|
tone = 35 - min(max(tone, 1), 220)
|
|
//new_tone = rgb(220 + tone, 220 + tone, 220 + tone)
|
|
new_tone = blend_skintone(tone,tone,tone)
|
|
else
|
|
return null
|
|
else if (new_tone)
|
|
new_tone = standard_skintones[new_tone]
|
|
|
|
return new_tone
|
|
|
|
/**
|
|
* Blends given rgb values with old human mob skin color (#ffca95) to return a new rgb value
|
|
*/
|
|
/proc/blend_skintone(var/r1, var/g1, var/b1)
|
|
//I expect we will only need to darken the already pale white image.
|
|
var/r = min(r1 + 255, 255) //ff
|
|
var/g = min(g1 + 202, 255) //ca
|
|
var/b = min(b1 + 149, 255) //95
|
|
return rgb(r,g,b)
|
|
|
|
/**
|
|
* Returns a string based on the current job and antag role of the mob e.g. Staff Assistant [Traitor]
|
|
*/
|
|
/proc/getRole(var/mob/M, strip = 0)
|
|
if (!M || !istype(M)) return
|
|
|
|
var/role
|
|
if (istype(M, /mob/living/carbon/cube/meat/krampus))
|
|
role += "Krampus"
|
|
if (M.mind)
|
|
if (M.mind.assigned_role == "MODE")
|
|
if (M.job)
|
|
role += M.job
|
|
else
|
|
role += M.mind.assigned_role
|
|
|
|
if (M.mind.special_role)
|
|
var/special = uppertext(copytext(M.mind.special_role, 1, 2)) + copytext(M.mind.special_role, 2)
|
|
if (!strip)
|
|
special = "<span style='color: red;'>[special]</span>"
|
|
|
|
role += " \[[special]]"
|
|
|
|
else
|
|
role += M.job
|
|
|
|
return role
|
|
|
|
var/regex/nameRegex = regex("\\xFF.","g")
|
|
/proc/strip_special(var/text)
|
|
return nameRegex.Replace( "[text]", "" )
|
|
|
|
|
|
/**
|
|
* Removes the special data inserted via use of \improper etc in strings
|
|
*/
|
|
/proc/stripTextMacros(text)
|
|
if (findtext(text, "\improper"))
|
|
text = replacetext(text, "\improper", "")
|
|
if (findtext(text, "\proper"))
|
|
text = replacetext(text, "\proper", "")
|
|
|
|
return text
|
|
|
|
/**
|
|
* Returns true if given string is just space characters
|
|
*/
|
|
/proc/is_blank_string(var/txt)
|
|
if (!istext(txt))
|
|
return 1 // if it's not a string I guess it's kinda blank??
|
|
for (var/i=1, i <= length(txt), i++)
|
|
if (copytext(txt, i, i+1) != " ")
|
|
return 0 // we say NAW GURL IT'S GOT OTHER STUFF TOO,
|
|
|
|
return 1 // otherwise YEAH GURL THAT SHIT IS HELLA BLANK
|
|
|
|
/**
|
|
* Returns true if given mob/client/mind is an admin
|
|
*/
|
|
/proc/isadmin(person)
|
|
if (ismob(person))
|
|
var/mob/M = person
|
|
return M.client ? M.client.holder ? TRUE : FALSE : FALSE
|
|
|
|
else if (isclient(person))
|
|
var/client/C = person
|
|
return C.holder ? TRUE : FALSE
|
|
|
|
else if (ismind(person))
|
|
var/datum/mind/M = person
|
|
return M.current ? M.current.client ? M.current.client.holder ? TRUE : FALSE : FALSE : FALSE
|
|
|
|
return FALSE
|
|
|
|
/**
|
|
* Returns span with a color gradient between two given colors of given message
|
|
*/
|
|
proc/gradientText(var/color1, var/color2, message)
|
|
var/color1hex = hex2num(copytext(color1, 2))
|
|
var/color2hex = hex2num(copytext(color2, 2))
|
|
var/r1 = (color1hex >> 16) & 0xFF
|
|
var/g1 = (color1hex >> 8) & 0xFF
|
|
var/b1 = color1hex & 0xFF
|
|
var/dr = ((color2hex >> 16) & 0xFF)- r1
|
|
var/dg = ((color2hex >> 8) & 0xFF) - g1
|
|
var/db = (color2hex & 0xFF) - b1
|
|
var/list/result = new/list()
|
|
var/n = rand(0,10)/10.0 // what a shitty name for a variable
|
|
var/dir = prob(50) ? -1 : 1
|
|
for(var/i=1, i<=length(message), i += 3)
|
|
n += dir * 0.2
|
|
if(prob(20))
|
|
dir = dir/abs(dir) * -1
|
|
if(n < 0)
|
|
n = 0
|
|
dir = 1
|
|
if(n > 1)
|
|
n = 1
|
|
dir = -1
|
|
var/col = rgb(r1 + dr*n, g1 + dg*n, b1 + db*n)
|
|
var/chars = copytext(message, i, i+3)
|
|
result += "<span style='color:[col]'>[chars]</span>"
|
|
. = result.Join()
|
|
|
|
/**
|
|
* Returns given text replaced by nonsense chars, on a 40% or given % basis
|
|
*/
|
|
proc/radioGarbleText(var/message, var/per_letter_corruption_chance=40)
|
|
var/list/corruptedChars = list("@","#","!",",",".","-","=","/","\\","'","\"","`","*","(",")","[","]","_","&")
|
|
. = ""
|
|
for(var/i=1 to length(message))
|
|
if(prob(per_letter_corruption_chance))
|
|
// corrupt that letter
|
|
. += pick(corruptedChars)
|
|
else
|
|
. += copytext(message, i, i+1)
|
|
|
|
/**
|
|
* Returns given text replaced entirely by nonsense chars
|
|
*/
|
|
proc/illiterateGarbleText(var/message)
|
|
. = radioGarbleText(message, 100)
|
|
|
|
/**
|
|
* Returns the time in seconds since a given timestamp
|
|
*/
|
|
proc/getTimeInSecondsSinceTime(var/timestamp)
|
|
var/time_of_day = world.timeofday + ((world.timeofday < timestamp) ? 864000 : 0) // Offset the time of day in case of midnight rollover
|
|
var/time_elapsed = (time_of_day - timestamp)/10
|
|
return time_elapsed
|
|
|
|
/**
|
|
* Handles the two states icon_size can be in: basic number, or string in WxH format
|
|
*/
|
|
proc/getIconSize()
|
|
if (istext(world.icon_size))
|
|
var/list/iconSizes = splittext(world.icon_size, "x")
|
|
return list("width" = text2num(iconSizes[1]), "height" = text2num(iconSizes[2]))
|
|
|
|
return world.icon_size
|
|
|
|
/**
|
|
* Finds a client by ckey, throws exception if not found
|
|
*/
|
|
proc/getClientFromCkey(ckey)
|
|
var/client/C
|
|
for (var/client/LC in clients)
|
|
if (LC.ckey == ckey)
|
|
C = LC
|
|
break
|
|
|
|
if (!C)
|
|
throw EXCEPTION("Client not found")
|
|
|
|
return C
|
|
|
|
/**
|
|
* Returns true if a given atom is within a given holder's contents
|
|
*/
|
|
/atom/proc/isInContents(var/atom/item, var/atom/holder)
|
|
//boutput(holder.contents)
|
|
for(var/atom/content in holder.contents)
|
|
if(content == item)
|
|
return true
|
|
if(isInContents(item,content))
|
|
return true
|
|
return false
|
|
|
|
/**
|
|
* Returns the vector magnitude of an x value and a y value
|
|
*/
|
|
proc/vector_magnitude(x,y)
|
|
//can early out
|
|
.= sqrt(x*x + y*y);
|
|
|
|
/**
|
|
* Transforms a supplied vector x & y to a direction
|
|
*/
|
|
proc/vector_to_dir(x,y)
|
|
.= angle_to_dir(atan2(y,x))
|
|
|
|
/**
|
|
* Transforms a given angle to a cardinal/ordinal direction
|
|
*/
|
|
proc/angle_to_dir(angle)
|
|
.= 0
|
|
if (angle >= 360)
|
|
return angle_to_dir(angle-360)
|
|
if (angle >= 0)
|
|
if (angle < 22.5)
|
|
.= NORTH
|
|
else if (angle <= 67.5)
|
|
.= NORTHEAST
|
|
else if (angle < 112.5)
|
|
.= EAST
|
|
else if (angle <= 157.5)
|
|
.= SOUTHEAST
|
|
else
|
|
.= SOUTH
|
|
else if (angle < 0)
|
|
if (angle > -22.5)
|
|
.= NORTH
|
|
else if (angle >= -67.5)
|
|
.= NORTHWEST
|
|
else if (angle > -112.5)
|
|
.= WEST
|
|
else if (angle >= -157.5)
|
|
.= SOUTHWEST
|
|
else
|
|
.= SOUTH
|
|
|
|
/**
|
|
* Transforms a supplied vector x & y to a direction
|
|
* user: the mob that adds a reagent to an atom that has a reagent whitelist
|
|
* TA: Target Atom. The thing that the user is adding the reagent to
|
|
*/
|
|
|
|
proc/check_whitelist(var/atom/TA, var/list/whitelist, var/mob/user as mob)
|
|
if (!whitelist || (!TA || !TA.reagents) || (islist(whitelist) && !whitelist.len))
|
|
return
|
|
|
|
var/found = 0
|
|
for (var/reagent_id in TA.reagents.reagent_list)
|
|
if (!whitelist.Find(reagent_id))
|
|
TA.reagents.del_reagent(reagent_id)
|
|
found = 1
|
|
if (found)
|
|
if (user)
|
|
user.show_text("[TA] identifies and removes a harmful substance.", "red") // haine: done -> //TODO: using usr in procs is evil shame on you
|
|
else if (ismob(TA.loc))
|
|
var/mob/M = TA.loc
|
|
M.show_text("[TA] identifies and removes a harmful substance.", "red")
|
|
else
|
|
TA.visible_message("<span style=\"color:red\">[src] identifies and removes a harmful substance.</span>")
|
|
|
|
|
|
/proc/in_cone_of_vision(var/atom/seer, var/atom/target)
|
|
/*
|
|
This proc checks if one atom is in the cone of vision of
|
|
another one. It uses the following map grid for the check,
|
|
where each point is an integer coordinate and the seer is
|
|
at point X:
|
|
*
|
|
* *
|
|
POV -> X * * *
|
|
* *
|
|
*
|
|
A '*' represents a point that is within X's FOV
|
|
*/
|
|
var/dir = get_dir(seer, target)
|
|
switch(dir)
|
|
if(NORTHEAST, SOUTHWEST)
|
|
var/abs_x = abs(target.x - seer.x)
|
|
var/abs_y = abs(target.y - seer.y)
|
|
|
|
if (abs_y > abs_x)
|
|
dir = turn(dir, 45)
|
|
else if (abs_x > abs_y)
|
|
dir = turn(dir, -45)
|
|
|
|
if(NORTHWEST, SOUTHEAST)
|
|
var/abs_x = abs(target.x - seer.x)
|
|
var/abs_y = abs(target.y - seer.y)
|
|
if (abs_y > abs_x)
|
|
dir = turn(dir, -45)
|
|
else if (abs_x > abs_y)
|
|
dir = turn(dir, 45)
|
|
|
|
return (seer.dir == dir)
|
|
|
|
|
|
|
|
/proc/lerp(var/a, var/b, var/t)
|
|
return a * (1 - t) + b * t
|
|
|
|
proc/get_manhattan_dist(atom/A,atom/B)
|
|
.= abs(A.x - B.x) + abs(A.y - B.y) |