Fixes Massive Radio Overtime, Implements a Spatial Grid System for Faster Searching Over Areas (#61422)

a month or two ago i realized that on master the reason why get_hearers_in_view() overtimes so much (ie one of our highest overtiming procs at highpop) is because when you transmit a radio signal over the common channel, it can take ~20 MILLISECONDS, which isnt good when 1. player verbs and commands usually execute after SendMaps processes for that tick, meaning they can execute AFTER the tick was supposed to start if master is overloaded and theres a lot of maptick 2. each of our server ticks are only 50 ms, so i started on optimizing this.

the main optimization was SSspatial_grid which allows searching through 15x15 spatial_grid_cell datums (one set for each z level) far faster than iterating over movables in view() to look for what you want. now all hearing sensitive movables in the 5x5 areas associated with each spatial_grid_cell datum are stored in the datum (so are client mobs). when you search for one of the stored "types" (hearable or client mob) in a radius around a center, it just needs to

    iterate over the cell datums in range
    add the content type you want from the datums to a list
    subtract contents that arent in range, then contents not in line of sight
    return the list

from benchmarks, this makes short range searches like what is used with radio code (it goes over every radio connected to a radio channel that can hear the signal then calls get_hearers_in_view() to search in the radios canhear_range which is at most 3) about 3-10 times faster depending on workload. the line of sight algorithm scales well with range but not very well if it has to check LOS to > 100 objects, which seems incredibly rare for this workload, the largest range any radio in the game searches through is only 3 tiles

the second optimization is to enforce complex setter vars for radios that removes them from the global radio list if they couldnt actually receive any radio transmissions from a given frequency in the first place.

the third optimization i did was massively reduce the number of hearables on the station by making hologram projectors not hear if dont have an active call/anything that would make them need hearing. so one of hte most common non player hearables that require view iteration to find is crossed out.

also implements a variation of an idea oranges had on how to speed up get_hearers_in_view() now that ive realized that view() cant be replicated by a raycasting algorithm. it distributes pregenerated abstract /mob/oranges_ear instances to all hearables in range such that theres at max one per turf and then iterates through only those mobs to take advantage of type-specific view() optimizations and just adds up the references in each one to create the list of hearing atoms, then puts the oranges_ear mobs back into nullspace. this is about 2x as fast as the get_hearers_in_view() on master

holy FUCK its fast. like really fucking fast. the only costly part of the radio transmission pipeline i dont touch is mob/living/Hear() which takes ~100 microseconds on live but searching through every radio in the world with get_hearers_in_radio_ranges() -> get_hearers_in_view() is much faster, as well as the filtering radios step

the spatial grid searching proc is about 36 microseconds/call at 10 range and 16 microseconds at 3 range in the captains office (relatively many hearables in view), the new get_hearers_in_view() was 4.16 times faster than get_hearers_in_view_old() at 10 range and 4.59 times faster at 3 range

SSspatial_grid could be used for a lot more things other than just radio and say code, i just didnt implement it. for example since the cells are datums you could get all cells in a radius then register for new objects entering them then activate when a player enters your radius. this is something that would require either very expensive view() calls or iterating over every player in the global list and calling get_dist() on them which isnt that expensive but is still worse than it needs to be

on normal get_hearers_in_view cost the new version that uses /mob/oranges_ear instances is about 2x faster than the old version, especially since the number of hearing sensitive movables has been brought down dramatically.

with get_hearers_in_view_oranges_ear() being the benchmark proc that implements this system and get_hearers_in_view() being a slightly optimized version of the version we have on master, get_hearers_in_view_as() being a more optimized version of the one we have on master, and get_hearers_in_LOS() being the raycasting version currently only used for radios because it cant replicate view()'s behavior perfectly.
This commit is contained in:
Kylerace
2021-12-16 19:49:27 -08:00
committed by GitHub
parent 77cc11a671
commit d005d76f0b
71 changed files with 1907 additions and 784 deletions
+4
View File
@@ -76,6 +76,10 @@
/code/modules/wiremod/ @Watermelon914
# Kylerace
/code/controllers/subsystem/spatial_gridmap.dm @Kylerace
/code/__DEFINES/spatial_gridmap.dm @Kylerace
# CONTRIBUTORS
# Ghilker
@@ -8,6 +8,8 @@
///from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args)
#define COMSIG_GLOB_NEW_Z "!new_z"
/// sent after world.maxx and/or world.maxy are expanded: (has_exapnded_world_maxx, has_expanded_world_maxy)
#define COMSIG_GLOB_EXPANDED_WORLD_BOUNDS "!expanded_world_bounds"
/// called after a successful var edit somewhere in the world: (list/args)
#define COMSIG_GLOB_VAR_EDIT "!var_edit"
/// called after an explosion happened : (epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
@@ -0,0 +1,6 @@
//spatial grid signals
///Called from base of /datum/controller/subsystem/spatial_grid/proc/enter_cell: (/atom/movable)
#define SPATIAL_GRID_CELL_ENTERED(contents_type) "spatial_grid_cell_entered_[contents_type]"
///Called from base of /datum/controller/subsystem/spatial_grid/proc/exit_cell: (/atom/movable)
#define SPATIAL_GRID_CELL_EXITED(contents_type) "spatial_grid_cell_exited_[contents_type]"
@@ -2,3 +2,6 @@
#define RECURSIVE_CONTENTS_AREA_SENSITIVE "recursive_contents_area_sensitive"
///the hearing channel of the important_recursive_contents list, everything in here will count as a hearing atom
#define RECURSIVE_CONTENTS_HEARING_SENSITIVE "recursive_contents_hearing_sensitive"
///the client mobs channel of the important_recursive_contents list, everything in here will be a mob with an attached client
///this is given to both a clients mob, and a clients eye, both point to the clients mob
#define RECURSIVE_CONTENTS_CLIENT_MOBS "recursive_contents_client_mobs"
+2
View File
@@ -30,6 +30,8 @@
#define CEILING(x, y) ( -round(-(x) / (y)) * (y) )
#define ROUND_UP(x) ( -round(-(x)))
// round() acts like floor(x, 1) by default but can't handle other values
#define FLOOR(x, y) ( round((x) / (y)) * (y) )
+3
View File
@@ -118,3 +118,6 @@
#define REQ_DEP_TYPE_ASSISTANCE (1<<0)
#define REQ_DEP_TYPE_SUPPLIES (1<<1)
#define REQ_DEP_TYPE_INFORMATION (1<<2)
///give this to can_receive to specify that there is no restriction on what z level this signal is sent to
#define RADIO_NO_Z_LEVEL_RESTRICTION 0
+16
View File
@@ -0,0 +1,16 @@
///each cell in a spatial_grid is this many turfs in length and width
#define SPATIAL_GRID_CELLSIZE 17
#define SPATIAL_GRID_CELLS_PER_SIDE(world_bounds) ROUND_UP((world_bounds) / SPATIAL_GRID_CELLSIZE)
#define SPATIAL_GRID_CHANNELS 2
//grid contents channels
///everything that is hearing sensitive is stored in this channel
#define SPATIAL_GRID_CONTENTS_TYPE_HEARING RECURSIVE_CONTENTS_HEARING_SENSITIVE
///every movable that has a client in it is stored in this channel
#define SPATIAL_GRID_CONTENTS_TYPE_CLIENTS RECURSIVE_CONTENTS_CLIENT_MOBS
///whether movable is itself or containing something which should be in one of the spatial grid channels.
#define HAS_SPATIAL_GRID_CONTENTS(movable) (movable.important_recursive_contents && (movable.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] || movable.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
+1
View File
@@ -135,6 +135,7 @@
#define INIT_ORDER_EARLY_ASSETS 48
#define INIT_ORDER_TIMETRACK 47
#define INIT_ORDER_NETWORKS 45
#define INIT_ORDER_SPATIAL_GRID 43
#define INIT_ORDER_ECONOMY 40
#define INIT_ORDER_OUTPUTS 35
#define INIT_ORDER_RESTAURANT 34
+3 -1
View File
@@ -427,12 +427,14 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Used for limbs.
#define TRAIT_DISABLED_BY_WOUND "disabled-by-wound"
//important_recursive_contents traits
/*
* Used for movables that need to be updated, via COMSIG_ENTER_AREA and COMSIG_EXIT_AREA, when transitioning areas.
* Use [/atom/movable/proc/become_area_sensitive(trait_source)] to properly enable it. How you remove it isn't as important.
*/
#define TRAIT_AREA_SENSITIVE "area-sensitive"
///every hearing sensitive atom has this trait
#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
/// Climbable trait, given and taken by the climbable element when added or removed. Exists to be easily checked via HAS_TRAIT().
+1
View File
@@ -34,6 +34,7 @@
. += checked_atom
///Step-towards method of determining whether one atom can see another. Similar to viewers()
///note: this is a line of sight algorithm, view() does not do any sort of raycasting and cannot be emulated by it accurately
/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)
-221
View File
@@ -11,171 +11,12 @@
return null
return format_text ? format_text(checked_area.name) : checked_area.name
/**
* Returns a list with the names of the areas around a center at a certain distance
* Returns the local area if no distance is indicated
* Returns an empty list if the center is null
**/
/proc/get_areas_in_range(distance = 0, atom/center = usr)
if(!distance)
var/turf/center_turf = get_turf(center)
return center_turf ? list(center_turf.loc) : list()
if(!center)
return list()
var/list/turfs = RANGE_TURFS(distance, center)
var/list/areas = list()
for(var/turf/checked_turf as anything in turfs)
areas |= checked_turf.loc
return areas
///Returns a list of all areas that are adjacent to the center atom's area, clear the list of nulls at the end.
/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))
)
list_clear_nulls(.)
///Returns the open turf next to the center in a specific direction
/proc/get_open_turf_in_dir(atom/center, dir)
var/turf/open/get_turf = get_ranged_target_turf(center, dir, 1)
if(istype(get_turf))
return get_turf
///Returns a list with all the adjacent open turfs. Clears the list of nulls in the end.
/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)
)
list_clear_nulls(.)
///Returns a list with all the adjacent areas by getting the adjacent open turfs
/proc/get_adjacent_open_areas(atom/center)
. = list()
var/list/adjacent_turfs = get_adjacent_open_turfs(center)
for(var/near_turf in adjacent_turfs)
. |= get_area(near_turf)
/**
* 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
. = view(range, source)
source.luminosity = lum
///Checks if the mob provided (must_be_alone) is alone in an area
/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/carbon in GLOB.alive_mob_list)
if(!istype(carbon, check_type))
continue
if(carbon == must_be_alone)
continue
if(our_area == get_area(carbon))
return FALSE
return TRUE
//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.
///Calculate the hypotenuse cheaply (this should be in maths.dm)
/proc/cheap_hypotenuse(Ax, Ay, Bx, By)
return sqrt(abs(Ax - Bx) ** 2 + abs(Ay - By) ** 2) //A squared + B squared = C squared
///Returns all atoms present in a circle around the center
/proc/circle_range(center = usr,radius = 3)
var/turf/center_turf = get_turf(center)
var/list/atoms = new/list()
var/rsq = radius * (radius + 0.5)
for(var/atom/checked_atom as anything in range(radius, center_turf))
var/dx = checked_atom.x - center_turf.x
var/dy = checked_atom.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
atoms += checked_atom
return atoms
///Returns all atoms present in a circle around the center but uses view() instead of range() (Currently not used)
/proc/circle_view(center=usr,radius=3)
var/turf/center_turf = get_turf(center)
var/list/atoms = new/list()
var/rsq = radius * (radius + 0.5)
for(var/atom/checked_atom as anything in view(radius, center_turf))
var/dx = checked_atom.x - center_turf.x
var/dy = checked_atom.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
atoms += checked_atom
return atoms
///Returns the distance between two atoms
/proc/get_dist_euclidian(atom/first_location as turf|mob|obj, atom/second_location as turf|mob|obj)
var/dx = first_location.x - second_location.x
var/dy = first_location.y - second_location.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
///Returns a list of turfs around a center based on view()
/proc/circle_view_turfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()?
var/turf/center_turf = get_turf(center)
var/list/turfs = new/list()
var/rsq = radius * (radius + 0.5)
for(var/turf/checked_turf in view(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
/** recursive_organ_check
* inputs: first_object (object to start with)
* outputs:
@@ -213,68 +54,6 @@
return
/// Returns a list of hearers in view(view_radius) from source (ignoring luminosity). uses important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]
/proc/get_hearers_in_view(view_radius, atom/source)
var/turf/center_turf = get_turf(source)
. = list()
if(!center_turf)
return
var/lum = center_turf.luminosity
center_turf.luminosity = 6 // This is the maximum luminosity
for(var/atom/movable/movable in view(view_radius, center_turf))
var/list/recursive_contents = LAZYACCESS(movable.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
if(recursive_contents)
. += recursive_contents
SEND_SIGNAL(movable, COMSIG_ATOM_HEARER_IN_VIEW, .)
center_turf.luminosity = lum
/// Returns a list of mobs who can hear any of the radios given in @radios
/proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios)
. = list()
for(var/obj/item/radio/this_radio in radios)
. |= get_hearers_in_view(this_radio.canhear_range, this_radio)
///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(IS_OPAQUE_TURF(T))
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<X2)
b+=m
while(X1!=X2 || Y1!=Y2)
if(round(m*X1+b-Y1))
Y1+=signY //Line exits tile vertically
else
X1+=signX //Line exits tile horizontally
T=locate(X1,Y1,Z)
if(IS_OPAQUE_TURF(T))
return FALSE
return TRUE
/proc/is_in_sight(atom/first_atom, atom/second_atom)
var/turf/first_turf = get_turf(first_atom)
var/turf/second_turf = get_turf(second_atom)
if(!first_turf || !second_turf)
return FALSE
return inLineOfSight(first_turf.x, first_turf.y, second_turf.x, second_turf.y, first_turf.z)
///Tries to move an atom to an adjacent turf, return TRUE if successful
/proc/try_move_adjacent(atom/movable/atom_to_move, trydir)
var/turf/atom_turf = get_turf(atom_to_move)
+37 -27
View File
@@ -30,34 +30,44 @@
* Uses the ultra-fast [Bresenham Line-Drawing Algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm).
*/
/proc/get_line(atom/starting_atom, atom/ending_atom)
var/px = starting_atom.x //starting x
var/py = starting_atom.y
var/line[] = list(locate(px, py, starting_atom.z))
var/dx = ending_atom.x - px //x distance
var/dy = ending_atom.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 get_line() 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, starting_atom.z)//Add the turf to the list
var/current_x_step = starting_atom.x//start at x and y, then add 1 or -1 to these to get every turf from starting_atom to ending_atom
var/current_y_step = starting_atom.y
var/starting_z = starting_atom.z
var/list/line = list(get_turf(starting_atom))//get_turf(atom) is faster than locate(x, y, z)
var/x_distance = ending_atom.x - current_x_step //x distance
var/y_distance = ending_atom.y - current_y_step
var/abs_x_distance = abs(x_distance)//Absolute value of x distance
var/abs_y_distance = abs(y_distance)
var/x_distance_sign = SIGN(x_distance) //Sign of x distance (+ or -)
var/y_distance_sign = SIGN(y_distance)
var/x = abs_x_distance >> 1 //Counters for steps taken, setting to distance/2
var/y = abs_y_distance >> 1 //Bit-shifting makes me l33t. It also makes get_line() unnessecarrily fast.
if(abs_x_distance >= abs_y_distance) //x distance is greater than y
for(var/distance_counter in 0 to (abs_x_distance - 1))//It'll take abs_x_distance steps to get there
y += abs_y_distance
if(y >= abs_x_distance) //Every abs_y_distance steps, step once in y direction
y -= abs_x_distance
current_y_step += y_distance_sign
current_x_step += x_distance_sign //Step on in x direction
line += locate(current_x_step, current_y_step, starting_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, starting_atom.z)
for(var/distance_counter in 0 to (abs_y_distance - 1))
x += abs_x_distance
if(x >= abs_y_distance)
x -= abs_y_distance
current_x_step += x_distance_sign
current_y_step += y_distance_sign
line += locate(current_x_step, current_y_step, starting_z)
return line
///Format a power value in W, kW, MW, or GW.
+380
View File
@@ -0,0 +1,380 @@
/turf
///what /mob/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/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/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/oranges_ear
icon_state = null
density = FALSE
move_resist = INFINITY
invisibility = 0
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
logging = null
held_items = null //all of these are list objects that should not exist for something like us
faction = null
alerts = null
screens = null
client_colours = null
hud_possible = 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/oranges_ear/Initialize(mapload)
SHOULD_CALL_PARENT(FALSE)
return INITIALIZE_HINT_NORMAL
/mob/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/oranges_ear/Move()
SHOULD_CALL_PARENT(FALSE)
stack_trace("SOMEHOW A /mob/oranges_ear MOVED")
return FALSE
/mob/oranges_ear/abstract_move(atom/destination)
SHOULD_CALL_PARENT(FALSE)
stack_trace("SOMEHOW A /mob/oranges_ear MOVED")
return FALSE
/mob/oranges_ear/Bump()
SHOULD_CALL_PARENT(FALSE)
return FALSE
///clean this oranges_ear up for future use
/mob/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/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/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/oranges_ear/ear in view(view_radius, center_turf))
. += ear.references
for(var/mob/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(IS_OPAQUE_TURF(inbetween_turf))//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/radio/radios)
. = list()
// Returns a list of mobs who can hear any of the radios given in @radios
for(var/obj/item/radio/radio as anything in radios)
. |= get_hearers_in_LOS(radio.canhear_range, radio, FALSE)
///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(IS_OPAQUE_TURF(T))
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<X2)
b+=m
while(X1!=X2 || Y1!=Y2)
if(round(m*X1+b-Y1))
Y1+=signY //Line exits tile vertically
else
X1+=signX //Line exits tile horizontally
T=locate(X1,Y1,Z)
if(IS_OPAQUE_TURF(T))
return FALSE
return TRUE
/proc/is_in_sight(atom/first_atom, atom/second_atom)
var/turf/first_turf = get_turf(first_atom)
var/turf/second_turf = get_turf(second_atom)
if(!first_turf || !second_turf)
return FALSE
return inLineOfSight(first_turf.x, first_turf.y, second_turf.x, second_turf.y, first_turf.z)
///Returns all atoms present in a circle around the center
/proc/circle_range(center = usr,radius = 3)
var/turf/center_turf = get_turf(center)
var/list/atoms = new/list()
var/rsq = radius * (radius + 0.5)
for(var/atom/checked_atom as anything in range(radius, center_turf))
var/dx = checked_atom.x - center_turf.x
var/dy = checked_atom.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
atoms += checked_atom
return atoms
///Returns all atoms present in a circle around the center but uses view() instead of range() (Currently not used)
/proc/circle_view(center=usr,radius=3)
var/turf/center_turf = get_turf(center)
var/list/atoms = new/list()
var/rsq = radius * (radius + 0.5)
for(var/atom/checked_atom as anything in view(radius, center_turf))
var/dx = checked_atom.x - center_turf.x
var/dy = checked_atom.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
atoms += checked_atom
return atoms
///Returns the distance between two atoms
/proc/get_dist_euclidian(atom/first_location as turf|mob|obj, atom/second_location as turf|mob|obj)
var/dx = first_location.x - second_location.x
var/dy = first_location.y - second_location.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
///Returns a list of turfs around a center based on view()
/proc/circle_view_turfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()?
var/turf/center_turf = get_turf(center)
var/list/turfs = new/list()
var/rsq = radius * (radius + 0.5)
for(var/turf/checked_turf in view(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
/**
* 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
. = view(range, source)
source.luminosity = lum
///Returns the open turf next to the center in a specific direction
/proc/get_open_turf_in_dir(atom/center, dir)
var/turf/open/get_turf = get_ranged_target_turf(center, dir, 1)
if(istype(get_turf))
return get_turf
///Returns a list with all the adjacent open turfs. Clears the list of nulls in the end.
/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)
)
list_clear_nulls(.)
///Returns a list with all the adjacent areas by getting the adjacent open turfs
/proc/get_adjacent_open_areas(atom/center)
. = list()
var/list/adjacent_turfs = get_adjacent_open_turfs(center)
for(var/near_turf in adjacent_turfs)
. |= get_area(near_turf)
/**
* Returns a list with the names of the areas around a center at a certain distance
* Returns the local area if no distance is indicated
* Returns an empty list if the center is null
**/
/proc/get_areas_in_range(distance = 0, atom/center = usr)
if(!distance)
var/turf/center_turf = get_turf(center)
return center_turf ? list(center_turf.loc) : list()
if(!center)
return list()
var/list/turfs = RANGE_TURFS(distance, center)
var/list/areas = list()
for(var/turf/checked_turf as anything in turfs)
areas |= checked_turf.loc
return areas
///Returns a list of all areas that are adjacent to the center atom's area, clear the list of nulls at the end.
/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))
)
list_clear_nulls(.)
///Checks if the mob provided (must_be_alone) is alone in an area
/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/carbon in GLOB.alive_mob_list)
if(!istype(carbon, check_type))
continue
if(carbon == must_be_alone)
continue
if(our_area == get_area(carbon))
return FALSE
return TRUE
+21 -23
View File
@@ -194,35 +194,33 @@
C.previous_turf = posobj
C.last_parallax_shift = world.time
for(var/thing in C.parallax_layers)
var/atom/movable/screen/parallax_layer/L = thing
L.update_status(screenmob)
if (L.view_sized != C.view)
L.update_o(C.view)
for(var/atom/movable/screen/parallax_layer/parallax_layer as anything in C.parallax_layers)
parallax_layer.update_status(screenmob)
if (parallax_layer.view_sized != C.view)
parallax_layer.update_o(C.view)
if(L.absolute)
L.offset_x = -(posobj.x - SSparallax.planet_x_offset) * L.speed
L.offset_y = -(posobj.y - SSparallax.planet_y_offset) * L.speed
if(parallax_layer.absolute)
parallax_layer.offset_x = -(posobj.x - SSparallax.planet_x_offset) * parallax_layer.speed
parallax_layer.offset_y = -(posobj.y - SSparallax.planet_y_offset) * parallax_layer.speed
else
L.offset_x -= offset_x * L.speed
L.offset_y -= offset_y * L.speed
parallax_layer.offset_x -= offset_x * parallax_layer.speed
parallax_layer.offset_y -= offset_y * parallax_layer.speed
if(L.offset_x > 240)
L.offset_x -= 480
if(L.offset_x < -240)
L.offset_x += 480
if(L.offset_y > 240)
L.offset_y -= 480
if(L.offset_y < -240)
L.offset_y += 480
if(parallax_layer.offset_x > 240)
parallax_layer.offset_x -= 480
if(parallax_layer.offset_x < -240)
parallax_layer.offset_x += 480
if(parallax_layer.offset_y > 240)
parallax_layer.offset_y -= 480
if(parallax_layer.offset_y < -240)
parallax_layer.offset_y += 480
L.screen_loc = "CENTER-7:[round(L.offset_x,1)],CENTER-7:[round(L.offset_y,1)]"
parallax_layer.screen_loc = "CENTER-7:[round(parallax_layer.offset_x,1)],CENTER-7:[round(parallax_layer.offset_y,1)]"
/atom/movable/proc/update_parallax_contents()
if(length(client_mobs_in_contents))
for(var/mob/client_mob as anything in client_mobs_in_contents)
if(length(client_mob?.client?.parallax_layers) && client_mob.hud_used)
client_mob.hud_used.update_parallax()
for(var/mob/client_mob as anything in client_mobs_in_contents)
if(length(client_mob?.client?.parallax_layers) && client_mob.hud_used)
client_mob.hud_used.update_parallax()
/mob/proc/update_parallax_teleport() //used for arrivals shuttle
if(client?.eye && hud_used && length(client.parallax_layers))
+4
View File
@@ -41,14 +41,18 @@ SUBSYSTEM_DEF(parallax)
continue
for (movable_eye; isloc(movable_eye.loc) && !isturf(movable_eye.loc); movable_eye = movable_eye.loc);
//get the last movable holding the mobs eye
if(movable_eye == processing_client.movingmob)
if (MC_TICK_CHECK)
return
continue
//eye and the last recorded eye are different, and the last recorded eye isnt just the clients mob
if(!isnull(processing_client.movingmob))
LAZYREMOVE(processing_client.movingmob.client_mobs_in_contents, processing_client.mob)
LAZYADD(movable_eye.client_mobs_in_contents, processing_client.mob)
processing_client.movingmob = movable_eye
if (MC_TICK_CHECK)
return
@@ -0,0 +1,593 @@
///the subsystem creates this many [/mob/oranges_ear] mob instances during init. allocations that require more than this create more.
#define NUMBER_OF_PREGENERATED_ORANGES_EARS 2500
// macros meant specifically to add/remove movables from the hearing_contents and client_contents lists of
// /datum/spatial_grid_cell, when empty they become references to a single list in SSspatial_grid and when filled they become their own list
// this is to save memory without making them lazylists as that slows down iteration through them
#define GRID_CELL_ADD(cell_contents_list, movable_or_list) \
if(!length(cell_contents_list)) { \
cell_contents_list = list(); \
cell_contents_list += movable_or_list; \
} else { \
cell_contents_list += movable_or_list; \
};
#define GRID_CELL_SET(cell_contents_list, movable_or_list) \
if(!length(cell_contents_list)) { \
cell_contents_list = list(); \
cell_contents_list += movable_or_list; \
} else { \
cell_contents_list |= movable_or_list; \
};
//dont use these outside of SSspatial_grid's scope use the procs it has for this purpose
#define GRID_CELL_REMOVE(cell_contents_list, movable_or_list) \
cell_contents_list -= movable_or_list; \
if(!length(cell_contents_list)) {\
cell_contents_list = dummy_list; \
};
/**
* # Spatial Grid Cell
*
* used by [/datum/controller/subsystem/spatial_grid] to cover every z level so that the coordinates of every turf in the world corresponds to one of these in
* the subsystems list of grid cells by z level. each one of these contains content lists holding all atoms meeting a certain criteria that is in our borders.
* these datums shouldnt have significant behavior, they should just hold data. the lists are filled and emptied by the subsystem.
*/
/datum/spatial_grid_cell
///our x index in the list of cells. this is our index inside of our row list
var/cell_x
///our y index in the list of cells. this is the index of our row list inside of our z level grid
var/cell_y
///which z level we belong to, corresponding to the index of our gridmap in SSspatial_grid.grids_by_z_level
var/cell_z
//every data point in a grid cell is separated by usecase
//when empty, the contents lists of these grid cell datums are just references to a dummy list from SSspatial_grid
//this is meant to allow a great compromise between memory usage and speed.
//now orthogonal_range_search() doesnt need to check if the list is null and each empty list is taking 12 bytes instead of 24
//the only downside is that it needs to be switched over to a new list when it goes from 0 contents to > 0 contents and switched back on the opposite case
///every hearing sensitive movable inside this cell
var/list/hearing_contents
///every client possessed mob inside this cell
var/list/client_contents
/datum/spatial_grid_cell/New(cell_x, cell_y, cell_z)
. = ..()
src.cell_x = cell_x
src.cell_y = cell_y
src.cell_z = cell_z
//cache for sanic speed (lists are references anyways)
var/list/dummy_list = SSspatial_grid.dummy_list
if(length(dummy_list))
dummy_list.Cut()
stack_trace("SSspatial_grid.dummy_list had something inserted into it at some point! this is a problem as it is supposed to stay empty")
hearing_contents = dummy_list
client_contents = dummy_list
/datum/spatial_grid_cell/Destroy(force, ...)
if(force)//the response to someone trying to qdel this is a right proper fuck you
stack_trace("dont try to destroy spatial grid cells without a good reason. if you need to do it use force")
return
. = ..()
/**
* # Spatial Grid
*
* a gamewide grid of spatial_grid_cell datums, each "covering" [SPATIAL_GRID_CELLSIZE] ^ 2 turfs.
* each spatial_grid_cell datum stores information about what is inside its covered area, so that searches through that area dont have to literally search
* through all turfs themselves to know what is within it since view() calls are expensive, and so is iterating through stuff you dont want.
* this allows you to only go through lists of what you want very cheaply.
*
* you can also register to objects entering and leaving a spatial cell, this allows you to do things like stay idle until a player enters, so you wont
* have to use expensive view() calls or iteratite over the global list of players and call get_dist() on every one. which is fineish for a few things, but is
* k * n operations for k objects iterating through n players.
*
* currently this system is only designed for searching for relatively uncommon things, small subsets of /atom/movable.
* dont add stupid shit to the cells please, keep the information that the cells store to things that need to be searched for often
*
* as of right now this system operates on a subset of the important_recursive_contents list for atom/movable, specifically
* [RECURSIVE_CONTENTS_HEARING_SENSITIVE] and [RECURSIVE_CONTENTS_CLIENT_MOBS] because both are those are both 1. important and 2. commonly searched for
*/
SUBSYSTEM_DEF(spatial_grid)
can_fire = FALSE
init_order = INIT_ORDER_SPATIAL_GRID
name = "Spatial Grid"
///list of the spatial_grid_cell datums per z level, arranged in the order of y index then x index
var/list/grids_by_z_level = list()
///everything that spawns before us is added to this list until we initialize
var/list/waiting_to_add_by_type = list(RECURSIVE_CONTENTS_HEARING_SENSITIVE = list(), RECURSIVE_CONTENTS_CLIENT_MOBS = list())
var/cells_on_x_axis = 0
var/cells_on_y_axis = 0
///empty spatial grid cell content lists are just a reference to this instead of a standalone list to save memory without needed to check if its null when iterating
var/list/dummy_list = list()
///list of all of /mob/oranges_ear instances we have pregenerated for view() iteration speedup
var/list/mob/oranges_ear/pregenerated_oranges_ears = list()
///how many pregenerated /mob/oranges_ear instances currently exist. this should hopefully never exceed its starting value
var/number_of_oranges_ears = NUMBER_OF_PREGENERATED_ORANGES_EARS
/datum/controller/subsystem/spatial_grid/Initialize(start_timeofday)
. = ..()
cells_on_x_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxx)
cells_on_y_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxy)
for(var/datum/space_level/z_level as anything in SSmapping.z_list)
propogate_spatial_grid_to_new_z(null, z_level)
CHECK_TICK_HIGH_PRIORITY
//go through the pre init queue for anything waiting to be let in the grid
for(var/channel_type in waiting_to_add_by_type)
for(var/atom/movable/movable as anything in waiting_to_add_by_type[channel_type])
var/turf/movable_turf = get_turf(movable)
if(movable_turf)
enter_cell(movable, movable_turf)
UnregisterSignal(movable, COMSIG_PARENT_PREQDELETED)
waiting_to_add_by_type[channel_type] -= movable
pregenerate_more_oranges_ears(NUMBER_OF_PREGENERATED_ORANGES_EARS)
RegisterSignal(SSdcs, COMSIG_GLOB_NEW_Z, .proc/propogate_spatial_grid_to_new_z)
RegisterSignal(SSdcs, COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, .proc/after_world_bounds_expanded)
///add a movable to the pre init queue for whichever type is specified so that when the subsystem initializes they get added to the grid
/datum/controller/subsystem/spatial_grid/proc/enter_pre_init_queue(atom/movable/waiting_movable, type)
RegisterSignal(waiting_movable, COMSIG_PARENT_PREQDELETED, .proc/queued_item_deleted, override = TRUE)
//override because something can enter the queue for two different types but that is done through unrelated procs that shouldnt know about eachother
waiting_to_add_by_type[type] += waiting_movable
///removes an initialized and probably deleted movable from our pre init queue before we're initialized
/datum/controller/subsystem/spatial_grid/proc/remove_from_pre_init_queue(atom/movable/movable_to_remove, exclusive_type)
if(exclusive_type)
waiting_to_add_by_type[exclusive_type] -= movable_to_remove
var/waiting_movable_is_in_other_queues = FALSE//we need to check if this movable is inside the other queues
for(var/type in waiting_to_add_by_type)
if(movable_to_remove in waiting_to_add_by_type[type])
waiting_movable_is_in_other_queues = TRUE
if(!waiting_movable_is_in_other_queues)
UnregisterSignal(movable_to_remove, COMSIG_PARENT_PREQDELETED)
return
UnregisterSignal(movable_to_remove, COMSIG_PARENT_PREQDELETED)
for(var/type in waiting_to_add_by_type)
waiting_to_add_by_type[type] -= movable_to_remove
///if a movable is inside our pre init queue before we're initialized and it gets deleted we need to remove that reference with this proc
/datum/controller/subsystem/spatial_grid/proc/queued_item_deleted(atom/movable/movable_being_deleted)
SIGNAL_HANDLER
remove_from_pre_init_queue(movable_being_deleted, null)
///creates the spatial grid for a new z level
/datum/controller/subsystem/spatial_grid/proc/propogate_spatial_grid_to_new_z(datum/controller/subsystem/processing/dcs/fucking_dcs, datum/space_level/z_level)
SIGNAL_HANDLER
var/list/new_cell_grid = list()
grids_by_z_level += list(new_cell_grid)
for(var/y in 1 to cells_on_y_axis)
new_cell_grid += list(list())
for(var/x in 1 to cells_on_x_axis)
var/datum/spatial_grid_cell/cell = new(x, y, z_level.z_value)
new_cell_grid[y] += cell
///creates number_to_generate new oranges_ear's and adds them to the subsystems list of ears.
///i really fucking hope this never gets called after init :clueless:
/datum/controller/subsystem/spatial_grid/proc/pregenerate_more_oranges_ears(number_to_generate)
for(var/new_ear in 1 to number_to_generate)
pregenerated_oranges_ears += new/mob/oranges_ear(null)
number_of_oranges_ears = length(pregenerated_oranges_ears)
///allocate one [/mob/oranges_ear] mob per turf containing atoms_that_need_ears and give them a reference to every listed atom in their turf.
///if an oranges_ear is allocated to a turf that already has an oranges_ear then the second one fails to allocate (and gives the existing one the atom it was assigned to)
/datum/controller/subsystem/spatial_grid/proc/assign_oranges_ears(list/atoms_that_need_ears)
var/input_length = length(atoms_that_need_ears)
if(input_length > number_of_oranges_ears)
stack_trace("somehow, for some reason, more than the preset generated number of oranges ears was requested. thats fucking [number_of_oranges_ears]. this is not good that should literally never happen")
pregenerate_more_oranges_ears(input_length - number_of_oranges_ears)//im still gonna DO IT but ill complain about it
. = list()
///the next unallocated /mob/oranges_ear that we try to allocate to assigned_atom's turf
var/mob/oranges_ear/current_ear
///the next atom in atoms_that_need_ears an ear assigned to it
var/atom/assigned_atom
///the turf loc of the current assigned_atom. turfs are used to track oranges_ears already assigned to one location so we dont allocate more than one
///because allocating more than one oranges_ear to a given loc wastes view iterations
var/turf/turf_loc
for(var/current_ear_index in 1 to input_length)
assigned_atom = atoms_that_need_ears[current_ear_index]
turf_loc = get_turf(assigned_atom)
if(!turf_loc)
continue
current_ear = pregenerated_oranges_ears[current_ear_index]
if(turf_loc.assigned_oranges_ear)
turf_loc.assigned_oranges_ear.references += assigned_atom
continue //if theres already an oranges_ear mob at assigned_movable's turf we give assigned_movable to it instead and dont allocate ourselves
current_ear.references += assigned_atom
current_ear.loc = turf_loc //normally this is bad, but since this is meant to be as fast as possible we literally just need to exist there for view() to see us
turf_loc.assigned_oranges_ear = current_ear
. += current_ear
///adds cells to the grid for every z level when world.maxx or world.maxy is expanded after this subsystem is initialized. hopefully this is never needed.
///because i never tested this.
/datum/controller/subsystem/spatial_grid/proc/after_world_bounds_expanded(datum/controller/subsystem/processing/dcs/fucking_dcs, has_expanded_world_maxx, has_expanded_world_maxy)
SIGNAL_HANDLER
var/old_x_axis = cells_on_x_axis
var/old_y_axis = cells_on_y_axis
cells_on_x_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxx)
cells_on_y_axis = SPATIAL_GRID_CELLS_PER_SIDE(world.maxy)
for(var/z_level in 1 to length(grids_by_z_level))
var/list/z_level_gridmap = grids_by_z_level[z_level]
for(var/cell_row_for_expanded_y_axis in 1 to cells_on_y_axis)
if(cell_row_for_expanded_y_axis > old_y_axis)//we are past the old length of the number of rows, so add to the list
z_level_gridmap += list(list())
//now we know theres a row at this position, so add cells to it that need to be added and update the ones that already exist
var/list/cell_row = z_level_gridmap[cell_row_for_expanded_y_axis]
for(var/grid_cell_for_expanded_x_axis in 1 to cells_on_x_axis)
if(grid_cell_for_expanded_x_axis > old_x_axis)
var/datum/spatial_grid_cell/new_cell_inserted = new(grid_cell_for_expanded_x_axis, cell_row_for_expanded_y_axis, z_level)
cell_row += new_cell_inserted
continue
//now we know the cell index we're at contains an already existing cell that needs its x and y values updated
var/datum/spatial_grid_cell/old_cell_that_needs_updating = cell_row[grid_cell_for_expanded_x_axis]
old_cell_that_needs_updating.cell_x = grid_cell_for_expanded_x_axis
old_cell_that_needs_updating.cell_y = cell_row_for_expanded_y_axis
///the left or bottom side index of a box composed of spatial grid cells with the given actual center x or y coordinate
#define BOUNDING_BOX_MIN(center_coord) max(ROUND_UP((center_coord - range) / SPATIAL_GRID_CELLSIZE), 1)
///the right or upper side index of a box composed of spatial grid cells with the given center x or y coordinate.
///outputted value cant exceed the number of cells on that axis
#define BOUNDING_BOX_MAX(center_coord, axis_size) min(ROUND_UP((center_coord + range) / SPATIAL_GRID_CELLSIZE), axis_size)
/**
* https://en.wikipedia.org/wiki/Range_searching#Orthogonal_range_searching
*
* searches through the grid cells intersecting a rectangular search space (with sides of length 2 * range) then returns all contents of type inside them.
* much faster than iterating through view() to find all of what you want.
*
* this does NOT return things only in range distance from center! the search space is a square not a circle, if you want only things in a certain distance
* then you need to filter that yourself
*
* * center - the atom that is the center of the searched circle
* * type - the type of grid contents you are looking for, see __DEFINES/spatial_grid.dm
* * range - the bigger this is, the more spatial grid cells the search space intersects
*/
/datum/controller/subsystem/spatial_grid/proc/orthogonal_range_search(atom/center, type, range)
var/turf/center_turf = get_turf(center)
var/center_x = center_turf.x//used inside the macros
var/center_y = center_turf.y
. = list()
//cache for sanic speeds
var/cells_on_y_axis = src.cells_on_y_axis
var/cells_on_x_axis = src.cells_on_x_axis
//technically THIS list only contains lists, but inside those lists are grid cell datums and we can go without a SINGLE var init if we do this
var/list/datum/spatial_grid_cell/grid_level = grids_by_z_level[center_turf.z]
switch(type)
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
for(var/row in BOUNDING_BOX_MIN(center_y) to BOUNDING_BOX_MAX(center_y, cells_on_y_axis))
for(var/x_index in BOUNDING_BOX_MIN(center_x) to BOUNDING_BOX_MAX(center_x, cells_on_x_axis))
. += grid_level[row][x_index].client_contents
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
for(var/row in BOUNDING_BOX_MIN(center_y) to BOUNDING_BOX_MAX(center_y, cells_on_y_axis))
for(var/x_index in BOUNDING_BOX_MIN(center_x) to BOUNDING_BOX_MAX(center_x, cells_on_x_axis))
. += grid_level[row][x_index].hearing_contents
return .
///get the grid cell encomapassing targets coordinates
/datum/controller/subsystem/spatial_grid/proc/get_cell_of(atom/target)
var/turf/target_turf = get_turf(target)
if(!target_turf)
return
return grids_by_z_level[target_turf.z][ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)][ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)]
///get all grid cells intersecting the bounding box around center with sides of length 2 * range
/datum/controller/subsystem/spatial_grid/proc/get_cells_in_range(atom/center, range)
var/turf/center_turf = get_turf(center)
var/center_x = center_turf.x
var/center_y = center_turf.y
var/list/intersecting_grid_cells = list()
//the minimum x and y cell indexes to test
var/min_x = max(ROUND_UP((center_x - range) / SPATIAL_GRID_CELLSIZE), 1)
var/min_y = max(ROUND_UP((center_y - range) / SPATIAL_GRID_CELLSIZE), 1)//calculating these indices only takes around 2 microseconds
//the maximum x and y cell indexes to test
var/max_x = min(ROUND_UP((center_x + range) / SPATIAL_GRID_CELLSIZE), cells_on_x_axis)
var/max_y = min(ROUND_UP((center_y + range) / SPATIAL_GRID_CELLSIZE), cells_on_y_axis)
var/list/grid_level = grids_by_z_level[center_turf.z]
for(var/row in min_y to max_y)
var/list/grid_row = grid_level[row]
for(var/x_index in min_x to max_x)
intersecting_grid_cells += grid_row[x_index]
return intersecting_grid_cells
///find the spatial map cell that target belongs to, then add target's important_recusive_contents to it.
///make sure to provide the turf new_target is "in"
/datum/controller/subsystem/spatial_grid/proc/enter_cell(atom/movable/new_target, turf/target_turf)
if(!target_turf || !new_target?.important_recursive_contents)
CRASH("/datum/controller/subsystem/spatial_grid/proc/enter_cell() was given null arguments or a new_target without important_recursive_contents!")
var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
var/z_index = target_turf.z
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
if(new_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
GRID_CELL_SET(intersecting_cell.client_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(RECURSIVE_CONTENTS_CLIENT_MOBS), new_target)
if(new_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE])
GRID_CELL_SET(intersecting_cell.hearing_contents, new_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(RECURSIVE_CONTENTS_HEARING_SENSITIVE), new_target)
/**
* find the spatial map cell that target used to belong to, then subtract target's important_recusive_contents from it.
* make sure to provide the turf old_target used to be "in"
*
* * old_target - the thing we want to remove from the spatial grid cell
* * target_turf - the turf we use to determine the cell we're removing from
* * exclusive_type - either null or a valid contents channel. if you just want to remove a single type from the grid cell then use this
*/
/datum/controller/subsystem/spatial_grid/proc/exit_cell(atom/movable/old_target, turf/target_turf, exclusive_type)
if(!target_turf || !old_target?.important_recursive_contents)
CRASH("/datum/controller/subsystem/spatial_grid/proc/exit_cell() was given null arguments or a new_target without important_recursive_contents!")
var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
var/z_index = target_turf.z
var/list/grid = grids_by_z_level[z_index]
var/datum/spatial_grid_cell/intersecting_cell = grid[y_index][x_index]
if(exclusive_type && old_target.important_recursive_contents[exclusive_type])
switch(exclusive_type)
if(RECURSIVE_CONTENTS_CLIENT_MOBS)
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
if(RECURSIVE_CONTENTS_HEARING_SENSITIVE)
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target)
return
if(old_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), old_target)
if(old_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE])
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target.important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(RECURSIVE_CONTENTS_HEARING_SENSITIVE), old_target)
///find the cell this movable is associated with and removes it from all lists
/datum/controller/subsystem/spatial_grid/proc/force_remove_from_cell(atom/movable/to_remove, datum/spatial_grid_cell/input_cell)
if(!initialized)
remove_from_pre_init_queue(to_remove)//the spatial grid doesnt exist yet, so just take it out of the queue
return
if(!input_cell)
input_cell = get_cell_of(to_remove)
if(!input_cell)
find_hanging_cell_refs_for_movable(to_remove, TRUE)
return
GRID_CELL_REMOVE(input_cell.client_contents, to_remove)
GRID_CELL_REMOVE(input_cell.hearing_contents, to_remove)
///if shit goes south, this will find hanging references for qdeleting movables inside the spatial grid
/datum/controller/subsystem/spatial_grid/proc/find_hanging_cell_refs_for_movable(atom/movable/to_remove, remove_from_cells = TRUE)
var/list/queues_containing_movable = list()
for(var/queue_channel in waiting_to_add_by_type)
var/list/queue_list = waiting_to_add_by_type[queue_channel]
if(to_remove in queue_list)
queues_containing_movable += queue_channel//just add the associative key
if(remove_from_cells)
queue_list -= to_remove
if(!initialized)
return queues_containing_movable
var/list/containing_cells = list()
for(var/list/z_level_grid as anything in grids_by_z_level)
for(var/list/cell_row as anything in z_level_grid)
for(var/datum/spatial_grid_cell/cell as anything in cell_row)
if(to_remove in (cell.hearing_contents | cell.client_contents))
containing_cells += cell
if(remove_from_cells)
force_remove_from_cell(to_remove, cell)
return containing_cells
///debug proc for checking if a movable is in multiple cells when it shouldnt be (ie always unless multitile entering is implemented)
/atom/proc/find_all_cells_containing(remove_from_cells = FALSE)
var/datum/spatial_grid_cell/real_cell = SSspatial_grid.get_cell_of(src)
var/list/containing_cells = SSspatial_grid.find_hanging_cell_refs_for_movable(src, FALSE, remove_from_cells)
message_admins("[src] is located in the contents of [length(containing_cells)] spatial grid cells")
var/cell_coords = "the following cells contain [src]: "
for(var/datum/spatial_grid_cell/cell as anything in containing_cells)
cell_coords += "([cell.cell_x], [cell.cell_y], [cell.cell_z]), "
message_admins(cell_coords)
message_admins("[src] is supposed to only be contained in the cell at indexes ([real_cell.cell_x], [real_cell.cell_y], [real_cell.cell_z]). but is contained at the cells at [cell_coords]")
///debug proc for finding how full the cells of src's z level are
/atom/proc/find_grid_statistics_for_z_level(insert_clients = 0)
var/raw_clients = 0
var/raw_hearables = 0
var/cells_with_clients = 0
var/cells_with_hearables = 0
var/list/client_list = list()
var/list/hearable_list = list()
var/total_cells = (world.maxx / SPATIAL_GRID_CELLSIZE) ** 2
var/average_clients_per_cell = 0
var/average_hearables_per_cell = 0
var/hearable_min_x = (world.maxx / SPATIAL_GRID_CELLSIZE)
var/hearable_max_x = 1
var/hearable_min_y = (world.maxy / SPATIAL_GRID_CELLSIZE)
var/hearable_max_y = 1
var/client_min_x = (world.maxx / SPATIAL_GRID_CELLSIZE)
var/client_max_x = 1
var/client_min_y = (world.maxy / SPATIAL_GRID_CELLSIZE)
var/client_max_y = 1
var/list/inserted_clients = list()
if(insert_clients)
var/list/turfs
var/level = SSmapping.get_level(z)
if(is_station_level(level))
turfs = GLOB.station_turfs
else
turfs = block(locate(1,1,z), locate(world.maxx, world.maxy, z))
for(var/client_to_insert in 0 to insert_clients)
var/turf/random_turf = pick(turfs)
var/mob/fake_client = new()
fake_client.important_recursive_contents = list(SPATIAL_GRID_CONTENTS_TYPE_HEARING = list(fake_client), SPATIAL_GRID_CONTENTS_TYPE_CLIENTS = list(fake_client))
fake_client.forceMove(random_turf)
inserted_clients += fake_client
var/list/all_z_level_cells = SSspatial_grid.get_cells_in_range(src, 1000)
for(var/datum/spatial_grid_cell/cell as anything in all_z_level_cells)
var/client_length = length(cell.client_contents)
var/hearable_length = length(cell.hearing_contents)
raw_clients += client_length
raw_hearables += hearable_length
if(client_length)
cells_with_clients++
client_list += cell.client_contents
if(cell.cell_x < client_min_x)
client_min_x = cell.cell_x
if(cell.cell_x > client_max_x)
client_max_x = cell.cell_x
if(cell.cell_y < client_min_y)
client_min_y = cell.cell_y
if(cell.cell_y > client_max_y)
client_max_y = cell.cell_y
if(hearable_length)
cells_with_hearables++
hearable_list += cell.hearing_contents
if(cell.cell_x < hearable_min_x)
hearable_min_x = cell.cell_x
if(cell.cell_x > hearable_max_x)
hearable_max_x = cell.cell_x
if(cell.cell_y < hearable_min_y)
hearable_min_y = cell.cell_y
if(cell.cell_y > hearable_max_y)
hearable_max_y = cell.cell_y
var/total_client_distance = 0
var/total_hearable_distance = 0
var/average_client_distance = 0
var/average_hearable_distance = 0
for(var/hearable in hearable_list)//n^2 btw
for(var/other_hearable in hearable_list)
if(hearable == other_hearable)
continue
total_hearable_distance += get_dist(hearable, other_hearable)
for(var/client in client_list)//n^2 btw
for(var/other_client in client_list)
if(client == other_client)
continue
total_client_distance += get_dist(client, other_client)
if(length(hearable_list))
average_hearable_distance = total_hearable_distance / length(hearable_list)
if(length(client_list))
average_client_distance = total_client_distance / length(client_list)
average_clients_per_cell = raw_clients / total_cells
average_hearables_per_cell = raw_hearables / total_cells
for(var/mob/inserted_client as anything in inserted_clients)
qdel(inserted_client)
message_admins("on z level [z] there are [raw_clients] clients ([insert_clients] of whom are fakes inserted to random station turfs) \
and [raw_hearables] hearables. all of whom are inside the bounding box given by \
clients: ([client_min_x], [client_min_y]) x ([client_max_x], [client_max_y]) \
and hearables: ([hearable_min_x], [hearable_min_y]) x ([hearable_max_x], [hearable_max_y]) \
on average there are [average_clients_per_cell] clients per cell and [average_hearables_per_cell] hearables per cell. \
[cells_with_clients] cells have clients and [cells_with_hearables] have hearables, \
the average client distance is: [average_client_distance] and the average hearable_distance is [average_hearable_distance].")
#undef GRID_CELL_ADD
#undef GRID_CELL_REMOVE
#undef GRID_CELL_SET
+2 -1
View File
@@ -36,7 +36,8 @@
/datum/component/mood/Destroy()
STOP_PROCESSING(SSmood, src)
REMOVE_TRAIT(parent, TRAIT_AREA_SENSITIVE, MOOD_COMPONENT_TRAIT)
var/atom/movable/movable_parent = parent
movable_parent.lose_area_sensitivity(MOOD_COMPONENT_TRAIT)
unmodify_hud()
return ..()
+5 -8
View File
@@ -19,7 +19,7 @@
/// Active timers with this datum as the target
var/list/active_timers
/// Status traits attached to this datum
/// Status traits attached to this datum. associative list of the form: list(trait name (string) = list(source1, source2, source3,...))
var/list/status_traits
/**
@@ -98,8 +98,7 @@
var/list/timers = active_timers
active_timers = null
for(var/thing in timers)
var/datum/timedevent/timer = thing
for(var/datum/timedevent/timer as anything in timers)
if (timer.spent && !(timer.flags & TIMER_DELETE_ME))
continue
qdel(timer)
@@ -115,9 +114,8 @@
if(dc)
var/all_components = dc[/datum/component]
if(length(all_components))
for(var/I in all_components)
var/datum/component/C = I
qdel(C, FALSE, TRUE)
for(var/datum/component/component as anything in all_components)
qdel(component, FALSE, TRUE)
else
var/datum/component/C = all_components
qdel(C, FALSE, TRUE)
@@ -136,8 +134,7 @@
for(var/sig in lookup)
var/list/comps = lookup[sig]
if(length(comps))
for(var/i in comps)
var/datum/component/comp = i
for(var/datum/component/comp as anything in comps)
comp.UnregisterSignal(src, sig)
else
var/datum/component/comp = comps
+6 -2
View File
@@ -59,7 +59,9 @@
if(current_area)
exit_area(source, current_area)
beauty_counter -= source
REMOVE_TRAIT(source, TRAIT_AREA_SENSITIVE, BEAUTY_ELEMENT_TRAIT)
var/atom/movable/movable_source = source
if(istype(movable_source))
movable_source.lose_area_sensitivity(BEAUTY_ELEMENT_TRAIT)
else //lower the 'counter' down by one, update the area, and call parent if it's reached zero.
beauty_counter[source]--
if(current_area && !current_area.outdoors)
@@ -69,4 +71,6 @@
. = ..()
UnregisterSignal(source, list(COMSIG_ENTER_AREA, COMSIG_EXIT_AREA))
beauty_counter -= source
REMOVE_TRAIT(source, TRAIT_AREA_SENSITIVE, BEAUTY_ELEMENT_TRAIT)
var/atom/movable/movable_source = source
if(istype(movable_source))
movable_source.lose_area_sensitivity(BEAUTY_ELEMENT_TRAIT)
+54 -48
View File
@@ -22,17 +22,25 @@
//this datum manages it's own references
/datum/holocall
var/mob/living/user //the one that called
var/obj/machinery/holopad/calling_holopad //the one that sent the call
var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null)
var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered
///the one that called
var/mob/living/user
///the holopad that sent the call to another holopad
var/obj/machinery/holopad/calling_holopad
///the one that answered the call (may be null)
var/obj/machinery/holopad/connected_holopad
///populated with all holopads that are either being dialed or have that have answered us, will be cleared out to just connected_holopad once answered
var/list/dialed_holopads
var/mob/camera/ai_eye/remote/holo/eye //user's eye, once connected
var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected
var/datum/action/innate/end_holocall/hangup //hangup action
///user's eye, once connected
var/mob/camera/ai_eye/remote/holo/eye
///user's hologram, once connected
var/obj/effect/overlay/holo_pad_hologram/hologram
///hangup action
var/datum/action/innate/end_holocall/hangup
var/call_start_time
var/head_call = FALSE //calls from a head of staff autoconnect, if the receiving pad is not secure.
///calls from a head of staff autoconnect, if the receiving pad is not secure.
var/head_call = FALSE
//creates a holocall made by `caller` from `calling_pad` to `callees`
/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE)
@@ -43,19 +51,18 @@
head_call = elevated_access
dialed_holopads = list()
for(var/I in callees)
var/obj/machinery/holopad/H = I
if(!QDELETED(H) && H.is_operational)
dialed_holopads += H
for(var/obj/machinery/holopad/connected_holopad as anything in callees)
if(!QDELETED(connected_holopad) && connected_holopad.is_operational)
dialed_holopads += connected_holopad
if(head_call)
if(H.secure)
if(connected_holopad.secure)
calling_pad.say("Auto-connection refused, falling back to call mode.")
H.say("Incoming call.")
connected_holopad.say("Incoming call.")
else
H.say("Incoming connection.")
connected_holopad.say("Incoming connection.")
else
H.say("Incoming call.")
LAZYADD(H.holo_calls, src)
connected_holopad.say("Incoming call.")
connected_holopad.set_holocall(src)
if(!dialed_holopads.len)
calling_pad.say("Connection failure.")
@@ -83,12 +90,12 @@
QDEL_NULL(hologram)
hologram = null
for(var/I in dialed_holopads)
var/obj/machinery/holopad/H = I
LAZYREMOVE(H.holo_calls, src)
for(var/obj/machinery/holopad/dialed_holopad as anything in dialed_holopads)
dialed_holopad.set_holocall(src, FALSE)
dialed_holopads.Cut()
if(calling_holopad)
if(calling_holopad)//if the call is answered, then calling_holopad wont be in dialed_holopads and thus wont have set_holocall(src, FALSE) called
calling_holopad.calling = FALSE
calling_holopad.outgoing_call = null
calling_holopad.SetLightsAndPower()
@@ -112,75 +119,74 @@
ConnectionFailure(H, TRUE)
//Forcefully disconnects a holopad `H` from a call. Pads not in the call are ignored.
/datum/holocall/proc/ConnectionFailure(obj/machinery/holopad/H, graceful = FALSE)
//Forcefully disconnects disconnected_holopad from a call. Pads not in the call are ignored.
/datum/holocall/proc/ConnectionFailure(obj/machinery/holopad/disconnected_holopad, graceful = FALSE)
testing("Holocall connection failure: graceful [graceful]")
if(H == connected_holopad || H == calling_holopad)
if(!graceful && H != calling_holopad)
if(disconnected_holopad == connected_holopad || disconnected_holopad == calling_holopad)
if(!graceful && disconnected_holopad != calling_holopad)
calling_holopad.say("Connection failure.")
qdel(src)
return
LAZYREMOVE(H.holo_calls, src)
dialed_holopads -= H
disconnected_holopad.set_holocall(src, FALSE)
dialed_holopads -= disconnected_holopad
if(!dialed_holopads.len)
if(graceful)
calling_holopad.say("Call rejected.")
testing("No recipients, terminating")
qdel(src)
//Answers a call made to a holopad `H` which cannot be the calling holopad. Pads not in the call are ignored
/datum/holocall/proc/Answer(obj/machinery/holopad/H)
///Answers a call made to answering_holopad which cannot be the calling holopad. Pads not in the call are ignored
/datum/holocall/proc/Answer(obj/machinery/holopad/answering_holopad)
testing("Holocall answer")
if(H == calling_holopad)
if(answering_holopad == calling_holopad)
CRASH("How cute, a holopad tried to answer itself.")
if(!(H in dialed_holopads))
if(!(answering_holopad in dialed_holopads))
return
if(connected_holopad)
CRASH("Multi-connection holocall")
for(var/I in dialed_holopads)
if(I == H)
for(var/obj/machinery/holopad/other_dialed_holopad as anything in dialed_holopads)
if(other_dialed_holopad == answering_holopad)
continue
Disconnect(I)
Disconnect(other_dialed_holopad)
for(var/I in H.holo_calls)
var/datum/holocall/HC = I
if(HC != src)
HC.Disconnect(H)
for(var/datum/holocall/previously_answered_holocall as anything in answering_holopad.holo_calls)//disconnect the other holocalls answering_holopad is occupied with
if(previously_answered_holocall != src)
previously_answered_holocall.Disconnect(answering_holopad)
connected_holopad = H
connected_holopad = answering_holopad
if(!Check())
return
calling_holopad.calling = FALSE
hologram = H.activate_holo(user)
hologram = answering_holopad.activate_holo(user)
hologram.HC = src
//eyeobj code is horrid, this is the best copypasta I could make
eye = new
eye.origin = H
eye.origin = answering_holopad
eye.eye_initialized = TRUE
eye.eye_user = user
eye.name = "Camera Eye ([user.name])"
user.remote_control = eye
user.reset_perspective(eye)
eye.setLoc(H.loc)
eye.setLoc(answering_holopad.loc)
hangup = new(eye, src)
hangup.Grant(user)
playsound(H, 'sound/machines/ping.ogg', 100)
H.say("Connection established.")
playsound(answering_holopad, 'sound/machines/ping.ogg', 100)
answering_holopad.say("Connection established.")
//Checks the validity of a holocall and qdels itself if it's not. Returns TRUE if valid, FALSE otherwise
/datum/holocall/proc/Check()
for(var/I in dialed_holopads)
var/obj/machinery/holopad/H = I
if(!H.is_operational)
ConnectionFailure(H)
for(var/obj/machinery/holopad/dialed_holopad as anything in dialed_holopads)
if(!dialed_holopad.is_operational)
ConnectionFailure(dialed_holopad)
if(QDELETED(src))
return FALSE
+1 -1
View File
@@ -110,7 +110,7 @@
randomize()
/datum/wires/proc/repair()
cut_wires.Cut()
cut_wires.Cut()//a negative times a negative equals a positive
/datum/wires/proc/get_wire(color)
return colors[color]
+4 -4
View File
@@ -19,9 +19,9 @@
var/obj/item/radio/R = holder
switch(index)
if(WIRE_SIGNAL)
R.listening = !R.listening
R.broadcasting = R.listening
R.set_listening(!R.get_listening())
R.set_broadcasting(R.get_listening())
if(WIRE_RX)
R.listening = !R.listening
R.set_listening(!R.get_listening())
if(WIRE_TX)
R.broadcasting = !R.broadcasting
R.set_broadcasting(!R.get_broadcasting())
+88 -20
View File
@@ -36,14 +36,16 @@
var/generic_canpass = TRUE
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
var/list/client_mobs_in_contents // This contains all the client mobs within this container
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
/**
* an associative lazylist of relevant nested contents by "channel", the list is of the form: list(channel = list(important nested contents of that type))
* each channel has a specific purpose and is meant to replace potentially expensive nested contents iteration
* each channel has a specific purpose and is meant to replace potentially expensive nested contents iteration.
* do NOT add channels to this for little reason as it can add considerable memory usage.
*/
var/list/important_recursive_contents
///contains every client mob corresponding to every client eye in this container. lazily updated by SSparallax and is sparse:
///only the last container of a client eye has this list assuming no movement since SSparallax's last fire
var/list/client_mobs_in_contents
/**
* In case you have multiple types, you automatically use the most useful one.
@@ -81,7 +83,6 @@
/// The degree of pressure protection that mobs in list/contents have from the external environment, between 0 and 1
var/contents_pressure_protection = 0
/atom/movable/Initialize(mapload)
. = ..()
switch(blocks_emissive)
@@ -132,19 +133,20 @@
orbiting.end_orbit(src)
orbiting = null
if(important_recursive_contents && (important_recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] || important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
SSspatial_grid.force_remove_from_cell(src)
LAZYCLEARLIST(client_mobs_in_contents)
LAZYCLEARLIST(important_recursive_contents)//has to be before moveToNullspace() so that we can exit our spatial_grid cell if we're in it
. = ..()
for(var/movable_content in contents)
qdel(movable_content)
LAZYCLEARLIST(client_mobs_in_contents)
moveToNullspace()
//We add ourselves to this list, best to clear it out
//DO it after moveToNullspace so memes can be had
LAZYCLEARLIST(important_recursive_contents)
vis_locs = null //clears this atom out of all viscontents
vis_contents.Cut()
@@ -645,7 +647,7 @@
if (!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
newtonian_move(movement_dir)
if (length(client_mobs_in_contents))
if (client_mobs_in_contents)
update_parallax_contents()
move_stacks--
@@ -663,6 +665,20 @@
if (old_turf?.z != new_turf?.z)
on_changed_z_level(old_turf, new_turf)
if(HAS_SPATIAL_GRID_CONTENTS(src))
if(old_turf && new_turf && (old_turf.z != new_turf.z \
|| ROUND_UP(old_turf.x / SPATIAL_GRID_CELLSIZE) != ROUND_UP(new_turf.x / SPATIAL_GRID_CELLSIZE) \
|| ROUND_UP(old_turf.y / SPATIAL_GRID_CELLSIZE) != ROUND_UP(new_turf.y / SPATIAL_GRID_CELLSIZE)))
SSspatial_grid.exit_cell(src, old_turf)
SSspatial_grid.enter_cell(src, new_turf)
else if(old_turf && !new_turf)
SSspatial_grid.exit_cell(src, old_turf)
else if(new_turf && !old_turf)
SSspatial_grid.enter_cell(src, new_turf)
return TRUE
// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
@@ -745,33 +761,85 @@
///allows this movable to hear and adds itself to the important_recursive_contents list of itself and every movable loc its in
/atom/movable/proc/become_hearing_sensitive(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_HEARING_SENSITIVE), .proc/on_hearing_sensitive_trait_loss)
//RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_HEARING_SENSITIVE), .proc/on_hearing_sensitive_trait_loss)
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, src)
var/turf/our_turf = get_turf(src)
if(our_turf && SSspatial_grid.initialized)
SSspatial_grid.enter_cell(src, our_turf)
else if(our_turf && !SSspatial_grid.initialized)//SSspatial_grid isnt init'd yet, add ourselves to the queue
SSspatial_grid.enter_pre_init_queue(src, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
/**
* removes the hearing sensitivity channel from the important_recursive_contents list of this and all nested locs containing us if there are no more sources of the trait left
* since RECURSIVE_CONTENTS_HEARING_SENSITIVE is also a spatial grid content type, removes us from the spatial grid if the trait is removed
*
* * trait_source - trait source define or ALL, if ALL, force removes hearing sensitivity. if a trait source define, removes hearing sensitivity only if the trait is removed
*/
/atom/movable/proc/lose_hearing_sensitivity(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
return
REMOVE_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
if(HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
return
var/turf/our_turf = get_turf(src)
if(our_turf && SSspatial_grid.initialized)
SSspatial_grid.exit_cell(src, our_turf)
else if(our_turf && !SSspatial_grid.initialized)
SSspatial_grid.remove_from_pre_init_queue(src, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, src)
///allows this movable to know when it has "entered" another area no matter how many movable atoms its stuffed into, uses important_recursive_contents
/atom/movable/proc/become_area_sensitive(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_AREA_SENSITIVE), .proc/on_area_sensitive_trait_loss)
//RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_AREA_SENSITIVE), .proc/on_area_sensitive_trait_loss)
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
ADD_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
/atom/movable/proc/on_area_sensitive_trait_loss()
SIGNAL_HANDLER
///removes the area sensitive channel from the important_recursive_contents list of this and all nested locs containing us if there are no more source of the trait left
/atom/movable/proc/lose_area_sensitivity(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
return
REMOVE_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
if(HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
return
UnregisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_AREA_SENSITIVE))
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
/atom/movable/proc/on_hearing_sensitive_trait_loss()
SIGNAL_HANDLER
///propogates new_client's mob through our nested contents, similar to other important_recursive_contents procs
///main difference is that client contents need to possibly duplicate recursive contents for the clients mob AND its eye
/atom/movable/proc/enable_client_mobs_in_contents(client/new_client)
var/turf/our_turf = get_turf(src)
UnregisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_HEARING_SENSITIVE))
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, src)
if(our_turf && SSspatial_grid.initialized)
SSspatial_grid.enter_cell(src, our_turf, RECURSIVE_CONTENTS_CLIENT_MOBS)
else if(our_turf && !SSspatial_grid.initialized)
SSspatial_grid.enter_pre_init_queue(src, RECURSIVE_CONTENTS_CLIENT_MOBS)
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
LAZYORASSOCLIST(movable_loc.important_recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS, new_client.mob)
///Clears the clients channel of this movables important_recursive_contents list and all nested locs
/atom/movable/proc/clear_important_client_contents(client/former_client)
var/turf/our_turf = get_turf(src)
if(our_turf && SSspatial_grid.initialized)
SSspatial_grid.exit_cell(src, our_turf, RECURSIVE_CONTENTS_CLIENT_MOBS)
else if(our_turf && !SSspatial_grid.initialized)
SSspatial_grid.remove_from_pre_init_queue(src, RECURSIVE_CONTENTS_CLIENT_MOBS)
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
LAZYREMOVEASSOC(movable_loc.important_recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS, former_client.mob)
///Sets the anchored var and returns if it was sucessfully changed or not.
/atom/movable/proc/set_anchored(anchorvalue)
+1
View File
@@ -64,6 +64,7 @@
so i made radios not use the radio controller.
*/
GLOBAL_LIST_EMPTY(all_radios)
/proc/add_radio(obj/item/radio, freq)
if(!freq || !radio)
return
+1 -1
View File
@@ -208,7 +208,7 @@
if(our_area)
UnregisterSignal(our_area, COMSIG_AREA_POWER_CHANGE)
REMOVE_TRAIT(src, TRAIT_AREA_SENSITIVE, INNATE_TRAIT)
lose_area_sensitivity(INNATE_TRAIT)
UnregisterSignal(src, COMSIG_ENTER_AREA)
UnregisterSignal(src, COMSIG_EXIT_AREA)
+1
View File
@@ -17,6 +17,7 @@
radio = new(src)
radio.subspace_transmission = TRUE
radio.canhear_range = 0
radio.set_listening(FALSE)
radio.recalculateChannels()
/obj/machinery/computer/bank_machine/Destroy()
+1 -1
View File
@@ -277,7 +277,7 @@
. = ..()
radio = new(src)
radio.keyslot = new radio_key
radio.listening = FALSE
radio.set_listening(FALSE)
radio.recalculateChannels()
/obj/item/bounty_cube/Destroy()
+1 -1
View File
@@ -45,7 +45,7 @@ GLOBAL_LIST_INIT(orion_events, generate_orion_events())
/obj/machinery/computer/arcade/orion_trail/Initialize(mapload)
. = ..()
radio = new /obj/item/radio(src)
radio.listening = 0
radio.set_listening(FALSE)
setup_events()
/obj/machinery/computer/arcade/orion_trail/proc/setup_events()
@@ -17,7 +17,7 @@
/obj/machinery/computer/chef_order/Initialize(mapload)
. = ..()
radio = new(src)
radio.frequency = FREQ_SUPPLY
radio.set_frequency(FREQ_SUPPLY)
radio.subspace_transmission = TRUE
radio.canhear_range = 0
radio.recalculateChannels()
+1 -1
View File
@@ -48,7 +48,7 @@
. = ..()
Radio = new/obj/item/radio(src)
Radio.listening = 0
Radio.set_listening(FALSE)
/obj/machinery/door_timer/Initialize(mapload)
. = ..()
+77 -19
View File
@@ -1,3 +1,8 @@
#define CAN_HEAR_MASTERS (1<<0)
#define CAN_HEAR_ACTIVE_HOLOCALLS (1<<1)
#define CAN_HEAR_RECORD_MODE (1<<2)
#define CAN_HEAR_ALL_FLAGS (CAN_HEAR_MASTERS|CAN_HEAR_ACTIVE_HOLOCALLS|CAN_HEAR_RECORD_MODE)
/* Holograms!
* Contains:
* Holopad
@@ -41,7 +46,8 @@ Possible to do for anyone motivated enough:
max_integrity = 300
armor = list(MELEE = 50, BULLET = 20, LASER = 20, ENERGY = 20, BOMB = 0, BIO = 0, FIRE = 50, ACID = 0)
circuit = /obj/item/circuitboard/machine/holopad
/// List of living mobs that use the holopad
/// associative lazylist of the form: list(mob calling us = hologram representing that mob).
/// this is only populated for holopads answering calls from another holopad
var/list/masters
/// Holoray-mob link
var/list/holorays
@@ -49,9 +55,10 @@ Possible to do for anyone motivated enough:
var/last_request = 0
/// Change to change how far the AI can move away from the holopad before deactivating
var/holo_range = 5
/// Array of /datum/holocalls
/// Array of /datum/holocalls that are calling US. this is only filled for holopads answering calls from another holopad
var/list/holo_calls
/// Currently outgoing holocall, do not modify the datums only check and call the public procs
/// Currently outgoing holocall, cannot call any other holopads unless this is null.
/// creating a new holocall from us to another holopad sets this var to that holocall datum
var/datum/holocall/outgoing_call
/// Record disk
var/obj/item/disk/holodisk/disk
@@ -78,10 +85,8 @@ Possible to do for anyone motivated enough:
var/secure = FALSE
/// If we are currently calling another holopad
var/calling = FALSE
/obj/machinery/holopad/Initialize(mapload)
. = ..()
become_hearing_sensitive()
///bitfield. used to turn on and off hearing sensitivity depending on if we can act on Hear() at all - meant for lowering the number of unessesary hearable atoms
var/can_hear_flags = NONE
/obj/machinery/holopad/secure
name = "secure holopad"
@@ -156,9 +161,8 @@ Possible to do for anyone motivated enough:
if(outgoing_call)
outgoing_call.ConnectionFailure(src)
for(var/I in holo_calls)
var/datum/holocall/HC = I
HC.ConnectionFailure(src)
for(var/datum/holocall/holocall_to_disconnect as anything in holo_calls)
holocall_to_disconnect.ConnectionFailure(src)
for (var/I in masters)
clear_holo(I)
@@ -363,13 +367,57 @@ Possible to do for anyone motivated enough:
outgoing_call.Disconnect(src)
return TRUE
//setters
/**
* setter for can_hear_flags. handles adding or removing the given flag on can_hear_flags and then adding hearing sensitivity or removing it depending on the final state
* this is necessary because holopads are a significant fraction of the hearable atoms on station which increases the cost of procs that iterate through hearables
* so we need holopads to not be hearable until it is needed
*
* * flag - one of the can_hear_flags flag defines
* * set_flag - boolean, if TRUE sets can_hear_flags to that flag and might add hearing sensitivity if can_hear_flags was NONE before,
* if FALSE unsets the flag and possibly removes hearing sensitivity
*/
/obj/machinery/holopad/proc/set_can_hear_flags(flag, set_flag = TRUE)
if(!(flag & CAN_HEAR_ALL_FLAGS))
return FALSE //the given flag doesnt exist
if(set_flag)
if(can_hear_flags == NONE)//we couldnt hear before, so become hearing sensitive
become_hearing_sensitive()
can_hear_flags |= flag
return TRUE
else
can_hear_flags &= ~flag
if(can_hear_flags == NONE)
lose_hearing_sensitivity()
return TRUE
///setter for adding/removing holocalls to this holopad. used to update the holo_calls list and can_hear_flags
///adds the given holocall if add_holocall is TRUE, removes if FALSE
/obj/machinery/holopad/proc/set_holocall(datum/holocall/holocall_to_update, add_holocall = TRUE)
if(!istype(holocall_to_update))
return FALSE
if(add_holocall)
set_can_hear_flags(CAN_HEAR_ACTIVE_HOLOCALLS)
LAZYADD(holo_calls, holocall_to_update)
else
LAZYREMOVE(holo_calls, holocall_to_update)
if(!LAZYLEN(holo_calls))
set_can_hear_flags(CAN_HEAR_ACTIVE_HOLOCALLS, FALSE)
return TRUE
/**
* hangup_all_calls: Disconnects all current holocalls from the holopad
*/
/obj/machinery/holopad/proc/hangup_all_calls()
for(var/I in holo_calls)
var/datum/holocall/HC = I
HC.Disconnect(src)
for(var/datum/holocall/holocall_to_disconnect as anything in holo_calls)
holocall_to_disconnect.Disconnect(src)
/obj/machinery/holopad/attack_ai_secondary(mob/living/silicon/ai/user)
if (!istype(user))
@@ -400,6 +448,8 @@ Possible to do for anyone motivated enough:
clear_holo(user)
return
//this really should not be processing by default with how common holopads are
//everything in here can start processing if need be once first set and stop processing after being unset
/obj/machinery/holopad/process()
if(LAZYLEN(masters))
for(var/mob/living/master as anything in masters)
@@ -475,13 +525,12 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
if(masters[master] && speaker != master)
master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)
for(var/I in holo_calls)
var/datum/holocall/HC = I
if(HC.connected_holopad == src)
if(speaker == HC.hologram && HC.user.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat))
HC.user.create_chat_message(speaker, message_language, raw_message, spans)
for(var/datum/holocall/holocall_to_update as anything in holo_calls)
if(holocall_to_update.connected_holopad == src)//if we answered this call originating from another holopad
if(speaker == holocall_to_update.hologram && holocall_to_update.user.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat))
holocall_to_update.user.create_chat_message(speaker, message_language, raw_message, spans)
else
HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)
holocall_to_update.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)
if(outgoing_call?.hologram && speaker == outgoing_call.user)
outgoing_call.hologram.say(raw_message, sanitize = FALSE)
@@ -510,6 +559,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
/obj/machinery/holopad/proc/set_holo(mob/living/user, obj/effect/overlay/holo_pad_hologram/h)
LAZYSET(masters, user, h)
LAZYSET(holorays, user, new /obj/effect/overlay/holoray(loc))
set_can_hear_flags(CAN_HEAR_MASTERS)
var/mob/living/silicon/ai/AI = user
if(istype(AI))
AI.current = src
@@ -527,6 +577,8 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
if(istype(AI) && AI.current == src)
AI.current = null
LAZYREMOVE(masters, user) // Discard AI from the list of those who use holopad
if(!LAZYLEN(masters))
set_can_hear_flags(CAN_HEAR_MASTERS, set_flag = FALSE)
qdel(holorays[user])
LAZYREMOVE(holorays, user)
SetLightsAndPower()
@@ -639,6 +691,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
return
disk.record = new
record_mode = TRUE
set_can_hear_flags(CAN_HEAR_RECORD_MODE)
record_start = world.time
record_user = user
disk.record.set_caller_image(user)
@@ -708,6 +761,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
if(record_mode)
record_mode = FALSE
record_user = null
set_can_hear_flags(CAN_HEAR_RECORD_MODE, FALSE)
/obj/machinery/holopad/proc/record_clear()
if(disk?.record)
@@ -747,3 +801,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
#undef HOLOPAD_PASSIVE_POWER_USAGE
#undef HOLOGRAM_POWER_USAGE
#undef CAN_HEAR_MASTERS
#undef CAN_HEAR_ACTIVE_HOLOCALLS
#undef CAN_HEAR_RECORD_MODE
#undef CAN_HEAR_ALL_FLAGS
+1 -1
View File
@@ -118,7 +118,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/requests_console, 30)
GLOB.req_console_ckey_departments[ckey(department)] = department
Radio = new /obj/item/radio(src)
Radio.listening = 0
Radio.set_listening(FALSE)
/obj/machinery/requests_console/Destroy()
QDEL_NULL(Radio)
+33 -19
View File
@@ -128,7 +128,7 @@
copy.levels = levels
return copy
// This is the meat function for making radios hear vocal transmissions.
/// This is the meat function for making radios hear vocal transmissions.
/datum/signal/subspace/vocal/broadcast()
set waitfor = FALSE
@@ -140,47 +140,60 @@
if(compression > 0)
message = Gibberish(message, compression >= 30)
var/list/signal_reaches_every_z_level = levels
if(0 in levels)
signal_reaches_every_z_level = RADIO_NO_Z_LEVEL_RESTRICTION
// Assemble the list of radios
var/list/radios = list()
switch (transmission_method)
if (TRANSMISSION_SUBSPACE)
// Reaches any radios on the levels
for(var/obj/item/radio/R in GLOB.all_radios["[frequency]"])
if(R.can_receive(frequency, levels))
radios += R
var/list/all_radios_of_our_frequency = GLOB.all_radios["[frequency]"]
radios = all_radios_of_our_frequency.Copy()
for(var/obj/item/radio/subspace_radio in radios)
if(!subspace_radio.can_receive(frequency, signal_reaches_every_z_level))
radios -= subspace_radio
// Syndicate radios can hear all well-known radio channels
if (num2text(frequency) in GLOB.reverseradiochannels)
for(var/obj/item/radio/R in GLOB.all_radios["[FREQ_SYNDICATE]"])
if(R.can_receive(FREQ_SYNDICATE, list(R.z)))
radios |= R
for(var/obj/item/radio/syndicate_radios in GLOB.all_radios["[FREQ_SYNDICATE]"])
if(syndicate_radios.can_receive(FREQ_SYNDICATE, RADIO_NO_Z_LEVEL_RESTRICTION))
radios |= syndicate_radios
if (TRANSMISSION_RADIO)
// Only radios not currently in subspace mode
for(var/obj/item/radio/R in GLOB.all_radios["[frequency]"])
if(!R.subspace_transmission && R.can_receive(frequency, levels))
radios += R
for(var/obj/item/radio/non_subspace_radio in GLOB.all_radios["[frequency]"])
if(!non_subspace_radio.subspace_transmission && non_subspace_radio.can_receive(frequency, levels))
radios += non_subspace_radio
if (TRANSMISSION_SUPERSPACE)
// Only radios which are independent
for(var/obj/item/radio/R in GLOB.all_radios["[frequency]"])
if(R.independent && R.can_receive(frequency, levels))
radios += R
for(var/obj/item/radio/independent_radio in GLOB.all_radios["[frequency]"])
if(independent_radio.independent && independent_radio.can_receive(frequency, levels))
radios += independent_radio
// From the list of radios, find all mobs who can hear those.
var/list/receive = get_mobs_in_radio_ranges(radios)
var/list/receive = get_hearers_in_radio_ranges(radios)
// Add observers who have ghost radio enabled.
for(var/mob/dead/observer/M in GLOB.player_list)
if(M.client?.prefs.chat_toggles & CHAT_GHOSTRADIO)
receive |= M
for(var/mob/dead/observer/ghost in GLOB.player_list)
if(ghost.client.prefs?.chat_toggles & CHAT_GHOSTRADIO)
receive |= ghost
// Render the message and have everybody hear it.
// Always call this on the virtualspeaker to avoid issues.
var/spans = data["spans"]
var/list/message_mods = data["mods"]
var/rendered = virt.compose_message(virt, language, message, frequency, spans)
for(var/atom/movable/hearer in receive)
for(var/atom/movable/hearer as anything in receive)
if(!hearer)
stack_trace("null found in the hearers list returned by the spatial grid. this is bad")
continue
hearer.Hear(rendered, virt, language, message, frequency, spans, message_mods)
// This following recording is intended for research and feedback in the use of department radio channels
@@ -198,7 +211,8 @@
var/log_text = "\[[get_radio_name(frequency)]\] [spans_part]\"[message]\" (language: [lang_name])"
var/mob/source_mob = virt.source
if(istype(source_mob))
if(ismob(source_mob))
source_mob.log_message(log_text, LOG_TELECOMMS)
else
log_telecomms("[virt.source] [log_text] [loc_name(get_turf(virt.source))]")
@@ -114,7 +114,7 @@
return
else
for(var/obj/machinery/telecomms/T in links)
T.links.Remove(src)
remove_link(T)
network = params["value"]
links = list()
log_game("[key_name(operator)] has changed the network for [src] at [AREACOORD(src)] to [network].")
@@ -138,22 +138,11 @@
if("unlink")
var/obj/machinery/telecomms/T = links[text2num(params["value"])]
if(T)
// Remove link entries from both T and src.
if(T.links)
T.links.Remove(src)
links.Remove(T)
log_game("[key_name(operator)] unlinked [src] and [T] at [AREACOORD(src)].")
. = TRUE
. = remove_link(T, operator)
if("link")
if(heldmultitool)
var/obj/machinery/telecomms/T = heldmultitool.buffer
if(istype(T) && T != src)
if(!(src in T.links))
T.links += src
if(!(T in links))
links += T
log_game("[key_name(operator)] linked [src] for [T] at [AREACOORD(src)].")
. = TRUE
. = add_new_link(T, operator)
if("buffer")
heldmultitool.buffer = src
. = TRUE
@@ -164,6 +153,42 @@
add_act(action, params)
. = TRUE
///adds new_connection to src's links list AND vice versa. also updates links_by_telecomms_type
/obj/machinery/telecomms/proc/add_new_link(obj/machinery/telecomms/new_connection, mob/user)
if(!istype(new_connection) || new_connection == src)
return FALSE
if((new_connection in links) && (src in new_connection.links))
return FALSE
links |= new_connection
new_connection.links |= src
LAZYADDASSOCLIST(links_by_telecomms_type, new_connection.telecomms_type, new_connection)
LAZYADDASSOCLIST(new_connection.links_by_telecomms_type, telecomms_type, src)
if(user)
log_game("[key_name(user)] linked [src] for [new_connection] at [AREACOORD(src)].")
return TRUE
///removes old_connection from src's links list AND vice versa. also updates links_by_telecomms_type
/obj/machinery/telecomms/proc/remove_link(obj/machinery/telecomms/old_connection, mob/user)
if(!istype(old_connection) || old_connection == src)
return FALSE
if(old_connection in links)
links -= old_connection
LAZYREMOVEASSOC(links_by_telecomms_type, old_connection.telecomms_type, old_connection)
if(src in old_connection.links)
old_connection.links -= src
LAZYREMOVEASSOC(old_connection.links_by_telecomms_type, telecomms_type, src)
if(user)
log_game("[key_name(user)] unlinked [src] and [old_connection] at [AREACOORD(src)].")
return TRUE
/obj/machinery/telecomms/proc/add_option()
return
@@ -12,6 +12,7 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages
name = "subspace broadcaster"
icon_state = "broadcaster"
desc = "A dish-shaped machine used to broadcast processed subspace signals."
telecomms_type = /obj/machinery/telecomms/broadcaster
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 25
@@ -12,6 +12,7 @@
name = "bus mainframe"
icon_state = "bus"
desc = "A mighty piece of hardware used to send massive amounts of data quickly."
telecomms_type = /obj/machinery/telecomms/bus
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 50
@@ -12,6 +12,7 @@
name = "telecommunication hub"
icon_state = "hub"
desc = "A mighty piece of hardware used to send/receive massive amounts of data."
telecomms_type = /obj/machinery/telecomms/hub
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 80
@@ -75,6 +75,7 @@
icon_state = "message_server"
name = "Messaging Server"
desc = "A machine that processes and routes PDA and request console messages."
telecomms_type = /obj/machinery/telecomms/message_server
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 10
@@ -10,6 +10,7 @@
name = "processor unit"
icon_state = "processor"
desc = "This machine is used to process large quantities of information."
telecomms_type = /obj/machinery/telecomms/processor
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 30
@@ -10,6 +10,7 @@
name = "subspace receiver"
icon_state = "broadcast receiver"
desc = "This machine has a dish-like shape and green lights. It is designed to detect and process subspace radio activity."
telecomms_type = /obj/machinery/telecomms/receiver
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 30
@@ -10,6 +10,7 @@
name = "telecommunication relay"
icon_state = "relay"
desc = "A mighty piece of hardware used to send massive amounts of data far away."
telecomms_type = /obj/machinery/telecomms/relay
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 30
@@ -30,8 +31,7 @@
else
signal.levels |= relay_turf.z
// Checks to see if it can send/receive.
/// Checks to see if it can send/receive.
/obj/machinery/telecomms/relay/proc/can(datum/signal/signal)
if(!on)
return FALSE
@@ -9,6 +9,7 @@
name = "telecommunication server"
icon_state = "comm_server"
desc = "A machine used to store data and network statistics."
telecomms_type = /obj/machinery/telecomms/server
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 15
@@ -17,28 +17,48 @@ GLOBAL_LIST_EMPTY(telecomms_list)
/obj/machinery/telecomms
icon = 'icons/obj/machines/telecomms.dmi'
critical_machine = TRUE
var/list/links = list() // list of machines this machine is linked to
var/traffic = 0 // value increases as traffic increases
var/netspeed = 2.5 // how much traffic to lose per second (50 gigabytes/second * netspeed)
var/list/autolinkers = list() // list of text/number values to link with
var/id = "NULL" // identification string
var/network = "NULL" // the network of the machinery
/// list of machines this machine is linked to
var/list/links = list()
/**
* associative lazylist list of the telecomms_type of linked telecomms machines and a list of said machines.
* eg list(telecomms_type1 = list(everything linked to us with that type), telecomms_type2 = list(everything linked to us with THAT type)...)
*/
var/list/links_by_telecomms_type
/// value increases as traffic increases
var/traffic = 0
/// how much traffic to lose per second (50 gigabytes/second * netspeed)
var/netspeed = 2.5
/// list of text/number values to link with
var/list/autolinkers = list()
/// identification string
var/id = "NULL"
/// the relevant type path of this telecomms machine eg /obj/machinery/telecomms/server but not server/preset. used for links_by_telecomms_type
var/telecomms_type = null
/// the network of the machinery
var/network = "NULL"
var/list/freq_listening = list() // list of frequencies to tune into: if none, will listen to all
// list of frequencies to tune into: if none, will listen to all
var/list/freq_listening = list()
var/on = TRUE
var/toggled = TRUE // Is it toggled on
var/long_range_link = FALSE // Can you link it across Z levels or on the otherside of the map? (Relay & Hub)
var/hide = FALSE // Is it a hidden machine?
/// Is it toggled on
var/toggled = TRUE
/// Can you link it across Z levels or on the otherside of the map? (Relay & Hub)
var/long_range_link = FALSE
/// Is it a hidden machine?
var/hide = FALSE
///Looping sounds for any servers
var/datum/looping_sound/server/soundloop
/// relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending
/obj/machinery/telecomms/proc/relay_information(datum/signal/subspace/signal, filter, copysig, amount = 20)
// relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending
if(!on)
return
if(!filter || !ispath(filter, /obj/machinery/telecomms))
CRASH("null or non /obj/machinery/telecomms typepath given as the filter argument! given typepath: [filter]")
var/send_count = 0
// Apply some lag based on traffic rates
@@ -47,24 +67,23 @@ GLOBAL_LIST_EMPTY(telecomms_list)
signal.data["slow"] = netlag
// Loop through all linked machines and send the signal or copy.
for(var/obj/machinery/telecomms/machine in links)
if(filter && !istype( machine, filter ))
continue
if(!machine.on)
for(var/obj/machinery/telecomms/filtered_machine in links_by_telecomms_type?[filter])
if(!filtered_machine.on)
continue
if(amount && send_count >= amount)
break
if(z != machine.loc.z && !long_range_link && !machine.long_range_link)
if(z != filtered_machine.loc.z && !long_range_link && !filtered_machine.long_range_link)
continue
send_count++
if(machine.is_freq_listening(signal))
machine.traffic++
if(filtered_machine.is_freq_listening(signal))
filtered_machine.traffic++
if(copysig)
machine.receive_information(signal.copy(), src)
filtered_machine.receive_information(signal.copy(), src)
else
machine.receive_information(signal, src)
filtered_machine.receive_information(signal, src)
if(send_count > 0 && is_freq_listening(signal))
traffic++
@@ -75,12 +94,13 @@ GLOBAL_LIST_EMPTY(telecomms_list)
// send signal directly to a machine
machine.receive_information(signal, src)
///receive information from linked machinery
/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
// receive information from linked machinery
return
/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal)
// return TRUE if found, FALSE if not found
return signal && (!freq_listening.len || (signal.frequency in freq_listening))
return signal && (!length(freq_listening) || (signal.frequency in freq_listening))
/obj/machinery/telecomms/Initialize(mapload)
. = ..()
@@ -92,27 +112,28 @@ GLOBAL_LIST_EMPTY(telecomms_list)
/obj/machinery/telecomms/LateInitialize()
..()
for(var/obj/machinery/telecomms/T in (long_range_link ? GLOB.telecomms_list : urange(20, src, 1)))
add_link(T)
add_automatic_link(T)
/obj/machinery/telecomms/Destroy()
GLOB.telecomms_list -= src
QDEL_NULL(soundloop)
for(var/obj/machinery/telecomms/comm in GLOB.telecomms_list)
comm.links -= src
remove_link(comm)
links = list()
return ..()
// Used in auto linking
/obj/machinery/telecomms/proc/add_link(obj/machinery/telecomms/T)
/// Used in auto linking
/obj/machinery/telecomms/proc/add_automatic_link(obj/machinery/telecomms/T)
var/turf/position = get_turf(src)
var/turf/T_position = get_turf(T)
if((position.z == T_position.z) || (long_range_link && T.long_range_link))
if(src != T)
for(var/x in autolinkers)
if(x in T.autolinkers)
links |= T
T.links |= src
if((position.z != T_position.z) && !(long_range_link && T.long_range_link))
return
if(src == T)
return
for(var/autolinker_id in autolinkers)
if(autolinker_id in T.autolinkers)
add_new_link(T)
return
/obj/machinery/telecomms/update_icon_state()
icon_state = "[initial(icon_state)][panel_open ? "_o" : null][on ? null : "_off"]"
@@ -25,8 +25,8 @@ GLOBAL_LIST_INIT(channel_tokens, list(
canhear_range = 0 // can't hear headsets from very far away
slot_flags = ITEM_SLOT_EARS
var/obj/item/encryptionkey/keyslot2 = null
dog_fashion = null
var/obj/item/encryptionkey/keyslot2 = null
/obj/item/radio/headset/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] begins putting \the [src]'s antenna up [user.p_their()] nose! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer!"))
@@ -55,26 +55,24 @@ GLOBAL_LIST_INIT(channel_tokens, list(
/obj/item/radio/headset/Initialize(mapload)
. = ..()
set_listening(TRUE)
recalculateChannels()
possibly_deactivate_in_loc()
/obj/item/radio/headset/proc/possibly_deactivate_in_loc()
if(ismob(loc))
set_listening(should_be_listening)
else
set_listening(FALSE, actual_setting = FALSE)
/obj/item/radio/headset/Moved(atom/OldLoc, Dir)
. = ..()
possibly_deactivate_in_loc()
/obj/item/radio/headset/Destroy()
QDEL_NULL(keyslot2)
return ..()
/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans, datum/language/language, list/message_mods)
if (!listening)
return ITALICS | REDUCE_RANGE
return ..()
/obj/item/radio/headset/can_receive(freq, level, AIuser)
if(ishuman(src.loc))
var/mob/living/carbon/human/H = src.loc
if(H.ears == src)
return ..(freq, level)
else if(AIuser)
return ..(freq, level)
return FALSE
/obj/item/radio/headset/ui_data(mob/user)
. = ..()
.["headset"] = TRUE
@@ -288,9 +286,6 @@ GLOBAL_LIST_INIT(channel_tokens, list(
keyslot2 = new /obj/item/encryptionkey/ai
command = TRUE
/obj/item/radio/headset/silicon/can_receive(freq, level)
return ..(freq, level, TRUE)
/obj/item/radio/headset/attackby(obj/item/W, mob/user, params)
user.set_machine(src)
@@ -335,11 +330,11 @@ GLOBAL_LIST_INIT(channel_tokens, list(
/obj/item/radio/headset/recalculateChannels()
..()
. = ..()
if(keyslot2)
for(var/ch_name in keyslot2.channels)
if(!(ch_name in src.channels))
channels[ch_name] = keyslot2.channels[ch_name]
LAZYSET(channels, ch_name, keyslot2.channels[ch_name])
if(keyslot2.translate_binary)
translate_binary = TRUE
@@ -348,8 +343,8 @@ GLOBAL_LIST_INIT(channel_tokens, list(
if (keyslot2.independent)
independent = TRUE
for(var/ch_name in channels)
secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name])
for(var/ch_name in channels)
secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name])
/obj/item/radio/headset/AltClick(mob/living/user)
if(!istype(user) || !Adjacent(user) || user.incapacitated())
@@ -80,24 +80,18 @@
/obj/item/radio/intercom/ui_state(mob/user)
return GLOB.default_state
/obj/item/radio/intercom/can_receive(freq, level)
if(!on)
return FALSE
if(wires.is_cut(WIRE_RX))
return FALSE
if(!(0 in level))
/obj/item/radio/intercom/can_receive(freq, list/levels)
if(levels != RADIO_NO_Z_LEVEL_RESTRICTION)
var/turf/position = get_turf(src)
if(isnull(position) || !(position.z in level))
if(isnull(position) || !(position.z in levels))
return FALSE
if(!listening)
return FALSE
if(freq == FREQ_SYNDICATE)
if(!(syndie))
return FALSE//Prevents broadcast of messages over devices lacking the encryption
return TRUE
/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, list/message_mods = list())
if(message_mods[RADIO_EXTENSION] == MODE_INTERCOM)
return // Avoid hearing the same thing twice
@@ -126,9 +120,9 @@
SIGNAL_HANDLER
var/area/current_area = get_area(src)
if(!current_area)
on = FALSE
set_on(FALSE)
else
on = current_area.powered(AREA_USAGE_EQUIP) // set "on" to the equipment power status of our area.
set_on(current_area.powered(AREA_USAGE_EQUIP)) // set "on" to the equipment power status of our area.
update_appearance()
/obj/item/radio/intercom/add_blood_DNA(list/blood_dna)
@@ -148,8 +142,11 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/item/radio/intercom, 26)
/obj/item/radio/intercom/chapel
name = "Confessional intercom"
anonymize = TRUE
frequency = 1481
broadcasting = TRUE
/obj/item/radio/intercom/chapel/Initialize(mapload, ndir, building)
. = ..()
set_frequency(1481)
set_broadcasting(TRUE)
MAPPING_DIRECTIONAL_HELPERS(/obj/item/radio/intercom/prison, 26)
MAPPING_DIRECTIONAL_HELPERS(/obj/item/radio/intercom/chapel, 26)
+301 -196
View File
@@ -17,36 +17,85 @@
custom_materials = list(/datum/material/iron=75, /datum/material/glass=25)
obj_flags = USES_TGUI
var/on = TRUE
var/frequency = FREQ_COMMON
var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives.
var/emped = 0 // Tracks the number of EMPs currently stacked.
///if FALSE, broadcasting and listening dont matter and this radio shouldnt do anything
VAR_PRIVATE/on = TRUE
///the "default" radio frequency this radio is set to, listens and transmits to this frequency by default. wont work if the channel is encrypted
VAR_PRIVATE/frequency = FREQ_COMMON
var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby.
var/listening = TRUE // Whether the radio is currently receiving.
var/prison_radio = FALSE // If true, the transmit wire starts cut.
var/unscrewed = FALSE // Whether wires are accessible. Toggleable by screwdrivering.
var/freerange = FALSE // If true, the radio has access to the full spectrum.
var/subspace_transmission = FALSE // If true, the radio transmits and receives on subspace exclusively.
var/subspace_switchable = FALSE // If true, subspace_transmission can be toggled at will.
var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios.
var/use_command = FALSE // If true, broadcasts will be large and BOLD.
var/command = FALSE // If true, use_command can be toggled at will.
/// Whether the radio will transmit dialogue it hears nearby into its radio channel.
VAR_PRIVATE/broadcasting = FALSE
/// Whether the radio is currently receiving radio messages from its radio frequencies.
VAR_PRIVATE/listening = TRUE
//the below three vars are used to track listening and broadcasting should they be forced off for whatever reason but "supposed" to be active
//eg player sets the radio to listening, but an emp or whatever turns it off, its still supposed to be activated but was forced off,
//when it wears off it sets listening to should_be_listening
///used for tracking what broadcasting should be in the absence of things forcing it off, eg its set to broadcast but gets emp'd temporarily
var/should_be_broadcasting = FALSE
///used for tracking what listening should be in the absence of things forcing it off, eg its set to listen but gets emp'd temporarily
var/should_be_listening = TRUE
/// Both the range around the radio in which mobs can hear what it receives and the range the radio can hear
var/canhear_range = 3
/// Tracks the number of EMPs currently stacked.
var/emped = 0
/// If true, the transmit wire starts cut.
var/prison_radio = FALSE
/// Whether wires are accessible. Toggleable by screwdrivering.
var/unscrewed = FALSE
/// If true, the radio has access to the full spectrum.
var/freerange = FALSE
/// If true, the radio transmits and receives on subspace exclusively.
var/subspace_transmission = FALSE
/// If true, subspace_transmission can be toggled at will.
var/subspace_switchable = FALSE
/// Frequency lock to stop the user from untuning specialist radios.
var/freqlock = FALSE
/// If true, broadcasts will be large and BOLD.
var/use_command = FALSE
/// If true, use_command can be toggled at will.
var/command = FALSE
///makes anyone who is talking through this anonymous.
var/anonymize = FALSE
// Encryption key handling
/// Encryption key handling
var/obj/item/encryptionkey/keyslot
var/translate_binary = FALSE // If true, can hear the special binary channel.
var/independent = FALSE // If true, can say/hear on the special CentCom channel.
var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel.
var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h)
/// If true, can hear the special binary channel.
var/translate_binary = FALSE
/// If true, can say/hear on the special CentCom channel.
var/independent = FALSE
/// If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel.
var/syndie = FALSE
/// associative list of the encrypted radio channels this radio is currently set to listen/broadcast to, of the form: list(channel name = TRUE or FALSE)
var/list/channels
/// associative list of the encrypted radio channels this radio can listen/broadcast to, of the form: list(channel name = channel frequency)
var/list/secure_radio_connections
/obj/item/radio/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!"))
return BRUTELOSS
/obj/item/radio/Initialize(mapload)
wires = new /datum/wires/radio(src)
if(prison_radio)
wires.cut(WIRE_TX) // OH GOD WHY
secure_radio_connections = list()
. = ..()
for(var/ch_name in channels)
secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name])
set_listening(listening)
set_broadcasting(broadcasting)
set_frequency(sanitize_frequency(frequency, freerange))
set_on(on)
AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES)
/obj/item/radio/Destroy()
remove_radio_all(src) //Just to be sure
QDEL_NULL(wires)
QDEL_NULL(keyslot)
return ..()
/obj/item/radio/proc/set_frequency(new_frequency)
SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args)
@@ -74,6 +123,7 @@
// Used for cyborg override
/obj/item/radio/proc/resetChannels()
channels = list()
secure_radio_connections = list()
translate_binary = FALSE
syndie = FALSE
independent = FALSE
@@ -81,33 +131,9 @@
/obj/item/radio/proc/make_syndie() // Turns normal radios into Syndicate radios!
qdel(keyslot)
keyslot = new /obj/item/encryptionkey/syndicate
syndie = 1
syndie = TRUE
recalculateChannels()
/obj/item/radio/Destroy()
remove_radio_all(src) //Just to be sure
QDEL_NULL(wires)
QDEL_NULL(keyslot)
return ..()
/obj/item/radio/Initialize(mapload)
wires = new /datum/wires/radio(src)
if(prison_radio)
wires.cut(WIRE_TX) // OH GOD WHY
secure_radio_connections = new
. = ..()
frequency = sanitize_frequency(frequency, freerange)
set_frequency(frequency)
for(var/ch_name in channels)
secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name])
become_hearing_sensitive(ROUNDSTART_TRAIT)
/obj/item/radio/ComponentInitialize()
. = ..()
AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES)
/obj/item/radio/interact(mob/user)
if(unscrewed && !isAI(user))
wires.interact(user)
@@ -115,6 +141,211 @@
else
..()
//simple getters only because i NEED to enforce complex setter use for these vars for caching purposes but VAR_PROTECTED requires getter usage as well.
//if another decorator is made that doesnt require getters feel free to nuke these and change these vars over to that
///simple getter for the on variable. necessary due to VAR_PROTECTED
/obj/item/radio/proc/is_on()
return on
///simple getter for the frequency variable. necessary due to VAR_PROTECTED
/obj/item/radio/proc/get_frequency()
return frequency
///simple getter for the broadcasting variable. necessary due to VAR_PROTECTED
/obj/item/radio/proc/get_broadcasting()
return broadcasting
///simple getter for the listening variable. necessary due to VAR_PROTECTED
/obj/item/radio/proc/get_listening()
return listening
//now for setters for the above protected vars
/**
* setter for the listener var, adds or removes this radio from the global radio list if we are also on
*
* * new_listening - the new value we want to set listening to
* * actual_setting - whether or not the radio is supposed to be listening, sets should_be_listening to the new listening value if true, otherwise just changes listening
*/
/obj/item/radio/proc/set_listening(new_listening, actual_setting = TRUE)
listening = new_listening
if(actual_setting)
should_be_listening = listening
if(listening && on)
recalculateChannels()
add_radio(src, frequency)
else if(!listening)
remove_radio_all(src)
/**
* setter for broadcasting that makes us not hearing sensitive if not broadcasting and hearing sensitive if broadcasting
* hearing sensitive in this case only matters for the purposes of listening for words said in nearby tiles, talking into us directly bypasses hearing
*
* * new_broadcasting- the new value we want to set broadcasting to
* * actual_setting - whether or not the radio is supposed to be broadcasting, sets should_be_broadcasting to the new value if true, otherwise just changes broadcasting
*/
/obj/item/radio/proc/set_broadcasting(new_broadcasting, actual_setting = TRUE)
broadcasting = new_broadcasting
if(actual_setting)
should_be_broadcasting = broadcasting
if(broadcasting && on) //we dont need hearing sensitivity if we arent broadcasting, because talk_into doesnt care about hearing
become_hearing_sensitive(INNATE_TRAIT)
else if(!broadcasting)
lose_hearing_sensitivity(INNATE_TRAIT)
///setter for the on var that sets both broadcasting and listening to off or whatever they were supposed to be
/obj/item/radio/proc/set_on(new_on)
on = new_on
if(on)
set_broadcasting(should_be_broadcasting)//set them to whatever theyre supposed to be
set_listening(should_be_listening)
else
set_broadcasting(FALSE, actual_setting = FALSE)//fake set them to off
set_listening(FALSE, actual_setting = FALSE)
/obj/item/radio/talk_into(atom/movable/talking_movable, message, channel, list/spans, datum/language/language, list/message_mods)
if(HAS_TRAIT(talking_movable, TRAIT_SIGN_LANG)) //Forces Sign Language users to wear the translation gloves to speak over radios
var/mob/living/carbon/mute = talking_movable
if(istype(mute))
var/obj/item/clothing/gloves/radio/G = mute.get_item_by_slot(ITEM_SLOT_GLOVES)
if(!istype(G))
return FALSE
switch(mute.check_signables_state())
if(SIGN_ONE_HAND) // One hand full
message = stars(message)
if(SIGN_HANDS_FULL to SIGN_CUFFED)
return FALSE
if(!spans)
spans = list(talking_movable.speech_span)
if(!language)
language = talking_movable.get_selected_language()
INVOKE_ASYNC(src, .proc/talk_into_impl, talking_movable, message, channel, spans.Copy(), language, message_mods)
return ITALICS | REDUCE_RANGE
/obj/item/radio/proc/talk_into_impl(atom/movable/talking_movable, message, channel, list/spans, datum/language/language, list/message_mods)
if(!on)
return // the device has to be on
if(!talking_movable || !message)
return
if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios
return
if(!talking_movable.IsVocal())
return
if(use_command)
spans |= SPAN_COMMAND
/*
Roughly speaking, radios attempt to make a subspace transmission (which
is received, processed, and rebroadcast by the telecomms satellite) and
if that fails, they send a mundane radio transmission.
Headsets cannot send/receive mundane transmissions, only subspace.
Syndicate radios can hear transmissions on all well-known frequencies.
CentCom radios can hear the CentCom frequency no matter what.
*/
// From the channel, determine the frequency and get a reference to it.
var/freq
if(channel && channels && channels.len > 0)
if(channel == MODE_DEPARTMENT)
channel = channels[1]
freq = secure_radio_connections[channel]
if (!channels[channel]) // if the channel is turned off, don't broadcast
return
else
freq = frequency
channel = null
// Nearby active jammers prevent the message from transmitting
var/turf/position = get_turf(src)
for(var/obj/item/jammer/jammer as anything in GLOB.active_jammers)
var/turf/jammer_turf = get_turf(jammer)
if(position?.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range))
return
// Determine the identity information which will be attached to the signal.
var/atom/movable/virtualspeaker/speaker = new(null, talking_movable, src)
// Construct the signal
var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans, message_mods)
// Independent radios, on the CentCom frequency, reach all independent radios
if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE || freq == FREQ_CTF_GREEN || freq == FREQ_CTF_YELLOW))
signal.data["compression"] = 0
signal.transmission_method = TRANSMISSION_SUPERSPACE
signal.levels = list(0)
signal.broadcast()
return
// All radios make an attempt to use the subspace system first
signal.send_to_receivers()
// If the radio is subspace-only, that's all it can do
if (subspace_transmission)
return
// Non-subspace radios will check in a couple of seconds, and if the signal
// was never received, send a mundane broadcast (no headsets).
addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20)
/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal)
var/turf/T = get_turf(src)
if (signal.data["done"] && (T.z in signal.levels))
return
// Okay, the signal was never processed, send a mundane broadcast.
signal.data["compression"] = 0
signal.transmission_method = TRANSMISSION_RADIO
signal.levels = list(T.z)
signal.broadcast()
/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range)
return
var/filtered_mods = list()
if (message_mods[MODE_CUSTOM_SAY_EMOTE])
filtered_mods[MODE_CUSTOM_SAY_EMOTE] = message_mods[MODE_CUSTOM_SAY_EMOTE]
filtered_mods[MODE_CUSTOM_SAY_ERASE_INPUT] = message_mods[MODE_CUSTOM_SAY_ERASE_INPUT]
if(message_mods[RADIO_EXTENSION] == MODE_L_HAND || message_mods[RADIO_EXTENSION] == MODE_R_HAND)
// try to avoid being heard double
if (loc == speaker && ismob(speaker))
var/mob/M = speaker
var/idx = M.get_held_index_of_item(src)
// left hands are odd slots
if (idx && (idx % 2) == (message_mods[RADIO_EXTENSION] == MODE_L_HAND))
return
talk_into(speaker, raw_message, , spans, language=message_language, message_mods=filtered_mods)
/// Checks if this radio can receive on the given frequency.
/obj/item/radio/proc/can_receive(input_frequency, list/levels)
// deny checks
if (levels != RADIO_NO_Z_LEVEL_RESTRICTION)
var/turf/position = get_turf(src)
if(!position || !(position.z in levels))
return FALSE
if (input_frequency == FREQ_SYNDICATE && !syndie)
return FALSE
// allow checks: are we listening on that frequency?
if (input_frequency == frequency)
return TRUE
for(var/ch_name in channels)
if(channels[ch_name] & FREQ_LISTENING)
if(GLOB.radiochannels[ch_name] == text2num(input_frequency) || syndie)
return TRUE
return FALSE
/obj/item/radio/ui_state(mob/user)
return GLOB.inventory_state
@@ -165,10 +396,10 @@
if(.)
set_frequency(sanitize_frequency(tune, freerange))
if("listen")
listening = !listening
set_listening(!listening)
. = TRUE
if("broadcast")
broadcasting = !broadcasting
set_broadcasting(!broadcasting)
. = TRUE
if("channel")
var/channel = params["channel"]
@@ -191,146 +422,9 @@
recalculateChannels()
. = TRUE
/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language, list/message_mods)
if(HAS_TRAIT(M, TRAIT_SIGN_LANG)) //Forces Sign Language users to wear the translation gloves to speak over radios
var/mob/living/carbon/mute = M
if(istype(mute))
var/obj/item/clothing/gloves/radio/G = mute.get_item_by_slot(ITEM_SLOT_GLOVES)
if(!istype(G))
return FALSE
switch(mute.check_signables_state())
if(SIGN_ONE_HAND) // One hand full
message = stars(message)
if(SIGN_HANDS_FULL to SIGN_CUFFED)
return FALSE
if(!spans)
spans = list(M.speech_span)
if(!language)
language = M.get_selected_language()
INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language, message_mods)
return ITALICS | REDUCE_RANGE
/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language, list/message_mods)
if(!on)
return // the device has to be on
if(!M || !message)
return
if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios
return
if(!M.IsVocal())
return
if(use_command)
spans |= SPAN_COMMAND
/*
Roughly speaking, radios attempt to make a subspace transmission (which
is received, processed, and rebroadcast by the telecomms satellite) and
if that fails, they send a mundane radio transmission.
Headsets cannot send/receive mundane transmissions, only subspace.
Syndicate radios can hear transmissions on all well-known frequencies.
CentCom radios can hear the CentCom frequency no matter what.
*/
// From the channel, determine the frequency and get a reference to it.
var/freq
if(channel && channels && channels.len > 0)
if(channel == MODE_DEPARTMENT)
channel = channels[1]
freq = secure_radio_connections[channel]
if (!channels[channel]) // if the channel is turned off, don't broadcast
return
else
freq = frequency
channel = null
// Nearby active jammers prevent the message from transmitting
var/turf/position = get_turf(src)
for(var/obj/item/jammer/jammer in GLOB.active_jammers)
var/turf/jammer_turf = get_turf(jammer)
if(position?.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range))
return
// Determine the identity information which will be attached to the signal.
var/atom/movable/virtualspeaker/speaker = new(null, M, src)
// Construct the signal
var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans, message_mods)
// Independent radios, on the CentCom frequency, reach all independent radios
if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE || freq == FREQ_CTF_GREEN || freq == FREQ_CTF_YELLOW))
signal.data["compression"] = 0
signal.transmission_method = TRANSMISSION_SUPERSPACE
signal.levels = list(0) // reaches all Z-levels
signal.broadcast()
return
// All radios make an attempt to use the subspace system first
signal.send_to_receivers()
// If the radio is subspace-only, that's all it can do
if (subspace_transmission)
return
// Non-subspace radios will check in a couple of seconds, and if the signal
// was never received, send a mundane broadcast (no headsets).
addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20)
/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal)
var/turf/T = get_turf(src)
if (signal.data["done"] && (T.z in signal.levels))
return
// Okay, the signal was never processed, send a mundane broadcast.
signal.data["compression"] = 0
signal.transmission_method = TRANSMISSION_RADIO
signal.levels = list(T.z)
signal.broadcast()
/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range)
return
var/filtered_mods = list()
if (message_mods[MODE_CUSTOM_SAY_EMOTE])
filtered_mods[MODE_CUSTOM_SAY_EMOTE] = message_mods[MODE_CUSTOM_SAY_EMOTE]
filtered_mods[MODE_CUSTOM_SAY_ERASE_INPUT] = message_mods[MODE_CUSTOM_SAY_ERASE_INPUT]
if(message_mods[RADIO_EXTENSION] == MODE_L_HAND || message_mods[RADIO_EXTENSION] == MODE_R_HAND)
// try to avoid being heard double
if (loc == speaker && ismob(speaker))
var/mob/M = speaker
var/idx = M.get_held_index_of_item(src)
// left hands are odd slots
if (idx && (idx % 2) == (message_mods[RADIO_EXTENSION] == MODE_L_HAND))
return
talk_into(speaker, raw_message, , spans, language=message_language, message_mods=filtered_mods)
// Checks if this radio can receive on the given frequency.
/obj/item/radio/proc/can_receive(freq, level)
// deny checks
if (!on || !listening || wires.is_cut(WIRE_RX))
return FALSE
if (freq == FREQ_SYNDICATE && !syndie)
return FALSE
if (freq == FREQ_CENTCOM)
return independent // hard-ignores the z-level check
if (!(0 in level))
var/turf/position = get_turf(src)
if(!position || !(position.z in level))
return FALSE
// allow checks: are we listening on that frequency?
if (freq == frequency)
return TRUE
for(var/ch_name in channels)
if(channels[ch_name] & FREQ_LISTENING)
//the GLOB.radiochannels list is located in communications.dm
if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie)
return TRUE
return FALSE
/obj/item/radio/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!"))
return BRUTELOSS
/obj/item/radio/examine(mob/user)
. = ..()
@@ -360,18 +454,26 @@
var/curremp = emped //Remember which EMP this was
if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice
to_chat(loc, span_warning("\The [src] overloads."))
broadcasting = FALSE
listening = FALSE
for (var/ch_name in channels)
channels[ch_name] = 0
on = FALSE
set_on(FALSE)
addtimer(CALLBACK(src, .proc/end_emp_effect, curremp), 200)
/obj/item/radio/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!"))
return BRUTELOSS
/obj/item/radio/Destroy()
remove_radio_all(src) //Just to be sure
QDEL_NULL(wires)
QDEL_NULL(keyslot)
return ..()
/obj/item/radio/proc/end_emp_effect(curremp)
if(emped != curremp) //Don't fix it if it's been EMP'd again
return FALSE
emped = FALSE
on = TRUE
set_on(TRUE)
return TRUE
///////////////////////////////
@@ -391,10 +493,10 @@
var/mob/living/silicon/robot/R = loc
if(istype(R))
for(var/ch_name in R.model.radio_channels)
channels[ch_name] = 1
channels[ch_name] = TRUE
/obj/item/radio/borg/syndicate
syndie = 1
syndie = TRUE
keyslot = new /obj/item/encryptionkey/syndicate
/obj/item/radio/borg/syndicate/Initialize(mapload)
@@ -436,5 +538,8 @@
/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag.
listening = 0 // And it's nice to have a subtype too for future features.
dog_fashion = /datum/dog_fashion/back
/obj/item/radio/off/Initialize()
. = ..()
set_listening(FALSE)
+7 -4
View File
@@ -39,10 +39,13 @@ GLOBAL_LIST_INIT(freqtospan, list(
//SHOULD_BE_PURE(TRUE)
return TRUE
/atom/movable/proc/send_speech(message, range = 7, obj/source = src, bubble_type, list/spans, datum/language/message_language = null, list/message_mods = list())
/atom/movable/proc/send_speech(message, range = 7, obj/source = src, bubble_type, list/spans, datum/language/message_language, list/message_mods = list())
var/rendered = compose_message(src, message_language, message, , spans, message_mods)
for(var/atom/movable/AM as anything in get_hearers_in_view(range, source))
AM.Hear(rendered, src, message_language, message, , spans, message_mods)
for(var/atom/movable/hearing_movable as anything in get_hearers_in_view(range, source))
if(!hearing_movable)//theoretically this should use as anything because it shouldnt be able to get nulls but there are reports that it does.
stack_trace("somehow theres a null returned from get_hearers_in_view() in send_speech!")
continue
hearing_movable.Hear(rendered, src, message_language, message, , spans, message_mods)
/atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), face_name = FALSE)
//This proc uses text() because it is faster than appending strings. Thanks BYOND.
@@ -170,7 +173,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
return "[src]" //Returns the atom's name, prepended with 'The' if it's not a proper noun
/atom/movable/proc/IsVocal()
return 1
return TRUE
/atom/movable/proc/get_alt_name()
+6 -8
View File
@@ -78,14 +78,12 @@ falloff_distance - Distance at which falloff begins. Sound is at peak volume (in
if(below_turf && istransparentturf(turf_source))
listeners += SSmobs.clients_by_zlevel[below_turf.z]
for(var/P in listeners)
var/mob/M = P
if(get_dist(M, turf_source) <= maxdistance)
M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
for(var/P in SSmobs.dead_players_by_zlevel[source_z])
var/mob/M = P
if(get_dist(M, turf_source) <= maxdistance)
M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
for(var/mob/listening_mob as anything in listeners)
if(get_dist(listening_mob, turf_source) <= maxdistance)
listening_mob.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
for(var/mob/listening_mob as anything in SSmobs.dead_players_by_zlevel[source_z])
if(get_dist(listening_mob, turf_source) <= maxdistance)
listening_mob.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
/*! playsound
-1
View File
@@ -377,7 +377,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
return (mover.movement_type & PHASING)
return TRUE
/turf/open/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
. = ..()
//melting
@@ -280,12 +280,10 @@
/obj/item/abductor/silencer/proc/radio_off_mob(mob/living/carbon/human/M)
var/list/all_items = M.get_all_contents()
for(var/obj/I in all_items)
if(istype(I, /obj/item/radio/))
var/obj/item/radio/r = I
r.listening = 0
if(!istype(I, /obj/item/radio/headset))
r.broadcasting = 0 //goddamned headset hacks
for(var/obj/item/radio/radio in all_items)
radio.set_listening(FALSE)
if(!istype(radio, /obj/item/radio/headset))
radio.set_broadcasting(FALSE) //goddamned headset hacks
/obj/item/abductor/mind_device
name = "mental interface device"
@@ -168,7 +168,7 @@
radio = new(src)
radio.keyslot = new radio_key
radio.listening = 0
radio.set_listening(FALSE)
radio.recalculateChannels()
investigate_log("has been created.", INVESTIGATE_HYPERTORUS)
+1
View File
@@ -164,6 +164,7 @@
var/list/parallax_layers
var/list/parallax_layers_cached
///this is the last recorded client eye by SSparallax/fire()
var/atom/movable/movingmob
var/turf/previous_turf
///world.time of when we can state animate()ing parallax again
+3 -2
View File
@@ -532,10 +532,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
send2adminchat("Server", "[cheesy_message] (No admins online)")
QDEL_LIST_ASSOC_VAL(char_render_holders)
if(movingmob != null)
movingmob.client_mobs_in_contents -= mob
UNSETEMPTY(movingmob.client_mobs_in_contents)
LAZYREMOVE(movingmob.client_mobs_in_contents, mob)
movingmob = null
active_mousedown_item = null
SSambience.remove_ambience_client(src)
QDEL_NULL(view_size)
+1 -1
View File
@@ -2,7 +2,7 @@
GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
"tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key",
"power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup",
"client_mobs_in_contents", "bodyparts", "internal_organs", "hand_bodyparts", "overlays_standing", "hud_list",
"important_recursive_contents", "bodyparts", "internal_organs", "hand_bodyparts", "overlays_standing", "hud_list",
"actions", "AIStatus", "appearance", "managed_overlays", "managed_vis_overlays", "computer_id", "lastKnownIP", "implants",
"tgui_shared_states"
))
@@ -228,7 +228,7 @@ GLOBAL_LIST_EMPTY(security_officer_distribution)
/obj/item/radio/headset/headset_sec/alt/department/Initialize(mapload)
. = ..()
wires = new/datum/wires/radio(src)
secure_radio_connections = new
secure_radio_connections = list()
recalculateChannels()
/obj/item/radio/headset/headset_sec/alt/department/engi
+11 -4
View File
@@ -148,12 +148,16 @@
var/list/bounds
src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
for(var/I in gridSets)
var/datum/grid_set/gset = I
//used for sending the maxx and maxy expanded global signals at the end of this proc
var/has_expanded_world_maxx = FALSE
var/has_expanded_world_maxy = FALSE
for(var/datum/grid_set/gset as anything in gridSets)
var/ycrd = gset.ycrd + y_offset - 1
var/zcrd = gset.zcrd + z_offset - 1
if(!cropMap && ycrd > world.maxy)
world.maxy = ycrd // Expand Y here. X is expanded in the loop below
has_expanded_world_maxy = TRUE
var/zexpansion = zcrd > world.maxz
if(zexpansion)
if(cropMap)
@@ -179,6 +183,7 @@
break
else
world.maxx = xcrd
has_expanded_world_maxx = TRUE
if(xcrd >= 1)
var/model_key = copytext(line, tpos, tpos + key_len)
@@ -207,11 +212,13 @@
CHECK_TICK
if(!no_changeturf)
for(var/t in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ])))
var/turf/T = t
for(var/turf/T as anything in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ])))
//we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs
T.AfterChange(CHANGETURF_IGNORE_AIR)
if(has_expanded_world_maxx || has_expanded_world_maxy)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, has_expanded_world_maxx, has_expanded_world_maxy)
#ifdef TESTING
if(turfsSkipped)
testing("Skipped loading [turfsSkipped] default turfs")
@@ -19,7 +19,6 @@
/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level)
UNTIL(!adding_new_zlevel)
adding_new_zlevel = TRUE
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, args)
var/new_z = z_list.len + 1
if (world.maxz < new_z)
world.incrementMaxZ()
@@ -28,6 +27,7 @@
var/datum/space_level/S = new z_type(new_z, name, traits)
z_list += S
adding_new_zlevel = FALSE
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, S)
return S
/datum/controller/subsystem/mapping/proc/get_level(z)
@@ -18,7 +18,7 @@ GLOBAL_LIST(labor_sheet_values)
/obj/machinery/mineral/labor_claim_console/Initialize(mapload)
. = ..()
Radio = new /obj/item/radio(src)
Radio.listening = FALSE
Radio.set_listening(FALSE)
locate_stacking_machine()
//If we can't find a stacking machine end it all ok?
if(!stacking_machine)
+8 -8
View File
@@ -18,7 +18,7 @@
/obj/item/mmi/Initialize(mapload)
. = ..()
radio = new(src) //Spawns a radio inside the MMI.
radio.broadcasting = FALSE //researching radio mmis turned the robofabs into radios because this didnt start as 0.
radio.set_broadcasting(FALSE) //researching radio mmis turned the robofabs into radios because this didnt start as 0.
laws.set_laws_config()
/obj/item/mmi/Destroy()
@@ -104,8 +104,8 @@
/obj/item/mmi/attack_self(mob/user)
if(!brain)
radio.on = !radio.on
to_chat(user, span_notice("You toggle [src]'s radio system [radio.on==1 ? "on" : "off"]."))
radio.set_on(!radio.is_on())
to_chat(user, span_notice("You toggle [src]'s radio system [radio.is_on() == TRUE ? "on" : "off"]."))
else
eject_brain(user)
update_appearance()
@@ -204,12 +204,12 @@
if(brainmob.stat)
to_chat(brainmob, span_warning("Can't do that while incapacitated or dead!"))
if(!radio.on)
if(!radio.is_on())
to_chat(brainmob, span_warning("Your radio is disabled!"))
return
radio.listening = !radio.listening
to_chat(brainmob, span_notice("Radio is [radio.listening ? "now" : "no longer"] receiving broadcast."))
radio.set_listening(!radio.get_listening())
to_chat(brainmob, span_notice("Radio is [radio.get_listening() ? "now" : "no longer"] receiving broadcast."))
/obj/item/mmi/emp_act(severity)
. = ..()
@@ -235,7 +235,7 @@
/obj/item/mmi/examine(mob/user)
. = ..()
if(radio)
. += span_notice("There is a switch to toggle the radio system [radio.on ? "off" : "on"].[brain ? " It is currently being covered by [brain]." : null]")
. += span_notice("There is a switch to toggle the radio system [radio.is_on() ? "off" : "on"].[brain ? " It is currently being covered by [brain]." : null]")
if(brainmob)
var/mob/living/brain/B = brainmob
if(!B.key || !B.mind || B.stat == DEAD)
@@ -284,4 +284,4 @@
/obj/item/mmi/syndie/Initialize(mapload)
. = ..()
laws = new /datum/ai_laws/syndicate_override()
radio.on = FALSE
radio.set_on(FALSE)
@@ -30,7 +30,7 @@
/datum/species/dullahan/on_species_gain(mob/living/carbon/human/H, datum/species/old_species)
. = ..()
REMOVE_TRAIT(src, TRAIT_HEARING_SENSITIVE, TRAIT_GENERIC)
H.lose_hearing_sensitivity(TRAIT_GENERIC)
var/obj/item/bodypart/head/head = H.get_bodypart(BODY_ZONE_HEAD)
if(head)
head.drop_limb()
@@ -129,11 +129,13 @@
owner = new_owner
START_PROCESSING(SSobj, src)
RegisterSignal(owner, COMSIG_CLICK_SHIFT, .proc/examinate_check)
RegisterSignal(src, COMSIG_ATOM_HEARER_IN_VIEW, .proc/include_owner)
RegisterSignal(owner, COMSIG_LIVING_REGENERATE_LIMBS, .proc/unlist_head)
RegisterSignal(owner, COMSIG_LIVING_REVIVE, .proc/retrieve_head)
become_hearing_sensitive(ROUNDSTART_TRAIT)
/obj/item/dullahan_relay/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
owner.Hear(arglist(args))
/obj/item/dullahan_relay/process()
if(!istype(loc, /obj/item/bodypart/head) || QDELETED(owner))
. = PROCESS_KILL
+10 -7
View File
@@ -240,7 +240,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
// radios don't pick up whispers very well
radio_message = stars(radio_message)
spans |= SPAN_ITALICS
var/radio_return = radio(radio_message, message_mods, spans, language)
var/radio_return = radio(radio_message, message_mods, spans, language)//roughly 27% of living/say()'s total cost
if(radio_return & ITALICS)
spans |= SPAN_ITALICS
if(radio_return & REDUCE_RANGE)
@@ -248,7 +248,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
if(!message_mods[WHISPER_MODE])
message_mods[WHISPER_MODE] = MODE_WHISPER
if(radio_return & NOPASS)
return 1
return TRUE
//No screams in space, unless you're next to someone.
var/turf/T = get_turf(src)
@@ -260,13 +260,13 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message
spans |= SPAN_ITALICS
send_speech(message, message_range, src, bubble_type, spans, language, message_mods)
send_speech(message, message_range, src, bubble_type, spans, language, message_mods)//roughly 58% of living/say()'s total cost
if(succumbed)
succumb(1)
to_chat(src, compose_message(src, language, message, , spans, message_mods))
return 1
return TRUE
/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args)
@@ -315,7 +315,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
deaf_type = 2 // Since you should be able to hear yourself without looking
// Create map text prior to modifying message for goonchat
if (client?.prefs.read_preference(/datum/preference/toggle/enable_runechat) && !(stat == UNCONSCIOUS || stat == HARD_CRIT) && (client.prefs.read_preference(/datum/preference/toggle/enable_runechat_non_mobs) || ismob(speaker)) && can_hear())
if (client?.prefs.read_preference(/datum/preference/toggle/enable_runechat) && !(stat == UNCONSCIOUS || stat == HARD_CRIT) && (ismob(speaker) || client.prefs.read_preference(/datum/preference/toggle/enable_runechat_non_mobs)) && can_hear())
if (message_mods[MODE_CUSTOM_SAY_ERASE_INPUT])
create_chat_message(speaker, null, message_mods[MODE_CUSTOM_SAY_EMOTE], spans, EMOTE_MESSAGE)
else
@@ -357,7 +357,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
continue //Remove if underlying cause (likely byond issue) is fixed. See TG PR #49004.
if(player_mob.stat != DEAD) //not dead, not important
continue
if(get_dist(player_mob, src) > 7 || player_mob.z != z) //they're out of range of normal hearing
if(player_mob.z != z || get_dist(player_mob, src) > 7) //they're out of range of normal hearing
if(eavesdrop_range)
if(!(player_mob.client?.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off
continue
@@ -374,6 +374,9 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
var/rendered = compose_message(src, message_language, message, , spans, message_mods)
for(var/atom/movable/listening_movable as anything in listening)
if(!listening_movable)
stack_trace("somehow theres a null returned from get_hearers_in_view() in send_speech!")
continue
if(eavesdrop_range && get_dist(source, listening_movable) > message_range && !(the_dead[listening_movable]))
listening_movable.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mods)
else
@@ -444,7 +447,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
/mob/living/proc/radio(message, list/message_mods = list(), list/spans, language)
var/obj/item/implant/radio/imp = locate() in src
if(imp?.radio.on)
if(imp?.radio.is_on())
if(message_mods[MODE_HEADSET])
imp.radio.talk_into(src, message, , spans, language, message_mods)
return ITALICS | REDUCE_RANGE
+6 -6
View File
@@ -849,17 +849,17 @@
modules_action = new(malf_picker)
modules_action.Grant(src)
/mob/living/silicon/ai/reset_perspective(atom/A)
/mob/living/silicon/ai/reset_perspective(atom/new_eye)
if(camera_light_on)
light_cameras()
if(istype(A, /obj/machinery/camera))
current = A
if(istype(new_eye, /obj/machinery/camera))
current = new_eye
if(client)
if(ismovable(A))
if(A != GLOB.ai_camera_room_landmark)
if(ismovable(new_eye))
if(new_eye != GLOB.ai_camera_room_landmark)
end_multicam()
client.perspective = EYE_PERSPECTIVE
client.eye = A
client.eye = new_eye
else
end_multicam()
if(isturf(loc))
+1 -1
View File
@@ -318,7 +318,7 @@
return
if(Autochan == "Default") //Autospeak on whatever frequency to which the radio is set, usually Common.
radiomod = ";"
Autochan += " ([radio.frequency])"
Autochan += " ([radio.get_frequency()])"
else if(Autochan == "None") //Prevents use of the radio for automatic annoucements.
radiomod = ""
else //For department channels, if any, given by the internal radio.
+2 -1
View File
@@ -62,7 +62,8 @@
return FALSE
//We do this here to prevent hanging refs from ghostize or whatever, since if we were in another mob before this'll take care of it
clear_client_in_contents()
clear_important_client_contents(client)
enable_client_mobs_in_contents(client)
SEND_SIGNAL(src, COMSIG_MOB_LOGIN)
+2
View File
@@ -15,4 +15,6 @@
var/datum/callback/CB = foo
CB.Invoke()
clear_important_client_contents(client)
return TRUE
+35 -35
View File
@@ -36,8 +36,7 @@
for (var/alert in alerts)
clear_alert(alert, TRUE)
if(observers?.len)
for(var/M in observers)
var/mob/dead/observe = M
for(var/mob/dead/observe as anything in observers)
observe.reset_perspective(null)
qdel(hud_used)
QDEL_LIST(client_colours)
@@ -404,40 +403,43 @@
/**
* Reset the attached clients perspective (viewpoint)
*
* reset_perspective() set eye to common default : mob on turf, loc otherwise
* reset_perspective(null) set eye to common default : mob on turf, loc otherwise
* reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective)
*/
/mob/proc/reset_perspective(atom/A)
if(client)
if(A)
if(ismovable(A))
//Set the the thing unless it's us
if(A != src)
client.perspective = EYE_PERSPECTIVE
client.eye = A
else
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else if(isturf(A))
//Set to the turf unless it's our current turf
if(A != loc)
client.perspective = EYE_PERSPECTIVE
client.eye = A
else
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
/mob/proc/reset_perspective(atom/new_eye)
SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
if(!client)
return
if(new_eye)
if(ismovable(new_eye))
//Set the new eye unless it's us
if(new_eye != src)
client.perspective = EYE_PERSPECTIVE
client.eye = new_eye
else
//Do nothing
else
//Reset to common defaults: mob if on turf, otherwise current loc
if(isturf(loc))
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else
else if(isturf(new_eye))
//Set to the turf unless it's our current turf
if(new_eye != loc)
client.perspective = EYE_PERSPECTIVE
client.eye = loc
return 1
SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
client.eye = new_eye
else
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else
return TRUE //no setting eye to stupid things like areas or whatever
else
//Reset to common defaults: mob if on turf, otherwise current loc
if(isturf(loc))
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else
client.perspective = EYE_PERSPECTIVE
client.eye = loc
return TRUE
/**
* Examine a mob
@@ -1314,14 +1316,12 @@
SIGNAL_HANDLER
set_active_storage(null)
///Clears the client in contents list of our current "eye". Prevents hard deletes
///clears the client mob in our client_mobs_in_contents list
/mob/proc/clear_client_in_contents()
if(client?.movingmob) //In the case the client was transferred to another mob and not deleted.
client.movingmob.client_mobs_in_contents -= src
UNSETEMPTY(client.movingmob.client_mobs_in_contents)
if(client?.movingmob)
LAZYREMOVE(client.movingmob.client_mobs_in_contents, src)
client.movingmob = null
///Shows a tgui window with memories
/mob/verb/memory()
set name = "Memories"
@@ -255,7 +255,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
SSpoints_of_interest.make_point_of_interest(src)
radio = new(src)
radio.keyslot = new radio_key
radio.listening = 0
radio.set_listening(FALSE)
radio.recalculateChannels()
investigate_log("has been created.", INVESTIGATE_SUPERMATTER)
if(is_main_engine)
+1 -1
View File
@@ -718,7 +718,7 @@
if(!T || !istype(T.loc, area_type))
continue
for (var/atom/movable/movable as anything in T)
if (length(movable.client_mobs_in_contents))
if (movable.client_mobs_in_contents)
movable.update_parallax_contents()
/obj/docking_port/mobile/proc/check_transit_zone()
+9 -9
View File
@@ -128,14 +128,14 @@
<b>Radio settings:</b><br>
Microphone:
[radio? "<a href='?src=[REF(src)];rmictoggle=1'>\
<span id=\"rmicstate\">[radio.broadcasting?"Engaged":"Disengaged"]</span></a>":"Error"]<br>
<span id=\"rmicstate\">[radio.get_broadcasting()?"Engaged":"Disengaged"]</span></a>":"Error"]<br>
Speaker:
[radio? "<a href='?src=[REF(src)];rspktoggle=1'><span id=\"rspkstate\">\
[radio.listening?"Engaged":"Disengaged"]</span></a>":"Error"]<br>
[radio.get_listening()?"Engaged":"Disengaged"]</span></a>":"Error"]<br>
Frequency:
[radio? "<a href='?src=[REF(src)];rfreq=-10'>-</a>":"-"]
[radio? "<a href='?src=[REF(src)];rfreq=-2'>-</a>":"-"]
<span id=\"rfreq\">[radio?"[format_frequency(radio.frequency)]":"Error"]</span>
<span id=\"rfreq\">[radio?"[format_frequency(radio.get_frequency())]":"Error"]</span>
[radio? "<a href='?src=[REF(src)];rfreq=2'>+</a>":"+"]
[radio? "<a href='?src=[REF(src)];rfreq=10'>+</a>":"+"]<br>
</div>
@@ -336,21 +336,21 @@
//Toggles radio broadcasting
if(href_list["rmictoggle"])
radio.broadcasting = !radio.broadcasting
send_byjax(usr,"exosuit.browser","rmicstate",(radio.broadcasting?"Engaged":"Disengaged"))
radio.set_broadcasting(!radio.get_broadcasting())
send_byjax(usr,"exosuit.browser","rmicstate",(radio.get_broadcasting()?"Engaged":"Disengaged"))
return
//Toggles radio listening
if(href_list["rspktoggle"])
radio.listening = !radio.listening
send_byjax(usr,"exosuit.browser","rspkstate",(radio.listening?"Engaged":"Disengaged"))
radio.set_listening(!radio.get_listening())
send_byjax(usr,"exosuit.browser","rspkstate",(radio.get_listening()?"Engaged":"Disengaged"))
return
//Changes radio freqency.
if(href_list["rfreq"])
var/new_frequency = radio.frequency + text2num(href_list["rfreq"])
var/new_frequency = radio.get_frequency() + text2num(href_list["rfreq"])
radio.set_frequency(sanitize_frequency(new_frequency, radio.freerange))
send_byjax(usr,"exosuit.browser","rfreq","[format_frequency(radio.frequency)]")
send_byjax(usr,"exosuit.browser","rfreq","[format_frequency(radio.get_frequency())]")
return
//Changes the exosuit name.
+1 -1
View File
@@ -211,7 +211,7 @@
else if(circuit && (circuit.onstation != onstation)) //check if they're not the same to minimize the amount of edited values.
onstation = circuit.onstation //if it was constructed outside mapload, sync the vendor up with the circuit's var so you can't bypass price requirements by moving / reconstructing it off station.
Radio = new /obj/item/radio(src)
Radio.listening = 0
Radio.set_listening(FALSE)
/obj/machinery/vending/Destroy()
QDEL_NULL(wires)
+4
View File
@@ -143,6 +143,7 @@
#include "code\__DEFINES\space.dm"
#include "code\__DEFINES\spaceman_dmm.dm"
#include "code\__DEFINES\span.dm"
#include "code\__DEFINES\spatial_gridmap.dm"
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\stat_tracking.dm"
#include "code\__DEFINES\station.dm"
@@ -206,6 +207,7 @@
#include "code\__DEFINES\dcs\signals\signals_reagent.dm"
#include "code\__DEFINES\dcs\signals\signals_restaurant.dm"
#include "code\__DEFINES\dcs\signals\signals_scangate.dm"
#include "code\__DEFINES\dcs\signals\signals_spatial_grid.dm"
#include "code\__DEFINES\dcs\signals\signals_specie.dm"
#include "code\__DEFINES\dcs\signals\signals_storage.dm"
#include "code\__DEFINES\dcs\signals\signals_subsystem.dm"
@@ -282,6 +284,7 @@
#include "code\__HELPERS\roundend.dm"
#include "code\__HELPERS\sanitize_values.dm"
#include "code\__HELPERS\shell.dm"
#include "code\__HELPERS\spatial_info.dm"
#include "code\__HELPERS\spawns.dm"
#include "code\__HELPERS\stack_trace.dm"
#include "code\__HELPERS\stat_tracking.dm"
@@ -457,6 +460,7 @@
#include "code\controllers\subsystem\sound_loops.dm"
#include "code\controllers\subsystem\sounds.dm"
#include "code\controllers\subsystem\spacedrift.dm"
#include "code\controllers\subsystem\spatial_gridmap.dm"
#include "code\controllers\subsystem\statpanel.dm"
#include "code\controllers\subsystem\stickyban.dm"
#include "code\controllers\subsystem\sun.dm"