diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm
index 57ff4b70138..b10463b4584 100644
--- a/code/_helpers/game.dm
+++ b/code/_helpers/game.dm
@@ -248,8 +248,11 @@
. |= M // Since we're already looping through mobs, why bother using |= ? This only slows things down.
return .
-/proc/get_mobs_and_objs_in_view_fast(var/turf/T, var/range, var/checkghosts = 1)
-
+//Uses dview to quickly return mobs and objects in view,
+// then adds additional mobs or objects if they are in range 'smartly',
+// based on their presence in lists of players or registered objects
+// Type: 1-audio, 2-visual, 0-neither
+/proc/get_mobs_and_objs_in_view_fast(var/turf/T, var/range, var/type = 1)
var/list/mobs = list()
var/list/objs = list()
@@ -261,21 +264,30 @@
mobs += AM
hearturfs += AM.locs[1]
else if(isobj(AM))
- objs += AM
+ objs += AM
hearturfs += AM.locs[1]
-
+ //A list of every mob with a client
for(var/mob/M in player_list)
- if(checkghosts && M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
- mobs |= M
- continue
if(M.loc && M.locs[1] in hearturfs)
mobs |= M
+ else if(M.stat == DEAD)
+ switch(type)
+ if(1) //Audio messages use ghost_ears
+ if(M.is_preference_enabled(/datum/client_preference/ghost_ears))
+ mobs |= M
+ if(2) //Visual messages use ghost_sight
+ if(M.is_preference_enabled(/datum/client_preference/ghost_sight))
+ mobs |= M
+
+ //For objects below the top level who still want to hear
+ for(var/obj/O in listening_objects)
+ if(O.loc && O.locs[1] in hearturfs)
+ objs |= O
+
return list("mobs" = mobs, "objs" = objs)
-
-
#define SIGN(X) ((X<0)?-1:1)
proc
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index 1a1eced63a1..9a90a3676b7 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -11,6 +11,7 @@ var/global/list/human_mob_list = list() //List of all human mobs and sub-type
var/global/list/silicon_mob_list = list() //List of all silicon mobs, including clientless
var/global/list/living_mob_list = list() //List of all alive mobs, including clientless. Excludes /mob/new_player
var/global/list/dead_mob_list = list() //List of all dead mobs, including clientless. Excludes /mob/new_player
+var/global/list/listening_objects = list() //List of all objects which care about receiving messages (communicators, radios, etc)
var/global/list/cable_list = list() //Index for all cables, so that powernets don't have to look through the entire world all the time
var/global/list/chemical_reactions_list //list of all /datum/chemical_reaction datums. Used during chemical reactions
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 66d453ac36d..7b9cf82cc52 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -6,12 +6,14 @@ var/datum/global_hud/global_hud = new()
var/list/global_huds = list(
global_hud.druggy,
global_hud.blurry,
+ global_hud.whitense,
global_hud.vimpaired,
global_hud.darkMask,
global_hud.nvg,
global_hud.thermal,
global_hud.meson,
- global_hud.science)
+ global_hud.science
+ )
/datum/hud/var/obj/screen/grab_intent
/datum/hud/var/obj/screen/hurt_intent
@@ -21,6 +23,7 @@ var/list/global_huds = list(
/datum/global_hud
var/obj/screen/druggy
var/obj/screen/blurry
+ var/obj/screen/whitense
var/list/vimpaired
var/list/darkMask
var/obj/screen/nvg
@@ -53,6 +56,14 @@ var/list/global_huds = list(
blurry.layer = 17
blurry.mouse_opacity = 0
+ //static overlay effect for cameras and the like
+ whitense = new /obj/screen()
+ whitense.screen_loc = ui_entire_screen
+ whitense.icon = 'icons/effects/static.dmi'
+ whitense.icon_state = "1 light"
+ whitense.layer = 17
+ whitense.mouse_opacity = 0
+
nvg = setup_overlay("nvg_hud")
thermal = setup_overlay("thermal_hud")
meson = setup_overlay("meson_hud")
diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm
index bc80bc9db36..e7831391361 100644
--- a/code/game/objects/items/devices/communicator/communicator.dm
+++ b/code/game/objects/items/devices/communicator/communicator.dm
@@ -17,6 +17,8 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
origin_tech = list(TECH_ENGINEERING = 2, TECH_MAGNET = 2, TECH_BLUESPACE = 2, TECH_DATA = 2)
matter = list(DEFAULT_WALL_MATERIAL = 30,"glass" = 10)
+ var/video_range = 4
+
var/list/voice_mobs = list()
var/list/voice_requests = list()
var/list/voice_invites = list()
@@ -474,6 +476,24 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
name = "[owner]'s [initial(name)]"
+
+// Proc: add_communicating()
+// Parameters: 1 (comm - the communicator to add to communicating)
+// Description: Used when this communicator gets a new communicator to relay say/me messages to
+/obj/item/device/communicator/proc/add_communicating(obj/item/device/communicator/comm)
+ if(!comm || !istype(comm)) return
+
+ communicating |= comm
+ listening_objects |= src
+
+// Proc: del_communicating()
+// Parameters: 1 (comm - the communicator to remove from communicating)
+// Description: Used when this communicator is being asked to stop relaying say/me messages to another
+/obj/item/device/communicator/proc/del_communicating(obj/item/device/communicator/comm)
+ if(!comm || !istype(comm)) return
+
+ communicating.Remove(comm)
+
// Proc: open_connection()
// Parameters: 2 (user - the person who initiated the connecting being opened, candidate - the communicator or observer that will connect to the device)
// Description: Typechecks the candidate, then calls the correct proc for further connecting.
@@ -507,8 +527,8 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
comm.visible_message("\icon[src] Connection to [src] at [exonet.address] established.")
sleep(20)
- communicating |= comm
- comm.communicating |= src
+ src.add_communicating(comm)
+ comm.add_communicating(src)
// Proc: open_connection_to_ghost()
// Parameters: 2 (user - the person who initiated this, candidate - the ghost that will be turned into a voice mob)
@@ -530,6 +550,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
new_voice.mind = candidate.mind //Transfer the mind, if any.
new_voice.ckey = candidate.ckey //Finally, bring the client over.
voice_mobs.Add(new_voice)
+ listening_objects |= src
var/obj/screen/blackness = new() //Makes a black screen, so the candidate can't see what's going on before actually 'connecting' to the communicator.
blackness.screen_loc = ui_entire_screen
@@ -586,10 +607,13 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
continue
comm.visible_message("\icon[src] [reason].")
visible_message("\icon[src] [reason].")
- comm.communicating.Remove(src)
- communicating.Remove(comm)
+ src.del_communicating(comm)
+ comm.del_communicating(src)
update_icon()
+ if(voice_mobs.len == 0 && communicating.len == 0)
+ listening_objects.Remove(src)
+
// Proc: request()
// Parameters: 1 (candidate - the ghost or communicator wanting to call the device)
// Description: Response to a communicator or observer trying to call the device. Adds them to the list of requesters
@@ -675,6 +699,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
voice_invites.Cut()
all_communicators -= src
processing_objects -= src
+ listening_objects.Remove(src)
if(exonet)
exonet.remove_address()
exonet = null
@@ -700,10 +725,18 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
/obj/item/device/communicator/see_emote(mob/living/M, text)
var/rendered = "\icon[src] [text]"
for(var/obj/item/device/communicator/comm in communicating)
- for(var/mob/mob in viewers(get_turf(comm))) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other.
- mob.show_message(rendered)
- for(var/mob/living/voice/V in comm.contents)
- V.show_message(rendered)
+ var/turf/T = get_turf(comm)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) //Range of 3 since it's a tiny video display
+ var/list/mobs_to_relay = in_range["mobs"]
+
+ for(var/mob/mob in mobs_to_relay) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other.
+ var/dst = get_dist(get_turf(mob),get_turf(comm))
+ if(dst <= video_range)
+ mob.show_message(rendered)
+ else
+ mob << "You can barely see some movement on \the [src]'s display."
+
..()
// Proc: hear_talk()
@@ -712,9 +745,12 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
// Description: Relays the speech to all linked communicators.
/obj/item/device/communicator/hear_talk(mob/living/M, text, verb, datum/language/speaking)
for(var/obj/item/device/communicator/comm in communicating)
- var/list/mobs_to_relay = list()
- mobs_to_relay |= viewers(get_turf(comm))
- mobs_to_relay |= comm.contents //Needed so ghost-callers can see speech.
+
+ var/turf/T = get_turf(comm)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
+ var/list/mobs_to_relay = in_range["mobs"]
+
for(var/mob/mob in mobs_to_relay)
//Can whoever is hearing us understand?
if(!mob.say_understands(M, speaking))
@@ -736,7 +772,12 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
/obj/item/device/communicator/show_message(msg, type, alt, alt_type)
var/rendered = "\icon[src] [msg]"
for(var/obj/item/device/communicator/comm in communicating)
- for(var/mob/mob in hearers(get_turf(comm))) //Ditto for audible messages.
+ var/turf/T = get_turf(comm)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
+ var/list/mobs_to_relay = in_range["mobs"]
+
+ for(var/mob/mob in mobs_to_relay)
mob.show_message(rendered)
..()
@@ -822,4 +863,4 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
usr << "You can't do that because you are dead!"
return
- src.attack_self(usr)
\ No newline at end of file
+ src.attack_self(usr)
diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index 3e6ed273021..2a0f3b66071 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -106,7 +106,13 @@
log_ooc("(LOCAL) [mob.name]/[key] : [msg]")
var/mob/source = mob.get_looc_source()
- var/list/heard = get_mobs_or_objects_in_view(7, get_turf(source), 1, 0)
+ var/turf/T = get_turf(source)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
+ var/list/m_viewers = in_range["mobs"]
+
+ var/list/receivers = list() // Clients, not mobs.
+ var/list/r_receivers = list()
var/display_name = key
if(holder && holder.fakekey)
@@ -114,34 +120,33 @@
if(mob.stat != DEAD)
display_name = mob.name
- for(var/client/target in clients)
- if(target.is_preference_enabled(/datum/client_preference/show_looc))
- var/prefix = ""
- var/admin_stuff = ""
- var/send = 0
+ // Everyone in normal viewing range of the LOOC
+ for(var/mob/viewer in m_viewers)
+ if(viewer.client && viewer.client.is_preference_enabled(/datum/client_preference/show_looc))
+ receivers |= viewer.client
+ else if(istype(viewer,/mob/observer/eye)) // For AI eyes and the like
+ var/mob/observer/eye/E = viewer
+ if(E.owner && E.owner.client)
+ receivers |= E.owner.client
- if(target in admins)
- admin_stuff += "/([key])"
- if(target != src)
- admin_stuff += "([admin_jump_link(mob, target.holder)])"
+ // Admins with RLOOC displayed who weren't already in
+ for(var/client/admin in admins)
+ if(!(admin in receivers) && admin.is_preference_enabled(/datum/client_preference/holder/show_rlooc))
+ r_receivers |= admin
- if(target.mob in heard)
- send = 1
- if(isAI(target.mob))
- prefix = "(Core) "
+ // Send a message
+ for(var/client/target in receivers)
+ var/admin_stuff = ""
+
+ if(target in admins)
+ admin_stuff += "/([key])"
- else if(isAI(target.mob)) // Special case
- var/mob/living/silicon/ai/A = target.mob
- if(A.eyeobj in hearers(7, source))
- send = 1
- prefix = "(Eye) "
+ target << "" + create_text_tag("looc", "LOOC:", target) + " [display_name][admin_stuff]: [msg]"
- if(!send && (target in admins) && target.is_preference_enabled(/datum/client_preference/holder/show_rlooc))
- send = 1
- prefix = "(R)"
-
- if(send)
- target << "" + create_text_tag("looc", "LOOC:", target) + " [prefix][display_name][admin_stuff]: [msg]"
+ for(var/client/target in r_receivers)
+ var/admin_stuff = "/([key])([admin_jump_link(mob, target.holder)])"
+
+ target << "" + create_text_tag("looc", "LOOC:", target) + " (R)[display_name][admin_stuff]: [msg]"
/mob/proc/get_looc_source()
return src
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index 7fb80529095..15b99923752 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -1,7 +1,7 @@
// All mobs should have custom emote, really..
//m_type == 1 --> visual.
//m_type == 2 --> audible
-/mob/proc/custom_emote(var/m_type=1,var/message = null)
+/mob/proc/custom_emote(var/m_type=1,var/message = null,var/range=world.view)
if(stat || !use_me && usr == src)
src << "You are unable to emote."
return
@@ -23,42 +23,23 @@
if (message)
log_emote("[name]/[key] : [message]")
- //Hearing gasp and such every five seconds is not good emotes were not global for a reason.
+ // Hearing gasp and such every five seconds is not good emotes were not global for a reason.
// Maybe some people are okay with that.
- for(var/mob/M in player_list)
- if (!M.client)
- continue //skip monkeys and leavers
- if (istype(M, /mob/new_player))
- continue
- if(findtext(message," snores.")) //Because we have so many sleeping people.
- break
- if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_sight) && !(M in viewers(src,null)))
- M.show_message(message, m_type)
+ var/turf/T = get_turf(src)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,range,2)
+ var/list/m_viewers = in_range["mobs"]
+ var/list/o_viewers = in_range["objs"]
- if (m_type & 1)
- var/list/see = get_mobs_or_objects_in_view(world.view,src) | viewers(get_turf(src), null)
- for(var/I in see)
- if(isobj(I))
- spawn(0)
- if(I) //It's possible that it could be deleted in the meantime.
- var/obj/O = I
- O.see_emote(src, message, 1)
- else if(ismob(I))
- var/mob/M = I
- M.show_message(message, 1)
-
- else if (m_type & 2)
- var/list/hear = get_mobs_or_objects_in_view(world.view,src)
- for(var/I in hear)
- if(isobj(I))
- spawn(0)
- if(I) //It's possible that it could be deleted in the meantime.
- var/obj/O = I
- O.see_emote(src, message, 2)
- else if(ismob(I))
- var/mob/M = I
- M.show_message(message, 2)
+ for(var/mob/M in m_viewers)
+ spawn(0) // It's possible that it could be deleted in the meantime, or that it runtimes.
+ if(M)
+ M.show_message(message, m_type)
+ for(var/obj/O in o_viewers)
+ spawn(0)
+ if(O)
+ O.see_emote(src, message, m_type)
/mob/proc/emote_dead(var/message)
diff --git a/code/modules/mob/living/voice/voice.dm b/code/modules/mob/living/voice/voice.dm
index e4eac0f439c..a27b0ffc948 100644
--- a/code/modules/mob/living/voice/voice.dm
+++ b/code/modules/mob/living/voice/voice.dm
@@ -42,12 +42,9 @@
// Parameters: None
// Description: Adds a static overlay to the client's screen.
/mob/living/voice/Login()
- var/obj/screen/static_effect = new() //Since what the player sees is essentially a video feed, from a vast distance away, the view isn't going to be perfect.
- static_effect.screen_loc = ui_entire_screen
- static_effect.icon = 'icons/effects/static.dmi'
- static_effect.icon_state = "1 light"
- static_effect.mouse_opacity = 0 //So the static doesn't get in the way of clicking.
- client.screen.Add(static_effect)
+ ..()
+ client.screen |= global_hud.whitense
+ client.screen |= global_hud.darkMask
// Proc: Destroy()
// Parameters: None
@@ -110,7 +107,7 @@
// Proc: say()
// Parameters: 4 (generic say() arguments)
// Description: Adds a speech bubble to the communicator device, then calls ..() to do the real work.
-/mob/living/voice/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="")
+/mob/living/voice/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="", var/whispering=0)
//Speech bubbles.
if(comm)
var/speech_bubble_test = say_test(message)
@@ -122,4 +119,8 @@
M << speech_bubble
src << speech_bubble
- ..(message, speaking, verb, alt_name) //mob/living/say() can do the actual talking.
\ No newline at end of file
+ ..(message, speaking, verb, alt_name, whispering) //mob/living/say() can do the actual talking.
+
+/mob/living/voice/custom_emote(var/m_type=1,var/message = null,var/range=world.view)
+ if(!comm) return
+ ..(m_type,message,comm.video_range)
diff --git a/code/modules/surgery/limb_reattach.dm b/code/modules/surgery/limb_reattach.dm
index ee0dfecb4fd..8e6e8da21ff 100644
--- a/code/modules/surgery/limb_reattach.dm
+++ b/code/modules/surgery/limb_reattach.dm
@@ -40,7 +40,7 @@
end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/E = tool
- user.visible_message("[user] has attached [target]'s [E.name] to the [E.amputation_point].>", \
+ user.visible_message("[user] has attached [target]'s [E.name] to the [E.amputation_point].", \
"You have attached [target]'s [E.name] to the [E.amputation_point].")
user.drop_from_inventory(E)
E.replaced(target)