diff --git a/code/__defines/_macros.dm b/code/__defines/_macros.dm
index 83fa1dd8260..b40d982437f 100644
--- a/code/__defines/_macros.dm
+++ b/code/__defines/_macros.dm
@@ -52,6 +52,8 @@
#define isbrain(A) istype(A, /mob/living/carbon/brain)
+#define isvirtualmob(A) istype(A, /mob/abstract/observer/virtual)
+
#define iscarbon(A) istype(A, /mob/living/carbon)
#define iscorgi(A) istype(A, /mob/living/simple_animal/corgi)
diff --git a/code/__defines/dview.dm b/code/__defines/dview.dm
index 55764fb9a37..88116e9492b 100644
--- a/code/__defines/dview.dm
+++ b/code/__defines/dview.dm
@@ -11,4 +11,4 @@
dview_mob.loc = center; \
dview_mob.see_invisible = invis_flags; \
output = view(range, dview_mob); \
- dview_mob.loc = null;
+ dview_mob.loc = null;
\ No newline at end of file
diff --git a/code/controllers/subsystems/garbage-debug.dm b/code/controllers/subsystems/garbage-debug.dm
index c2c10530218..9a158d64480 100644
--- a/code/controllers/subsystems/garbage-debug.dm
+++ b/code/controllers/subsystems/garbage-debug.dm
@@ -150,15 +150,11 @@
SearchVar(round_progressing)
SearchVar(master_mode)
SearchVar(secret_force_mode)
- SearchVar(host)
- SearchVar(jobMax)
SearchVar(bombers)
SearchVar(admin_log)
SearchVar(lastsignalers)
SearchVar(lawchanges)
SearchVar(reg_dna)
- SearchVar(monkeystart)
- SearchVar(wizardstart)
SearchVar(newplayer_start)
SearchVar(latejoin)
SearchVar(latejoin_gateway)
@@ -167,15 +163,10 @@
SearchVar(latejoin_merchant)
SearchVar(latejoin_living_quarters_lift)
SearchVar(kickoffsloc)
- SearchVar(prisonwarp)
- SearchVar(holdingfacility)
- SearchVar(xeno_spawn)
SearchVar(tdome1)
SearchVar(tdome2)
SearchVar(tdomeobserve)
SearchVar(tdomeadmin)
- SearchVar(prisonsecuritywarp)
- SearchVar(prisonwarped)
SearchVar(ninjastart)
SearchVar(cardinal)
SearchVar(cornerdirs)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 0f15c075a46..dab523f51e9 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -48,6 +48,10 @@
if (bound_overlay)
QDEL_NULL(bound_overlay)
+ if(virtual_mob && !ispath(virtual_mob))
+ qdel(virtual_mob)
+ virtual_mob = null
+
// This is called when this atom is prevented from moving by atom/A.
/atom/movable/proc/Collide(atom/A)
if(airflow_speed > 0 && airflow_dest)
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index 49baba6ba53..739b84f9eb1 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -16,10 +16,6 @@
/obj/effect/landmark/proc/do_landmark_effect()
switch(name) //some of these are probably obsolete
- if("monkey")
- monkeystart += loc
- delete_me = 1
- return
if("start")
newplayer_start = get_turf(loc)
delete_me = 1
@@ -55,12 +51,6 @@
latejoin_merchant += loc
delete_me = 1
return
- if("prisonwarp")
- prisonwarp += loc
- delete_me = 1
- return
- if("Holding Facility")
- holdingfacility += loc
if("tdome1")
tdome1 += loc
if("tdome2")
@@ -69,14 +59,6 @@
tdomeadmin += loc
if("tdomeobserve")
tdomeobserve += loc
- if("prisonsecuritywarp")
- prisonsecuritywarp += loc
- delete_me = 1
- return
- if("xeno_spawn")
- xeno_spawn += loc
- delete_me = 1
- return
if("endgame_exit")
endgame_safespawns += loc
delete_me = 1
diff --git a/code/global.dm b/code/global.dm
index 7e4cfaf7672..1f1d52adc5d 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -34,9 +34,6 @@ var/round_progressing = 1
var/master_mode = "extended" // "extended"
var/secret_force_mode = "secret" // if this is anything but "secret", the secret rotation will forceably choose this mode.
-var/host = null //only here until check @ code\modules\ghosttrap\trap.dm:112 is fixed
-
-var/list/jobMax = list()
var/list/bombers = list()
var/list/admin_log = list()
var/list/signal_log = list()
@@ -44,8 +41,6 @@ var/list/lastsignalers = list() // Keeps last 100 signals here in format: "[src]
var/list/lawchanges = list() // Stores who uploaded laws to which silicon-based lifeform, and what the law was.
var/list/reg_dna = list()
-var/list/monkeystart = list()
-var/list/wizardstart = list()
var/turf/newplayer_start = null
//Spawnpoints.
@@ -59,16 +54,11 @@ var/list/latejoin_living_quarters_lift = list()
var/list/kickoffsloc = list()
var/list/virtual_reality_spawn = list()
-var/list/prisonwarp = list() // Prisoners go to these
-var/list/holdingfacility = list() // Captured people go here
-var/list/xeno_spawn = list() // Aliens spawn at at these.
var/list/asteroid_spawn = list() // Asteroid "Dungeons" spawn at these.
var/list/tdome1 = list()
var/list/tdome2 = list()
var/list/tdomeobserve = list()
var/list/tdomeadmin = list()
-var/list/prisonsecuritywarp = list() // Prison security goes to these.
-var/list/prisonwarped = list() // List of players already warped.
var/list/ninjastart = list()
var/list/cardinal = list(NORTH, SOUTH, EAST, WEST)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index ebf044ea549..06ea1c12d3c 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -77,8 +77,7 @@ proc/admin_notice(var/message, var/rights)
"}
if(M.client)
- body += "| Prison | "
- body += "Wind | "
+ body += "| Wind | "
body += "\ Send back to Lobby"
var/muted = M.client.prefs.muted
body += {"
Mute:
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 010c5d4015b..190b9981f55 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -493,47 +493,6 @@
log_admin("[key_name(usr)] forced [key_name(M)] to say: [speech]",admin_key=key_name(usr))
message_admins("[key_name_admin(usr)] forced [key_name_admin(M)] to say: [speech]")
- else if(href_list["sendtoprison"])
- if(!check_rights(R_ADMIN)) return
-
- if(alert(usr, "Send to admin prison for the round?", "Message", "Yes", "No") != "Yes")
- return
-
- var/mob/M = locate(href_list["sendtoprison"])
- if(!ismob(M))
- to_chat(usr, "This can only be used on instances of type /mob")
- return
- if(istype(M, /mob/living/silicon/ai))
- to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai")
- return
-
- var/turf/prison_cell = pick(prisonwarp)
- if(!prison_cell) return
-
- var/obj/structure/closet/secure_closet/brig/locker = new /obj/structure/closet/secure_closet/brig(prison_cell)
- locker.opened = 0
- locker.locked = 1
-
- //strip their stuff and stick it in the crate
- for(var/obj/item/I in M)
- M.drop_from_inventory(I, locker)
- M.update_icon()
-
- //so they black out before warping
- M.Paralyse(5)
- sleep(5)
- if(!M) return
-
- M.forceMove(prison_cell)
- if(istype(M, /mob/living/carbon/human))
- var/mob/living/carbon/human/prisoner = M
- prisoner.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(prisoner), slot_w_uniform)
- prisoner.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(prisoner), slot_shoes)
-
- to_chat(M, "You have been sent to the prison station!")
- log_admin("[key_name(usr)] sent [key_name(M)] to the prison station.",,admin_key=key_name(usr),ckey=key_name(M))
- message_admins("[key_name_admin(usr)] sent [key_name_admin(M)] to the prison station.", 1)
-
else if(href_list["sendbacktolobby"])
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 310bad78b86..20575e41752 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -16,32 +16,6 @@
message_admins("[key_name_admin(usr)] made [key_name_admin(M)] drop everything!", 1)
feedback_add_details("admin_verb","DEVR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/cmd_admin_prison(mob/M as mob in mob_list)
- set category = "Admin"
- set name = "Prison"
- if(!holder)
- to_chat(src, "Only administrators may use this command.")
- return
- if (ismob(M))
- if(istype(M, /mob/living/silicon/ai))
- alert("The AI can't be sent to prison you jerk!", null, null, null, null, null)
- return
- //strip their stuff before they teleport into a cell :downs:
- for(var/obj/item/W in M)
- M.drop_from_inventory(W)
- //teleport person to cell
- M.Paralyse(5)
- sleep(5) //so they black out before warping
- M.forceMove(pick(prisonwarp))
- if(istype(M, /mob/living/carbon/human))
- var/mob/living/carbon/human/prisoner = M
- prisoner.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(prisoner), slot_w_uniform)
- prisoner.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(prisoner), slot_shoes)
- spawn(50)
- to_chat(M, "You have been sent to the prison station!")
- log_admin("[key_name(usr)] sent [key_name(M)] to the prison station.",admin_key=key_name(usr),ckey=key_name(M))
- message_admins("[key_name_admin(usr)] sent [key_name_admin(M)] to the prison station.", 1)
- feedback_add_details("admin_verb","PRISON") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_subtle_message(mob/M as mob in mob_list)
set category = "Special Verbs"
diff --git a/code/modules/mob/abstract/new_player/new_player.dm b/code/modules/mob/abstract/new_player/new_player.dm
index 4e773ab2d18..4eb75a680b1 100644
--- a/code/modules/mob/abstract/new_player/new_player.dm
+++ b/code/modules/mob/abstract/new_player/new_player.dm
@@ -14,6 +14,7 @@
anchored = 1 // don't get pushed around
simulated = FALSE
+ virtual_mob = null // Hear no evil, speak no evil
var/last_ready_name // This has to be saved because the client is nulled prior to Logout()
diff --git a/code/modules/mob/abstract/virtual/helpers.dm b/code/modules/mob/abstract/virtual/helpers.dm
index a0c1bea0858..8503a23b9f6 100644
--- a/code/modules/mob/abstract/virtual/helpers.dm
+++ b/code/modules/mob/abstract/virtual/helpers.dm
@@ -1,13 +1,103 @@
-/proc/mobs_in_range(var/atom/movable/AM)
- . = list()
- for(var/mob/abstract/observer/virtual/v_mob in range(world.view, get_turf(AM)))
- if(ismob(v_mob.host))
- . += v_mob.host
+/*
+* These calls could easily be setup to be a bunch of call()() with relevant procs and predicates but performance is a concern.
+* BYOND is also a bit inflexible, as some predicates are of the sort /proc/name(host), others host.proc_name(), and some even do host.proc_name(parameter).
+* Nothing that cannot be worked around, but it'd be a little messy. I miss C# lambdas...
+*/
-/proc/clients_in_range(var/atom/movable/AM)
+// Procs are arranged by "in range/hearers/viewers()" usage, as opposed to virtual mob hear/see abilities.
+// Most of these procs can technically take any movable atom but unless they have a virtual mob the returned objects may not be the expected ones
+
+#define ACQUIRE_VIRTUAL_OR_TURF(A) A = (isvirtualmob(A) ? A : (((istype(A) && A.virtual_mob) ? A.virtual_mob : get_turf(A)))) ; if(!A) return
+#define ACQUIRE_VIRTUAL_OR_RETURN(A) A = (isvirtualmob(A) ? A : (((istype(A) && A.virtual_mob) ? A.virtual_mob : null))) ; if(!A) return
+
+/****************
+* Range Helpers *
+****************/
+/proc/clients_in_range(var/atom/movable/center_vmob)
. = list()
- for(var/mob/abstract/observer/virtual/v_mob in range(world.view, get_turf(AM)))
- if(ismob(v_mob.host))
- var/mob/M = v_mob.host
- if(M.client)
- . |= M.client
\ No newline at end of file
+
+ ACQUIRE_VIRTUAL_OR_TURF(center_vmob)
+ for(var/mob/abstract/observer/virtual/v_mob in range(world.view, center_vmob))
+ var/client/C = v_mob.get_client()
+ if(C)
+ . |= C
+
+/proc/hearers_in_range(var/atom/movable/center_vmob, var/hearing_range = world.view)
+ . = list()
+
+ ACQUIRE_VIRTUAL_OR_TURF(center_vmob)
+ for(var/mob/abstract/observer/virtual/v_mob in range(hearing_range, center_vmob))
+ if(v_mob.abilities & VIRTUAL_ABILITY_HEAR)
+ . |= v_mob.host
+
+/proc/viewers_in_range(var/atom/movable/center_vmob)
+ . = list()
+
+ ACQUIRE_VIRTUAL_OR_TURF(center_vmob)
+ for(var/mob/abstract/observer/virtual/v_mob in range(world.view, center_vmob))
+ if(v_mob.abilities & VIRTUAL_ABILITY_SEE)
+ . |= v_mob.host
+
+/***************
+* Hear Helpers *
+***************/
+// A mob hears another mob if they have direct line of sight, ignoring turf luminosity.
+// If there is an opaque object beteween the mobs then they cannot hear each other, even if their respective turfs can be seen.
+// Thus, unlike viewing hearing is communicative. I.e. if Mob A can hear Mob B then Mob B can also hear Mob A.
+
+// Gets the hosts of all the virtual mobs that can hear the given movable atom (or rather, it's virtual mob or turf in that existence order)
+/proc/all_hearers(var/atom/movable/heard_vmob, var/range = world.view)
+ . = list()
+
+ ACQUIRE_VIRTUAL_OR_TURF(heard_vmob)
+ for(var/mob/abstract/observer/virtual/v_mob in hearers(range, heard_vmob))
+ if(v_mob.abilities & VIRTUAL_ABILITY_HEAR)
+ . |= v_mob.host
+
+/***************
+* View Helpers *
+***************/
+// A mob can see another mob if:
+// * Within visual range, with the following differences for (N)PCs.
+// * PCs: Target is within client.view range, with center originating from either the mob or client.eye depending on client.eye_perspective.
+// * NPCs: Target is within world.view range, with center always originating from the mob.
+// * Either of the following is true:
+// * The target mob is in direct line of sight and not standing on a turf with luminosity = 0 unless the viewing mob is close enough for see_in_dark to also be in range
+// * The viewing mob has the SEE_MOBS sight flag.
+
+// Gets the hosts of all virtual mobs that can see the given atom movable as well as its turf
+/proc/all_viewers(var/mob/abstract/observer/virtual/viewed_atom)
+ . = list()
+
+ viewed_atom = istype(viewed_atom) ? viewed_atom.host : viewed_atom
+ var/turf/T = get_turf(viewed_atom)
+ if(!T)
+ return
+
+ for(var/mob/abstract/observer/virtual/seeing_v_mob in viewers(world.view, viewed_atom))
+ if(!(seeing_v_mob.abilities & VIRTUAL_ABILITY_SEE))
+ continue
+ var/atom/movable/host = seeing_v_mob.host
+ if(host.virtual_can_see_turf(T))
+ . |= host
+
+// This proc returns all hosts of virtual mobs in the given atom's view range (using its turf), ignoring invisibility, VIRUAL_ABILITY_SEE, and most other restrictions.
+// In most cases you actually want the all_* procs above. This helper was designed with LOOC in mind.
+/proc/hosts_in_view_range(var/atom/movable/viewing_atom, var/range = world.view)
+ . = list()
+
+ ACQUIRE_VIRTUAL_OR_TURF(viewing_atom)
+ // As per http://www.byond.com/docs/ref/info.html#/proc/view by using a non-mob/client this automatically skips the vast majority of sight checks
+ for(var/mob/abstract/observer/virtual/v_mob in viewers(range, get_turf(viewing_atom.loc)))
+ . |= v_mob.host
+
+/*
+ Misc. helper
+*/
+
+// Eye mobs technically see everything always, the owner just has an overlay applied, thus this helper
+/atom/movable/proc/virtual_can_see_turf(var/turf/T)
+ return TRUE // We assume objects have already been filtered using viewers() or similar proc
+
+#undef ACQUIRE_VIRTUAL_OR_TURF
+#undef ACQUIRE_VIRTUAL_OR_RETURN
diff --git a/code/modules/mob/living/carbon/human/human_species.dm b/code/modules/mob/living/carbon/human/human_species.dm
index 25107161283..deaf8431755 100644
--- a/code/modules/mob/living/carbon/human/human_species.dm
+++ b/code/modules/mob/living/carbon/human/human_species.dm
@@ -1,6 +1,7 @@
/mob/living/carbon/human/dummy
real_name = "Test Dummy"
status_flags = GODMODE|CANPUSH
+ virtual_mob = null
/mob/living/carbon/human/dummy/mannequin
mob_thinks = FALSE
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index a0e2126b9a2..167f1ec8245 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -4,6 +4,7 @@
animate_movement = 2
flags = PROXMOVE
sight = DEFAULT_SIGHT
+ virtual_mob = /mob/abstract/observer/virtual/mob
var/datum/mind/mind
var/stat = 0 //Whether a mob is alive or dead. TODO: Move this to living - Nodrak
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 987e4d360b6..1b4af238663 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -1290,3 +1290,15 @@ proc/is_blind(A)
/mob/proc/get_talk_bubble()
return 'icons/mob/talk.dmi'
+
+/datum/proc/get_client()
+ return null
+
+/client/get_client()
+ return src
+
+/mob/get_client()
+ return client
+
+/mob/abstract/observer/virtual/get_client()
+ return host.get_client()
diff --git a/code/modules/modular_computers/file_system/programs/generic/signaler.dm b/code/modules/modular_computers/file_system/programs/generic/signaler.dm
index a9f689c463f..28e3d44cf11 100644
--- a/code/modules/modular_computers/file_system/programs/generic/signaler.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/signaler.dm
@@ -77,7 +77,7 @@
if(href_list["send"])
radio.send_signal("ACTIVATE")
- computer.output_message("[icon2html(host, viewers(get_turf(src)))] *beep* *beep*", 1)
+ computer.output_message("[icon2html(computer, viewers(get_turf(src)))] *beep* *beep*", 1)
return TRUE
else if(href_list["freq"])
diff --git a/code/modules/world_api/commands/server_query.dm b/code/modules/world_api/commands/server_query.dm
index b1e168e7bca..936cb87d300 100644
--- a/code/modules/world_api/commands/server_query.dm
+++ b/code/modules/world_api/commands/server_query.dm
@@ -12,7 +12,6 @@
s["enter"] = config.enter_allowed
s["vote"] = config.allow_vote_mode
s["ai"] = config.allow_ai
- s["host"] = host ? host : null
s["stationtime"] = worldtime2text()
s["roundduration"] = get_round_duration_formatted()
s["gameid"] = game_id
diff --git a/code/unit_tests/observation_tests.dm b/code/unit_tests/observation_tests.dm
index 029344617e8..cf37770e3de 100644
--- a/code/unit_tests/observation_tests.dm
+++ b/code/unit_tests/observation_tests.dm
@@ -49,6 +49,8 @@ datum/unit_test/observation/moved_shall_not_register_on_enter_without_listeners/
var/turf/T = locate(20,20,1)
var/mob/living/carbon/human/H = new(T)
var/obj/structure/closet/C = new(T)
+ qdel(H.virtual_mob)
+ H.virtual_mob = null
H.forceMove(C)
if(!is_listening_to_movement(C, H))
diff --git a/html/changelogs/mattatlas-repent.yml b/html/changelogs/mattatlas-repent.yml
new file mode 100644
index 00000000000..36d330f527a
--- /dev/null
+++ b/html/changelogs/mattatlas-repent.yml
@@ -0,0 +1,41 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+# balance
+# admin
+# backend
+# security
+# refactor
+#################################
+
+# Your name.
+author: MattAtlas
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - bugfix: "Fixed mobs being unable to hear from inside machines or objects."