mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
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:
@@ -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"
|
||||
|
||||
@@ -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) )
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
@@ -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
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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 ..()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
. = ..()
|
||||
|
||||
Radio = new/obj/item/radio(src)
|
||||
Radio.listening = 0
|
||||
Radio.set_listening(FALSE)
|
||||
|
||||
/obj/machinery/door_timer/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -15,4 +15,6 @@
|
||||
var/datum/callback/CB = foo
|
||||
CB.Invoke()
|
||||
|
||||
clear_important_client_contents(client)
|
||||
|
||||
return TRUE
|
||||
|
||||
+35
-35
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user