mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 12:41:09 +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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user