Files
Yogstation/code/__HELPERS/unsorted.dm
2024-07-10 15:37:04 -05:00

1293 lines
40 KiB
Plaintext

/*
* A large number of misc global procs.
*/
//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!")
else if(length_char(HTMLstring) != 7)
CRASH("Given non-hex symbols in argument!")
var/textr = copytext(HTMLstring, 2, 4)
var/textg = copytext(HTMLstring, 4, 6)
var/textb = copytext(HTMLstring, 6, 8)
return rgb(255 - hex2num(textr), 255 - hex2num(textg), 255 - hex2num(textb))
/proc/Get_Angle(atom/movable/start,atom/movable/end)//For beams.
if(!start || !end)
return 0
var/dy
var/dx
dy=(32*end.y+end.pixel_y)-(32*start.y+start.pixel_y)
dx=(32*end.x+end.pixel_x)-(32*start.x+start.pixel_x)
if(!dy)
return (dx>=0)?90:270
.=arctan(dx/dy)
if(dy<0)
.+=180
else if(dx<0)
.+=360
/proc/Get_Pixel_Angle(y, x)//for getting the angle when animating something's pixel_x and pixel_y
if(!y)
return (x >= 0) ? 90 : 270
. = arctan(x / y)
if(y < 0)
. += 180
else if(x < 0)
. += 360
/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
//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 FALSE
var/i, ch, len = length(key)
for (i = 7, i <= len, ++i) //we know the first 6 chars are Guest-
ch = text2ascii(key, i)
if (ch < 48 || ch > 57) //0-9
return FALSE
return TRUE
//Generalised helper proc for letting mobs rename themselves. Used to be clname() and ainame()
/mob/proc/apply_pref_name(preference_type, client/C)
if(!C)
C = client
var/oldname = real_name
var/newname
var/loop = 1
var/safety = 0
var/banned = C ? is_banned_from(C.ckey, "Appearance") : null
while(loop && safety < 5)
if(!safety && !banned)
newname = C?.prefs?.read_preference(preference_type)
else
var/datum/preference/preference = GLOB.preference_entries[preference_type]
newname = preference.create_informed_default_value(C.prefs)
for(var/mob/living/M in GLOB.player_list)
if(M == src)
continue
if(!newname || M.real_name == newname)
newname = null
loop++ // name is already taken so we roll again
break
loop--
safety++
if(newname)
fully_replace_character_name(oldname,newname)
return TRUE
return FALSE
//Picks a string of symbols to display as the law number for hacked or ion laws
/proc/ionnum()
return "[pick("!","@","#","$","%","^","&")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")]"
//Returns a list of unslaved cyborgs
/proc/active_free_borgs()
. = list()
for(var/mob/living/silicon/robot/R in GLOB.alive_mob_list)
if(R.connected_ai || R.shell)
continue
if(R.stat == DEAD)
continue
if(R.emagged || R.scrambledcodes)
continue
. += R
//Returns a list of AI's
/proc/active_ais(check_mind=0)
. = list()
for(var/mob/living/silicon/ai/A in GLOB.alive_mob_list)
if(A.stat == DEAD)
continue
if(A.control_disabled)
continue
if(check_mind)
if(!A.mind)
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_free_borg(mob/user)
var/list/borgs = active_free_borgs()
if(borgs.len)
if(user)
. = input(user,"Unshackled cyborg signals detected:", "Cyborg Selection", borgs[1]) in sortList(borgs)
else
. = pick(borgs)
return .
/proc/select_active_ai(mob/user)
var/list/ais = active_ais()
if(ais.len)
if(user)
. = input(user,"AI signals detected:", "AI Selection", ais[1]) in sortList(ais)
else
. = pick(ais)
return .
//Returns a list of all items of interest with their name
/proc/getpois(mobs_only = FALSE, skip_mindless = FALSE, specify_dead_role = TRUE)
var/list/mobs = sortmobs()
var/list/namecounts = list()
var/list/pois = list()
for(var/mob/M in mobs)
if(skip_mindless && (!M.mind && !M.ckey))
if(!isbot(M) && !iscameramob(M) && !ismegafauna(M))
continue
if(M.client && M.client.holder && M.client.holder.fakekey) //stealthmins
continue
var/name = avoid_assoc_duplicate_keys(M.name, namecounts)
if(M.real_name && M.real_name != M.name)
name += " \[[M.real_name]\]"
if(M.stat == DEAD && specify_dead_role)
if(isobserver(M))
name += " \[ghost\]"
else
name += " \[dead\]"
pois[name] = M
if(!mobs_only)
for(var/atom/A in GLOB.poi_list)
if(!A || !A.loc)
continue
pois[avoid_assoc_duplicate_keys(A.name, namecounts)] = A
return pois
//Orders mobs by type then by name
/proc/sortmobs()
var/list/moblist = list()
var/list/sortmob = sortNames(GLOB.mob_list)
for(var/mob/living/silicon/ai/M in sortmob)
moblist.Add(M)
for(var/mob/camera/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/brain/M in sortmob)
moblist.Add(M)
for(var/mob/living/carbon/alien/M in sortmob)
moblist.Add(M)
for(var/mob/dead/observer/M in sortmob)
moblist.Add(M)
for(var/mob/dead/new_player/M in sortmob)
moblist.Add(M)
for(var/mob/living/carbon/monkey/M in sortmob)
moblist.Add(M)
for(var/mob/living/simple_animal/slime/M in sortmob)
moblist.Add(M)
for(var/mob/living/simple_animal/M in sortmob)
moblist.Add(M)
for(var/mob/living/carbon/true_devil/M in sortmob)
moblist.Add(M)
return moblist
// Format a power value in W, kW, MW, or GW.
/proc/DisplayPower(powerused)
if(powerused < 1000) //Less than a kW
return "[powerused] W"
else if(powerused < 1000000) //Less than a MW
return "[round((powerused * 0.001),0.01)] kW"
else if(powerused < 1000000000) //Less than a GW
return "[round((powerused * 0.000001),0.001)] MW"
return "[round((powerused * 0.000000001),0.0001)] GW"
// Format an energy value in J, kJ, MJ, or GJ. 1W = 1J/s.
/proc/DisplayJoules(units)
if (units < 1000) // Less than a kJ
return "[round(units, 0.1)] J"
else if (units < 1000000) // Less than a MJ
return "[round(units * 0.001, 0.01)] kJ"
else if (units < 1000000000) // Less than a GJ
return "[round(units * 0.000001, 0.001)] MJ"
return "[round(units * 0.000000001, 0.0001)] GJ"
// Format an energy value measured in Power Cell units.
/proc/DisplayEnergy(units)
// APCs process every (SSmachines.wait * 0.1) seconds, and turn 1 W of
// excess power into GLOB.CELLRATE energy units when charging cells.
// With the current configuration of wait=20 and CELLRATE=0.002, this
// means that one unit is 1 kJ.
return DisplayJoules(units * SSmachines.wait * 0.1 / GLOB.CELLRATE)
/proc/get_mob_by_ckey(key)
if(!key)
return
var/list/mobs = sortmobs()
for(var/mob/M in mobs)
if(M.ckey == key)
return M
/*
Gets all contents of contents and returns them all in a list.
*/
/atom/proc/get_all_contents(T, ignore_flag_1)
var/list/processing_list = list(src)
if(T)
. = list()
var/i = 0
while(i < length(processing_list))
var/atom/A = processing_list[++i]
//Byond does not allow things to be in multiple contents, or double parent-child hierarchies, so only += is needed
//This is also why we don't need to check against assembled as we go along
if (!(A.flags_1 & ignore_flag_1))
processing_list += A.contents
if(istype(A,T))
. += A
else
var/i = 0
while(i < length(processing_list))
var/atom/A = processing_list[++i]
if (!(A.flags_1 & ignore_flag_1))
processing_list += A.contents
return processing_list
/atom/proc/get_all_contentsIgnoring(list/ignore_typecache)
if(!length(ignore_typecache))
return get_all_contents()
var/list/processing = list(src)
. = list()
var/i = 0
while(i < length(processing))
var/atom/A = processing[++i]
if(!ignore_typecache[A.type])
processing += A.contents
. += A
/atom/proc/get_all_contents_type(type)
var/list/processing_list = list(src)
. = list()
while(length(processing_list))
var/atom/checked_atom = processing_list[1]
processing_list.Cut(1, 2)
processing_list += checked_atom.contents
if(istype(checked_atom, type))
. += checked_atom
///Returns a list of all locations (except the area) the movable is within.
/proc/get_nested_locs(atom/movable/atom_on_location, include_turf = FALSE)
. = list()
var/atom/location = atom_on_location.loc
var/turf/our_turf = get_turf(atom_on_location)
while(location && location != our_turf)
. += location
location = location.loc
if(our_turf && include_turf) //At this point, only the turf is left, provided it exists.
. += our_turf
//Step-towards method of determining whether one atom can see another. Similar to viewers()
/proc/can_see(atom/source, atom/target, length=5) // I couldnt be arsed to do actual raycasting :I This is horribly inaccurate.
var/turf/current = get_turf(source)
var/turf/target_turf = get_turf(target)
var/steps = 1
if(current != target_turf)
current = get_step_towards(current, target_turf)
while(current != target_turf)
if(steps > length)
return FALSE
if(IS_OPAQUE_TURF(current))
return FALSE
current = get_step_towards(current, target_turf)
steps++
return TRUE
/proc/is_anchored_dense_turf(turf/T) //like the older version of the above, fails only if also anchored
if(T.density)
return 1
for(var/i in T)
var/atom/movable/A = i
if(A.density && A.anchored)
return 1
return 0
/proc/get_step_towards2(atom/ref , atom/trg)
var/base_dir = get_dir(ref, get_step_towards(ref,trg))
var/turf/temp = get_step_towards(ref,trg)
if(temp.is_blocked_turf())
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(!turf_last1.is_blocked_turf())
free_tile = turf_last1
break
if(!turf_last2.is_blocked_turf())
free_tile = turf_last2
break
turf_last1 = get_step(turf_last1,dir_alt1)
turf_last2 = get_step(turf_last2,dir_alt2)
breakpoint++
if(!free_tile)
return get_step(ref, base_dir)
else
return get_step_towards(ref,free_tile)
else
return get_step(ref, base_dir)
//Takes: Anything that could possibly have variables and a varname to check.
//Returns: 1 if found, 0 if not.
/proc/hasvar(datum/A, varname)
if(A.vars.Find(lowertext(varname)))
return 1
else
return 0
//Repopulates sortedAreas list
/proc/repopulate_sorted_areas()
GLOB.sortedAreas = list()
for(var/area/A in world)
GLOB.sortedAreas.Add(A)
sortTim(GLOB.sortedAreas, /proc/cmp_name_asc)
/area/proc/addSorted()
GLOB.sortedAreas.Add(src)
sortTim(GLOB.sortedAreas, /proc/cmp_name_asc)
/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)
/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/parse_zone(zone)
if(zone == BODY_ZONE_PRECISE_R_HAND)
return "right hand"
else if (zone == BODY_ZONE_PRECISE_L_HAND)
return "left hand"
else if (zone == BODY_ZONE_L_ARM)
return "left arm"
else if (zone == BODY_ZONE_R_ARM)
return "right arm"
else if (zone == BODY_ZONE_L_LEG)
return "left leg"
else if (zone == BODY_ZONE_R_LEG)
return "right leg"
else if (zone == BODY_ZONE_PRECISE_L_FOOT)
return "left foot"
else if (zone == BODY_ZONE_PRECISE_R_FOOT)
return "right foot"
else
return zone
//Finds the distance between two atoms, in pixels
//centered = FALSE counts from turf edge to edge
//centered = TRUE counts from turf center to turf center
//of course mathematically this is just adding world.icon_size on again
/proc/getPixelDistance(atom/A, atom/B, centered = TRUE)
if(!istype(A)||!istype(B))
return 0
. = bounds_dist(A, B) + sqrt((((A.pixel_x+B.pixel_x)**2) + ((A.pixel_y+B.pixel_y)**2)))
if(centered)
. += world.icon_size
/proc/get(atom/loc, type)
while(loc)
if(istype(loc, type))
return loc
loc = loc.loc
return null
//For objects that should embed, but make no sense being is_sharp or is_pointed()
//e.g: rods
GLOBAL_LIST_INIT(can_embed_types, typecacheof(list(
/obj/item/stack/rods,
/obj/item/pipe)))
/proc/can_embed(obj/item/W)
if(W.is_sharp())
return TRUE
if(is_type_in_typecache(W, GLOB.can_embed_types))
return TRUE
if(W.taped)
return TRUE
/*
Checks if that loc and dir has an item on the wall
*/
GLOBAL_LIST_INIT(WALLITEMS, typecacheof(list(
/obj/machinery/power/apc, /obj/machinery/airalarm, /obj/item/radio/intercom,
/obj/structure/extinguisher_cabinet, /obj/structure/reagent_dispensers/peppertank,
/obj/machinery/status_display, /obj/machinery/requests_console, /obj/machinery/light_switch, /obj/structure/sign,
/obj/machinery/newscaster, /obj/machinery/firealarm, /obj/structure/noticeboard, /obj/machinery/button,
/obj/machinery/computer/security/telescreen, /obj/machinery/embedded_controller/radio/simple_vent_controller,
/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/structure/sign/picture_frame, /obj/machinery/bounty_board
)))
GLOBAL_LIST_INIT(WALLITEMS_EXTERNAL, typecacheof(list(
/obj/machinery/camera, /obj/structure/camera_assembly,
/obj/structure/light_construct, /obj/machinery/light)))
GLOBAL_LIST_INIT(WALLITEMS_INVERSE, typecacheof(list(
/obj/structure/light_construct, /obj/machinery/light)))
/proc/gotwallitem(loc, dir, check_external = 0)
var/locdir = get_step(loc, dir)
for(var/obj/O in loc)
if(is_type_in_typecache(O, GLOB.WALLITEMS) && check_external != 2)
//Direction works sometimes
if(is_type_in_typecache(O, GLOB.WALLITEMS_INVERSE))
if(O.dir == turn(dir, 180))
return 1
else if(O.dir == dir)
return 1
//Some stuff doesn't use dir properly, so we need to check pixel instead
//That's exactly what get_turf_pixel() does
if(get_turf_pixel(O) == locdir)
return 1
if(is_type_in_typecache(O, GLOB.WALLITEMS_EXTERNAL) && check_external)
if(is_type_in_typecache(O, GLOB.WALLITEMS_INVERSE))
if(O.dir == turn(dir, 180))
return 1
else if(O.dir == dir)
return 1
//Some stuff is placed directly on the wallturf (signs)
for(var/obj/O in locdir)
if(is_type_in_typecache(O, GLOB.WALLITEMS) && check_external != 2)
if(O.pixel_x == 0 && O.pixel_y == 0)
return 1
return 0
/proc/format_text(text)
return replacetext(replacetext(text,"\proper ",""),"\improper ","")
/proc/check_target_facings(mob/living/initator, mob/living/target)
/*This can be used to add additional effects on interactions between mobs depending on how the mobs are facing each other, such as adding a crit damage to blows to the back of a guy's head.
Given how click code currently works (Nov '13), the initiating mob will be facing the target mob most of the time
That said, this proc should not be used if the change facing proc of the click code is overridden at the same time*/
if(!ismob(target) || !(target.mobility_flags & MOBILITY_STAND))
//Make sure we are not doing this for things that can't have a logical direction to the players given that the target would be on their side
return FALSE
if(initator.dir == target.dir) //mobs are facing the same direction
return FACING_SAME_DIR
if(is_A_facing_B(initator,target) && is_A_facing_B(target,initator)) //mobs are facing each other
return FACING_EACHOTHER
if(initator.dir + 2 == target.dir || initator.dir - 2 == target.dir || initator.dir + 6 == target.dir || initator.dir - 6 == target.dir) //Initating mob is looking at the target, while the target mob is looking in a direction perpendicular to the 1st
return FACING_INIT_FACING_TARGET_TARGET_FACING_PERPENDICULAR
/proc/random_step(atom/movable/AM, steps, chance)
var/initial_chance = chance
while(steps > 0)
if(prob(chance))
step(AM, pick(GLOB.alldirs))
chance = max(chance - (initial_chance / steps), 0)
steps--
/proc/living_player_count()
var/living_player_count = 0
for(var/mob in GLOB.player_list)
if(mob in GLOB.alive_mob_list)
living_player_count += 1
return living_player_count
/proc/randomColor(mode = 0) //if 1 it doesn't pick white, black or gray
switch(mode)
if(0)
return pick("white","black","gray","red","green","blue","brown","yellow","orange","darkred",
"crimson","lime","darkgreen","cyan","navy","teal","purple","indigo")
if(1)
return pick("red","green","blue","brown","yellow","orange","darkred","crimson",
"lime","darkgreen","cyan","navy","teal","purple","indigo")
else
return "white"
/proc/params2turf(scr_loc, turf/origin, client/C)
if(!scr_loc)
return null
var/tX = splittext(scr_loc, ",")
var/tY = splittext(tX[2], ":")
var/tZ = origin.z
tY = tY[1]
tX = splittext(tX[1], ":")
tX = tX[1]
var/list/actual_view = getviewsize(C ? C.view : world.view)
tX = clamp(origin.x + text2num(tX) - round(actual_view[1] / 2) - 1, 1, world.maxx)
tY = clamp(origin.y + text2num(tY) - round(actual_view[2] / 2) - 1, 1, world.maxy)
return locate(tX, tY, tZ)
/proc/screen_loc2turf(text, turf/origin, client/C)
if(!text)
return null
var/tZ = splittext(text, ",")
var/tX = splittext(tZ[1], "-")
var/tY = text2num(tX[2])
tX = splittext(tZ[2], "-")
tX = text2num(tX[2])
tZ = origin.z
var/list/actual_view = getviewsize(C ? C.view : world.view)
tX = clamp(origin.x + round(actual_view[1] / 2) - tX, 1, world.maxx)
tY = clamp(origin.y + round(actual_view[2] / 2) - tY, 1, world.maxy)
return locate(tX, tY, tZ)
/proc/IsValidSrc(datum/D)
if(istype(D))
return !QDELETED(D)
return 0
//Compare A's dir, the clockwise dir of A and the anticlockwise dir of A
//To the opposite dir of the dir returned by get_dir(B,A)
//If one of them is a match, then A is facing B
/proc/is_A_facing_B(atom/A,atom/B)
if(!istype(A) || !istype(B))
return FALSE
if(isliving(A))
var/mob/living/LA = A
if(!(LA.mobility_flags & MOBILITY_STAND))
return FALSE
var/goal_dir = get_dir(A,B)
var/clockwise_A_dir = turn(A.dir, -45)
var/anticlockwise_A_dir = turn(A.dir, 45)
if(A.dir == goal_dir || clockwise_A_dir == goal_dir || anticlockwise_A_dir == goal_dir)
return TRUE
return FALSE
/*
rough example of the "cone" made by the 3 dirs checked
B
\
\
>
<
\
\
B --><-- A
/
/
<
>
/
/
B
*/
//Center's an image.
//Requires:
//The Image
//The x dimension of the icon file used in the image
//The y dimension of the icon file used in the image
// eg: center_image(I, 32,32)
// eg2: center_image(I, 96,96)
/proc/center_image(image/I, x_dimension = 0, y_dimension = 0)
if(!I)
return
if(!x_dimension || !y_dimension)
return
if((x_dimension == world.icon_size) && (y_dimension == world.icon_size))
return I
//Offset the image so that it's bottom left corner is shifted this many pixels
//This makes it infinitely easier to draw larger inhands/images larger than world.iconsize
//but still use them in game
var/x_offset = -((x_dimension/world.icon_size)-1)*(world.icon_size*0.5)
var/y_offset = -((y_dimension/world.icon_size)-1)*(world.icon_size*0.5)
//Correct values under world.icon_size
if(x_dimension < world.icon_size)
x_offset *= -1
if(y_dimension < world.icon_size)
y_offset *= -1
I.pixel_x = x_offset
I.pixel_y = y_offset
return I
//ultra range (no limitations on distance, faster than range for distances > 8); including areas drastically decreases performance
/proc/urange(dist=0, atom/center=usr, orange=0, areas=0)
if(!dist)
if(!orange)
return list(center)
else
return list()
var/list/turfs = RANGE_TURFS(dist, center)
if(orange)
turfs -= get_turf(center)
. = list()
for(var/V in turfs)
var/turf/T = V
. += T
. += T.contents
if(areas)
. |= T.loc
//similar function to range(), but with no limitations on the distance; will search spiralling outwards from the center
/proc/spiral_range(dist=0, center=usr, orange=0)
var/list/L = list()
var/turf/t_center = get_turf(center)
if(!t_center)
return list()
if(!orange)
L += t_center
L += t_center.contents
if(!dist)
return L
var/turf/T
var/y
var/x
var/c_dist = 1
while( c_dist <= dist )
y = t_center.y + c_dist
x = t_center.x - c_dist + 1
for(x in x to t_center.x+c_dist)
T = locate(x,y,t_center.z)
if(T)
L += T
L += T.contents
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
L += T.contents
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
L += T.contents
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
L += T.contents
c_dist++
return L
/atom/proc/contains(atom/A)
if(!A)
return 0
for(var/atom/location = A.loc, location, location = location.loc)
if(location == src)
return 1
/proc/flick_overlay_static(O, atom/A, duration)
set waitfor = 0
if(!A || !O)
return
A.add_overlay(O)
sleep(duration)
A.cut_overlay(O)
/proc/get_closest_atom(type, list, source)
var/list/closest_atoms = list()
var/closest_distance
for(var/A in list)
if(!istype(A, type))
continue
var/distance = get_dist(source, A)
if(!closest_atoms.len)
closest_distance = distance
closest_atoms = list(A)
else
if(closest_distance > distance)
closest_distance = distance
closest_atoms = list(A)
else if(closest_distance == distance)
closest_atoms += A
return pick(closest_atoms) //if there are multiple atoms with the same distance, picks randomly from a list of them
proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types())
if (value == FALSE) //nothing should be calling us with a number, so this is safe
value = input("Enter type to find (blank for all, cancel to cancel)", "Search for type") as null|text
if (isnull(value))
return
value = trim(value)
if(!isnull(value) && value != "")
matches = filter_fancy_list(matches, value)
if(matches.len==0)
return
var/chosen
if(matches.len==1)
chosen = matches[1]
else
chosen = input("Select a type", "Pick Type", matches[1]) as null|anything in sortList(matches)
if(!chosen)
return
chosen = matches[chosen]
return chosen
//gives us the stack trace from CRASH() without ending the current proc.
/proc/stack_trace(msg)
CRASH(msg)
/datum/proc/stack_trace(msg)
CRASH(msg)
GLOBAL_REAL_VAR(list/stack_trace_storage)
/proc/gib_stack_trace()
stack_trace_storage = list()
stack_trace()
stack_trace_storage.Cut(1, min(3,stack_trace_storage.len))
. = stack_trace_storage
stack_trace_storage = null
//Key thing that stops lag. Cornerstone of performance in ss13, Just sitting here, in unsorted.dm.
//Increases delay as the server gets more overloaded,
//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful
#define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1)
///returns the number of ticks slept
/proc/stoplag(initial_delay)
if (!Master || Master.init_stage_completed < INITSTAGE_MAX)
sleep(world.tick_lag)
return 1
if (!initial_delay)
initial_delay = world.tick_lag
// Unit tests are not the normal environemnt. The mc can get absolutely thigh crushed, and sleeping procs running for ages is much more common
// We don't want spurious hard deletes off this, so let's only sleep for the requested period of time here yeah?
#ifdef UNIT_TESTS
sleep(initial_delay)
return CEILING(DS2TICKS(initial_delay), 1)
#else
. = 0
var/i = DS2TICKS(initial_delay)
do
. += CEILING(i * DELTA_CALC, 1)
sleep(i * world.tick_lag * DELTA_CALC)
i *= 2
while (TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit))
#endif
#undef DELTA_CALC
/proc/flash_color(mob_or_client, flash_color="#960000", flash_time=2 SECONDS)
var/client/C
if(ismob(mob_or_client))
var/mob/M = mob_or_client
if(M.client)
C = M.client
else
return
else if(istype(mob_or_client, /client))
C = mob_or_client
if(!istype(C))
return
var/animate_color = C.color
C.color = flash_color
animate(C, color = animate_color, time = flash_time)
#define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255)))
/proc/random_nukecode(max_length = 5)
var/val = rand(0, (10 ** max_length - 1))
var/str = "[val]"
while(length(str) < max_length)
str = "0" + str
. = str
/atom/proc/Shake(pixelshiftx = 15, pixelshifty = 15, duration = 250)
var/initialpixelx = pixel_x
var/initialpixely = pixel_y
var/shiftx = rand(-pixelshiftx,pixelshiftx)
var/shifty = rand(-pixelshifty,pixelshifty)
animate(src, pixel_x = pixel_x + shiftx, pixel_y = pixel_y + shifty, time = 0.02 SECONDS, loop = duration)
pixel_x = initialpixelx
pixel_y = initialpixely
/proc/weightclass2text(w_class)
switch(w_class)
if(WEIGHT_CLASS_TINY)
. = "tiny"
if(WEIGHT_CLASS_SMALL)
. = "small"
if(WEIGHT_CLASS_NORMAL)
. = "normal-sized"
if(WEIGHT_CLASS_BULKY)
. = "bulky"
if(WEIGHT_CLASS_HUGE)
. = "huge"
if(WEIGHT_CLASS_GIGANTIC)
. = "gigantic"
else
. = ""
GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
//Version of view() which ignores darkness, because BYOND doesn't have it (I actually suggested it but it was tagged redundant, BUT HEARERS IS A T- /rant).
/proc/dview(range = world.view, center, invis_flags = 0)
if(!center)
return
GLOB.dview_mob.loc = center
GLOB.dview_mob.see_invisible = invis_flags
. = view(range, GLOB.dview_mob)
GLOB.dview_mob.loc = null
/mob/dview
name = "INTERNAL DVIEW MOB"
invisibility = 101
density = FALSE
move_resist = INFINITY
var/ready_to_die = FALSE
/mob/dview/Initialize(mapload) //Properly prevents this mob from gaining huds or joining any global lists
SHOULD_CALL_PARENT(FALSE)
if(flags_1 & INITIALIZED_1)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
return INITIALIZE_HINT_NORMAL
/mob/dview/Destroy(force = FALSE)
if(!ready_to_die)
stack_trace("ALRIGHT WHICH FUCKER TRIED TO DELETE *MY* DVIEW?")
if (!force)
return QDEL_HINT_LETMELIVE
log_world("EVACUATE THE SHITCODE IS TRYING TO STEAL MUH JOBS")
GLOB.dview_mob = new
return ..()
#define FOR_DVIEW(type, range, center, invis_flags) \
GLOB.dview_mob.loc = center; \
GLOB.dview_mob.see_invisible = invis_flags; \
for(type in view(range, GLOB.dview_mob))
#define FOR_DVIEW_END GLOB.dview_mob.loc = null
//can a window be here, or is there a window blocking it?
/proc/valid_window_location(turf/T, dir_to_check)
if(!T)
return FALSE
for(var/obj/O in T)
if(istype(O, /obj/machinery/door/window) && (O.dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR))
return FALSE
if(istype(O, /obj/structure/windoor_assembly))
var/obj/structure/windoor_assembly/W = O
if(W.ini_dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR)
return FALSE
if(istype(O, /obj/structure/window))
var/obj/structure/window/W = O
if(W.ini_dir == dir_to_check || W.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR)
return FALSE
if(istype(O, /obj/structure/railing))
var/obj/structure/railing/rail = O
if(rail.ini_dir == dir_to_check || rail.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR)
return FALSE
return TRUE
#define UNTIL(X) while(!(X)) stoplag()
/proc/pass(...)
return
/proc/get_mob_or_brainmob(occupant)
var/mob/living/mob_occupant
if(isliving(occupant))
mob_occupant = occupant
else if(isbodypart(occupant))
var/obj/item/bodypart/head/head = occupant
mob_occupant = head.brainmob
else if(isorgan(occupant))
var/obj/item/organ/brain/brain = occupant
mob_occupant = brain.brainmob
return mob_occupant
//counts the number of bits in Byond's 16-bit width field
//in constant time and memory!
/proc/BitCount(bitfield)
var/temp = bitfield - ((bitfield>>1)&46811) - ((bitfield>>2)&37449) //0133333 and 0111111 respectively
temp = ((temp + (temp>>3))&29127) % 63 //070707
return temp
/**
* \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
* If it ever becomes necesary to get a more performant REF(), this lies here in wait
* #define REF(thing) (thing && isdatum(thing) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : text_ref(thing))
**/
/proc/REF(input)
if(isdatum(input))
var/datum/thing = input
if(thing.datum_flags & DF_USE_TAG)
if(!thing.tag)
stack_trace("A ref was requested of an object with DF_USE_TAG set but no tag: [thing]")
thing.datum_flags &= ~DF_USE_TAG
else
return "\[[url_encode(thing.tag)]\]"
return text_ref(input)
//returns a GUID like identifier (using a mostly made up record format)
//guids are not on their own suitable for access or security tokens, as most of their bits are predictable.
// (But may make a nice salt to one)
/proc/GUID()
var/const/GUID_VERSION = "b"
var/const/GUID_VARIANT = "d"
var/node_id = copytext_char(md5("[rand()*rand(1,9999999)][world.name][world.hub][world.hub_password][world.internet_address][world.address][world.contents.len][world.status][world.port][rand()*rand(1,9999999)]"), 1, 13)
var/time_high = "[num2hex(text2num(time2text(world.realtime,"YYYY")), 2)][num2hex(world.realtime, 6)]"
var/time_mid = num2hex(world.timeofday, 4)
var/time_low = num2hex(world.time, 3)
var/time_clock = num2hex(TICK_DELTA_TO_MS(world.tick_usage), 3)
return "{[time_high]-[time_mid]-[GUID_VERSION][time_low]-[GUID_VARIANT][time_clock]-[node_id]}"
// Makes a call in the context of a different usr
// Use sparingly
/world/proc/PushUsr(mob/M, datum/callback/CB, ...)
var/temp = usr
usr = M
if (length(args) > 2)
. = CB.Invoke(arglist(args.Copy(3)))
else
. = CB.Invoke()
usr = temp
//Returns a list of all servants of Ratvar and observers.
/proc/servants_and_ghosts()
. = list()
for(var/V in GLOB.player_list)
if(is_servant_of_ratvar(V) || isobserver(V))
. += V
#define VARSET_LIST_CALLBACK(target, var_name, var_value) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___callbackvarset), ##target, ##var_name, ##var_value)
//dupe code because dm can't handle 3 level deep macros
#define VARSET_CALLBACK(datum, var, var_value) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___callbackvarset), ##datum, NAMEOF(##datum, ##var), ##var_value)
/proc/___callbackvarset(list_or_datum, var_name, var_value)
if(length(list_or_datum))
list_or_datum[var_name] = var_value
return
var/datum/D = list_or_datum
if(IsAdminAdvancedProcCall())
D.vv_edit_var(var_name, var_value) //same result generally, unless badmemes
else
D.vars[var_name] = var_value
/proc/get_random_food()
var/list/blocked = list(
/obj/item/reagent_containers/food/snacks/store/bread,
/obj/item/reagent_containers/food/snacks/breadslice,
/obj/item/reagent_containers/food/snacks/store/cake,
/obj/item/reagent_containers/food/snacks/cakeslice,
/obj/item/reagent_containers/food/snacks/store,
/obj/item/reagent_containers/food/snacks/pie,
/obj/item/reagent_containers/food/snacks/kebab,
/obj/item/reagent_containers/food/snacks/pizza,
/obj/item/reagent_containers/food/snacks/pizzaslice,
/obj/item/reagent_containers/food/snacks/salad,
/obj/item/reagent_containers/food/snacks/meat,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/soup,
/obj/item/reagent_containers/food/snacks/grown,
/obj/item/reagent_containers/food/snacks/grown/mushroom,
/obj/item/reagent_containers/food/snacks/deepfryholder,
/obj/item/reagent_containers/food/snacks/clothing,
/obj/item/reagent_containers/food/snacks/grown/shell, //base types
/obj/item/reagent_containers/food/snacks/store/bread,
/obj/item/reagent_containers/food/snacks/grown/nettle,
/obj/item/reagent_containers/food/snacks/fish, // debug fish
/obj/item/reagent_containers/food/snacks/powercrepe //obscenely strong for a food item and shouldn't just be randomly spawned
)
blocked |= typesof(/obj/item/reagent_containers/food/snacks/customizable)
return pick(subtypesof(/obj/item/reagent_containers/food/snacks) - blocked)
/proc/get_random_drink()
var/list/blocked = list(/obj/item/reagent_containers/food/drinks/soda_cans,
/obj/item/reagent_containers/food/drinks/bottle
)
return pick(subtypesof(/obj/item/reagent_containers/food/drinks) - blocked)
/proc/get_random_goat()
var/list/blocked = list(/mob/living/simple_animal/hostile/retaliate/goat/huge,
/mob/living/simple_animal/hostile/retaliate/goat/clown,
/mob/living/simple_animal/hostile/retaliate/goat/stack,
/mob/living/simple_animal/hostile/retaliate/goat/radioactive,
/mob/living/simple_animal/hostile/retaliate/goat/blue,
/mob/living/simple_animal/hostile/retaliate/goat/brown,
/mob/living/simple_animal/hostile/retaliate/goat/chocolate,
/mob/living/simple_animal/hostile/retaliate/goat/rainbow,
/mob/living/simple_animal/hostile/retaliate/goat/green,
/mob/living/simple_animal/hostile/retaliate/goat/red,
/mob/living/simple_animal/hostile/retaliate/goat/black,
/mob/living/simple_animal/hostile/retaliate/goat/panda,
/mob/living/simple_animal/hostile/retaliate/goat/watercolor,
/mob/living/simple_animal/hostile/retaliate/goat/orange,
/mob/living/simple_animal/hostile/retaliate/goat/purple,
/mob/living/simple_animal/hostile/retaliate/goat/yellow,
/mob/living/simple_animal/hostile/retaliate/goat/legitgoat,
/mob/living/simple_animal/hostile/retaliate/goat/memory,
/mob/living/simple_animal/hostile/retaliate/goat/ghost,
/mob/living/simple_animal/hostile/retaliate/goat/king,
/mob/living/simple_animal/hostile/retaliate/goat/brick,
/mob/living/simple_animal/hostile/retaliate/goat/guard
)
return pick(subtypesof(new /mob/living/simple_animal/hostile/retaliate/goat) - blocked)
/proc/get_random_goat_colorful()
var/list/blocked = list(/mob/living/simple_animal/hostile/retaliate/goat/huge,
/mob/living/simple_animal/hostile/retaliate/goat/clown,
/mob/living/simple_animal/hostile/retaliate/goat/stack,
/mob/living/simple_animal/hostile/retaliate/goat/radioactive,
/mob/living/simple_animal/hostile/retaliate/goat/ras,
/mob/living/simple_animal/hostile/retaliate/goat/christmas,
/mob/living/simple_animal/hostile/retaliate/goat/confetti,
/mob/living/simple_animal/hostile/retaliate/goat/cottoncandy,
/mob/living/simple_animal/hostile/retaliate/goat/glowing,
/mob/living/simple_animal/hostile/retaliate/goat/goatgoat,
/mob/living/simple_animal/hostile/retaliate/goat/horror,
/mob/living/simple_animal/hostile/retaliate/goat/inverted,
/mob/living/simple_animal/hostile/retaliate/goat/mirrored,
/mob/living/simple_animal/hostile/retaliate/goat/paper,
/mob/living/simple_animal/hostile/retaliate/goat/pixel,
/mob/living/simple_animal/hostile/retaliate/goat/cute,
/mob/living/simple_animal/hostile/retaliate/goat/legitgoat,
/mob/living/simple_animal/hostile/retaliate/goat/memory,
/mob/living/simple_animal/hostile/retaliate/goat/ghost,
/mob/living/simple_animal/hostile/retaliate/goat/king,
/mob/living/simple_animal/hostile/retaliate/goat/guard,
/mob/living/simple_animal/hostile/retaliate/goat/star,
/mob/living/simple_animal/hostile/retaliate/goat/twisted,
/mob/living/simple_animal/hostile/retaliate/goat/tiny,
/mob/living/simple_animal/hostile/retaliate/goat/brick,
/mob/living/simple_animal/hostile/retaliate/goat/skiddo,
/mob/living/simple_animal/hostile/retaliate/goat/gogoat,
/mob/living/simple_animal/hostile/retaliate/goat/sanic,
/mob/living/simple_animal/hostile/retaliate/goat/plunger,
/mob/living/simple_animal/hostile/retaliate/goat/suspicious,
/mob/living/simple_animal/hostile/retaliate/goat/thrumbo
)
return pick(subtypesof(new /mob/living/simple_animal/hostile/retaliate/goat) - blocked)
/proc/special_list_filter(list/L, datum/callback/condition)
if(!islist(L) || !length(L) || !istype(condition))
return list()
. = list()
for(var/i in L)
if(condition.Invoke(i))
. |= i
/proc/generate_items_inside(list/items_list, where_to)
for(var/each_item in items_list)
for(var/i in 1 to items_list[each_item])
new each_item(where_to)
//sends a message to chat
//config_setting should be one of the following
//null - noop
//empty string - use TgsTargetBroadcast with admin_only = FALSE
//other string - use TgsChatBroadcast with the tag that matches config_setting, only works with TGS4, if using TGS3 the above method is used
/proc/send2chat(message, config_setting)
if(config_setting == null || !world.TgsAvailable())
return
var/datum/tgs_version/version = world.TgsVersion()
if(config_setting == "" || version.suite == 3)
world.TgsTargetedChatBroadcast(message, FALSE)
return
var/list/channels_to_use = list()
for(var/I in world.TgsChatChannelInfo())
var/datum/tgs_chat_channel/channel = I
if(channel.tag == config_setting)
channels_to_use += channel
if(channels_to_use.len)
world.TgsChatBroadcast()
/proc/CallAsync(datum/source, proctype, list/arguments)
set waitfor = FALSE
return call(source, proctype)(arglist(arguments))
/proc/tgui_login_data(mob/user, obj/machine, silicon_access = TRUE, admin_ghost_access = TRUE, access_requirement)
var/list/data = list()
if(!user || (!machine && !access_requirement))
return data
if(issilicon(user) && silicon_access)
var/mob/living/silicon/borg = user
data["username"] = borg.name
data["has_access"] = TRUE
if(IsAdminGhost(user) && admin_ghost_access)
data["username"] = user.client.holder.admin_signature
data["has_access"] = TRUE
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/username = H.get_authentification_name("Unknown")
data["username"] = H.get_authentification_name("Unknown")
if(username != "Unknown")
var/datum/data/record/record
for(var/RP in GLOB.data_core.general)
var/datum/data/record/R = RP
if(!istype(R))
continue
if(R.fields["name"] == username)
record = R
break
if(record)
if(istype(record.fields["photo_front"], /obj/item/photo))
var/obj/item/photo/P1 = record.fields["photo_front"]
var/icon/picture = icon(P1.picture.picture_image)
picture.Crop(10, 32, 22, 22)
var/md5 = md5(fcopy_rsc(picture))
if(!SSassets.cache["photo_[md5]_cropped.png"])
SSassets.transport.register_asset("photo_[md5]_cropped.png", picture)
SSassets.transport.send_assets(user, list("photo_[md5]_cropped.png" = picture))
data["user_image"] = SSassets.transport.get_asset_url("photo_[md5]_cropped.png")
if(machine)
data["has_access"] = machine.check_access(user.get_idcard())
else
var/obj/item/id_giver = user.get_idcard()
var/access_list = id_giver.GetAccess()
data["has_access"] = (access_requirement in access_list)
return data
/proc/tgui_login_act(mob/user, obj/machine, silicon_access = TRUE, admin_ghost_access = TRUE, access_requirement)
if(!user || (!machine && !access_requirement))
return FALSE
if(issilicon(user) && silicon_access)
return TRUE
if(IsAdminGhost(user) && admin_ghost_access)
return TRUE
var/mob/living/carbon/human/H = user
if(!istype(H))
return FALSE
if(machine)
if(machine.check_access(H.get_idcard()))
return TRUE
else
var/obj/item/id_giver = H.get_idcard()
var/access_list = id_giver.GetAccess()
if(access_requirement in access_list)
return TRUE
return FALSE