//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a #define RANGE_TURFS(RADIUS, CENTER) \ block( \ locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \ locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ ) #define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL)) #define CULT_POLL_WAIT 2400 /// Returns a list of turfs similar to CORNER_BLOCK but with offsets #define CORNER_BLOCK_OFFSET(corner, width, height, offset_x, offset_y) ( \ (block(locate(corner.x + offset_x, corner.y + offset_y, corner.z), \ locate(min(corner.x + (width - 1) + offset_x, world.maxx), \ min(corner.y + (height - 1) + offset_y, world.maxy), corner.z)))) /// Returns an outline (neighboring turfs) of the given block #define CORNER_OUTLINE(corner, width, height) ( \ CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, -1) + \ CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, height) + \ CORNER_BLOCK_OFFSET(corner, 1, height, -1, 0) + \ CORNER_BLOCK_OFFSET(corner, 1, height, width, 0)) /proc/get_area_name(atom/X, format_text = FALSE, is_sensor = FALSE) var/area/A = isarea(X) ? X : get_area(X) if(!A) return null var/name = A.name if (is_sensor && !A.show_on_sensors) name = Gibberish(name, TRUE, 90) return format_text ? format_text(name) : name /proc/get_areas_in_range(dist=0, atom/center=usr) if(!dist) var/turf/T = get_turf(center) return T ? list(T.loc) : list() if(!center) return list() var/list/turfs = RANGE_TURFS(dist, center) var/list/areas = list() for(var/V in turfs) var/turf/T = V areas |= T.loc return areas /proc/get_adjacent_areas(atom/center) . = list(get_area(get_ranged_target_turf(center, NORTH, 1)), get_area(get_ranged_target_turf(center, SOUTH, 1)), get_area(get_ranged_target_turf(center, EAST, 1)), get_area(get_ranged_target_turf(center, WEST, 1))) listclearnulls(.) /proc/get_open_turf_in_dir(atom/center, dir) var/turf/open/T = get_ranged_target_turf(center, dir, 1) if(istype(T)) return T /proc/get_adjacent_open_turfs(atom/center) . = list(get_open_turf_in_dir(center, NORTH), get_open_turf_in_dir(center, SOUTH), get_open_turf_in_dir(center, EAST), get_open_turf_in_dir(center, WEST)) listclearnulls(.) /proc/get_adjacent_open_areas(atom/center) . = list() var/list/adjacent_turfs = get_adjacent_open_turfs(center) for(var/I in adjacent_turfs) . |= get_area(I) /** * Get a bounding box of a list of atoms. * * Arguments: * - atoms - List of atoms. Can accept output of view() and range() procs. * * Returns: list(x1, y1, x2, y2) */ /proc/get_bbox_of_atoms(list/atoms) var/list/list_x = list() var/list/list_y = list() for(var/_a in atoms) var/atom/a = _a list_x += a.x list_y += a.y return list( min(list_x), min(list_y), max(list_x), max(list_y)) // Like view but bypasses luminosity check /proc/get_hear(range, atom/source) var/lum = source.luminosity source.luminosity = 6 var/list/heard = view(range, source) source.luminosity = lum return heard /proc/alone_in_area(area/the_area, mob/must_be_alone, check_type = /mob/living/carbon) var/area/our_area = get_area(the_area) for(var/C in GLOB.alive_mob_list) if(!istype(C, check_type)) continue if(C == must_be_alone) continue if(our_area == get_area(C)) return 0 return 1 //We used to use linear regression to approximate the answer, but Mloc realized this was actually faster. //And lo and behold, it is, and it's more accurate to boot. /proc/cheap_hypotenuse(Ax,Ay,Bx,By) return sqrt(abs(Ax - Bx)**2 + abs(Ay - By)**2) //A squared + B squared = C squared /proc/circlerange(center=usr,radius=3) var/turf/centerturf = get_turf(center) var/list/turfs = new/list() var/rsq = radius * (radius+0.5) for(var/atom/T in range(radius, centerturf)) var/dx = T.x - centerturf.x var/dy = T.y - centerturf.y if(dx*dx + dy*dy <= rsq) turfs += T //turfs += centerturf return turfs /proc/circleview(center=usr,radius=3) var/turf/centerturf = get_turf(center) var/list/atoms = new/list() var/rsq = radius * (radius+0.5) for(var/atom/A in view(radius, centerturf)) var/dx = A.x - centerturf.x var/dy = A.y - centerturf.y if(dx*dx + dy*dy <= rsq) atoms += A //turfs += centerturf return atoms /proc/get_dist_euclidian(atom/Loc1 as turf|mob|obj,atom/Loc2 as turf|mob|obj) var/dx = Loc1.x - Loc2.x var/dy = Loc1.y - Loc2.y var/dist = sqrt(dx**2 + dy**2) return dist ///Returns a list of turfs around a center based on RANGE_TURFS() /proc/circle_range_turfs(center = usr, radius = 3) var/turf/center_turf = get_turf(center) var/list/turfs = new/list() var/rsq = radius * (radius + 0.5) for(var/turf/checked_turf as anything in RANGE_TURFS(radius, center_turf)) var/dx = checked_turf.x - center_turf.x var/dy = checked_turf.y - center_turf.y if(dx * dx + dy * dy <= rsq) turfs += checked_turf return turfs /proc/circleviewturfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()? // Yes var/turf/centerturf = get_turf(center) var/list/turfs = new/list() var/rsq = radius * (radius+0.5) for(var/turf/T in view(radius, centerturf)) var/dx = T.x - centerturf.x var/dy = T.y - centerturf.y if(dx*dx + dy*dy <= rsq) turfs += T return turfs //This is the new version of recursive_mob_check, used for say(). //The other proc was left intact because morgue trays use it. //Sped this up again for real this time /proc/recursive_hear_check(O) var/list/processing_list = list(O) . = list() var/i = 0 while(i < length(processing_list)) var/atom/A = processing_list[++i] if(A.flags_1 & HEAR_1) . += A processing_list += A.contents /** recursive_organ_check * inputs: O (object to start with) * outputs: * description: A pseudo-recursive loop based off of the recursive mob check, this check looks for any organs held * within 'O', toggling their frozen flag. This check excludes items held within other safe organ * storage units, so that only the lowest level of container dictates whether we do or don't decompose */ /proc/recursive_organ_check(atom/O) var/list/processing_list = list(O) var/list/processed_list = list() var/index = 1 var/obj/item/organ/found_organ while(index <= length(processing_list)) var/atom/A = processing_list[index] if(istype(A, /obj/item/organ)) found_organ = A found_organ.organ_flags ^= ORGAN_FROZEN else if(istype(A, /mob/living/carbon)) var/mob/living/carbon/Q = A for(var/organ in Q.internal_organs) found_organ = organ found_organ.organ_flags ^= ORGAN_FROZEN for(var/atom/B in A) //objects held within other objects are added to the processing list, unless that object is something that can hold organs safely if(!processed_list[B] && !istype(B, /obj/structure/closet/crate/freezer) && !istype(B, /obj/structure/closet/secure_closet/freezer)) processing_list+= B index++ processed_list[A] = A return // Better recursive loop, technically sort of not actually recursive cause that shit is retarded, enjoy. //No need for a recursive limit either /proc/recursive_mob_check(atom/O,client_check=1,sight_check=1,include_radio=1) var/list/processing_list = list(O) var/list/processed_list = list() var/list/found_mobs = list() while(processing_list.len) var/atom/A = processing_list[1] var/passed = 0 if(ismob(A)) var/mob/A_tmp = A passed=1 if(client_check && !A_tmp.client) passed=0 if(sight_check && !isInSight(A_tmp, O)) passed=0 else if(include_radio && istype(A, /obj/item/radio)) passed=1 if(sight_check && !isInSight(A, O)) passed=0 if(passed) found_mobs |= A for(var/atom/B in A) if(!processed_list[B]) processing_list |= B processing_list.Cut(1, 2) processed_list[A] = A return found_mobs /proc/get_hearers_in_view(R, atom/source) // Returns a list of hearers in view(R) from source (ignoring luminosity). Used in saycode. var/turf/T = get_turf(source) . = list() if(!T) return var/list/processing_list = list() if (R == 0) // if the range is zero, we know exactly where to look for, we can skip view processing_list += T.contents // We can shave off one iteration by assuming turfs cannot hear else // A variation of get_hear inlined here to take advantage of the compiler's fastpath for obj/mob in view var/lum = T.luminosity T.luminosity = 6 // This is the maximum luminosity for(var/mob/M in view(R, T)) processing_list += M for(var/obj/O in view(R, T)) processing_list += O T.luminosity = lum var/i = 0 while(i < length(processing_list)) // recursive_hear_check inlined here var/atom/A = processing_list[++i] if(A.flags_1 & HEAR_1) . += A processing_list += A.contents /proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios) . = list() // Returns a list of mobs who can hear any of the radios given in @radios for(var/obj/item/radio/R in radios) if(R) . |= get_hearers_in_view(R.canhear_range, R) #define SIGNV(X) ((X<0)?-1:1) /proc/inLineOfSight(X1,Y1,X2,Y2,Z=1,PX1=16.5,PY1=16.5,PX2=16.5,PY2=16.5) var/turf/T if(X1==X2) if(Y1==Y2) return 1 //Light cannot be blocked on same tile else var/s = SIGN(Y2-Y1) Y1+=s while(Y1!=Y2) T=locate(X1,Y1,Z) if(T.opacity) return 0 Y1+=s else var/m=(32*(Y2-Y1)+(PY2-PY1))/(32*(X2-X1)+(PX2-PX1)) var/b=(Y1+PY1/32-0.015625)-m*(X1+PX1/32-0.015625) //In tiles var/signX = SIGN(X2-X1) var/signY = SIGN(Y2-Y1) if(X1 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/try_move_adjacent(atom/movable/AM) var/turf/T = get_turf(AM) for(var/direction in GLOB.cardinals) if(AM.Move(get_step(T, direction))) break /proc/get_mob_by_key(key) var/ckey = ckey(key) for(var/i in GLOB.player_list) var/mob/M = i if(M.ckey == ckey) return M return null /proc/considered_alive(datum/mind/M, enforce_human = TRUE) if(M && M.current) if(enforce_human) var/mob/living/carbon/human/H if(ishuman(M.current)) H = M.current return M.current.stat != DEAD && !issilicon(M.current) && !isbrain(M.current) && (!H || H.dna.species.id != "memezombies") else if(isliving(M.current)) if(isAI(M.current)) var/mob/living/silicon/ai/AI = M.current if(AI.is_dying) return FALSE return M.current.stat != DEAD return FALSE /proc/considered_afk(datum/mind/M) return !M || !M.current || !M.current.client || M.current.client.is_afk() /proc/ScreenText(obj/O, maptext="", screen_loc="CENTER-7,CENTER-7", maptext_height=480, maptext_width=480) if(!isobj(O)) O = new /atom/movable/screen/text() O.maptext = maptext O.maptext_height = maptext_height O.maptext_width = maptext_width O.screen_loc = screen_loc return O /// Removes an image from a client's `.images`. Useful as a callback. /proc/remove_image_from_client(image/image, client/remove_from) remove_from?.images -= image /proc/remove_images_from_clients(image/I, list/show_to) for(var/client/C in show_to) C.images -= I /proc/flick_overlay(image/I, list/show_to, duration) for(var/client/C in show_to) C.images += I addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_images_from_clients), I, show_to), duration, TIMER_CLIENT_TIME) /proc/flick_overlay_view(image/I, atom/target, duration) //wrapper for the above, flicks to everyone who can see the target atom var/list/viewing = list() for(var/m in viewers(target)) var/mob/M = m if(M.client) viewing += M.client flick_overlay(I, viewing, duration) /proc/get_active_player_count(alive_check = 0, afk_check = 0, human_check = 0) // Get active players who are playing in the round var/active_players = 0 for(var/i = 1; i <= GLOB.player_list.len; i++) var/mob/M = GLOB.player_list[i] if(M && M.client) if(alive_check && M.stat) continue else if(afk_check && M.client.is_afk()) continue else if(human_check && !ishuman(M)) continue else if(isnewplayer(M)) // exclude people in the lobby continue else if(isobserver(M)) // Ghosts are fine if they were playing once (didn't start as observers) var/mob/dead/observer/O = M if(O.started_as_observer) // Exclude people who started as observers continue active_players++ return active_players /proc/showCandidatePollWindow(mob/M, poll_time, Question, list/candidates, ignore_category, time_passed, flashwindow = TRUE) set waitfor = 0 SEND_SOUND(M, 'sound/misc/notice3.ogg') //Alerting them to their consideration if(flashwindow) window_flash(M.client) var/list/answers = ignore_category ? list("Yes", "No", "Never for this round") : list("Yes", "No") switch(tgui_alert(M, Question, "A limited-time offer!", answers, poll_time, autofocus = FALSE)) if("Yes") to_chat(M, span_notice("Choice registered: Yes.")) if(time_passed + poll_time <= world.time) to_chat(M, span_danger("Sorry, you answered too late to be considered!")) SEND_SOUND(M, 'sound/machines/buzz-sigh.ogg') candidates -= M else candidates += M if("No") to_chat(M, span_danger("Choice registered: No.")) candidates -= M if("Never for this round") var/list/L = GLOB.poll_ignore[ignore_category] if(!L) GLOB.poll_ignore[ignore_category] = list() GLOB.poll_ignore[ignore_category] += M.ckey to_chat(M, span_danger("Choice registered: Never for this round.")) candidates -= M else candidates -= M /** * Poll all ghosts for looking for a candidate * * Poll all ghosts a question * returns people who voted yes in a list * Arguments: * * Question: String, what do you want to ask them * * jobbanType: List, Which roles/jobs to exclude from being asked * * gametypeCheck: Datum, Check if they have the time required for that role * * be_special_flag: Bool, Only notify ghosts with special antag on * * poll_time: Integer, How long to poll for in deciseconds(0.1s) * * ignore_category: Define, ignore_category: People with this category(defined in poll_ignore.dm) turned off dont get the message * * flashwindow: Bool, Flash their window to grab their attention */ /proc/pollGhostCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE) var/list/candidates = list() if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE)) return candidates for(var/mob/dead/observer/G in GLOB.player_list) candidates += G return pollCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category, flashwindow, candidates) /** * Poll all mentor ghosts for looking for a candidate * * Poll all mentor ghosts a question * returns people who voted yes in a list * Arguments: * * Question: String, what do you want to ask them * * jobbanType: List, Which roles/jobs to exclude from being asked * * gametypeCheck: Datum, Check if they have the time required for that role * * be_special_flag: Bool, Only notify ghosts with special antag on * * poll_time: Integer, How long to poll for in deciseconds(0.1s) * * ignore_category: Define, ignore_category: People with this category(defined in poll_ignore.dm) turned off dont get the message * * flashwindow: Bool, Flash their window to grab their attention */ /proc/pollMentorGhostCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE) var/list/candidates = list() if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE)) return candidates for(var/mob/dead/observer/G in GLOB.player_list) if(is_mentor(G)) candidates += G return pollCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category, flashwindow, candidates) /** * Poll all in the group for a candidate * * Poll group for question * returns people who voted yes in a list * Arguments: * * Question: String, what do you want to ask them * * jobbanType: List, Which roles/jobs to exclude from being asked * * gametypeCheck: Datum, Check if they have the time required for that role * * be_special_flag: Bool, Only notify ghosts with special antag on * * poll_time: Integer, How long to poll for in deciseconds(0.1s) * * ignore_category: Define, ignore_category: People with this category(defined in poll_ignore.dm) turned off dont get the message * * flashwindow: Bool, Flash their window to grab their attention * * group: List, Group of people to poll. list of datum/minds */ /proc/pollCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE, list/group = null) var/time_passed = world.time if (!Question) Question = "Would you like to be a special role?" var/list/result = list() for(var/m in group) var/mob/M = m if(!M.key || !M.client || (ignore_category && GLOB.poll_ignore[ignore_category] && (M.ckey in GLOB.poll_ignore[ignore_category]))) continue if(be_special_flag) if(!(M.client.prefs) || !(be_special_flag in M.client.prefs.be_special)) continue if(gametypeCheck) if(!gametypeCheck.age_check(M.client)) continue if(jobbanType) if(is_banned_from(M.ckey, list(jobbanType, ROLE_SYNDICATE)) || QDELETED(M)) continue showCandidatePollWindow(M, poll_time, Question, result, ignore_category, time_passed, flashwindow) sleep(poll_time) //Check all our candidates, to make sure they didn't log off or get deleted during the wait period. for(var/mob/M in result) if(!M.key || !M.client) result -= M listclearnulls(result) return result /** * Poll ghosts to take control of a mob * * Poll ghosts for mob control * returns people who voted yes in a list * Arguments: * * Question: String, what do you want to ask them * * jobbanType: List, Which roles/jobs to exclude from being asked * * gametypeCheck: Datum, Check if they have the time required for that role * * be_special_flag: Bool, Only notify ghosts with special antag on * * poll_time: Integer, How long to poll for in deciseconds(0.1s) * * M: Mob, /mob to offer * * ignore_category: Unknown */ /proc/pollCandidatesForMob(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, mob/M, ignore_category = null) var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) if(!M || QDELETED(M) || !M.loc) return list() return L /** * Poll mentor ghosts to take control of a mob * * Poll mentor ghosts for mob control * returns people who voted yes in a list * Arguments: * * Question: String, what do you want to ask them * * jobbanType: List, Which roles/jobs to exclude from being asked * * gametypeCheck: Datum, Check if they have the time required for that role * * be_special_flag: Bool, Only notify ghosts with special antag on * * poll_time: Integer, How long to poll for in deciseconds(0.1s) * * M: Mob, /mob to offer * * ignore_category: Unknown */ /proc/pollMentorCandidatesForMob(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, mob/M, ignore_category = null) var/list/L = pollMentorGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) if(!M || QDELETED(M) || !M.loc) return list() return L /** * Poll ghosts to take control of a mob * * Poll ghosts for mob control * returns people who voted yes in a list * Arguments: * * Question: String, what do you want to ask them * * jobbanType: List, Which roles/jobs to exclude from being asked * * gametypeCheck: Datum, Check if they have the time required for that role * * be_special_flag: Bool, Only notify ghosts with special antag on * * poll_time: Integer, How long to poll for in deciseconds(0.1s) * * mobs: List, list of mobs to offer up * * ignore_category: Unknown */ /proc/pollCandidatesForMobs(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, list/mobs, ignore_category = null) var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) var/i=1 for(var/v in mobs) var/atom/A = v if(!A || QDELETED(A) || !A.loc) mobs.Cut(i,i+1) else ++i return L /proc/makeBody(mob/dead/observer/G_found) // Uses stripped down and bastardized code from respawn character if(!G_found || !G_found.key) return //First we spawn a dude. var/mob/living/carbon/human/new_character = new//The mob being spawned. SSjob.SendToLateJoin(new_character) G_found.client.prefs.apply_prefs_to(new_character) new_character.dna.update_dna_identity() new_character.key = G_found.key return new_character /proc/send_to_playing_players(thing) //sends a whatever to all playing players; use instead of to_chat(world, where needed) for(var/M in GLOB.player_list) if(M && !isnewplayer(M)) to_chat(M, thing) /proc/window_flash(client/C, ignorepref = FALSE) if(ismob(C)) var/mob/M = C if(M.client) C = M.client if(!C || (!C.prefs.read_preference(/datum/preference/toggle/window_flashing) && !ignorepref)) return winset(C, "mainwindow", "flash=5") //Recursively checks if an item is inside a given type, even through layers of storage. Returns the atom if it finds it. /proc/recursive_loc_check(atom/movable/target, type) var/atom/A = target if(istype(A, type)) return A while(!istype(A.loc, type)) if(!A.loc) return A = A.loc return A.loc /proc/AnnounceArrival(mob/living/carbon/human/character, rank) if(!SSticker.IsRoundInProgress() || QDELETED(character)) return var/area/A = get_area(character) if(character.mind.role_alt_title) rank = character.mind.role_alt_title deadchat_broadcast(" has arrived at the station at [span_name("[A.name]")].", "[span_name("[character.real_name]")] ([rank])", follow_target = character, message_type=DEADCHAT_ARRIVALRATTLE) if((!GLOB.announcement_systems.len) || (!character.mind)) return if((character.mind.assigned_role == "Cyborg") || (character.mind.assigned_role == character.mind.special_role)) return var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) announcer.announce("ARRIVAL", character.real_name, rank, list()) //make the list empty to make it announce it in common /proc/lavaland_equipment_pressure_check(turf/T) . = FALSE if(!istype(T)) return var/datum/gas_mixture/environment = T.return_air() if(!istype(environment)) return var/pressure = environment.return_pressure() if(pressure <= LAVALAND_EQUIPMENT_EFFECT_PRESSURE) . = TRUE /proc/ispipewire(item) var/static/list/pire_wire = list( /obj/machinery/atmospherics, /obj/structure/disposalpipe, /obj/structure/cable ) return (is_type_in_list(item, pire_wire)) // Find a obstruction free turf that's within the range of the center. Can also condition on if it is of a certain area type. /proc/find_obstruction_free_location(range, atom/center, area/specific_area) var/list/turfs = RANGE_TURFS(range, center) var/list/possible_loc = list() for(var/turf/found_turf in turfs) var/area/turf_area = get_area(found_turf) // We check if both the turf is a floor, and that it's actually in the area. // We also want a location that's clear of any obstructions. if (specific_area) if (!istype(turf_area, specific_area)) continue if (!isspaceturf(found_turf)) if (!found_turf.is_blocked_turf()) possible_loc.Add(found_turf) // Need at least one free location. if (possible_loc.len < 1) return FALSE return pick(possible_loc) /proc/power_fail(duration_min, duration_max) var/list/data_core_areas = list() for(var/obj/machinery/ai/data_core/core as anything in GLOB.data_cores) if(!core.valid_data_core()) continue if(!isarea(core.loc)) continue var/area/A = core.loc data_core_areas[A.type] = TRUE for(var/P in GLOB.apcs_list) var/obj/machinery/power/apc/C = P if(C.cell && SSmapping.level_trait(C.z, ZTRAIT_STATION)) var/area/A = C.area if(GLOB.typecache_powerfailure_safe_areas[A.type]) continue if(data_core_areas[A.type]) continue C.energy_fail(rand(duration_min,duration_max)) /// For legacy procs using addtimer in callbacks. Don't use this. /proc/_addtimer_here(callback, time) addtimer(callback, time)