diff --git a/code/__DEFINES/mob_defines.dm b/code/__DEFINES/mob_defines.dm index 81d6c5d61221e..1de9803499240 100644 --- a/code/__DEFINES/mob_defines.dm +++ b/code/__DEFINES/mob_defines.dm @@ -273,7 +273,7 @@ #define ispathanimal(A) (ispath(A, /mob/living/simple_animal)) #define iscameramob(A) (istype((A), /mob/camera)) -#define is_ai_eye(A) (istype((A), /mob/camera/eye)) +#define is_ai_eye(A) (istype((A), /mob/camera/eye/ai)) #define isovermind(A) (istype((A), /mob/camera/blob)) #define isobserver(A) (istype((A), /mob/dead/observer)) diff --git a/code/__DEFINES/sight.dm b/code/__DEFINES/sight.dm index 842bc77edbe73..3f9e6c40d1917 100644 --- a/code/__DEFINES/sight.dm +++ b/code/__DEFINES/sight.dm @@ -48,3 +48,8 @@ #define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses #define VISOR_DARKNESSVIEW (1<<3) #define VISOR_INVISVIEW (1<<4) + +// Should AI eyes be counted for get_mobs_in_view? +#define AI_EYE_EXCLUDE 0 +#define AI_EYE_REQUIRE_HEAR 1 +#define AI_EYE_INCLUDE 2 diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 771023644467b..b7b2b5fca1edd 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -146,7 +146,7 @@ return turfs /// Recursively loops through the contents of this atom looking for mobs, optionally requiring them to have a client. -/proc/collect_nested_mobs(atom/parent, list/mobs, recursion_limit = 3, client_check = TRUE) +/proc/collect_nested_mobs(atom/parent, list/mobs, recursion_limit = 3, client_check = TRUE, ai_eyes = AI_EYE_EXCLUDE) var/list/next_layer = list(parent) for(var/depth in 1 to recursion_limit) var/list/layer = next_layer @@ -156,7 +156,16 @@ continue var/mob/this_mob = thing if(!client_check || this_mob.client) - mobs += this_mob + if(is_ai(this_mob)) + // AIs can get messages from their eye as well as themselves, so use |= to make sure they don't get double messages. + mobs |= this_mob + else + // Everything else can only be visited once, so use += for efficiency. + mobs += this_mob + else if(ai_eyes != AI_EYE_EXCLUDE && is_ai_eye(this_mob)) + var/mob/camera/eye/ai/eye = this_mob + if((ai_eyes == AI_EYE_INCLUDE || eye.relay_speech) && eye.ai && (!client_check || eye.ai.client)) + mobs |= eye.ai for(var/mob/dead/observer/ghost in this_mob.observers) if(!client_check || ghost.client) mobs += ghost @@ -166,7 +175,7 @@ // The old system would loop through lists for a total of 5000 per function call, in an empty server. // This new system will loop at around 1000 in an empty server. -/proc/get_mobs_in_view(R, atom/source, include_clientless = FALSE) +/proc/get_mobs_in_view(R, atom/source, include_clientless = FALSE, ai_eyes = AI_EYE_EXCLUDE) // Returns a list of mobs in range of R from source. Used in radio and say code. #ifdef GAME_TESTS // kind of feels cleaner clobbering here than changing the loop? @@ -181,7 +190,7 @@ for(var/atom/A in hear(R, T)) if(isobj(A) || ismob(A)) - collect_nested_mobs(A, hear, 3, !include_clientless) + collect_nested_mobs(A, hear, 3, !include_clientless, ai_eyes) return hear diff --git a/code/datums/emote.dm b/code/datums/emote.dm index bfdc4d593336b..f97f08652b0fe 100644 --- a/code/datums/emote.dm +++ b/code/datums/emote.dm @@ -292,7 +292,7 @@ var/runechat_text = text if(length(text) > 100) runechat_text = "[copytext(text, 1, 101)]..." - var/list/can_see = get_mobs_in_view(1, user) //Allows silicon & mmi mobs carried around to see the emotes of the person carrying them around. + var/list/can_see = get_mobs_in_view(1, user, ai_eyes=AI_EYE_INCLUDE) //Allows silicon & mmi mobs carried around to see the emotes of the person carrying them around. can_see |= viewers(user, null) for(var/mob/O as anything in can_see) if(O.status_flags & PASSEMOTES) @@ -304,6 +304,10 @@ if(O.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT) O.create_chat_message(user, runechat_text, symbol = RUNECHAT_SYMBOL_EMOTE) + if(is_ai_eye(O)) + var/mob/camera/eye/ai/eye = O + if(!(eye.ai in can_see) && eye.ai?.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT) + eye.ai.create_chat_message(user, runechat_text, symbol = RUNECHAT_SYMBOL_EMOTE) /** * Check whether or not an emote can be used due to a cooldown. diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 8a888c4f195a0..76a49169c7b6e 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1170,7 +1170,7 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) if(!message) return var/list/speech_bubble_hearers = list() - for(var/mob/M as anything in get_mobs_in_view(7, src)) + for(var/mob/M as anything in get_mobs_in_view(7, src, ai_eyes=AI_EYE_REQUIRE_HEAR)) M.show_message("[src] [atom_say_verb], \"[message]\"", 2, null, 1) if(M.client) speech_bubble_hearers += M.client diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm index 83e7c32d8b69f..ad8964afbb078 100644 --- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm +++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm @@ -578,7 +578,7 @@ else if(istype(loc, /obj/machinery/hologram/holopad)) var/obj/machinery/hologram/holopad/H = loc name = "[H]" - for(var/mob/M as anything in get_mobs_in_view(7, H)) + for(var/mob/M as anything in get_mobs_in_view(7, H, ai_eyes = AI_EYE_REQUIRE_HEAR)) M.hear_say(message_pieces, verb, FALSE, src) name = real_name return TRUE @@ -594,7 +594,7 @@ /mob/living/simple_animal/demon/pulse_demon/visible_message(message, self_message, blind_message, chat_message_type) // overriden because pulse demon is quite often in non-turf locs, and /mob/visible_message acts differently there - for(var/mob/M as anything in get_mobs_in_view(7, src)) + for(var/mob/M as anything in get_mobs_in_view(7, src, ai_eyes = AI_EYE_INCLUDE)) if(M.see_invisible < invisibility) continue //can't view the invisible var/msg = message diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm index 12bbbb31bc7b3..86848b4911394 100644 --- a/code/game/objects/items/devices/megaphone.dm +++ b/code/game/objects/items/devices/megaphone.dm @@ -83,7 +83,7 @@ for(var/obj/O in view(14, get_turf(src))) O.hear_talk(user, message_to_multilingual("[message]")) - for(var/mob/M as anything in get_mobs_in_view(7, src)) + for(var/mob/M as anything in get_mobs_in_view(7, src, ai_eyes = AI_EYE_REQUIRE_HEAR)) if((M.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT) && M.can_hear()) M.create_chat_message(user, message, FALSE, "big") diff --git a/code/game/objects/items/devices/radio/radio_objects.dm b/code/game/objects/items/devices/radio/radio_objects.dm index 98a12cb914520..dda8d996a335b 100644 --- a/code/game/objects/items/devices/radio/radio_objects.dm +++ b/code/game/objects/items/devices/radio/radio_objects.dm @@ -547,7 +547,7 @@ GLOBAL_LIST_EMPTY(deadsay_radio_systems) /obj/item/radio/proc/send_announcement() if(is_listening()) - return get_mobs_in_view(canhear_range, src) + return get_mobs_in_view(canhear_range, src, ai_eyes = AI_EYE_REQUIRE_HEAR) return null diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 5bc8d953e2a98..bd849b8974fc9 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -150,7 +150,7 @@ /mob/visible_message(message, self_message, blind_message, chat_message_type) if(!isturf(loc)) // mobs inside objects (such as lockers) shouldn't have their actions visible to those outside the object - for(var/mob/M as anything in get_mobs_in_view(3, src)) + for(var/mob/M as anything in get_mobs_in_view(3, src, ai_eyes = AI_EYE_INCLUDE)) if(M.see_invisible < invisibility) continue //can't view the invisible var/msg = message @@ -162,7 +162,7 @@ msg = blind_message M.show_message(msg, EMOTE_VISIBLE, blind_message, EMOTE_AUDIBLE, chat_message_type) return - for(var/mob/M as anything in get_mobs_in_view(7, src)) + for(var/mob/M as anything in get_mobs_in_view(7, src, ai_eyes = AI_EYE_INCLUDE)) if(M.see_invisible < invisibility) continue //can't view the invisible var/msg = message @@ -175,7 +175,7 @@ // message is output to anyone who can see, e.g. "The [src] does something!" // blind_message (optional) is what blind people will hear e.g. "You hear something!" /atom/proc/visible_message(message, blind_message) - for(var/mob/M as anything in get_mobs_in_view(7, src)) + for(var/mob/M as anything in get_mobs_in_view(7, src, ai_eyes = AI_EYE_INCLUDE)) if(!M.client) continue M.show_message(message, EMOTE_VISIBLE, blind_message, EMOTE_AUDIBLE) @@ -191,7 +191,7 @@ if(hearing_distance) range = hearing_distance var/msg = message - for(var/mob/M as anything in get_mobs_in_view(range, src)) + for(var/mob/M as anything in get_mobs_in_view(range, src, ai_eyes = AI_EYE_REQUIRE_HEAR)) M.show_message(msg, EMOTE_AUDIBLE, deaf_message, EMOTE_VISIBLE) // based on say code @@ -217,7 +217,7 @@ var/range = 7 if(hearing_distance) range = hearing_distance - for(var/mob/M as anything in get_mobs_in_view(range, src)) + for(var/mob/M as anything in get_mobs_in_view(range, src, ai_eyes = AI_EYE_REQUIRE_HEAR)) M.show_message(message, EMOTE_AUDIBLE, deaf_message, EMOTE_VISIBLE) /mob/proc/findname(msg)