diff --git a/code/modules/mob/say_ch.dm b/code/modules/mob/say_ch.dm
new file mode 100644
index 0000000000..9f0225f7ef
--- /dev/null
+++ b/code/modules/mob/say_ch.dm
@@ -0,0 +1,53 @@
+/mob/proc/get_all_in_bellies()
+ var/list/mobs_in_belly = list()
+ for (var/obj/belly/belly in contents) //dogborg sleeper, etc
+ for (var/mob/M in belly.contents)
+ mobs_in_belly |= M.get_all_in_bellies()
+ mobs_in_belly |= M
+ return mobs_in_belly
+
+/atom/proc/get_ultimate_mob()
+ var/mob/ultimate_mob
+ var/atom/to_check = loc
+ var/n = 0
+ while (to_check && !isturf(to_check) && n < 6)
+ if (ismob(to_check))
+ ultimate_mob = to_check
+ to_check = to_check.loc
+ n = 0
+ n++
+ return ultimate_mob
+
+/atom/proc/get_ultimate_belly()
+ var/obj/belly/ultimate_belly
+ var/atom/to_check = loc
+ var/n = 0
+ while (to_check && !isturf(to_check) && n < 6)
+ if (isbelly(to_check))
+ ultimate_belly = to_check
+ to_check = to_check.loc
+ n = 0
+ n++
+ return ultimate_belly
+
+/mob/verb/me_verb_subtle_distanced(message as message) //This would normally go in say.dm
+ set name = "Subtle-Targeted"
+ set category = "IC"
+ set desc = "Emote to nearby people (and your pred/prey), picking from selected targets afterwards."
+
+ if(say_disabled) //This is here to try to identify lag problems
+ to_chat(usr, "Speech is currently admin-disabled.")
+ return
+ //VOREStation Addition Start
+ if(forced_psay)
+ pme(message)
+ return
+ //VOREStation Addition End
+
+ message = sanitize_or_reflect(message,src) //VOREStation Edit - Reflect too-long messages (within reason)
+ if(!message)
+ return
+
+ set_typing_indicator(FALSE)
+ if(use_me)
+ usr.custom_emote_vr(4,message,TRUE)
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index c0a2a4b5e2..1fb88910e1 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -26,7 +26,7 @@
else
usr.emote_vr(message)
-/mob/proc/custom_emote_vr(var/m_type=1,var/message = null) //This would normally go in emote.dm
+/mob/proc/custom_emote_vr(var/m_type=1,var/message = null, var/distanced=FALSE) //This would normally go in emote.dm //CHOMPEdit: subtle-distance
if(stat || !use_me && usr == src)
to_chat(src, "You are unable to emote.")
return
@@ -60,7 +60,102 @@
var/list/vis_mobs = vis["mobs"]
var/list/vis_objs = vis["objs"]
- for(var/mob/M as anything in vis_mobs)
+ //CHOMPEdit: subtle target selection
+ var/all_targets_mobs = list()
+ var/all_targets_objs = list()
+ if (distanced)
+ var/obj/belly/u_belly = get_ultimate_belly()
+ var/mob/living/u_pred = u_belly?.owner || src
+
+ var/valid_targets = list("One tile radius" = "otr", "Single tile" = "st", "All in belly and preds" = "aibap")
+ for (var/mob/M as anything in vis_mobs)
+ if (M == src)
+ continue
+ if (istype(M, /mob/living) && isturf(M.loc) && !u_belly)
+ valid_targets["[M]"] = "\ref[M]"
+ continue
+ if (get_turf(M) == get_turf(src)) //so we aren't going through everything, it's a safe bet the belly is in the mob it's supposed to be in
+ var/obj/belly/belly = M.get_ultimate_belly()
+ if (belly?.owner == u_pred || u_pred == M)
+ valid_targets["[M]"] = "\ref[M]"
+ for (var/obj/item/i in vis_objs)
+ if (LAZYLEN(i.possessed_voice) && i.get_ultimate_mob() == src)
+ valid_targets["[i]"] = "\ref[i]"
+ valid_targets += list("Cancel" = "c")
+ valid_targets += list("Cancel and print to chat" = "captc")
+ var/selected = input(src, "Choose the target to send it to.", "Subtle Distance", "One tile radius") as anything in valid_targets //default to one tile radius
+ var/target = valid_targets[selected]
+ if (target == "c" || isnull(target))
+ return
+ if (target == "captc")
+ to_chat(src, "Cancelled message (pressed cancel): [message]")
+ return
+
+ vis = get_mobs_and_objs_in_view_fast(get_turf(src),1,2) //in case it changed
+ vis_mobs = vis["mobs"]
+ vis_objs = vis["objs"]
+
+ var/cancelled
+
+ switch(target)
+ if ("otr", null)
+ all_targets_mobs |= vis_mobs
+ all_targets_objs |= vis_objs
+ if ("st")
+ for (var/mob/M as anything in vis_mobs)
+ if (isobserver(M) || get_dist(get_turf(src), get_turf(M)) < 1)
+ all_targets_mobs |= M
+ for (var/obj/M as anything in vis_objs)
+ if (isobserver(M) || get_dist(get_turf(src), get_turf(M)) < 1)
+ all_targets_objs |= M
+ if ("aibap")
+ var/obj/belly/belly = get_ultimate_belly() //in case it's changed
+ var/mob/pred = belly?.owner || src
+ if (pred)
+ all_targets_mobs |= pred.get_all_in_bellies()
+ for (var/mob/M as anything in vis_mobs)
+ if (isobserver(M))
+ all_targets_mobs |= M
+ continue
+ if (get_dist(get_turf(src), get_turf(M)) > 1)
+ continue
+ if (!isturf(M.loc) && !(M in all_targets_mobs)) //possessed items, mobs in bags, etc
+ if (M.get_ultimate_belly() in pred.contents)
+ all_targets_mobs |= M
+ if (istype(M, /mob/living/dominated_brain) && M.get_ultimate_mob() == pred)
+ all_targets_mobs |= M
+ for (var/obj/item/i in vis_objs)
+ if (LAZYLEN(i.possessed_voice) && (i.get_ultimate_belly() in pred.contents))
+ all_targets_objs |= i
+ all_targets_mobs |= i.possessed_voice
+ all_targets_mobs |= pred
+ else
+ cancelled = "no belly owner"
+ else
+ var/target_reffed = locate(target)
+ if (!target_reffed || (ismob(target_reffed) && !(target_reffed in vis_mobs)) || (isitem(target_reffed) && !(target_reffed in vis_objs)))
+ cancelled = "mob not available"
+ else
+ if (ismob(target_reffed))
+ all_targets_mobs |= target_reffed
+ if (isitem(target_reffed))
+ var/obj/item/i = target_reffed
+ all_targets_objs |= target_reffed
+ all_targets_mobs |= i.possessed_voice
+ for (var/mob/M as anything in vis_mobs)
+ if (isobserver(M))
+ all_targets_mobs |= M
+
+ all_targets_mobs |= src
+
+ if (cancelled)
+ to_chat(src, "Cancelled message ([cancelled]): [message]")
+ return
+ else
+ all_targets_mobs = vis_mobs
+ all_targets_objs = vis_objs
+
+ for(var/mob/M as anything in all_targets_mobs) //CHOMPEdit end
if(isnewplayer(M))
continue
if(isobserver(M) && !(is_preference_enabled(/datum/client_preference/whisubtle_vis) || (isbelly(M.loc) && src == M.loc:owner)) && !M.client?.holder) //CHOMPEdit - Added the belly check so that ghosts in bellies can still see their pred's messages.
@@ -73,7 +168,7 @@
if(voice_sounds_list) //CHOMPEdit, changes to subtle emotes to use mob voice instead
M << sound(pick(voice_sounds_list), volume = 25)
- for(var/obj/O as anything in vis_objs)
+ for(var/obj/O as anything in all_targets_objs) //CHOMPEdit, allows for subtle distances
spawn(0)
O.see_emote(src, message, 2)
diff --git a/vorestation.dme b/vorestation.dme
index 59eaa32eb3..5df5eac1f2 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -2856,6 +2856,7 @@
#include "code\modules\mob\mob_planes_vr.dm"
#include "code\modules\mob\mob_transformation_simple.dm"
#include "code\modules\mob\say.dm"
+#include "code\modules\mob\say_ch.dm"
#include "code\modules\mob\say_vr.dm"
#include "code\modules\mob\skillset.dm"
#include "code\modules\mob\transform_procs.dm"