/turf ///what /mob/abstract/oranges_ear instance is already assigned to us as there should only ever be one. ///used for guaranteeing there is only one oranges_ear per turf when assigned, speeds up view() iteration var/mob/abstract/oranges_ear/assigned_oranges_ear /** # Oranges Ear * * turns out view() spends a significant portion of its processing time generating lists of contents of viewable turfs which includes EVERYTHING on it visible * and the turf itself. there is an optimization to view() which makes it only generate lists of a certain atom type - this system takes advantage of that. * a fuckton of these are generated as part of its SS's init and stored in a list, when requested for a list of movables returned by the spatial grid or by some * superset of the final output that must be narrowed down by view() one of these gets put on every turf that contains the movables that need filtering * and each is given references to the movables they represent. that way you can do for(var/mob/abstract/oranges_ear/ear in view(...)) and check what they reference * as opposed to for(var/atom/movable/target in view(...)) and checking if they have the properties you want which leads to much larger lists generated by view() * and also leads to iterating through more movables to filter them. * * TLDR: iterating through just mobs is much faster than all movables when iterating through view(), this system leverages that to boost speed * enough to offset the cost of allocating the mobs * * named because the idea was first made by oranges and i didnt know what else to call it (note that this system was originally made for get_hearers_in_view()) */ /mob/abstract/oranges_ear icon_state = null invisibility = 0 mouse_opacity = MOUSE_OPACITY_TRANSPARENT faction = null screens = null /// references to everything "on" the turf we are assigned to, that we care about. populated in assign() and cleared in unassign(). /// movables iside of other movables count as being "on" if they have get_turf(them) == our turf. intentionally not a lazylist var/list/references = list() /mob/abstract/oranges_ear/Initialize(mapload) SHOULD_CALL_PARENT(FALSE) initialized = TRUE return INITIALIZE_HINT_NORMAL /mob/abstract/oranges_ear/Destroy(force) var/old_length = length(SSspatial_grid.pregenerated_oranges_ears) SSspatial_grid.pregenerated_oranges_ears -= src if(length(SSspatial_grid.pregenerated_oranges_ears) < old_length) SSspatial_grid.number_of_oranges_ears -= 1 var/turf/our_loc = get_turf(src) if(our_loc && our_loc.assigned_oranges_ear == src) our_loc.assigned_oranges_ear = null . = ..() /mob/abstract/oranges_ear/Move() SHOULD_CALL_PARENT(FALSE) crash_with("SOMEHOW A /mob/abstract/oranges_ear MOVED") return FALSE /mob/abstract/oranges_ear/forceMove(atom/destination) SHOULD_CALL_PARENT(FALSE) crash_with("SOMEHOW A /mob/abstract/oranges_ear MOVED") return FALSE /mob/abstract/oranges_ear/Bump() SHOULD_CALL_PARENT(FALSE) return FALSE ///clean this oranges_ear up for future use /mob/abstract/oranges_ear/proc/unassign() var/turf/turf_loc = loc turf_loc.assigned_oranges_ear = null//trollface. our loc should ALWAYS be a turf, no exceptions. if it isnt then this doubles as an error message ;) loc = null references.Cut() /** * returns every hearaing movable in view to the turf of source not taking into account lighting * useful when you need to maintain always being able to hear something if a sound is emitted from it and you can see it (and youre in range). * otherwise this is just a more expensive version of get_hearers_in_LOS() * * * view_radius - what radius search circle we are using, worse performance as this increases * * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area */ /proc/get_hearers_in_view(view_radius, atom/source) var/turf/center_turf = get_turf(source) if(!center_turf) return . = list() if(view_radius <= 0)//special case for if only source cares for(var/atom/movable/target as anything in center_turf) var/list/recursive_contents = target.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE] if(recursive_contents) . += recursive_contents return . var/list/hearables_from_grid = SSspatial_grid.orthogonal_range_search(source, RECURSIVE_CONTENTS_HEARING_SENSITIVE, view_radius) if(!length(hearables_from_grid))//we know that something is returned by the grid, but we dont know if we need to actually filter down the output return . var/list/assigned_oranges_ears = SSspatial_grid.assign_oranges_ears(hearables_from_grid) var/old_luminosity = center_turf.luminosity center_turf.luminosity = 6 //man if only we had an inbuilt dview() //this is the ENTIRE reason all this shit is worth it due to how view() works and can be optimized //view() constructs lists of viewed atoms by default and specifying a specific type of atom to look for limits the lists it constructs to those of that //primitive type and then when the view operation is completed the output is then typechecked to only iterate through objects in view with the same //typepath. by assigning one /mob/abstract/oranges_ear to every turf with hearable atoms on it and giving them references to each one means that: //1. view() only constructs lists of atoms with the mob primitive type and //2. the mobs returned by view are fast typechecked to only iterate through /mob/abstract/oranges_ear mobs, which guarantees at most one per turf //on a whole this can outperform iterating through all movables in view() by ~2x especially when hearables are a tiny percentage of movables in view for(var/mob/abstract/oranges_ear/ear in view(view_radius, center_turf)) . += ear.references for(var/mob/abstract/oranges_ear/remaining_ear as anything in assigned_oranges_ears)//we need to clean up our mess remaining_ear.unassign() center_turf.luminosity = old_luminosity return . /** * Returns a list of movable atoms that are hearing sensitive in view_radius and line of sight to source * the majority of the work is passed off to the spatial grid if view_radius > 0 * because view() isnt a raycasting algorithm, this does not hold symmetry to it. something in view might not be hearable with this. * if you want that use get_hearers_in_view() - however thats significantly more expensive * * * view_radius - what radius search circle we are using, worse performance as this increases but not as much as it used to * * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area */ /proc/get_hearers_in_LOS(view_radius, atom/source) var/turf/center_turf = get_turf(source) if(!center_turf) return if(view_radius <= 0)//special case for if only source cares . = list() for(var/atom/movable/target as anything in center_turf) var/list/hearing_contents = target.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE] if(hearing_contents) . += hearing_contents return . = SSspatial_grid.orthogonal_range_search(source, SPATIAL_GRID_CONTENTS_TYPE_HEARING, view_radius) for(var/atom/movable/target as anything in .) var/turf/target_turf = get_turf(target) var/distance = get_dist(center_turf, target_turf) if(distance > view_radius) . -= target continue else if(distance < 2) //we should always be able to see something 0 or 1 tiles away continue //this turf search algorithm is the worst scaling part of this proc, scaling worse than view() for small-moderate ranges and > 50 length contents_to_return //luckily its significantly faster than view for large ranges in large spaces and/or relatively few contents_to_return //i can do things that would scale better, but they would be slower for low volume searches which is the vast majority of the current workload //maybe in the future a high volume algorithm would be worth it var/turf/inbetween_turf = center_turf //this is the lowest overhead way of doing a loop in dm other than a goto. distance is guaranteed to be >= steps taken to target by this algorithm for(var/step_counter in 1 to distance) inbetween_turf = get_step_towards(inbetween_turf, target_turf) if(inbetween_turf == target_turf)//we've gotten to target's turf without returning due to turf opacity, so we must be able to see target break if(inbetween_turf.opacity)//this turf or something on it is opaque so we cant see through it . -= target break /proc/get_hearers_in_radio_ranges(list/obj/item/device/radio/radios) . = list() // Returns a list of mobs who can hear any of the radios given in @radios for(var/obj/item/device/radio/radio in radios) . |= get_hearers_in_LOS(radio.canhear_range, radio, FALSE) // get_hearers_in_LOS but for anything AIs care to target /proc/get_targets_in_LOS(view_radius, atom/source) var/turf/center_turf = get_turf(source) if(!center_turf) return if(view_radius <= 0) . = list() for(var/atom/movable/target as anything in center_turf) var/list/tgt_contents = target.important_recursive_contents?[RECURSIVE_CONTENTS_AI_TARGETS] if(tgt_contents) . += tgt_contents return . = SSspatial_grid.orthogonal_range_search(source, SPATIAL_GRID_CONTENTS_TYPE_TARGETS, view_radius) for(var/mob/target as anything in .) if(!check_los(source, target, view_radius)) . -= target continue /proc/check_los(atom/source, atom/target, view_radius = world.view) var/turf/source_turf = get_turf(source) var/turf/target_turf = get_turf(target) var/distance = get_dist(source_turf, target_turf) if(distance > view_radius) return FALSE if(distance < 2) return TRUE var/turf/mid_turf = source_turf for(var/step_counter in 1 to distance) mid_turf = get_step_towards(mid_turf, target_turf) if(mid_turf == target_turf) break if(IS_OPAQUE_TURF(mid_turf)) return FALSE return TRUE ///Calculate if two atoms are in sight, returns TRUE or FALSE /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 TRUE //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 FALSE 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