diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 9a32e23950..793454869f 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -89,7 +89,7 @@
#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area)
#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user)
-#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob)
+#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob), return flags also used by other signals.
#define COMPONENT_ALLOW_EXAMINATE 1
#define COMPONENT_DENY_EXAMINATE 2 //Higher priority compared to the above one
@@ -141,13 +141,17 @@
#define HEARING_SOURCE 8*/
#define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source)
#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination)
+
// /mind signals
#define COMSIG_PRE_MIND_TRANSFER "pre_mind_transfer" //from base of mind/transfer_to() before it's done: (new_character, old_character)
#define COMPONENT_STOP_MIND_TRANSFER 1 //stops the mind transfer from happening.
#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to() when it's done: (new_character, old_character)
// /mob signals
-#define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A)
+#define COMSIG_MOB_CLICKED_SHIFT_ON "mob_shift_click_on" //from base of /atom/ShiftClick(): (atom/A), for return values, see COMSIG_CLICK_SHIFT
+#define COMSIG_MOB_VISIBLE_ATOMS "mob_visible_atoms" //from base of mob/visible_atoms(): (list/visible_atoms)
+#define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A), for return values, see COMSIG_CLICK_SHIFT
+ #define COMPONENT_EXAMINATE_BLIND 3 //outputs the "something is there but you can't see it" message.
#define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed)
#define COMPONENT_BLOCK_DEATH_BROADCAST 1 //stops the death from being broadcasted in deadchat.
#define COMSIG_MOB_CLICKON "mob_clickon" //from base of mob/clickon(): (atom/A, params)
@@ -181,10 +185,15 @@
#define SPEECH_LANGUAGE 5
// #define SPEECH_IGNORE_SPAM 6
// #define SPEECH_FORCED 7
+#define COMSIG_MOB_IS_VIEWER "mob_is_viewer" //from base of /get_actual_viewers(): (atom/center, depth, viewers_list)
+#define COMSIG_MOB_GET_VISIBLE_MESSAGE "mob_get_visible_message" //from base of atom/visible_message(): (atom/A, msg, range, ignored_mobs)
+ #define COMPONENT_NO_VISIBLE_MESSAGE 1 //exactly what's said on the tin.
#define COMSIG_MOB_ANTAG_ON_GAIN "mob_antag_on_gain" //from base of /datum/antagonist/on_gain(): (antag_datum)
#define COMSIG_MOB_SPELL_CAN_CAST "mob_spell_can_cast" //from base of /obj/effect/proc_holder/spell/can_cast(): (spell)
+#define COMSIG_ROBOT_UPDATE_ICONS "robot_update_icons" //from base of robot/update_icons(): ()
+
// /mob/living signals
#define COMSIG_LIVING_REGENERATE_LIMBS "living_regenerate_limbs" //from base of /mob/living/regenerate_limbs(): (noheal, excluded_limbs)
#define COMSIG_LIVING_RESIST "living_resist" //from base of mob/living/resist() (/mob/living)
@@ -193,9 +202,13 @@
#define COMSIG_LIVING_ELECTROCUTE_ACT "living_electrocute_act" //from base of mob/living/electrocute_act(): (shock_damage, source, siemens_coeff, flags)
#define COMSIG_LIVING_MINOR_SHOCK "living_minor_shock" //sent by stuff like stunbatons and tasers: ()
#define COMSIG_LIVING_REVIVE "living_revive" //from base of mob/living/revive() (full_heal, admin_revive)
-#define COMSIG_MOB_CLIENT_LOGIN "comsig_mob_client_login" //sent when a mob/login() finishes: (client)
-#define COMSIG_MOB_CLIENT_LOGOUT "comsig_mob_client_logout" //sent when a mob/logout() starts: (client)
-#define COMSIG_MOB_CLIENT_MOVE "comsig_mob_client_move" //sent when client/Move() finishes with no early returns: (client, direction, n, oldloc)
+
+#define COMSIG_MOB_CLIENT_LOGIN "mob_client_login" //sent when a mob/login() finishes: (client)
+#define COMSIG_MOB_CLIENT_LOGOUT "mob_client_logout" //sent when a mob/logout() starts: (client)
+#define COMSIG_MOB_CLIENT_MOVE "mob_client_move" //sent when client/Move() finishes with no early returns: (client, direction, n, oldloc)
+#define COMSIG_MOB_CLIENT_CHANGE_VIEW "mob_client_change_view" //from base of /client/change_view(): (client, old_view, view)
+
+#define COMSIG_MOB_RESET_PERSPECTIVE "mob_reset_perspective" //from base of /mob/reset_perspective(): (atom/target)
#define COMSIG_LIVING_GUN_PROCESS_FIRE "living_gun_process_fire" //from base of /obj/item/gun/proc/process_fire(): (atom/target, params, zone_override)
// This returns flags as defined for block in __DEFINES/combat.dm!
#define COMSIG_LIVING_RUN_BLOCK "living_do_run_block" //from base of mob/living/do_run_block(): (real_attack, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone)
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index 396cf25be1..9d34bc14d2 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -31,8 +31,9 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define HOLOGRAM_1 (1<<12)
#define TESLA_IGNORE_1 (1<<13) // TESLA_IGNORE grants immunity from being targeted by tesla-style electricity
#define INITIALIZED_1 (1<<14) //Whether /atom/Initialize() has already run for the object
-#define ADMIN_SPAWNED_1 (1<<15) //was this spawned by an admin? used for stat tracking stuff.
+#define ADMIN_SPAWNED_1 (1<<15) //was this spawned by an admin? used for stat tracking stuff.
#define PREVENT_CONTENTS_EXPLOSION_1 (1<<16) /// should not get harmed if this gets caught by an explosion?
+#define BLOCK_FACE_ATOM_1 (1<<17) /// Early returns mob.face_atom()
//turf-only flags
#define NOJAUNT_1 (1<<0)
diff --git a/code/__DEFINES/layers_planes.dm b/code/__DEFINES/layers_planes.dm
index cffafcb81d..210e5a4a35 100644
--- a/code/__DEFINES/layers_planes.dm
+++ b/code/__DEFINES/layers_planes.dm
@@ -11,13 +11,34 @@
#define PLANE_SPACE_PARALLAX_RENDER_TARGET "PLANE_SPACE_PARALLAX"
#define OPENSPACE_LAYER 17 //Openspace layer over all
-#define OPENSPACE_PLANE -4 //Openspace plane below all turfs
-#define OPENSPACE_BACKDROP_PLANE -3 //Black square just over openspace plane to guaranteed cover all in openspace turf
+#define OPENSPACE_PLANE -10 //Openspace plane below all turfs
+#define OPENSPACE_BACKDROP_PLANE -9 //Black square just over openspace plane to guaranteed cover all in openspace turf
-#define FLOOR_PLANE -2
+#define FLOOR_PLANE -8
#define FLOOR_PLANE_RENDER_TARGET "FLOOR_PLANE"
-#define GAME_PLANE -1
+
+#define WALL_PLANE -7
+#define WALL_PLANE_RENDER_TARGET "WALL_PLANE"
+
+#define ABOVE_WALL_PLANE -6
+#define ABOVE_WALL_PLANE_RENDER_TARGET "ABOVE_WALL_PLANE"
+
+#define FIELD_OF_VISION_BLOCKER_PLANE -5
+#define FIELD_OF_VISION_BLOCKER_RENDER_TARGET "*FIELD_OF_VISION_BLOCKER_PLANE"
+
+#define FIELD_OF_VISION_PLANE -4
+#define FIELD_OF_VISION_RENDER_TARGET "*FIELD_OF_VISION_PLANE"
+#define FIELD_OF_VISION_LAYER 17 //used to place the visual (not the mask) shadow cone above any other floor plane stuff.
+
+#define GAME_PLANE -3
#define GAME_PLANE_RENDER_TARGET "GAME_PLANE"
+
+#define FIELD_OF_VISION_VISUAL_PLANE -2 //Yea, FoV does require quite a few planes to work with 513 filters to a decent degree.
+#define FIELD_OF_VISION_VISUAL_RENDER_TARGET "FIELD_OF_VISION_VISUAL_PLANE"
+
+#define CHAT_PLANE -1 //We don't want heard messages to be hidden by FoV.
+#define CHAT_LAYER 12.1 //Legacy, it doesn't matter that much because we are displayed above the game plane anyway.
+
#define BLACKNESS_PLANE 0 //To keep from conflicts with SEE_BLACKNESS internals
#define BLACKNESS_PLANE_RENDER_TARGET "BLACKNESS_PLANE"
@@ -92,8 +113,6 @@
#define MASSIVE_OBJ_LAYER 11
#define POINT_LAYER 12
-#define CHAT_LAYER 12.1
-
#define EMISSIVE_BLOCKER_PLANE 12
#define EMISSIVE_BLOCKER_LAYER 12
#define EMISSIVE_BLOCKER_RENDER_TARGET "*EMISSIVE_BLOCKER_PLANE"
@@ -137,12 +156,12 @@
#define HUD_LAYER 21
#define HUD_RENDER_TARGET "HUD_PLANE"
-#define VOLUMETRIC_STORAGE_BOX_PLANE 23
-#define VOLUMETRIC_STORAGE_BOX_LAYER 23
+#define VOLUMETRIC_STORAGE_BOX_PLANE 22
+#define VOLUMETRIC_STORAGE_BOX_LAYER 22
#define VOLUMETRIC_STORAGE_BOX_RENDER_TARGET "VOLUME_STORAGE_BOX_PLANE"
-#define VOLUMETRIC_STORAGE_ITEM_PLANE 24
-#define VOLUMETRIC_STORAGE_ITEM_LAYER 24
+#define VOLUMETRIC_STORAGE_ITEM_PLANE 23
+#define VOLUMETRIC_STORAGE_ITEM_LAYER 23
#define VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER 25
#define VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE 25
#define VOLUMETRIC_STORAGE_ITEM_RENDER_TARGET "VOLUME_STORAGE_ITEM_PLANE"
@@ -154,4 +173,3 @@
#define SPLASHSCREEN_LAYER 90
#define SPLASHSCREEN_PLANE 90
#define SPLASHSCREEN_RENDER_TARGET "SPLASHSCREEN_PLANE"
-
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 2edef77dbf..29ab67271c 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -297,3 +297,8 @@
#define GRAB_PIXEL_SHIFT_NECK 16
#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
+
+/// Field of vision defines.
+#define FOV_90_DEGREES 90
+#define FOV_180_DEGREES 180
+#define FOV_270_DEGREES 270
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 679f89f4d2..6c0ffa5caa 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -249,6 +249,15 @@
SEND_SIGNAL(A, COMSIG_ATOM_HEARER_IN_VIEW, processing, .)
processing += A.contents
+//viewers() but with a signal, for blacklisting.
+/proc/get_actual_viewers(depth = world.view, atom/center)
+ if(!center)
+ return
+ . = viewers(depth, center)
+ for(var/k in .)
+ var/mob/M = k
+ SEND_SIGNAL(M, COMSIG_MOB_IS_VIEWER, center, depth, .)
+
/proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios)
. = list()
// Returns a list of mobs who can hear any of the radios given in @radios
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index a22f430140..06dd746250 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1,5 +1,3 @@
-
-
/*
* A large number of misc global procs.
*/
@@ -391,6 +389,16 @@ Turf and target are separate in case you want to teleport some distance from a t
break
return loc
+//Returns a list of all locations the target is within.
+/proc/get_nested_locs(atom/movable/M, include_turf = FALSE)
+ . = list()
+ var/atom/A = M.loc
+ while(A && !isturf(A))
+ . += A
+ A = A.loc
+ if(A && include_turf) //At this point, only the turf is left.
+ . += A
+
// returns the turf located at the map edge in the specified direction relative to A
// used for mass driver
/proc/get_edge_target_turf(atom/A, direction)
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 738d72c6bf..5d2f916fba 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -324,10 +324,9 @@
return
/atom/proc/ShiftClick(mob/user)
- var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user)
+ var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) | SEND_SIGNAL(user, COMSIG_MOB_CLICKED_SHIFT_ON, src)
if(!(flags & COMPONENT_DENY_EXAMINATE) && user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE))
user.examinate(src)
- return
/*
Ctrl click
@@ -424,32 +423,36 @@
LE.fire()
// Simple helper to face what you clicked on, in case it should be needed in more than one place
-/mob/proc/face_atom(atom/A)
- if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y )
+/mob/proc/face_atom(atom/A, ismousemovement = FALSE)
+ if( buckled || stat != CONSCIOUS || !loc || !A || !A.x || !A.y )
return
- var/dx = A.x - x
- var/dy = A.y - y
+ var/atom/L = loc
+ if(L.flags_1 & BLOCK_FACE_ATOM_1)
+ return
+ var/turf/T = get_turf(src)
+ var/dx = A.x - T.x
+ var/dy = A.y - T.y
if(!dx && !dy) // Wall items are graphically shifted but on the floor
if(A.pixel_y > 16)
- setDir(NORTH)
+ setDir(NORTH, ismousemovement)
else if(A.pixel_y < -16)
- setDir(SOUTH)
+ setDir(SOUTH, ismousemovement)
else if(A.pixel_x > 16)
- setDir(EAST)
+ setDir(EAST, ismousemovement)
else if(A.pixel_x < -16)
- setDir(WEST)
+ setDir(WEST, ismousemovement)
return
if(abs(dx) < abs(dy))
if(dy > 0)
- setDir(NORTH)
+ setDir(NORTH, ismousemovement)
else
- setDir(SOUTH)
+ setDir(SOUTH, ismousemovement)
else
if(dx > 0)
- setDir(EAST)
+ setDir(EAST, ismousemovement)
else
- setDir(WEST)
+ setDir(WEST, ismousemovement)
//debug
/obj/screen/proc/scale_to(x1,y1)
diff --git a/code/_onclick/hud/plane_master.dm b/code/_onclick/hud/plane_master.dm
index a0dade37bd..d7557b00d3 100644
--- a/code/_onclick/hud/plane_master.dm
+++ b/code/_onclick/hud/plane_master.dm
@@ -24,6 +24,10 @@
blend_mode = BLEND_MULTIPLY
alpha = 255
+/obj/screen/plane_master/openspace/Initialize()
+ . = ..()
+ filters += filter(type="alpha", render_source=FIELD_OF_VISION_RENDER_TARGET, flags=MASK_INVERSE)
+
/obj/screen/plane_master/openspace/backdrop(mob/mymob)
filters = list()
filters += filter(type = "drop_shadow", color = "#04080FAA", size = -10)
@@ -46,6 +50,26 @@
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
+/obj/screen/plane_master/wall
+ name = "wall plane master"
+ plane = WALL_PLANE
+ appearance_flags = PLANE_MASTER
+
+/obj/screen/plane_master/wall/backdrop(mob/mymob)
+ if(mymob?.client?.prefs.ambientocclusion)
+ add_filter("ambient_occlusion", 0, AMBIENT_OCCLUSION)
+ else
+ remove_filter("ambient_occlusion")
+
+/obj/screen/plane_master/above_wall
+ name = "above wall plane master"
+ plane = ABOVE_WALL_PLANE
+ appearance_flags = PLANE_MASTER
+
+/obj/screen/plane_master/above_wall/Initialize()
+ . = ..()
+ add_filter("vision_cone", 100, list(type="alpha", render_source=FIELD_OF_VISION_RENDER_TARGET, flags=MASK_INVERSE))
+
///Contains most things in the game world
/obj/screen/plane_master/game_world
name = "game world plane master"
@@ -53,12 +77,50 @@
appearance_flags = PLANE_MASTER //should use client color
blend_mode = BLEND_OVERLAY
+/obj/screen/plane_master/game_world/Initialize()
+ . = ..()
+ add_filter("vision_cone", 100, list(type="alpha", render_source=FIELD_OF_VISION_RENDER_TARGET, flags=MASK_INVERSE))
+
/obj/screen/plane_master/game_world/backdrop(mob/mymob)
- if(istype(mymob) && mymob.client && mymob.client.prefs && mymob.client.prefs.ambientocclusion)
+ if(mymob?.client?.prefs.ambientocclusion)
add_filter("ambient_occlusion", 0, AMBIENT_OCCLUSION)
else
remove_filter("ambient_occlusion")
- update_filters()
+
+//Reserved to chat messages, so they are still displayed above the field of vision masking.
+/obj/screen/plane_master/chat_messages
+ name = "chat messages plane master"
+ plane = CHAT_PLANE
+ appearance_flags = PLANE_MASTER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+///Contains all shadow cone masks, whose image overrides are displayed only to their respective owners.
+/obj/screen/plane_master/field_of_vision
+ name = "field of vision mask plane master"
+ plane = FIELD_OF_VISION_PLANE
+ render_target = FIELD_OF_VISION_RENDER_TARGET
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/obj/screen/plane_master/field_of_vision/Initialize()
+ . = ..()
+ filters += filter(type="alpha", render_source=FIELD_OF_VISION_BLOCKER_RENDER_TARGET, flags=MASK_INVERSE)
+
+///Used to display the owner and its adjacent surroundings through the FoV plane mask.
+/obj/screen/plane_master/field_of_vision_blocker
+ name = "field of vision blocker plane master"
+ plane = FIELD_OF_VISION_BLOCKER_PLANE
+ render_target = FIELD_OF_VISION_BLOCKER_RENDER_TARGET
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+///Stores the visible portion of the FoV shadow cone.
+/obj/screen/plane_master/field_of_vision_visual
+ name = "field of vision visual plane master"
+ plane = FIELD_OF_VISION_VISUAL_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/obj/screen/plane_master/field_of_vision_visual/Initialize()
+ . = ..()
+ filters += filter(type="alpha", render_source=FIELD_OF_VISION_BLOCKER_RENDER_TARGET, flags=MASK_INVERSE)
///Contains all lighting objects
/obj/screen/plane_master/lighting
@@ -87,11 +149,12 @@
/obj/screen/plane_master/emissive/Initialize()
. = ..()
filters += filter(type="alpha", render_source=EMISSIVE_BLOCKER_RENDER_TARGET, flags=MASK_INVERSE)
+ filters += filter(type="alpha", render_source=FIELD_OF_VISION_RENDER_TARGET, flags=MASK_INVERSE)
/**
* Things placed on this always mask the lighting plane. Doesn't render directly.
*
- * Always masks the light plane, isn't blocked by anything. Use for on mob glows,
+ * Always masks the light plane, isn't blocked by anything (except Field of Vision). Use for on mob glows,
* magic stuff, etc.
*/
@@ -101,6 +164,10 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
render_target = EMISSIVE_UNBLOCKABLE_RENDER_TARGET
+/obj/screen/plane_master/emissive_unblockable/Initialize()
+ . = ..()
+ filters += filter(type="alpha", render_source=FIELD_OF_VISION_RENDER_TARGET, flags=MASK_INVERSE)
+
/**
* Things placed on this layer mask the emissive layer. Doesn't render directly
*
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 1dce4ecc42..1ead41d513 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -238,6 +238,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/mutable_appearance/MA = new /mutable_appearance(E)
if(MA)
MA.layer = ABOVE_HUD_LAYER
+ MA.plane = ABOVE_HUD_PLANE
MA.appearance_flags |= RESET_TRANSFORM
return MA
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index b35f4fe0e7..f8a66fce09 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -507,3 +507,9 @@
//Allows players to set a hexadecimal color of their choice as skin tone, on top of the standard ones.
/datum/config_entry/flag/allow_custom_skintones
+
+/**
+ * Enables the FoV component, which hides objects and mobs behind the parent from their sight, unless they turn around, duh.
+ * Camera mobs, AIs, ghosts and some other are of course exempt from this. This also doesn't influence simplemob AI, for the best.
+ */
+/datum/config_entry/flag/use_field_of_vision
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index 80a1bd2470..7ff25eb12f 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -44,7 +44,7 @@
return
if(world.time > next_check && world.time > next_scare)
next_check = world.time + 50
- var/list/seen_atoms = view(7, owner)
+ var/list/seen_atoms = owner.visible_atoms(7)
if(LAZYLEN(trigger_objs))
for(var/obj/O in seen_atoms)
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index cbcb34ac5e..64d6114f75 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -129,7 +129,7 @@
// Build message image
message = image(loc = message_loc, layer = CHAT_LAYER)
- message.plane = GAME_PLANE
+ message.plane = CHAT_PLANE
message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
message.alpha = 0
message.pixel_y = owner.bound_height * 0.95
diff --git a/code/datums/components/field_of_vision.dm b/code/datums/components/field_of_vision.dm
new file mode 100644
index 0000000000..b819604168
--- /dev/null
+++ b/code/datums/components/field_of_vision.dm
@@ -0,0 +1,354 @@
+#define CENTERED_RENDER_SOURCE(img, atom, FoV) \
+ atom.render_target = atom.render_target || ref(atom);\
+ img.render_source = atom.render_target;\
+ if(atom.icon){\
+ var/_cached_sizes = FoV.width_n_height_offsets[atom.icon];\
+ if(!_cached_sizes){\
+ var/icon/_I = icon(atom.icon);\
+ var/list/L = list();\
+ L += (_I.Width() - world.icon_size)/2;\
+ L += (_I.Height() - world.icon_size)/2;\
+ _cached_sizes = FoV.width_n_height_offsets[atom.icon] = L\
+ }\
+ img.pixel_x = _cached_sizes[1];\
+ img.pixel_y = _cached_sizes[2];\
+ img.loc = atom\
+ }
+
+#define REGISTER_NESTED_LOCS(source, list, comsig, proc) \
+ for(var/k in get_nested_locs(source)){\
+ var/atom/_A = k;\
+ RegisterSignal(_A, comsig, proc);\
+ list += _A\
+ }
+
+#define UNREGISTER_NESTED_LOCS(list, comsig, index) \
+ for(var/k in index to length(list)){\
+ var/atom/_A = list[k];\
+ UnregisterSignal(_A, comsig);\
+ list -= _A\
+ }
+
+/**
+ * Field of Vision component. Does totally what you probably think it does,
+ * ergo preventing players from seeing what's behind them.
+ */
+/datum/component/field_of_vision
+ can_transfer = TRUE
+
+/**
+ * That special invisible, almost neigh indestructible movable
+ * that holds both shadow cone mask and image and follows the player around.
+ */
+ var/atom/movable/fov_holder/fov
+ ///The current screen size this field of vision is meant to fit for.
+ var/current_fov_size = list(15, 15)
+ ///How much is the cone rotated clockwise, purely backend. Please use rotate_shadow_cone() if you must.
+ var/angle = 0
+ /// Used to scale the shadow cone when rotating it to fit over the edges of the screen.
+ var/rot_scale = 1
+ /// The inner angle of this cone, right hardset to 90, 180, or 270 degrees, until someone figures out a way to make it dynamic.
+ var/shadow_angle = FOV_90_DEGREES
+ /// The mask portion of the cone, placed on a * render target plane so while not visible it still applies the filter.
+ var/image/shadow_mask
+ /// The visual portion of the cone, placed on the highest layer of the wall plane
+ var/image/visual_shadow
+/**
+ * An image whose render_source is kept up to date to prevent the mob (or the topmost movable holding it) from being hidden by the mask.
+ * Will make it use vis_contents instead once a few byonds bugs with images and vis contents are fixed.
+ */
+ var/image/owner_mask
+/**
+ * A circle image used to somewhat uncover the adjacent portion of the shadow cone, making mobs and objects behind us somewhat visible.
+ * The owner mask is still required for those mob going over the default 32x32 px size btw.
+ */
+ var/image/adj_mask
+ /// A list of nested locations the mob is in, to ensure the above image works correctly.
+ var/list/nested_locs = list()
+/**
+ * A static list of offsets based on icon width and height, because render sources are centered unlike most other visuals,
+ * and that gives us some problems when the icon is larger or smaller than world.icon_size
+ */
+ var/static/list/width_n_height_offsets = list()
+
+/datum/component/field_of_vision/Initialize(fov_type = FOV_90_DEGREES, _angle = 0)
+ if(!ismob(parent))
+ return COMPONENT_INCOMPATIBLE
+ angle = _angle
+ shadow_angle = fov_type
+
+/datum/component/field_of_vision/RegisterWithParent()
+ . = ..()
+ var/mob/M = parent
+ if(M.client)
+ generate_fov_holder(M, angle)
+ RegisterSignal(M, COMSIG_MOB_CLIENT_LOGIN, .proc/on_mob_login)
+ RegisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_mob_logout)
+ RegisterSignal(M, COMSIG_MOB_GET_VISIBLE_MESSAGE, .proc/on_visible_message)
+ RegisterSignal(M, COMSIG_MOB_EXAMINATE, .proc/on_examinate)
+ RegisterSignal(M, COMSIG_MOB_VISIBLE_ATOMS, .proc/on_visible_atoms)
+ RegisterSignal(M, COMSIG_MOB_CLIENT_CHANGE_VIEW, .proc/on_change_view)
+ RegisterSignal(M, COMSIG_MOB_RESET_PERSPECTIVE, .proc/on_reset_perspective)
+ RegisterSignal(M, COMSIG_MOB_IS_VIEWER, .proc/is_viewer)
+
+/datum/component/field_of_vision/UnregisterFromParent()
+ . = ..()
+ var/mob/M = parent
+ if(!QDELETED(fov))
+ if(M.client)
+ UnregisterSignal(M, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_DEATH, COMSIG_LIVING_REVIVE))
+ M.client.images -= owner_mask
+ M.client.images -= shadow_mask
+ M.client.images -= visual_shadow
+ M.client.images -= adj_mask
+ qdel(fov, TRUE) // Forced.
+ fov = null
+ QDEL_NULL(owner_mask)
+ QDEL_NULL(adj_mask)
+ if(length(nested_locs))
+ UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
+ UnregisterSignal(M, list(COMSIG_MOB_CLIENT_LOGIN, COMSIG_MOB_CLIENT_LOGOUT,
+ COMSIG_MOB_GET_VISIBLE_MESSAGE, COMSIG_MOB_EXAMINATE,
+ COMSIG_MOB_VISIBLE_ATOMS, COMSIG_MOB_RESET_PERSPECTIVE,
+ COMSIG_MOB_CLIENT_CHANGE_VIEW, COMSIG_MOB_IS_VIEWER))
+
+/**
+ * Generates the holder and images (if not generated yet) and adds them to client.images.
+ * Run when the component is registered to a player mob, or upon login.
+ */
+/datum/component/field_of_vision/proc/generate_fov_holder(mob/M, _angle = 0)
+ if(QDELETED(fov))
+ fov = new(get_turf(M))
+ fov.icon_state = "[shadow_angle]"
+ fov.dir = M.dir
+ shadow_mask = image('icons/misc/field_of_vision.dmi', fov, "[shadow_angle]", FIELD_OF_VISION_LAYER)
+ shadow_mask.plane = FIELD_OF_VISION_PLANE
+ visual_shadow = image('icons/misc/field_of_vision.dmi', fov, "[shadow_angle]_v", FIELD_OF_VISION_LAYER)
+ visual_shadow.plane = FIELD_OF_VISION_VISUAL_PLANE
+ owner_mask = new
+ owner_mask.appearance_flags = RESET_TRANSFORM
+ owner_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
+ adj_mask = image('icons/misc/field_of_vision.dmi', fov, "adj_mask", FIELD_OF_VISION_LAYER)
+ adj_mask.appearance_flags = RESET_TRANSFORM
+ adj_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
+ if(_angle)
+ rotate_shadow_cone(_angle)
+ fov.alpha = M.stat == DEAD ? 0 : 255
+ RegisterSignal(M, COMSIG_MOB_DEATH, .proc/hide_fov)
+ RegisterSignal(M, COMSIG_LIVING_REVIVE, .proc/show_fov)
+ RegisterSignal(M, COMSIG_ATOM_DIR_CHANGE, .proc/on_dir_change)
+ RegisterSignal(M, COMSIG_MOVABLE_MOVED, .proc/on_mob_moved)
+ RegisterSignal(M, COMSIG_ROBOT_UPDATE_ICONS, .proc/manual_centered_render_source)
+ var/atom/A = M
+ if(M.loc && !isturf(M.loc))
+ REGISTER_NESTED_LOCS(M, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
+ A = nested_locs[nested_locs.len]
+ CENTERED_RENDER_SOURCE(owner_mask, A, src)
+ M.client.images += shadow_mask
+ M.client.images += visual_shadow
+ M.client.images += owner_mask
+ M.client.images += adj_mask
+ if(M.client.view != "[current_fov_size[1]]x[current_fov_size[2]]")
+ resize_fov(current_fov_size, getviewsize(M.client.view))
+
+///Rotates the shadow cone to a certain degree. Backend shenanigans.
+/datum/component/field_of_vision/proc/rotate_shadow_cone(new_angle)
+ var/simple_degrees = SIMPLIFY_DEGREES(new_angle - angle)
+ var/to_scale = cos(simple_degrees) * sin(simple_degrees)
+ if(to_scale)
+ var/old_rot_scale = rot_scale
+ rot_scale = 1 + to_scale
+ if(old_rot_scale != rot_scale)
+ visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Scale(rot_scale/old_rot_scale)
+ visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Turn(fov.transform, simple_degrees)
+
+/**
+ * Resizes the shadow to match the current screen size.
+ * Run when the client view size is changed, or if the player has a viewsize different than "15x15" on login/comp registration.
+ */
+/datum/component/field_of_vision/proc/resize_fov(list/old_view, list/view)
+ current_fov_size = view
+ var/old_size = max(old_view[1], old_view[2])
+ var/new_size = max(view[1], view[2])
+ if(old_size == new_size) //longest edges are still of the same length.
+ return
+ visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Scale(new_size/old_size)
+
+/datum/component/field_of_vision/proc/on_mob_login(mob/source, client/client)
+ generate_fov_holder(source, angle)
+
+/datum/component/field_of_vision/proc/on_mob_logout(mob/source, client/client)
+ UnregisterSignal(source, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_DEATH,
+ COMSIG_LIVING_REVIVE, COMSIG_ROBOT_UPDATE_ICONS))
+ if(length(nested_locs))
+ UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
+
+/datum/component/field_of_vision/proc/on_dir_change(mob/source, old_dir, new_dir)
+ fov.dir = new_dir
+
+///Hides the shadow, other visibility comsig procs will take it into account. Called when the mob dies.
+/datum/component/field_of_vision/proc/hide_fov(mob/source)
+ fov.alpha = 0
+
+/// Shows the shadow. Called when the mob is revived.
+/datum/component/field_of_vision/proc/show_fov(mob/source)
+ fov.alpha = 255
+
+/// Hides the shadow when looking through other items, shows it otherwise.
+/datum/component/field_of_vision/proc/on_reset_perspective(mob/source, atom/target)
+ if(source.client.eye == source || source.client.eye == source.loc)
+ fov.alpha = 255
+ else
+ fov.alpha = 0
+
+/// Called when the client view size is changed.
+/datum/component/field_of_vision/proc/on_change_view(mob/source, client, list/old_view, list/view)
+ resize_fov(old_view, view)
+
+/**
+ * Called when the owner mob moves around. Used to keep shadow located right behind us,
+ * As well as modify the owner mask to match the topmost item.
+ */
+/datum/component/field_of_vision/proc/on_mob_moved(mob/source, atom/oldloc, dir, forced)
+ var/turf/T
+ if(!isturf(source.loc)) //Recalculate all nested locations.
+ UNREGISTER_NESTED_LOCS( nested_locs, COMSIG_MOVABLE_MOVED, 1)
+ REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
+ var/atom/movable/topmost = nested_locs[nested_locs.len]
+ T = topmost.loc
+ CENTERED_RENDER_SOURCE(owner_mask, topmost, src)
+ else
+ T = source.loc
+ if(length(nested_locs))
+ UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
+ CENTERED_RENDER_SOURCE(owner_mask, source, src)
+ if(T)
+ fov.forceMove(T, harderforce = TRUE)
+
+/// Pretty much like the above, but meant for other movables the mob is stored in (bodybags, boxes, mechs etc).
+/datum/component/field_of_vision/proc/on_loc_moved(atom/movable/source, atom/oldloc, dir, forced)
+ if(isturf(source.loc) && isturf(oldloc)) //This is the case of the topmost movable loc moving around the world, skip.
+ fov.forceMove(source.loc, harderforce = TRUE)
+ return
+ var/atom/movable/prev_topmost = nested_locs[nested_locs.len]
+ if(prev_topmost != source)
+ UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, nested_locs.Find(source) + 1)
+ REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
+ var/atom/movable/topmost = nested_locs[nested_locs.len]
+ if(topmost != prev_topmost)
+ CENTERED_RENDER_SOURCE(owner_mask, topmost, src)
+ if(topmost.loc)
+ fov.forceMove(topmost.loc, harderforce = TRUE)
+
+/// A hacky comsig proc for things that somehow decide to change icon on the go. may make a change_icon_file() proc later but...
+/datum/component/field_of_vision/proc/manual_centered_render_source(mob/source, old_icon)
+ if(!isturf(source.loc))
+ return
+ CENTERED_RENDER_SOURCE(owner_mask, source, src)
+
+#undef CENTERED_RENDER_SOURCE
+#undef REGISTER_NESTED_LOCS
+#undef UNREGISTER_NESTED_LOCS
+
+/**
+ * Byond doc is not entirely correct on the integrated arctan() proc.
+ * When both x and y are negative, the output is also negative, cycling clockwise instead of counter-clockwise.
+ * That's also why I am extensively using the SIMPLIFY_DEGREES macro here.
+ *
+ * Overall this is the main macro that calculates wheter a target is within the shadow cone angle or not.
+ */
+#define FOV_ANGLE_CHECK(mob, target, zero_x_y_statement, success_statement) \
+ var/turf/T1 = get_turf(target);\
+ var/turf/T2 = get_turf(mob);\
+ if(!T1 || !T2){\
+ zero_x_y_statement\
+ }\
+ var/_x = (T1.x - T2.x);\
+ var/_y = (T1.y - T2.y);\
+ if(ISINRANGE(_x, -1, 1) && ISINRANGE(_y, -1, 1)){\
+ zero_x_y_statement\
+ }\
+ var/dir = (mob.dir & (EAST|WEST)) || mob.dir;\
+ var/_degree = -angle;\
+ var/_half = shadow_angle/2;\
+ switch(dir){\
+ if(EAST){\
+ _degree += 180;\
+ }\
+ if(NORTH){\
+ _degree += 270;\
+ }\
+ if(SOUTH){\
+ _degree += 90;\
+ }\
+ }\
+ var/_min = SIMPLIFY_DEGREES(_degree - _half);\
+ var/_max = SIMPLIFY_DEGREES(_degree + _half);\
+ if((_min > _max) ? !ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _max, _min) : ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _min, _max)){\
+ success_statement;\
+ }
+
+/datum/component/field_of_vision/proc/on_examinate(mob/source, atom/target)
+ if(fov.alpha)
+ FOV_ANGLE_CHECK(source, target, return, return COMPONENT_DENY_EXAMINATE|COMPONENT_EXAMINATE_BLIND)
+
+/datum/component/field_of_vision/proc/on_visible_message(mob/source, atom/target, message, range, list/ignored_mobs)
+ if(fov.alpha)
+ FOV_ANGLE_CHECK(source, target, return, return COMPONENT_NO_VISIBLE_MESSAGE)
+
+/datum/component/field_of_vision/proc/on_visible_atoms(mob/source, list/atoms)
+ if(!fov.alpha)
+ return
+ for(var/k in atoms)
+ var/atom/A = k
+ FOV_ANGLE_CHECK(source, A, continue, atoms -= A)
+
+/datum/component/field_of_vision/proc/is_viewer(mob/source, atom/center, depth, list/viewers_list)
+ if(fov.alpha)
+ FOV_ANGLE_CHECK(source, center, return, viewers_list -= source)
+
+#undef FOV_ANGLE_CHECK
+
+/**
+ * The shadow cone's mask and visual images holder which can't locate inside the mob,
+ * lest they inherit the mob opacity and cause a lot of hindrance
+ */
+/atom/movable/fov_holder
+ name = "field of vision holder"
+ pixel_x = -224 //the image is about 480x480 px, ergo 15 tiles (480/32) big, and we gotta center it.
+ pixel_y = -224
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ plane = FIELD_OF_VISION_PLANE
+ anchored = TRUE
+
+/atom/movable/fov_holder/ConveyorMove()
+ return
+
+/atom/movable/fov_holder/has_gravity(turf/T)
+ return FALSE
+
+/atom/movable/fov_holder/ex_act(severity)
+ return FALSE
+
+/atom/movable/fov_holder/singularity_act()
+ return
+
+/atom/movable/fov_holder/singularity_pull()
+ return
+
+/atom/movable/fov_holder/blob_act()
+ return
+
+/atom/movable/fov_holder/onTransitZ()
+ return
+
+/// Prevents people from moving these after creation, because they shouldn't be.
+/atom/movable/fov_holder/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
+ if(harderforce)
+ return ..()
+
+/// Last but not least, these shouldn't be deleted by anything but the component itself
+/atom/movable/fov_holder/Destroy(force = FALSE)
+ if(!force)
+ return QDEL_HINT_LETMELIVE
+ return ..()
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index 2e4a3db1a0..68ba0e4272 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -571,10 +571,9 @@
return
if(rustle_sound)
playsound(parent, "rustle", 50, 1, -5)
- for(var/mob/viewing in viewers(user, null))
- if(M == viewing)
- to_chat(usr, "You put [I] [insert_preposition]to [parent].")
- else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
+ to_chat(user, "You put [I] [insert_preposition]to [parent].")
+ for(var/mob/viewing in get_actual_viewers(world.view, user)-M)
+ if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL)
else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance...
viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 98027acc61..a63f63f4a4 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -927,14 +927,14 @@ Proc for attack log creation, because really why not
target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
// Filter stuff
-/atom/movable/proc/add_filter(name,priority,list/params)
+/atom/proc/add_filter(name,priority,list/params)
LAZYINITLIST(filter_data)
var/list/p = params.Copy()
p["priority"] = priority
filter_data[name] = p
update_filters()
-/atom/movable/proc/update_filters()
+/atom/proc/update_filters()
filters = null
filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE)
for(var/f in filter_data)
@@ -943,11 +943,11 @@ Proc for attack log creation, because really why not
arguments -= "priority"
filters += filter(arglist(arguments))
-/atom/movable/proc/get_filter(name)
+/atom/proc/get_filter(name)
if(filter_data && filter_data[name])
return filters[filter_data.Find(name)]
-/atom/movable/proc/remove_filter(name)
+/atom/proc/remove_filter(name)
if(filter_data && filter_data[name])
filter_data -= name
update_filters()
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index d817ca43f5..206864d44c 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -179,14 +179,15 @@
return TRUE
/atom/movable/proc/stop_pulling()
- if(pulling)
- pulling.pulledby = null
- var/mob/living/ex_pulled = pulling
- pulling = null
- setGrabState(0)
- if(isliving(ex_pulled))
- var/mob/living/L = ex_pulled
- L.update_mobility()// mob gets up if it was lyng down in a chokehold
+ if(!pulling)
+ return
+ pulling.pulledby = null
+ var/mob/living/ex_pulled = pulling
+ pulling = null
+ setGrabState(0)
+ if(isliving(ex_pulled))
+ var/mob/living/L = ex_pulled
+ L.update_mobility()// mob gets up if it was lyng down in a chokehold
/atom/movable/proc/Move_Pulled(atom/A)
if(!pulling)
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index 3f2edd7174..0416813f4f 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -37,6 +37,11 @@
reset_chem_buttons()
RefreshParts()
add_inital_chems()
+ new_occupant_dir = dir
+
+/obj/machinery/sleeper/setDir(newdir)
+ . = ..()
+ new_occupant_dir = dir
/obj/machinery/sleeper/on_deconstruction()
var/obj/item/reagent_containers/sleeper_buffer/buffer = new (loc)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 93121a0753..470c26ed0f 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -110,7 +110,8 @@ Class Procs:
var/state_open = FALSE
var/critical_machine = FALSE //If this machine is critical to station operation and should have the area be excempted from power failures.
var/list/occupant_typecache //if set, turned into typecache in Initialize, other wise, defaults to mob/living typecache
- var/atom/movable/occupant = null
+ var/atom/movable/occupant
+ var/new_occupant_dir = SOUTH //The direction the occupant will be set to look at when entering the machine.
var/speed_process = FALSE // Process as fast as possible?
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
// For storing and overriding ui id and dimensions
@@ -217,6 +218,7 @@ Class Procs:
if(target && !target.has_buckled_mobs() && (!isliving(target) || !mobtarget.buckled))
occupant = target
target.forceMove(src)
+ target.setDir(new_occupant_dir)
updateUsrDialog()
update_icon()
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index 8bf81b6061..8819020e3a 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -3,6 +3,7 @@
desc = "A remote control switch."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "doorctrl"
+ plane = ABOVE_WALL_PLANE
var/skin = "doorctrl"
power_channel = ENVIRON
var/obj/item/assembly/device
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index eb7f194229..967ee02f82 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -169,6 +169,7 @@
desc = "Used for watching an empty arena."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "telescreen"
+ plane = ABOVE_WALL_PLANE
network = list("thunder")
density = FALSE
circuit = null
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 3bc8aff809..4c12809184 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -470,15 +470,15 @@
if(welded)
weld_overlay = get_airlock_overlay("welded", overlays_file)
if(obj_integrity < integrity_failure * max_integrity)
- damag_overlay = get_airlock_overlay("sparks_broken", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ damag_overlay = get_airlock_overlay("sparks_broken", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
else if(obj_integrity < (0.75 * max_integrity))
- damag_overlay = get_airlock_overlay("sparks_damaged", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ damag_overlay = get_airlock_overlay("sparks_damaged", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(lights && hasPower())
if(locked)
- lights_overlay = get_airlock_overlay("lights_bolts", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ lights_overlay = get_airlock_overlay("lights_bolts", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
else if(emergency)
- lights_overlay = get_airlock_overlay("lights_emergency", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ lights_overlay = get_airlock_overlay("lights_emergency", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(note)
note_overlay = get_airlock_overlay(notetype, note_overlay_file)
@@ -496,18 +496,18 @@
else
panel_overlay = get_airlock_overlay("panel_closed", overlays_file)
if(obj_integrity < integrity_failure * max_integrity)
- damag_overlay = get_airlock_overlay("sparks_broken", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ damag_overlay = get_airlock_overlay("sparks_broken", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
else if(obj_integrity < (0.75 * max_integrity))
- damag_overlay = get_airlock_overlay("sparks_damaged", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ damag_overlay = get_airlock_overlay("sparks_damaged", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(welded)
weld_overlay = get_airlock_overlay("welded", overlays_file)
- lights_overlay = get_airlock_overlay("lights_denied", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ lights_overlay = get_airlock_overlay("lights_denied", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(note)
note_overlay = get_airlock_overlay(notetype, note_overlay_file)
if(AIRLOCK_EMAG)
frame_overlay = get_airlock_overlay("closed", icon)
- sparks_overlay = get_airlock_overlay("sparks", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ sparks_overlay = get_airlock_overlay("sparks", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(airlock_material)
filling_overlay = get_airlock_overlay("[airlock_material]_closed", overlays_file)
else
@@ -518,9 +518,9 @@
else
panel_overlay = get_airlock_overlay("panel_closed", overlays_file)
if(obj_integrity < integrity_failure * max_integrity)
- damag_overlay = get_airlock_overlay("sparks_broken", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ damag_overlay = get_airlock_overlay("sparks_broken", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
else if(obj_integrity < (0.75 * max_integrity))
- damag_overlay = get_airlock_overlay("sparks_damaged", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ damag_overlay = get_airlock_overlay("sparks_damaged", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(welded)
weld_overlay = get_airlock_overlay("welded", overlays_file)
if(note)
@@ -533,7 +533,7 @@
else
filling_overlay = get_airlock_overlay("fill_closing", icon)
if(lights && hasPower())
- lights_overlay = get_airlock_overlay("lights_closing", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ lights_overlay = get_airlock_overlay("lights_closing", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(panel_open)
if(security_level)
panel_overlay = get_airlock_overlay("panel_closing_protected", overlays_file)
@@ -554,7 +554,7 @@
else
panel_overlay = get_airlock_overlay("panel_open", overlays_file)
if(obj_integrity < (0.75 * max_integrity))
- damag_overlay = get_airlock_overlay("sparks_open", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ damag_overlay = get_airlock_overlay("sparks_open", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(note)
note_overlay = get_airlock_overlay("[notetype]_open", note_overlay_file)
@@ -565,7 +565,7 @@
else
filling_overlay = get_airlock_overlay("fill_opening", icon)
if(lights && hasPower())
- lights_overlay = get_airlock_overlay("lights_opening", overlays_file, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ lights_overlay = get_airlock_overlay("lights_opening", overlays_file, EMISSIVE_UNBLOCKABLE_LAYER, EMISSIVE_UNBLOCKABLE_PLANE)
if(panel_open)
if(security_level)
panel_overlay = get_airlock_overlay("panel_opening_protected", overlays_file)
diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm
index cd22b2dc05..1d39372dec 100644
--- a/code/game/machinery/doors/brigdoors.dm
+++ b/code/game/machinery/doors/brigdoors.dm
@@ -21,6 +21,7 @@
icon = 'icons/obj/status_display.dmi'
icon_state = "frame"
desc = "A remote control for a door."
+ plane = ABOVE_WALL_PLANE
req_access = list(ACCESS_SECURITY)
density = FALSE
var/id // id of linked machinery/lockers
diff --git a/code/game/machinery/embedded_controller/simple_vent_controller.dm b/code/game/machinery/embedded_controller/simple_vent_controller.dm
index 6c8467dbd3..6416de70f1 100644
--- a/code/game/machinery/embedded_controller/simple_vent_controller.dm
+++ b/code/game/machinery/embedded_controller/simple_vent_controller.dm
@@ -34,7 +34,7 @@
/obj/machinery/embedded_controller/radio/simple_vent_controller
icon = 'icons/obj/airlock_machines.dmi'
icon_state = "airlock_control_standby"
-
+ plane = ABOVE_WALL_PLANE
name = "vent controller"
density = FALSE
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index 6501a5b45e..8869ea396e 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -17,6 +17,7 @@
desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever."
icon = 'icons/obj/monitors.dmi'
icon_state = "fire0"
+ plane = ABOVE_WALL_PLANE
max_integrity = 250
integrity_failure = 0.4
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm
index 7bef255aff..f4f1aa0637 100644
--- a/code/game/machinery/flasher.dm
+++ b/code/game/machinery/flasher.dm
@@ -5,6 +5,7 @@
desc = "A wall-mounted flashbulb device."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "mflash1"
+ plane = ABOVE_WALL_PLANE
max_integrity = 250
integrity_failure = 0.4
light_color = LIGHT_COLOR_WHITE
@@ -20,6 +21,7 @@
name = "portable flasher"
desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements."
icon_state = "pflash1-p"
+ plane = GAME_PLANE
strength = 80
anchored = FALSE
base_state = "pflash"
diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm
index 244c300905..141f261688 100644
--- a/code/game/machinery/harvester.dm
+++ b/code/game/machinery/harvester.dm
@@ -20,6 +20,11 @@
. = ..()
if(prob(1))
name = "auto-autopsy"
+ new_occupant_dir = dir
+
+/obj/machinery/harvester/setDir(newdir)
+ . = ..()
+ new_occupant_dir = dir
/obj/machinery/harvester/RefreshParts()
interval = 0
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index b2a35dcf41..421e3433ca 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -5,6 +5,7 @@
name = "light switch"
icon = 'icons/obj/power.dmi'
icon_state = "light1"
+ plane = ABOVE_WALL_PLANE
desc = "Make dark."
var/on = TRUE
var/area/area = null
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index 000585a4be..d55faf2343 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -16,6 +16,7 @@ GLOBAL_LIST_EMPTY(allConsoles)
desc = "A console intended to send requests to different departments on the station."
icon = 'icons/obj/terminals.dmi'
icon_state = "req_comp0"
+ plane = ABOVE_WALL_PLANE
var/department = "Unknown" //The list of all departments on the station (Determined from this variable on each unit) Set this to the same thing if you want several consoles in one department
var/list/messages = list() //List of all messages
var/departmentType = 0
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index 746c17f225..02bb07b8ed 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -21,6 +21,7 @@
desc = null
icon = 'icons/obj/status_display.dmi'
icon_state = "frame"
+ plane = ABOVE_WALL_PLANE
density = FALSE
use_power = IDLE_POWER_USE
idle_power_usage = 10
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 02115d3e30..32be825489 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -23,11 +23,11 @@
layer = BELOW_MOB_LAYER//icon draw layer
infra_luminosity = 15 //byond implementation is bugged.
force = 5
- flags_1 = HEAR_1
+ flags_1 = HEAR_1|BLOCK_FACE_ATOM_1
var/can_move = 0 //time of next allowed movement
- var/mob/living/carbon/occupant = null
+ var/mob/living/occupant = null
var/step_in = 10 //make a step in step_in/10 sec.
- var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South.
+ var/dir_in = SOUTH //What direction will the mech face when entered/powered on? Defaults to South.
var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain.
var/step_energy_drain = 10
var/melee_energy_drain = 15
@@ -495,6 +495,10 @@
occupant_message("Air port connection teared off!")
mecha_log_message("Lost connection to gas port.")
+/obj/mecha/setDir(newdir)
+ . = ..()
+ occupant?.setDir(newdir)
+
/obj/mecha/Process_Spacemove(var/movement_dir = 0)
. = ..()
if(.)
diff --git a/code/game/mecha/mecha_topic.dm b/code/game/mecha/mecha_topic.dm
index 8d6328cf08..b1ab944b49 100644
--- a/code/game/mecha/mecha_topic.dm
+++ b/code/game/mecha/mecha_topic.dm
@@ -333,10 +333,13 @@
send_byjax(occupant,"exosuit.browser","t_port_connection","[internal_tank.connected_port?"Disconnect from":"Connect to"] gas port")
if(href_list["dna_lock"])
- if(occupant && !iscarbon(occupant))
- to_chat(occupant, " You do not have any DNA!")
+ if(!occupant)
return
- dna_lock = occupant.dna.unique_enzymes
+ var/mob/living/carbon/C = occupant
+ if(!istype(C) || !C.dna)
+ to_chat(C, " You do not have any DNA!")
+ return
+ dna_lock = C.dna.unique_enzymes
occupant_message("You feel a prick as the needle takes your DNA sample.")
if(href_list["reset_dna"])
diff --git a/code/game/objects/effects/contraband.dm b/code/game/objects/effects/contraband.dm
index 6b61eb2ebd..69aa3fb654 100644
--- a/code/game/objects/effects/contraband.dm
+++ b/code/game/objects/effects/contraband.dm
@@ -49,6 +49,7 @@
var/original_name
desc = "A large piece of space-resistant printed paper."
icon = 'icons/obj/contraband.dmi'
+ plane = ABOVE_WALL_PLANE
anchored = TRUE
var/ruined = FALSE
var/random_basetype
diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index 7f6e561f55..0676c439f0 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -143,8 +143,8 @@ RLD
//if user can't be seen from A (only checks surroundings' opaqueness) and can't see A.
//jarring, but it should stop people from targetting atoms they can't see...
//excluding darkness, to allow RLD to be used to light pitch black dark areas.
- if(!((user in view(view_range, A)) || (user in viewers(view_range, A))))
- to_chat(user, "You focus, pointing \the [src] at whatever outside your field of vision in the given direction... to no avail.")
+ if(!((user in view(view_range, A)) || (user in get_actual_viewers(view_range, A))))
+ to_chat(user, "You focus, pointing \the [src] at whatever outside your field of vision in that direction... to no avail.")
return FALSE
return TRUE
diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm
index 9c94348fc2..1d2a2c9ad1 100644
--- a/code/game/objects/items/devices/laserpointer.dm
+++ b/code/game/objects/items/devices/laserpointer.dm
@@ -135,7 +135,8 @@
outmsg = "You miss the lens of [C] with [src]!"
//catpeople
- for(var/mob/living/carbon/human/H in view(1,targloc))
+ var/list/viewers = get_actual_viewers(1,targloc)
+ for(var/mob/living/carbon/human/H in viewers)
if(!iscatperson(H) || H.incapacitated() || H.eye_blind )
continue
if(!H.lying)
@@ -150,7 +151,7 @@
H.visible_message("[H] stares at the light"," You stare at the light... ")
//cats!
- for(var/mob/living/simple_animal/pet/cat/C in view(1,targloc))
+ for(var/mob/living/simple_animal/pet/cat/C in viewers)
if(prob(50))
C.visible_message("[C] pounces on the light!","LIGHT!")
C.Move(targloc)
diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm
index ada598866b..f4c317c8ba 100644
--- a/code/game/objects/items/devices/radio/intercom.dm
+++ b/code/game/objects/items/devices/radio/intercom.dm
@@ -2,6 +2,7 @@
name = "station intercom"
desc = "Talk through this."
icon_state = "intercom"
+ plane = ABOVE_WALL_PLANE
anchored = TRUE
w_class = WEIGHT_CLASS_BULKY
canhear_range = 2
diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm
index e2140bd0fd..c785c22813 100644
--- a/code/game/objects/items/flamethrower.dm
+++ b/code/game/objects/items/flamethrower.dm
@@ -196,9 +196,8 @@
sleep(1)
previousturf = T
operating = FALSE
- for(var/mob/M in viewers(1, loc))
- if((M.client && M.machine == src))
- attack_self(M)
+ if(usr.machine == src)
+ attack_self(usr)
/obj/item/flamethrower/proc/default_ignite(turf/target, release_amount = 0.05)
diff --git a/code/game/objects/items/implants/implantpad.dm b/code/game/objects/items/implants/implantpad.dm
index 7c863017a1..f0fc76a2e2 100644
--- a/code/game/objects/items/implants/implantpad.dm
+++ b/code/game/objects/items/implants/implantpad.dm
@@ -66,7 +66,7 @@
if(ismob(loc))
attack_self(loc)
else
- for(var/mob/M in viewers(1, src))
+ for(var/mob/M in get_actual_viewers(1, src))
if(M.client)
attack_self(M)
add_fingerprint(usr)
diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm
index e177a9f9fd..ef70bd201d 100644
--- a/code/game/objects/items/storage/secure.dm
+++ b/code/game/objects/items/storage/secure.dm
@@ -105,10 +105,7 @@
if (length(code) > 5)
code = "ERROR"
add_fingerprint(usr)
- for(var/mob/M in viewers(1, loc))
- if ((M.client && M.machine == src))
- attack_self(M)
- return
+ attack_self(usr)
return
@@ -158,6 +155,7 @@
/obj/item/storage/secure/safe
name = "secure safe"
icon = 'icons/obj/storage.dmi'
+ plane = ABOVE_WALL_PLANE
icon_state = "safe"
icon_opened = "safe0"
icon_locking = "safeb"
diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm
index b5edb27704..d3272616b3 100644
--- a/code/game/objects/items/teleportation.dm
+++ b/code/game/objects/items/teleportation.dm
@@ -104,7 +104,7 @@
if (ismob(src.loc))
attack_self(src.loc)
else
- for(var/mob/M in viewers(1, src))
+ for(var/mob/M in get_actual_viewers(1, src))
if (M.client)
src.attack_self(M)
return
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 199f0c51e2..6916e7c24a 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -123,7 +123,7 @@
/obj/proc/updateUsrDialog()
if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI))
var/is_in_use = FALSE
- var/list/nearby = viewers(1, src)
+ var/list/nearby = get_actual_viewers(1, src)
for(var/mob/M in nearby)
if ((M.client && M.machine == src))
is_in_use = TRUE
@@ -152,7 +152,7 @@
if(obj_flags & IN_USE)
var/is_in_use = FALSE
if(update_viewers)
- for(var/mob/M in viewers(1, src))
+ for(var/mob/M in get_actual_viewers(1, src))
if ((M.client && M.machine == src))
is_in_use = TRUE
src.interact(M)
diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
index ae2e1a070a..6256de247a 100644
--- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
+++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
@@ -24,6 +24,7 @@
move_delay = TRUE
var/oldloc = loc
step(src, direction)
+ user.setDir(direction)
if(oldloc != loc)
addtimer(CALLBACK(src, .proc/ResetMoveDelay), (use_mob_movespeed ? user.movement_delay() : CONFIG_GET(number/movedelay/walk_delay)) * move_speed_multiplier)
else
@@ -42,7 +43,7 @@
Snake = L
break
if(Snake)
- alerted = viewers(7,src)
+ alerted = get_actual_viewers(world.view,src)
..()
if(LAZYLEN(alerted))
egged = world.time + SNAKE_SPAM_TICKS
diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm
index 9b736517be..3bc84deb4d 100644
--- a/code/game/objects/structures/extinguisher.dm
+++ b/code/game/objects/structures/extinguisher.dm
@@ -3,6 +3,7 @@
desc = "A small wall mounted cabinet designed to hold a fire extinguisher."
icon = 'icons/obj/wallmounts.dmi'
icon_state = "extinguisher_closed"
+ plane = ABOVE_WALL_PLANE
anchored = TRUE
density = FALSE
max_integrity = 200
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index e44698f96e..679a755321 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -7,6 +7,7 @@
anchored = TRUE
icon = 'icons/turf/walls/wall.dmi'
icon_state = "wall"
+ plane = WALL_PLANE
layer = LOW_OBJ_LAYER
density = TRUE
opacity = 1
diff --git a/code/game/objects/structures/fireaxe.dm b/code/game/objects/structures/fireaxe.dm
index 0f3a23dd0f..f4c1dd5ab9 100644
--- a/code/game/objects/structures/fireaxe.dm
+++ b/code/game/objects/structures/fireaxe.dm
@@ -3,6 +3,7 @@
desc = "There is a small label that reads \"For Emergency use only\" along with details for safe use of the axe. As if."
icon = 'icons/obj/wallmounts.dmi'
icon_state = "fireaxe"
+ plane = ABOVE_WALL_PLANE
anchored = TRUE
density = FALSE
armor = list("melee" = 50, "bullet" = 20, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 50)
diff --git a/code/game/objects/structures/guillotine.dm b/code/game/objects/structures/guillotine.dm
index 611a6d024d..b8137d1332 100644
--- a/code/game/objects/structures/guillotine.dm
+++ b/code/game/objects/structures/guillotine.dm
@@ -130,7 +130,7 @@
// The crowd is pleased
// The delay is to making large crowds have a longer laster applause
var/delay_offset = 0
- for(var/mob/M in viewers(src, 7))
+ for(var/mob/M in get_actual_viewers(world.view, src))
var/mob/living/carbon/human/C = M
if (ishuman(M))
addtimer(CALLBACK(C, /mob/.proc/emote, "clap"), delay_offset * 0.3)
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index e32def727a..65e1b7dd9a 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -4,6 +4,7 @@
desc = "Mirror mirror on the wall, who's the most robust of them all?"
icon = 'icons/obj/watercloset.dmi'
icon_state = "mirror"
+ plane = ABOVE_WALL_PLANE
density = FALSE
anchored = TRUE
max_integrity = 200
diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm
index f4bca1f900..68da53aa6c 100644
--- a/code/game/objects/structures/noticeboard.dm
+++ b/code/game/objects/structures/noticeboard.dm
@@ -3,6 +3,7 @@
desc = "A board for pinning important notices upon."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "nboard00"
+ plane = ABOVE_WALL_PLANE
density = FALSE
anchored = TRUE
max_integrity = 150
diff --git a/code/game/objects/structures/signs/_signs.dm b/code/game/objects/structures/signs/_signs.dm
index 84add2f65f..a1de5fe6a3 100644
--- a/code/game/objects/structures/signs/_signs.dm
+++ b/code/game/objects/structures/signs/_signs.dm
@@ -3,6 +3,7 @@
anchored = TRUE
opacity = 0
density = FALSE
+ plane = ABOVE_WALL_PLANE
layer = SIGN_LAYER
max_integrity = 100
armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm
index 99c7ed8231..c7aefbe0a6 100644
--- a/code/game/turfs/closed.dm
+++ b/code/game/turfs/closed.dm
@@ -1,5 +1,6 @@
/turf/closed
layer = CLOSED_TURF_LAYER
+ plane = WALL_PLANE
opacity = 1
density = TRUE
blocks_air = 1
diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm
index e79784290b..26ae200990 100644
--- a/code/modules/VR/vr_sleeper.dm
+++ b/code/modules/VR/vr_sleeper.dm
@@ -22,6 +22,11 @@
sparks.set_up(2,0)
sparks.attach(src)
update_icon()
+ new_occupant_dir = dir
+
+/obj/machinery/vr_sleeper/setDir(newdir)
+ . = ..()
+ new_occupant_dir = dir
/obj/machinery/vr_sleeper/attackby(obj/item/I, mob/user, params)
if(!state_open && !occupant)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index e9b8c274a8..289abbe250 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -106,6 +106,7 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list(
/client/proc/show_tip,
/client/proc/smite,
/client/proc/admin_away,
+ /client/proc/cmd_admin_toggle_fov,
/client/proc/roll_dices //CIT CHANGE - Adds dice verb
))
GLOBAL_PROTECT(admin_verbs_fun)
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 5530225130..d335cfb171 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -1218,6 +1218,58 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Hub Visibility", "[GLOB.hub_visibility ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/client/proc/cmd_admin_toggle_fov()
+ set category = "Fun"
+ set name = "Enable/Disable Field of Vision"
+
+ var/static/busy_toggling_fov = FALSE
+ if(!check_rights(R_ADMIN) || !check_rights(R_FUN))
+ return
+
+ var/on_off = CONFIG_GET(flag/use_field_of_vision)
+
+ if(busy_toggling_fov)
+ to_chat(usr, "A previous call of this function is still busy toggling FoV [on_off ? "on" : "off"]. Have some patiece.")
+ return
+ busy_toggling_fov = TRUE
+
+ log_admin("[key_name(usr)] has [on_off ? "disabled" : "enabled"] the Field of Vision configuration.")
+ CONFIG_SET(flag/use_field_of_vision, !on_off)
+
+ SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Field of Vision", "[on_off ? "Enabled" : "Disabled"]"))
+
+ if(on_off)
+ for(var/k in GLOB.mob_list)
+ if(!k)
+ continue
+ var/mob/M = k
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(!(H.dna?.species.has_field_of_vision))
+ continue
+ else if(!M.has_field_of_vision)
+ continue
+ var/datum/component/field_of_vision/FoV = M.GetComponent(/datum/component/field_of_vision)
+ if(FoV)
+ qdel(FoV)
+ CHECK_TICK
+ else
+ for(var/k in GLOB.clients)
+ if(!k)
+ continue
+ var/client/C = k
+ var/mob/M = C.mob
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(!(H.dna?.species.has_field_of_vision))
+ continue
+ else if(!M.has_field_of_vision)
+ continue
+ M.LoadComponent(/datum/component/field_of_vision, M.field_of_vision_type)
+ CHECK_TICK
+
+ busy_toggling_fov = FALSE
+
/client/proc/smite(mob/living/carbon/human/target as mob)
set name = "Smite"
set category = "Fun"
diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm
index 7998a33c7b..b89b681539 100644
--- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm
+++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm
@@ -328,8 +328,8 @@
// to_chat(user, "The ritual has been interrupted!")
// useLock = FALSE
// return
- user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
- target.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
+ user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
+ target.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
target.Jitter(25)
target.emote("laugh")
@@ -490,13 +490,14 @@
update_icon()
/obj/structure/bloodsucker/candelabrum/process()
- if(lit)
- for(var/mob/living/carbon/human/H in viewers(7, src))
- var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
- if(AmBloodsucker(H) || T) //We dont want vassals or vampires affected by this
- return
- H.hallucination = 20
- SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle)
+ if(!lit)
+ return
+ for(var/mob/living/carbon/human/H in get_actual_viewers(7, src))
+ var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
+ if(AmBloodsucker(H) || T) //We dont want vassals or vampires affected by this
+ return
+ H.hallucination = 20
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// OTHER THINGS TO USE: HUMAN BLOOD. /obj/effect/decal/cleanable/blood
diff --git a/code/modules/antagonists/bloodsucker/powers/cloak.dm b/code/modules/antagonists/bloodsucker/powers/cloak.dm
index 347700ca9a..f8f2bb4e34 100644
--- a/code/modules/antagonists/bloodsucker/powers/cloak.dm
+++ b/code/modules/antagonists/bloodsucker/powers/cloak.dm
@@ -19,10 +19,9 @@
if(!.)
return
// must have nobody around to see the cloak
- for(var/mob/living/M in viewers(9, owner))
- if(M != owner)
- to_chat(owner, "You may only vanish into the shadows unseen.")
- return FALSE
+ for(var/mob/living/M in get_actual_viewers(9, owner) - owner)
+ to_chat(owner, "You may only vanish into the shadows unseen.")
+ return FALSE
return TRUE
/datum/action/bloodsucker/cloak/ActivatePower()
@@ -35,7 +34,7 @@
// Pay Blood Toll (if awake)
owner.alpha = max(35, owner.alpha - min(75, 10 + 5 * level_current))
bloodsuckerdatum.AddBloodVolume(-0.2)
-
+
runintent = (user.m_intent == MOVE_INTENT_RUN)
var/turf/T = get_turf(user)
lum = T.get_lumcount()
@@ -50,7 +49,7 @@
if(!runintent)
user.toggle_move_intent()
REMOVE_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
-
+
sleep(5) // Check every few ticks
/datum/action/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
diff --git a/code/modules/antagonists/bloodsucker/powers/feed.dm b/code/modules/antagonists/bloodsucker/powers/feed.dm
index 8ac4fcebc1..da53b5cc81 100644
--- a/code/modules/antagonists/bloodsucker/powers/feed.dm
+++ b/code/modules/antagonists/bloodsucker/powers/feed.dm
@@ -169,8 +169,8 @@
vision_distance = notice_range, ignored_mobs = target) // Only people who AREN'T the target will notice this action.
// Warn Feeder about Witnesses...
var/was_unnoticed = TRUE
- for(var/mob/living/M in viewers(notice_range, owner))
- if(M != owner && M != target && iscarbon(M) && M.mind && !M.silicon_privileges && !M.eye_blind && !M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
+ for(var/mob/living/M in get_actual_viewers(notice_range, owner) - owner - target)
+ if(M.client && !M.silicon_privileges && !M.eye_blind && !M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
was_unnoticed = FALSE
break
if(was_unnoticed)
diff --git a/code/modules/antagonists/bloodsucker/powers/go_home.dm b/code/modules/antagonists/bloodsucker/powers/go_home.dm
index 4788d7639e..4892530d63 100644
--- a/code/modules/antagonists/bloodsucker/powers/go_home.dm
+++ b/code/modules/antagonists/bloodsucker/powers/go_home.dm
@@ -64,8 +64,8 @@
var/turf/T = get_turf(user)
if(T && T.lighting_object && T.get_lumcount()>= 0.1)
// B) Check for Viewers
- for(var/mob/living/M in viewers(get_turf(owner)))
- if(M != owner && isliving(M) && M.mind && !M.silicon_privileges && !M.eye_blind) // M.client <--- add this in after testing!
+ for(var/mob/living/M in get_actual_viewers(world.view, get_turf(owner)) - owner)
+ if(M.client && !M.silicon_privileges && !M.eye_blind)
am_seen = TRUE
if (!M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
drop_item = TRUE
diff --git a/code/modules/antagonists/bloodsucker/powers/mesmerize.dm b/code/modules/antagonists/bloodsucker/powers/mesmerize.dm
index 814ad2abd7..04d3be439f 100644
--- a/code/modules/antagonists/bloodsucker/powers/mesmerize.dm
+++ b/code/modules/antagonists/bloodsucker/powers/mesmerize.dm
@@ -64,7 +64,7 @@
to_chat(owner, "Your victim's eyes are glazed over. They cannot perceive you.")
return FALSE
// Check: Target See Me? (behind wall)
- if(!(target in viewers(target_range, get_turf(owner))))
+ if(!(owner in target.visible_atoms()))
// Sub-Check: GET CLOSER
//if (!(owner in range(target_range, get_turf(target)))
// if (display_error)
@@ -137,7 +137,7 @@
if(istype(target) && success)
target.notransform = FALSE
REMOVE_TRAIT(target, TRAIT_COMBAT_MODE_LOCKED, src)
- if(istype(L) && target.stat == CONSCIOUS && (target in view(10, get_turf(L)))) // They Woke Up! (Notice if within view)
+ if(istype(L) && target.stat == CONSCIOUS && (target in L.visible_atoms(10))) // They Woke Up! (Notice if within view)
to_chat(L, "[target] has snapped out of their trance.")
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index 2638a6a8e3..5ee673dddc 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -27,6 +27,7 @@
sight = SEE_SELF
throwforce = 0
blood_volume = 0
+ has_field_of_vision = FALSE //we are a spoopy ghost
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index 21eed2caf4..733c760855 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -67,6 +67,7 @@
desc = "A machine that monitors atmosphere levels. Goes off if the area is dangerous."
icon = 'icons/obj/monitors.dmi'
icon_state = "alarm0"
+ plane = ABOVE_WALL_PLANE
use_power = IDLE_POWER_USE
idle_power_usage = 4
active_power_usage = 8
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 989ccaf450..ab1db47db6 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -902,23 +902,27 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
new_size = "15x15"
//END OF CIT CHANGES
+ var/list/old_view = getviewsize(view)
view = new_size
- apply_clickcatcher()
+ var/list/actualview = getviewsize(view)
+ apply_clickcatcher(actualview)
mob.reload_fullscreen()
if (isliving(mob))
var/mob/living/M = mob
M.update_damage_hud()
if (prefs.auto_fit_viewport)
fit_viewport()
+ SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_CHANGE_VIEW, src, old_view, actualview)
/client/proc/generate_clickcatcher()
if(!void)
void = new()
screen += void
-/client/proc/apply_clickcatcher()
+/client/proc/apply_clickcatcher(list/actualview)
generate_clickcatcher()
- var/list/actualview = getviewsize(view)
+ if(!actualview)
+ actualview = getviewsize(view)
void.UpdateGreed(actualview[1],actualview[2])
/client/proc/AnnouncePR(announcement)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index bb105f2652..100dbc66ae 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -2385,8 +2385,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("ambientocclusion")
ambientocclusion = !ambientocclusion
if(parent && parent.screen && parent.screen.len)
- var/obj/screen/plane_master/game_world/PM = locate(/obj/screen/plane_master/game_world) in parent.screen
+ var/obj/screen/plane_master/game_world/PM = parent.mob.hud_used.plane_masters["[GAME_PLANE]"]
+ var/obj/screen/plane_master/wall/W = parent.mob.hud_used.plane_masters["[WALL_PLANE]"]
PM.backdrop(parent.mob)
+ W.backdrop(parent.mob)
if("auto_fit_viewport")
auto_fit_viewport = !auto_fit_viewport
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index 20b6034cd4..d5aa49072d 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -915,8 +915,8 @@
/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/worn_overlays(isinhands = FALSE, icon_file, used_state, style_flags = NONE)
. = ..()
if(!isinhands)
- var/mutable_appearance/energy_overlay = mutable_appearance(icon_file, "knight_cydonia_overlay", ABOVE_LIGHTING_LAYER)
- energy_overlay.plane = ABOVE_LIGHTING_LAYER
+ var/mutable_appearance/energy_overlay = mutable_appearance(icon_file, "knight_cydonia_overlay", EMISSIVE_LAYER)
+ energy_overlay.plane = EMISSIVE_PLANE
energy_overlay.color = energy_color
. += energy_overlay
@@ -946,8 +946,8 @@
/obj/item/clothing/suit/space/hardsuit/lavaknight/worn_overlays(isinhands = FALSE, icon_file, used_state, style_flags = NONE)
. = ..()
if(!isinhands)
- var/mutable_appearance/energy_overlay = mutable_appearance(icon_file, "knight_cydonia_overlay", ABOVE_LIGHTING_LAYER)
- energy_overlay.plane = ABOVE_LIGHTING_LAYER
+ var/mutable_appearance/energy_overlay = mutable_appearance(icon_file, "knight_cydonia_overlay", EMISSIVE_LAYER)
+ energy_overlay.plane = EMISSIVE_PLANE
energy_overlay.color = energy_color
. += energy_overlay
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index 0c2cbc0c67..f361a72d17 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -679,9 +679,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
var/list/mob/living/carbon/people = list()
var/mob/living/carbon/person = null
var/datum/language/understood_language = target.get_random_understood_language()
- for(var/mob/living/carbon/H in view(target))
- if(H == target)
- continue
+ for(var/mob/living/carbon/H in view(target) - target)
if(!person)
person = H
else
@@ -1064,6 +1062,8 @@ GLOBAL_LIST_INIT(hallucination_list, list(
qdel(src)
/obj/effect/hallucination/danger
+ layer = TURF_LAYER
+ plane = FLOOR_PLANE
var/image/image
/obj/effect/hallucination/danger/proc/show_icon()
@@ -1087,7 +1087,8 @@ GLOBAL_LIST_INIT(hallucination_list, list(
name = "lava"
/obj/effect/hallucination/danger/lava/show_icon()
- image = image('icons/turf/floors/lava.dmi',src,"smooth",TURF_LAYER)
+ image = image('icons/turf/floors/lava.dmi',src,"smooth",layer)
+ image.plane = plane
if(target.client)
target.client.images += image
@@ -1257,7 +1258,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
..()
if(!target.halbody)
var/list/possible_points = list()
- for(var/turf/open/floor/F in view(target,world.view))
+ for(var/turf/open/floor/F in target.visible_atoms(world.view))
possible_points += F
if(possible_points.len)
var/turf/open/floor/husk_point = pick(possible_points)
@@ -1288,7 +1289,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
set waitfor = FALSE
..()
var/list/turf/startlocs = list()
- for(var/turf/open/T in view(world.view+1,target)-view(world.view,target))
+ for(var/turf/open/T in target.visible_atoms(world.view+1)-view(world.view,target))
startlocs += T
if(!startlocs.len)
qdel(src)
diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm
index ef63ba97f8..384991e976 100644
--- a/code/modules/integrated_electronics/core/assemblies.dm
+++ b/code/modules/integrated_electronics/core/assemblies.dm
@@ -873,3 +873,8 @@
pixel_x = -31
if(WEST)
pixel_x = 31
+ plane = ABOVE_WALL_PLANE
+
+/obj/item/electronic_assembly/wallmount/Moved(atom/OldLoc, Dir, Forced = FALSE) //reset the plane if moved off the wall.
+ . = ..()
+ plane = GAME_PLANE
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index b8e56f3d7c..942a99abd9 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -707,7 +707,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
//this is a mob verb instead of atom for performance reasons
//see /mob/verb/examinate() in mob.dm for more info
//overridden here and in /mob/living for different point span classes and sanity checks
-/mob/dead/observer/pointed(atom/A as mob|obj|turf in view())
+/mob/dead/observer/pointed(atom/A as mob|obj|turf in visible_atoms())
if(!..())
return 0
usr.visible_message("[src] points to [A].")
diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm
index e8a9b50e98..3547d5f846 100644
--- a/code/modules/mob/living/bloodcrawl.dm
+++ b/code/modules/mob/living/bloodcrawl.dm
@@ -164,7 +164,7 @@
if(!B)
return
forceMove(B.loc)
- src.client.eye = src
+ reset_perspective(src)
src.visible_message("[src] rises out of the pool of blood!")
exit_blood_effect(B)
if(iscarbon(src))
diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm
index 4bca3e7f62..08d415fc3c 100644
--- a/code/modules/mob/living/brain/brain.dm
+++ b/code/modules/mob/living/brain/brain.dm
@@ -4,6 +4,7 @@
var/emp_damage = 0//Handles a type of MMI damage
var/datum/dna/stored/stored_dna // dna var for brain. Used to store dna, brain dna is not considered like actual dna, brain.has_dna() returns FALSE.
stat = DEAD //we start dead by default
+ has_field_of_vision = FALSE //Not really worth it.
see_invisible = SEE_INVISIBLE_LIVING
possible_a_intents = list(INTENT_HELP, INTENT_HARM) //for mechas
speech_span = SPAN_ROBOT
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index e0eec0e599..e7be540eb9 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -9,6 +9,7 @@
/// Enable stamina combat
combat_flags = COMBAT_FLAGS_DEFAULT
status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH|CANSTAGGER
+ has_field_of_vision = FALSE //Handled by species.
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
diff --git a/code/modules/mob/living/carbon/human/login.dm b/code/modules/mob/living/carbon/human/login.dm
new file mode 100644
index 0000000000..a89921143a
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/login.dm
@@ -0,0 +1,4 @@
+/mob/living/carbon/human/Login()
+ ..()
+ if(dna?.species?.has_field_of_vision && CONFIG_GET(flag/use_field_of_vision))
+ LoadComponent(/datum/component/field_of_vision, field_of_vision_type)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index c3f43cd6f0..65d09f6cf2 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -10,6 +10,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/default_color = "#FFF" // if alien colors are disabled, this is the color that will be used by that race
var/sexes = 1 // whether or not the race has sexual characteristics. at the moment this is only 0 for skeletons and shadows
+ var/has_field_of_vision = TRUE
//Species Icon Drawing Offsets - Pixel X, Pixel Y, Aka X = Horizontal and Y = Vertical, from bottom left corner
var/list/offset_features = list(
@@ -331,6 +332,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(mutant_bodyparts["meat_type"]) //I can't believe it's come to the meat
H.type_of_meat = GLOB.meat_types[H.dna.features["meat_type"]]
+ if(H.client && has_field_of_vision && CONFIG_GET(flag/use_field_of_vision))
+ H.LoadComponent(/datum/component/field_of_vision, H.field_of_vision_type)
+
C.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/species, TRUE, multiplicative_slowdown = speedmod)
SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species)
@@ -364,6 +368,11 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
C.dna.mutation_index[location] = new_species.inert_mutation
C.dna.mutation_index[new_species.inert_mutation] = create_sequence(new_species.inert_mutation)
+ if(!new_species.has_field_of_vision && has_field_of_vision && ishuman(C) && CONFIG_GET(flag/use_field_of_vision))
+ var/datum/component/field_of_vision/F = GetComponent(/datum/component/field_of_vision)
+ if(F)
+ qdel(F)
+
SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src)
/datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour)
@@ -1544,9 +1553,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
else if(aim_for_mouth && ( target_on_help || target_restrained || target_aiming_for_mouth))
playsound(target.loc, 'sound/weapons/slap.ogg', 50, 1, -1)
- user.visible_message(\
- "\The [user] slaps \the [target] in the face!",\
- "You slap [user == target ? "yourself" : "\the [target]"] in the face! ",\
+ target.visible_message(\
+ "\The [user] slaps \the [target] in the face!",\
+ "You [user == target ? "slap yourself" : "are slapped by \the [target]"] in the face! ",\
"You hear a slap."
)
user.do_attack_animation(target, ATTACK_EFFECT_FACE_SLAP)
@@ -1566,9 +1575,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if (!HAS_TRAIT(target, TRAIT_PERMABONER))
stop_wagging_tail(target)
playsound(target.loc, 'sound/weapons/slap.ogg', 50, 1, -1)
- user.visible_message(\
+ target.visible_message(\
"\The [user] slaps \the [target]'s ass!",\
- "You slap [user == target ? "your" : "\the [target]'s"] ass!",\
+ "You slap [user == target ? "slap your" : "are slapped by \the [target]'s in the"] ass!",\
"You hear a slap."
)
return FALSE
@@ -1864,19 +1873,19 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/targetatrest = !CHECK_MOBILITY(target, MOBILITY_STAND)
if((directional_blocked || !(target_collateral_human || target_shove_turf.shove_act(target, user))) && !targetatrest)
target.DefaultCombatKnockdown(SHOVE_KNOCKDOWN_SOLID)
- user.visible_message("[user.name] shoves [target.name], knocking them down!",
- "You shove [target.name], knocking them down!", null, COMBAT_MESSAGE_RANGE)
+ target.visible_message("[user.name] shoves [target.name], knocking them down!",
+ "You are shoved by [user.name] and knocked down!", null, COMBAT_MESSAGE_RANGE)
log_combat(user, target, "shoved", "knocking them down")
else if(target_collateral_human && !targetatrest)
target.DefaultCombatKnockdown(SHOVE_KNOCKDOWN_HUMAN)
target_collateral_human.DefaultCombatKnockdown(SHOVE_KNOCKDOWN_COLLATERAL)
- user.visible_message("[user.name] shoves [target.name] into [target_collateral_human.name]!",
- "You shove [target.name] into [target_collateral_human.name]!", null, COMBAT_MESSAGE_RANGE)
+ target.visible_message("[user.name] shoves [target.name] into [target_collateral_human.name]!",
+ "You are shoved by [user.name] into [target_collateral_human.name]!", null, COMBAT_MESSAGE_RANGE)
append_message += ", into [target_collateral_human.name]"
else
- user.visible_message("[user.name] shoves [target.name]!",
- "You shove [target.name]!", null, COMBAT_MESSAGE_RANGE)
+ target.visible_message("[user.name] shoves [target.name]!",
+ "You are shoved by [user.name]!", null, COMBAT_MESSAGE_RANGE)
var/obj/item/target_held_item = target.get_active_held_item()
if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types))
target_held_item = null
diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
index ef730da219..1d19f28aa8 100644
--- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
@@ -13,6 +13,7 @@
blacklisted = TRUE
limbs_id = "human"
skinned_type = /obj/item/stack/sheet/animalhide/human
+ has_field_of_vision = FALSE //Too much of a trouble, their vision is already bound to their severed head.
var/pumpkin = FALSE
var/obj/item/dullahan_relay/myhead
@@ -132,13 +133,13 @@
return INITIALIZE_HINT_QDEL
owner = new_owner
START_PROCESSING(SSobj, src)
- RegisterSignal(owner, COMSIG_CLICK_SHIFT, .proc/examinate_check)
+ RegisterSignal(owner, COMSIG_MOB_CLICKED_SHIFT_ON, .proc/examinate_check)
RegisterSignal(src, COMSIG_ATOM_HEARER_IN_VIEW, .proc/include_owner)
RegisterSignal(owner, COMSIG_LIVING_REGENERATE_LIMBS, .proc/unlist_head)
RegisterSignal(owner, COMSIG_LIVING_REVIVE, .proc/retrieve_head)
-/obj/item/dullahan_relay/proc/examinate_check(atom/source, mob/user)
- if(user.client.eye == src)
+/obj/item/dullahan_relay/proc/examinate_check(mob/source, atom/target)
+ if(source.client.eye == src)
return COMPONENT_ALLOW_EXAMINATE
/obj/item/dullahan_relay/proc/include_owner(datum/source, list/processing_list, list/hearers)
diff --git a/code/modules/mob/living/carbon/human/species_types/dwarves.dm b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
index 5d9269b2f0..e3beafa18a 100644
--- a/code/modules/mob/living/carbon/human/species_types/dwarves.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
@@ -121,7 +121,7 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //
return
//Filth Reactions - Since miasma now exists
var/filth_counter = 0 //Holder for the filth check cycle, basically contains how much filth dwarf sees numerically.
- for(var/fuck in view(owner,7)) //hello byond for view loop.
+ for(var/fuck in owner.visible_atoms(7)) //hello byond for view loop.
if(istype(fuck, /mob/living/carbon/human))
var/mob/living/carbon/human/H = fuck
if(H.stat == DEAD || (HAS_TRAIT(H, TRAIT_FAKEDEATH)))
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index ced9fa3200..c36568ce37 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -379,7 +379,7 @@
stop_pulling()
//same as above
-/mob/living/pointed(atom/A as mob|obj|turf in view())
+/mob/living/pointed(atom/A as mob|obj|turf in visible_atoms())
if(incapacitated())
return FALSE
if(HAS_TRAIT(src, TRAIT_DEATHCOMA))
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 1c92ffe9a5..f8abf94083 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -4,6 +4,7 @@
see_in_dark = 2
hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD,RAD_HUD)
pressure_resistance = 10
+ has_field_of_vision = TRUE
typing_indicator_enabled = TRUE
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index dff5584617..4e3093dda2 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -28,6 +28,7 @@
sec_hud = DATA_HUD_SECURITY_BASIC
d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED
mob_size = MOB_SIZE_LARGE
+ has_field_of_vision = FALSE //Vision through cameras.
var/list/network = list("ss13")
var/obj/machinery/camera/current
var/list/connected_robots = list()
@@ -868,30 +869,33 @@
light_cameras()
if(istype(A, /obj/machinery/camera))
current = A
- if(client)
- if(ismovable(A))
- if(A != GLOB.ai_camera_room_landmark)
- end_multicam()
- client.perspective = EYE_PERSPECTIVE
- client.eye = A
- else
+ if(!client)
+ return
+ if(ismovable(A))
+ if(A != GLOB.ai_camera_room_landmark)
end_multicam()
- if(isturf(loc))
- if(eyeobj)
- client.eye = eyeobj
- client.perspective = EYE_PERSPECTIVE
- else
- client.eye = client.mob
- client.perspective = MOB_PERSPECTIVE
- else
+ client.perspective = EYE_PERSPECTIVE
+ client.eye = A
+ else
+ end_multicam()
+ if(isturf(loc))
+ if(eyeobj)
+ client.eye = eyeobj
client.perspective = EYE_PERSPECTIVE
- client.eye = loc
- update_sight()
- if(client.eye != src)
- var/atom/AT = client.eye
- AT.get_remote_view_fullscreens(src)
+ else
+ client.eye = client.mob
+ client.perspective = MOB_PERSPECTIVE
else
- clear_fullscreen("remote_view", 0)
+ client.perspective = EYE_PERSPECTIVE
+ client.eye = loc
+ update_sight()
+ if(client.eye != src)
+ var/atom/AT = client.eye
+ AT.get_remote_view_fullscreens(src)
+ else
+ clear_fullscreen("remote_view", 0)
+ SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE, A)
+ return TRUE
/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0)
. = ..()
diff --git a/code/modules/mob/living/silicon/robot/update_icons.dm b/code/modules/mob/living/silicon/robot/update_icons.dm
index 72ad21c51e..a567446e9e 100644
--- a/code/modules/mob/living/silicon/robot/update_icons.dm
+++ b/code/modules/mob/living/silicon/robot/update_icons.dm
@@ -57,3 +57,5 @@
cut_overlays()
else
icon_state = "[module.cyborg_base_icon]"
+
+ SEND_SIGNAL(src, COMSIG_ROBOT_UPDATE_ICONS)
diff --git a/code/modules/mob/living/simple_animal/astral.dm b/code/modules/mob/living/simple_animal/astral.dm
index ebc89e2577..8e10376265 100644
--- a/code/modules/mob/living/simple_animal/astral.dm
+++ b/code/modules/mob/living/simple_animal/astral.dm
@@ -5,6 +5,7 @@
icon_state = "ghost"
icon_living = "ghost"
mob_biotypes = MOB_SPIRIT
+ has_field_of_vision = FALSE //we are a spoopy ghost
attacktext = "raises the hairs on the neck of"
response_harm = "disrupts the concentration of"
response_disarm = "wafts"
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index e4e81bb5db..d3d5a65eb2 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -418,7 +418,7 @@
else
..()
-/mob/living/simple_animal/bot/medbot/examinate(atom/A as mob|obj|turf in view())
+/mob/living/simple_animal/bot/medbot/examinate(atom/A as mob|obj|turf in visible_atoms())
..()
if(!is_blind(src))
chemscan(src, A)
diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm
index 2045e194d2..8aec10ec48 100644
--- a/code/modules/mob/living/simple_animal/hostile/alien.dm
+++ b/code/modules/mob/living/simple_animal/hostile/alien.dm
@@ -80,6 +80,7 @@
icon_state = "alienq"
icon_living = "alienq"
icon_dead = "alienq_dead"
+ pixel_x = -16
threat = 8
health = 250
maxHealth = 250
diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm
index dbb957a96b..b1a4f05e8c 100644
--- a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm
@@ -7,6 +7,7 @@
status_flags = 0
a_intent = INTENT_HARM
gender = NEUTER
+ has_field_of_vision = FALSE //You are a frikkin boss
var/list/boss_abilities = list() //list of /datum/action/boss
var/datum/boss_active_timed_battle/atb
var/point_regen_delay = 1
diff --git a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm b/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
index dbb9048ca4..19d4dad6e8 100644
--- a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
@@ -27,3 +27,4 @@
faction = list("spooky")
del_on_death = 1
+ field_of_vision_type = FOV_270_DEGREES //Obviously, it's one eyeball.
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index f19aa0a2a7..b556cce8fe 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -49,6 +49,7 @@
gold_core_spawnable = HOSTILE_SPAWN
see_in_dark = 4
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ has_field_of_vision = FALSE // 360° vision.
var/playable_spider = FALSE
var/datum/action/innate/spider/lay_web/lay_web
var/directive = "" //Message passed down to children, to relay the creator's orders
diff --git a/code/modules/mob/living/simple_animal/hostile/illusion.dm b/code/modules/mob/living/simple_animal/hostile/illusion.dm
index 89303702f3..6936a18cb9 100644
--- a/code/modules/mob/living/simple_animal/hostile/illusion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/illusion.dm
@@ -20,6 +20,7 @@
var/multiply_chance = 0 //if we multiply on hit
del_on_death = 1
deathmessage = "vanishes into thin air! It was a fake!"
+ has_field_of_vision = FALSE //not meant to be played anyway.
/mob/living/simple_animal/hostile/illusion/Life()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index d2680fbf61..1e63767649 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -232,6 +232,7 @@ Difficulty: Hard
density = FALSE
faction = list("mining", "boss")
weather_immunities = list("lava","ash")
+ has_field_of_vision = FALSE
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter/CanPass(atom/movable/mover, turf/target)
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum))
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
index 78987ebaa0..c406baf2a6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
@@ -27,6 +27,7 @@
mob_size = MOB_SIZE_LARGE
layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise
flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1
+ has_field_of_vision = FALSE //You are a frikkin boss
/// Crusher loot dropped when fauna killed with a crusher
var/list/crusher_loot
var/medal_type
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
index 4d20d4c7fb..2810bf6c17 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
@@ -82,6 +82,7 @@
crusher_loot = /obj/item/crusher_trophy/watcher_wing
loot = list()
butcher_results = list(/obj/item/stack/ore/diamond = 2, /obj/item/stack/sheet/sinew = 2, /obj/item/stack/sheet/bone = 1)
+ field_of_vision_type = FOV_270_DEGREES //Obviously, it's one eyeball.
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random/Initialize()
. = ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
index 2114612fce..81b541dc7b 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -21,6 +21,7 @@
layer = LARGE_MOB_LAYER
sentience_type = SENTIENCE_BOSS
hud_type = /datum/hud/lavaland_elite
+ has_field_of_vision = FALSE //You are a frikkin mini-boss
var/chosen_attack = 1
var/list/attack_action_types = list()
var/can_talk = FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm
index 48d16f63f3..7d1f08201f 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm
@@ -32,6 +32,7 @@
pressure_resistance = 300
gold_core_spawnable = NO_SPAWN //too spooky for science
blood_volume = 0
+ has_field_of_vision = FALSE
var/ghost_hair_style
var/ghost_hair_color
var/mutable_appearance/ghost_hair
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/spaceman.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/spaceman.dm
index 29b0c0b701..16358f736b 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/spaceman.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/spaceman.dm
@@ -23,6 +23,7 @@
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
del_on_death = 0
+ has_field_of_vision = FALSE //Legacy. Also they only have one dir visually.
do_footstep = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm
index 8c622c7f29..d693c96c8f 100644
--- a/code/modules/mob/living/simple_animal/hostile/statue.dm
+++ b/code/modules/mob/living/simple_animal/hostile/statue.dm
@@ -125,14 +125,10 @@
// This loop will, at most, loop twice.
for(var/atom/check in check_list)
- for(var/mob/living/M in viewers(world.view + 1, check) - src)
+ for(var/mob/living/M in get_actual_viewers(world.view + 1, check) - src)
if(M.client && CanAttack(M) && !M.silicon_privileges)
if(!M.eye_blind)
return M
- for(var/obj/mecha/M in view(world.view + 1, check)) //assuming if you can see them they can see you
- if(M.occupant && M.occupant.client)
- if(!M.occupant.eye_blind)
- return M.occupant
return null
// Cannot talk
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index 6aa7f8b401..1d22313edd 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -32,6 +32,7 @@
del_on_death = TRUE
initial_language_holder = /datum/language_holder/construct
blood_volume = 0
+ has_field_of_vision = FALSE //we are a spoopy ghost
/mob/living/simple_animal/shade/death()
deathmessage = "lets out a contented sigh as [p_their()] form unwinds."
diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm
index 0c7f126ad5..007140417c 100644
--- a/code/modules/mob/living/simple_animal/slime/powers.dm
+++ b/code/modules/mob/living/simple_animal/slime/powers.dm
@@ -28,7 +28,7 @@
return 0
var/list/choices = list()
- for(var/mob/living/C in view(1,src))
+ for(var/mob/living/C in visible_atoms(1,src))
if(C!=src && Adjacent(C))
choices += C
diff --git a/code/modules/mob/living/update_icons.dm b/code/modules/mob/living/update_icons.dm
index 8bf0dc98a9..b10d78aeb3 100644
--- a/code/modules/mob/living/update_icons.dm
+++ b/code/modules/mob/living/update_icons.dm
@@ -2,7 +2,6 @@
/mob/living/update_transform()
var/matrix/ntransform = matrix(transform) //aka transform.Copy()
var/final_pixel_y = pixel_y
- var/final_dir = dir
var/changed = 0
if(lying != lying_prev && rotate_on_lying)
changed++
@@ -14,7 +13,7 @@
pixel_y = get_standard_pixel_y_offset()
final_pixel_y = get_standard_pixel_y_offset(lying)
if(dir & (EAST|WEST)) //Facing east or west
- final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass
+ setDir(pick(NORTH, SOUTH)) //So you fall on your side rather than your face or ass
if(resize != RESIZE_DEFAULT_SIZE)
changed++
@@ -22,5 +21,5 @@
resize = RESIZE_DEFAULT_SIZE
if(changed)
- animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT)
+ animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, easing = EASE_IN|EASE_OUT)
setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life().
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index c266bbddd3..b7546becd8 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -55,3 +55,5 @@
log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP)
SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client)
+ if(has_field_of_vision && CONFIG_GET(flag/use_field_of_vision))
+ LoadComponent(/datum/component/field_of_vision, field_of_vision_type)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 6e4bd534af..7eec11892e 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -148,7 +148,8 @@
msg = blind_message
else if(T.lighting_object && T.lighting_object.invisibility <= M.see_invisible && T.is_softly_lit() && !in_range(T,M)) //the light object is dark and not invisible to us, darkness does not matter if you're directly next to the target
msg = blind_message
-
+ else if(SEND_SIGNAL(M, COMSIG_MOB_GET_VISIBLE_MESSAGE, src, message, vision_distance, ignored_mobs) & COMPONENT_NO_VISIBLE_MESSAGE)
+ msg = blind_message
if(!msg)
continue
M.show_message(msg, MSG_VISUAL,blind_message, MSG_AUDIBLE)
@@ -282,41 +283,48 @@ mob/visible_message(message, self_message, blind_message, vision_distance = DEFA
// reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective)
// reset_perspective() set eye to common default : mob on turf, loc otherwise
/mob/proc/reset_perspective(atom/A)
- if(client)
- if(A)
- if(ismovable(A))
- //Set the the thing unless it's us
- if(A != src)
- client.perspective = EYE_PERSPECTIVE
- client.eye = A
- else
- client.eye = client.mob
- client.perspective = MOB_PERSPECTIVE
- else if(isturf(A))
- //Set to the turf unless it's our current turf
- if(A != loc)
- client.perspective = EYE_PERSPECTIVE
- client.eye = A
- else
- client.eye = client.mob
- client.perspective = MOB_PERSPECTIVE
+ if(!client)
+ return
+ if(A)
+ if(ismovable(A))
+ //Set the the thing unless it's us
+ if(A != src)
+ client.perspective = EYE_PERSPECTIVE
+ client.eye = A
else
- //Do nothing
- else
- //Reset to common defaults: mob if on turf, otherwise current loc
- if(isturf(loc))
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
- else
+ else if(isturf(A))
+ //Set to the turf unless it's our current turf
+ if(A != loc)
client.perspective = EYE_PERSPECTIVE
- client.eye = loc
- return 1
+ client.eye = A
+ else
+ client.eye = client.mob
+ client.perspective = MOB_PERSPECTIVE
+ else
+ //Do nothing
+ else
+ //Reset to common defaults: mob if on turf, otherwise current loc
+ if(isturf(loc))
+ client.eye = client.mob
+ client.perspective = MOB_PERSPECTIVE
+ else
+ client.perspective = EYE_PERSPECTIVE
+ client.eye = loc
+ SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE, A)
+ return TRUE
/mob/proc/show_inv(mob/user)
return
+//view() but with a signal, to allow blacklisting some of the otherwise visible atoms.
+/mob/proc/visible_atoms(dist = world.view)
+ . = view(dist, src)
+ SEND_SIGNAL(src, COMSIG_MOB_VISIBLE_ATOMS, .)
+
//mob verbs are faster than object verbs. See https://secure.byond.com/forum/?post=1326139&page=2#comment8198716 for why this isn't atom/verb/examine()
-/mob/verb/examinate(atom/A as mob|obj|turf in view()) //It used to be oview(12), but I can't really say why
+/mob/verb/examinate(atom/A as mob|obj|turf in visible_atoms()) //It used to be oview(12), but I can't really say why
set name = "Examine"
set category = "IC"
@@ -329,15 +337,19 @@ mob/visible_message(message, self_message, blind_message, vision_distance = DEFA
return
face_atom(A)
+ var/flags = SEND_SIGNAL(src, COMSIG_MOB_EXAMINATE, A)
+ if(flags & COMPONENT_DENY_EXAMINATE)
+ if(flags & COMPONENT_EXAMINATE_BLIND)
+ to_chat(src, "Something is there but you can't see it!")
+ return
var/list/result = A.examine(src)
to_chat(src, result.Join("\n"))
- SEND_SIGNAL(src, COMSIG_MOB_EXAMINATE, A)
//same as above
//note: ghosts can point, this is intended
//visible_message will handle invisibility properly
//overridden here and in /mob/dead/observer for different point span classes and sanity checks
-/mob/verb/pointed(atom/A as mob|obj|turf in view())
+/mob/verb/pointed(atom/A as mob|obj|turf in visible_atoms())
set name = "Point To"
set category = "Object"
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 46249dbfa6..e551316e25 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -131,8 +131,9 @@
var/voluntary_ghosted = FALSE //whether or not they voluntarily ghosted.
- var/flavor_text = ""
- var/flavor_text_2 = "" //version of the above that only lasts for the current round.
+ var/has_field_of_vision = FALSE
+ var/field_of_vision_type = FOV_90_DEGREES
+
///////TYPING INDICATORS///////
/// Set to true if we want to show typing indicators.
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 6531df5e68..2bcfd47344 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -100,7 +100,8 @@
if(mob.throwing)
mob.throwing.finalize(FALSE)
- if(L.pulling && !(L.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ var/atom/movable/AM = L.pulling
+ if(AM && AM.density && !(L.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE) && !ismob(AM))
L.setDir(turn(L.dir, 180))
SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVE, src, direction, n, oldloc)
diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm
index cf86e962bd..6be1afb5de 100644
--- a/code/modules/mob/status_procs.dm
+++ b/code/modules/mob/status_procs.dm
@@ -88,18 +88,20 @@
/mob/proc/add_eyeblur()
if(!client)
return
- var/obj/screen/plane_master/game_world/GW = locate(/obj/screen/plane_master/game_world) in client.screen
- var/obj/screen/plane_master/floor/F = locate(/obj/screen/plane_master/floor) in client.screen
- GW.add_filter("blurry_eyes", 2, EYE_BLUR(clamp(eye_blurry*0.1,0.6,3)))
- F.add_filter("blurry_eyes", 2, EYE_BLUR(clamp(eye_blurry*0.1,0.6,3)))
+ var/list/screens = list(hud_used.plane_masters["[GAME_PLANE]"], hud_used.plane_masters["[FLOOR_PLANE]"],
+ hud_used.plane_masters["[WALL_PLANE]"], hud_used.plane_masters["[ABOVE_WALL_PLANE]"])
+ for(var/A in screens)
+ var/obj/screen/plane_master/P = A
+ P.add_filter("blurry_eyes", 2, EYE_BLUR(clamp(eye_blurry*0.1,0.6,3)))
/mob/proc/remove_eyeblur()
if(!client)
return
- var/obj/screen/plane_master/game_world/GW = locate(/obj/screen/plane_master/game_world) in client.screen
- var/obj/screen/plane_master/floor/F = locate(/obj/screen/plane_master/floor) in client.screen
- GW.remove_filter("blurry_eyes")
- F.remove_filter("blurry_eyes")
+ var/list/screens = list(hud_used.plane_masters["[GAME_PLANE]"], hud_used.plane_masters["[FLOOR_PLANE]"],
+ hud_used.plane_masters["[WALL_PLANE]"], hud_used.plane_masters["[ABOVE_WALL_PLANE]"])
+ for(var/A in screens)
+ var/obj/screen/plane_master/P = A
+ P.remove_filter("blurry_eyes")
///Adjust the drugginess of a mob
/mob/proc/adjust_drugginess(amount)
diff --git a/code/modules/newscaster/newscaster_machine.dm b/code/modules/newscaster/newscaster_machine.dm
index 95a24817a0..cb2d49fc64 100644
--- a/code/modules/newscaster/newscaster_machine.dm
+++ b/code/modules/newscaster/newscaster_machine.dm
@@ -12,6 +12,7 @@ GLOBAL_LIST_EMPTY(allCasters)
desc = "A standard Nanotrasen-licensed newsfeed handler for use in commercial space stations. All the news you absolutely have no use for, in one place!"
icon = 'icons/obj/terminals.dmi'
icon_state = "newscaster_normal"
+ plane = ABOVE_WALL_PLANE
verb_say = "beeps"
verb_ask = "beeps"
verb_exclaim = "beeps"
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index fd04ef0a1f..f1108189c7 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -48,7 +48,7 @@
/obj/machinery/power/apc
name = "area power controller"
desc = "A control terminal for the area's electrical systems."
-
+ plane = ABOVE_WALL_PLANE
icon_state = "apc0"
use_power = NO_POWER_USE
req_access = null
diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm
index ed3a098dae..1522a6e099 100644
--- a/code/modules/power/singularity/narsie.dm
+++ b/code/modules/power/singularity/narsie.dm
@@ -127,7 +127,7 @@
/obj/singularity/narsie/mezzer()
- for(var/mob/living/carbon/M in viewers(consume_range, src))
+ for(var/mob/living/carbon/M in get_actual_viewers(consume_range, src))
if(M.stat == CONSCIOUS)
if(!iscultist(M))
to_chat(M, "You feel conscious thought crumble away in an instant as you gaze upon [src.name]...")
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 28e9194eef..1d69ba3be8 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -433,7 +433,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
env.merge(removed)
air_update_turf()
- for(var/mob/living/carbon/human/l in view(src, HALLUCINATION_RANGE(power))) // If they can see it without mesons on. Bad on them.
+ for(var/mob/living/carbon/human/l in get_actual_viewers(HALLUCINATION_RANGE(power), src)) // If they can see it without mesons on. Bad on them.
if(!istype(l.glasses, /obj/item/clothing/glasses/meson))
var/D = sqrt(1 / max(1, get_dist(l, src)))
l.hallucination += power * config_hallucination_power * D
@@ -666,9 +666,10 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
//Some poor sod got eaten, go ahead and irradiate people nearby.
radiation_pulse(src, 3000, 2, TRUE)
+ var/list/viewers = get_actual_viewers(world.view, src)
for(var/mob/living/L in range(10))
investigate_log("has irradiated [key_name(L)] after consuming [AM].", INVESTIGATE_SUPERMATTER)
- if(L in view())
+ if(L in viewers)
L.show_message("As \the [src] slowly stops resonating, you find your skin covered in new radiation burns.", MSG_VISUAL,\
"The unearthly ringing subsides and you notice you have new radiation burns.", MSG_AUDIBLE)
else
diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm
index 3aa85c085f..5c65571ab0 100644
--- a/code/modules/projectiles/projectile/special/hallucination.dm
+++ b/code/modules/projectiles/projectile/special/hallucination.dm
@@ -50,7 +50,7 @@
if(M == hal_target)
to_chat(hal_target, "[M] is hit by \a [src] in the chest!")
hal_apply_effect()
- else if(M in view(hal_target))
+ else if(M in hal_target.visible_atoms())
to_chat(hal_target, "[M] is hit by \a [src] in the chest!!")
if(damage_type == BRUTE)
var/splatter_dir = dir
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index f4c06f39cc..7182f13646 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -535,7 +535,7 @@
add_reagent(P, cached_results[P]*multiplier, null, chem_temp)
- var/list/seen = viewers(4, get_turf(my_atom))//Sound and sight checkers
+ var/list/seen = get_actual_viewers(4, get_turf(my_atom))//Sound and sight checkers
var/iconhtml = icon2html(cached_my_atom, seen)
if(cached_my_atom)
if(!ismob(cached_my_atom)) // No bubbling mobs
@@ -617,10 +617,7 @@
handle_reactions()
update_total()
//Reaction sounds and words
- var/list/seen = viewers(5, get_turf(my_atom))
- var/iconhtml = icon2html(my_atom, seen)
- for(var/mob/M in seen)
- to_chat(M, "[iconhtml] [C.mix_message]")
+ my_atom.visible_message("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [C.mix_message]")
/datum/reagents/proc/fermiReact(selected_reaction, cached_temp, cached_pH, reactedVol, targetVol, cached_required_reagents, cached_results, multiplier)
var/datum/chemical_reaction/C = selected_reaction
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 141367c9d9..3ba883a2c7 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -765,7 +765,9 @@
/datum/reagent/toxin/rotatium/on_mob_life(mob/living/carbon/M)
if(M.hud_used)
if(current_cycle >= 20 && current_cycle%20 == 0)
- var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"], M.hud_used.plane_masters["[LIGHTING_PLANE]"])
+ var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"],
+ M.hud_used.plane_masters["[LIGHTING_PLANE]"], M.hud_used.plane_masters["[WALL_PLANE]"],
+ M.hud_used.plane_masters["[ABOVE_WALL_PLANE]"])
var/rotation = min(round(current_cycle/20), 89) // By this point the player is probably puking and quitting anyway
for(var/whole_screen in screens)
animate(whole_screen, transform = matrix(rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING, loop = -1)
@@ -793,15 +795,17 @@
/*
if(M.hud_used)
if(current_cycle >= 5 && current_cycle % 3 == 0)
- var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"], M.hud_used.plane_masters["[LIGHTING_PLANE]"])
- var/matrix/skew = matrix()
+ var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"],
+ M.hud_used.plane_masters["[LIGHTING_PLANE]"], M.hud_used.plane_masters["[WALL_PLANE]"],
+ M.hud_used.plane_masters["[ABOVE_WALL_PLANE]"]) var/matrix/skew = matrix()
var/intensity = 8
skew.set_skew(rand(-intensity,intensity), rand(-intensity,intensity))
var/matrix/newmatrix = skew
if(prob(33)) // 1/3rd of the time, let's make it stack with the previous matrix! Mwhahahaha!
- var/obj/screen/plane_master/PM = M.hud_used.plane_masters["[GAME_PLANE]"]
- newmatrix = skew * PM.transform
+ for(var/whole_screen in screens)
+ var/obj/screen/plane_master/PM = whole_screen
+ newmatrix = skew * PM.transform
for(var/whole_screen in screens)
animate(whole_screen, transform = newmatrix, time = 5, easing = QUAD_EASING, loop = -1)
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index ce1940994d..618ac256e2 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -440,9 +440,8 @@
mob_react = FALSE
/datum/chemical_reaction/foam/on_reaction(datum/reagents/holder, multiplier)
- var/location = get_turf(holder.my_atom)
- for(var/mob/M in viewers(5, location))
- to_chat(M, "The solution spews out foam!")
+ var/turf/location = get_turf(holder.my_atom)
+ location.visible_message("The solution spews out foam!")
var/datum/effect_system/foam_spread/s = new()
s.set_up(multiplier*2, location, holder)
s.start()
@@ -457,11 +456,8 @@
mob_react = FALSE
/datum/chemical_reaction/metalfoam/on_reaction(datum/reagents/holder, multiplier)
- var/location = get_turf(holder.my_atom)
-
- for(var/mob/M in viewers(5, location))
- to_chat(M, "The solution spews out a metallic foam!")
-
+ var/turf/location = get_turf(holder.my_atom)
+ location.visible_message("The solution spews out a metallic foam!")
var/datum/effect_system/foam_spread/metal/s = new()
s.set_up(multiplier*5, location, holder, 1)
s.start()
@@ -488,9 +484,8 @@
mob_react = FALSE
/datum/chemical_reaction/ironfoam/on_reaction(datum/reagents/holder, multiplier)
- var/location = get_turf(holder.my_atom)
- for(var/mob/M in viewers(5, location))
- to_chat(M, "The solution spews out a metallic foam!")
+ var/turf/location = get_turf(holder.my_atom)
+ location.visible_message("The solution spews out metallic foam!")
var/datum/effect_system/foam_spread/metal/s = new()
s.set_up(multiplier*5, location, holder, 2)
s.start()
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index f326b94e44..fbdbb5f656 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -160,11 +160,8 @@
/obj/item/reagent_containers/microwave_act(obj/machinery/microwave/M)
reagents.expose_temperature(1000)
if(container_flags & TEMP_WEAK)
- var/list/seen = viewers(5, get_turf(src))
- var/iconhtml = icon2html(src, seen)
- for(var/mob/H in seen)
- to_chat(H, "[iconhtml] \The [src]'s melts from the temperature!")
- playsound(get_turf(src), 'sound/FermiChem/heatmelt.ogg', 80, 1)
+ visible_message("[icon2html(src, viewers(DEFAULT_MESSAGE_RANGE, src))] [src]'s melts from the temperature!")
+ playsound(src, 'sound/FermiChem/heatmelt.ogg', 80, 1)
qdel(src)
..()
@@ -184,7 +181,7 @@
if((reagents.pH < 1.5) || (reagents.pH > 12.5))
START_PROCESSING(SSobj, src)
else if((reagents.pH < -3) || (reagents.pH > 17))
- visible_message("[icon2html(src, viewers(src))] \The [src] is damaged by the super pH and begins to deform!")
+ visible_message("[icon2html(src, viewers(DEFAULT_MESSAGE_RANGE, src))] \The [src] is damaged by the super pH and begins to deform!")
reagents.pH = clamp(reagents.pH, -3, 17)
container_HP -= 1
@@ -221,15 +218,11 @@
container_HP -= damage
- var/list/seen = viewers(5, get_turf(src))
- var/iconhtml = icon2html(src, seen)
-
var/damage_percent = ((container_HP / initial(container_HP)*100))
switch(damage_percent)
if(-INFINITY to 0)
- for(var/mob/M in seen)
- to_chat(M, "[iconhtml] \The [src]'s melts [cause]!")
- playsound(get_turf(src), 'sound/FermiChem/acidmelt.ogg', 80, 1)
+ visible_message("[icon2html(src, viewers(DEFAULT_MESSAGE_RANGE, src))] [src]'s melts [cause]!")
+ playsound(src, 'sound/FermiChem/acidmelt.ogg', 80, 1)
SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times beakers have melted")
STOP_PROCESSING(SSobj, src)
qdel(src)
@@ -246,5 +239,4 @@
update_icon()
if(prob(25))
- for(var/mob/M in seen)
- to_chat(M, "[iconhtml] \The [src]'s is damaged by [cause] and begins to deform!")
+ visible_message("[icon2html(src, viewers(DEFAULT_MESSAGE_RANGE, src))] [src]'s is damaged by [cause] and begins to deform!")
diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm
index e6fa18ba9c..36e3b0d47e 100644
--- a/code/modules/reagents/reagent_containers/pill.dm
+++ b/code/modules/reagents/reagent_containers/pill.dm
@@ -72,9 +72,8 @@
to_chat(user, "[target] is full.")
return
- to_chat(user, "You dissolve [src] in [target].")
- for(var/mob/O in viewers(2, user)) //viewers is necessary here because of the small radius
- to_chat(O, "[user] slips something into [target]!")
+ user.visible_message("[user] slips something into [target]!",
+ "You dissolve [src] in [target].", vision_distance = 2)
reagents.trans_to(target, reagents.total_volume)
qdel(src)
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 54ec327fdb..c8e50667a8 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -169,6 +169,7 @@
name = "pepper spray refiller"
desc = "Contains condensed capsaicin for use in law \"enforcement.\""
icon_state = "pepper"
+ plane = ABOVE_WALL_PLANE
anchored = TRUE
density = FALSE
reagent_id = /datum/reagent/consumable/condensedcapsaicin
@@ -182,6 +183,7 @@
name = "virus food dispenser"
desc = "A dispenser of low-potency virus mutagenic."
icon_state = "virus_food"
+ plane = ABOVE_WALL_PLANE
anchored = TRUE
density = FALSE
reagent_id = /datum/reagent/consumable/virus_food
diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm
index b9dc9b92d3..04f2de9bec 100644
--- a/code/modules/security_levels/keycard_authentication.dm
+++ b/code/modules/security_levels/keycard_authentication.dm
@@ -9,6 +9,7 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new)
desc = "This device is used to trigger station functions, which require more than one ID card to authenticate."
icon = 'icons/obj/monitors.dmi'
icon_state = "auth_off"
+ plane = ABOVE_WALL_PLANE
use_power = IDLE_POWER_USE
idle_power_usage = 2
active_power_usage = 6
diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm
index b0808cef5e..33fc4d8703 100644
--- a/code/modules/tgui/states.dm
+++ b/code/modules/tgui/states.dm
@@ -99,7 +99,7 @@
* return UI_state The state of the UI.
**/
/mob/living/proc/shared_living_ui_distance(atom/movable/src_object)
- if(!(src_object in view(src))) // If the object is obscured, close it.
+ if(!(src_object in visible_atoms())) // If the object is obscured, close it.
return UI_CLOSE
var/dist = get_dist(src_object, src)
diff --git a/code/modules/vehicles/sealed.dm b/code/modules/vehicles/sealed.dm
index fc81e2ec00..edaab8b982 100644
--- a/code/modules/vehicles/sealed.dm
+++ b/code/modules/vehicles/sealed.dm
@@ -1,5 +1,6 @@
/obj/vehicle/sealed
var/enter_delay = 20
+ flags_1 = BLOCK_FACE_ATOM_1
/obj/vehicle/sealed/generate_actions()
. = ..()
@@ -51,7 +52,7 @@
if(randomstep)
var/turf/target_turf = get_step(exit_location(M), pick(GLOB.cardinals))
M.throw_at(target_turf, 5, 10)
-
+
if(!silent)
M.visible_message("[M] drops out of \the [src]!")
return TRUE
@@ -102,7 +103,13 @@
if(iscarbon(i))
var/mob/living/carbon/C = i
C.DefaultCombatKnockdown(40)
-
-
+
+
/obj/vehicle/sealed/AllowDrop()
return FALSE
+
+/obj/vehicle/sealed/setDir(newdir)
+ . = ..()
+ for(var/k in occupants)
+ var/mob/M = k
+ M.setDir(newdir)
diff --git a/config/game_options.txt b/config/game_options.txt
index 2d326493bb..f65c1bfff5 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -658,4 +658,8 @@ THRESHOLD_BODY_SIZE_SLOWDOWN 0.85
BODY_SIZE_SLOWDOWN_MULTIPLIER 0.25
## Allows players to set a hexadecimal color of their choice as skin tone, on top of the standard ones.
-ALLOW_CUSTOM_SKINTONES
\ No newline at end of file
+ALLOW_CUSTOM_SKINTONES
+
+## Enables the FoV component, which hides objects and mobs behind the parent from their sight, unless they turn around, duh.
+## Camera mobs, AIs, ghosts and some other are of course exempt from this. This also doesn't influence simplemob AI, for the best.
+#USE_FIELD_OF_VISION
\ No newline at end of file
diff --git a/icons/misc/field_of_vision.dmi b/icons/misc/field_of_vision.dmi
new file mode 100644
index 0000000000..015cecd88c
Binary files /dev/null and b/icons/misc/field_of_vision.dmi differ
diff --git a/modular_citadel/code/_onclick/click.dm b/modular_citadel/code/_onclick/click.dm
index f077b3a71b..004cc2fe80 100644
--- a/modular_citadel/code/_onclick/click.dm
+++ b/modular_citadel/code/_onclick/click.dm
@@ -72,30 +72,3 @@
/mob/proc/AltRangedAttack(atom/A, params)
return FALSE
-
-/mob/proc/mouse_face_atom(atom/A) //Basically a copy of face_atom but with ismousemovement set to TRUE
- if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y )
- return
- var/dx = A.x - x
- var/dy = A.y - y
- if(!dx && !dy) // Wall items are graphically shifted but on the floor
- if(A.pixel_y > 16)
- setDir(NORTH, ismousemovement = TRUE)
- else if(A.pixel_y < -16)
- setDir(SOUTH, ismousemovement = TRUE)
- else if(A.pixel_x > 16)
- setDir(EAST, ismousemovement = TRUE)
- else if(A.pixel_x < -16)
- setDir(WEST, ismousemovement = TRUE)
- return
-
- if(abs(dx) < abs(dy))
- if(dy > 0)
- setDir(NORTH, ismousemovement = TRUE)
- else
- setDir(SOUTH, ismousemovement = TRUE)
- else
- if(dx > 0)
- setDir(EAST, ismousemovement = TRUE)
- else
- setDir(WEST, ismousemovement = TRUE)
diff --git a/modular_citadel/code/_onclick/other_mobs.dm b/modular_citadel/code/_onclick/other_mobs.dm
index 6d9ffdd6ca..2838a363ea 100644
--- a/modular_citadel/code/_onclick/other_mobs.dm
+++ b/modular_citadel/code/_onclick/other_mobs.dm
@@ -12,7 +12,7 @@
if(isturf(A) || incapacitated()) // pretty annoying to wave your fist at floors and walls. And useless.
return TRUE
changeNext_move(CLICK_CD_RANGE)
- var/list/target_viewers = viewers(11, A) //Byond proc, doesn't check for blindness.
+ var/list/target_viewers = get_actual_viewers(11, A) //doesn't check for blindness.
if(!(src in target_viewers)) //click catcher issuing calls for out of view objects.
return TRUE
if(!has_active_hand())
@@ -26,16 +26,16 @@
switch(a_intent)
if(INTENT_DISARM)
the_action = "shoos away [A]"
- what_action = "shoo away something you can't see"
+ what_action = "shoo away something out of your vision"
self_action = "shoo away [A]"
if(INTENT_GRAB)
the_action = "beckons [A] to come"
- what_action = "beckons something you can't see to come"
+ what_action = "beckons something out of your vision to come"
self_action = "beckon [A] to come"
if(INTENT_HARM)
var/pronoun = "[p_their()]"
the_action = "shakes [pronoun] fist at [A]"
- what_action = "shakes [pronoun] fist at something you can't see"
+ what_action = "shakes [pronoun] fist at something out of your vision"
self_action = "shake your fist at [A]"
if(!eye_blind)
diff --git a/modular_citadel/code/modules/mob/living/carbon/carbon.dm b/modular_citadel/code/modules/mob/living/carbon/carbon.dm
index e95948e562..0532f59afa 100644
--- a/modular_citadel/code/modules/mob/living/carbon/carbon.dm
+++ b/modular_citadel/code/modules/mob/living/carbon/carbon.dm
@@ -25,5 +25,5 @@
/mob/living/carbon/onMouseMove(object, location, control, params)
if(!(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
return
- mouse_face_atom(object)
+ face_atom(object, TRUE)
lastmousedir = dir
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/MKUltra.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/MKUltra.dm
index 89a7f58cee..cd672e2c6a 100644
--- a/modular_citadel/code/modules/reagents/chemistry/reagents/MKUltra.dm
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/MKUltra.dm
@@ -194,14 +194,14 @@ Creating a chem with a low purity will make you permanently fall in love with so
if (M.ckey == creatorID && creatorName == M.real_name)//If the creator drinks it, they fall in love randomly. If someone else drinks it, the creator falls in love with them.
if(M.has_status_effect(STATUS_EFFECT_INLOVE))//Can't be enthralled when enthralled, so to speak.
return
- var/list/seen = viewers(7, get_turf(M))
+ var/list/seen = (M.visible_atoms(M.client?.view || world.view) - M) | viewers(M.client?.view || world.view, M)
for(var/victim in seen)
if(ishuman(victim))
var/mob/living/carbon/V = victim
- if((V == M) || (!V.client) || (V.stat == DEAD))
- seen = seen - victim
+ if(!V.client || V.stat == DEAD)
+ seen -= victim
else
- seen = seen - victim
+ seen -= victim
if(LAZYLEN(seen))
return
@@ -213,7 +213,7 @@ Creating a chem with a low purity will make you permanently fall in love with so
var/mob/living/carbon/C = get_mob_by_key(creatorID)
if(M.has_status_effect(STATUS_EFFECT_INLOVE))
return
- if((C in viewers(7, get_turf(M))) && (C.client))
+ if(C.client && (M in C.visible_atoms(C.client.view)))
M.reagents.del_reagent(type)
FallInLove(C, M)
return
@@ -279,15 +279,13 @@ Creating a chem with a low purity will make you permanently fall in love with so
if(HAS_TRAIT(M, TRAIT_MINDSHIELD))
return ..()
if(!M.has_status_effect(STATUS_EFFECT_INLOVE))
- var/list/seen = viewers(7, get_turf(M))//Sound and sight checkers
+ var/list/seen = (M.visible_atoms(M.client?.view || world.view) - M) | viewers(M.client?.view || world.view, M)
for(var/victim in seen)
- if((istype(victim, /mob/living/simple_animal/pet/)) || (victim == M) || (M.stat == DEAD) || (!isliving(victim)))
- seen = seen - victim
- if(seen.len == 0)
+ if((isanimal(victim)) || (!isliving(victim)))
+ seen -= victim
+ if(!length(seen))
return
love = pick(seen)
- if(!love)
- return
M.apply_status_effect(STATUS_EFFECT_INLOVE, love)
lewd = (M.client?.prefs.cit_toggles & HYPNO) && (love.client?.prefs.cit_toggles & HYPNO)
to_chat(M, "[(lewd?"":"")][(lewd?"You develop a sudden crush on [love], your heart beginning to race as you look upon them with new eyes.":"You suddenly feel like making friends with [love].")] You feel strangely drawn towards them.")
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm
index dac6d97eef..8bc67b6d1d 100644
--- a/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm
@@ -98,10 +98,8 @@
M.emote("cough")
var/obj/item/toy/plush/P = pick(subtypesof(/obj/item/toy/plush))
new P(T)
- to_chat(M, "You feel a lump form in your throat, as you suddenly cough up what seems to be a hairball?")
- var/list/seen = viewers(8, T)
- for(var/mob/S in seen)
- to_chat(S, "[M] suddenly coughs up a [P.name]!")
+ M.visible_message("[M] suddenly coughs up a [P.name]!",\
+ "You feel a lump form in your throat, as you suddenly cough up what seems to be a hairball?")
var/T2 = get_random_station_turf()
P.throw_at(T2, 8, 1)
..()
@@ -120,10 +118,10 @@
to_chat(M, "You find yourself unable to supress the desire to howl!")
M.emote("awoo")
if(prob(20))
- var/list/seen = viewers(5, get_turf(M))//Sound and sight checkers
+ var/list/seen = M.visible_atoms() - M //Sound and sight checkers
for(var/victim in seen)
- if((istype(victim, /mob/living/simple_animal/pet/)) || (victim == M) || (!isliving(victim)))
- seen = seen - victim
+ if(isanimal(victim) || !isliving(victim))
+ seen -= victim
if(LAZYLEN(seen))
to_chat(M, "You notice [pick(seen)]'s bulge [pick("OwO!", "uwu!")]")
if(16)
@@ -141,10 +139,10 @@
to_chat(M, "You find yourself unable to supress the desire to howl!")
M.emote("awoo")
if(prob(5))
- var/list/seen = viewers(5, get_turf(M))//Sound and sight checkers
+ var/list/seen = M.visible_atoms() - M //Sound and sight checkers
for(var/victim in seen)
- if((istype(victim, /mob/living/simple_animal/pet/)) || (victim == M) || (!isliving(victim)))
- seen = seen - victim
+ if(isanimal(victim) || !isliving(victim))
+ seen -= victim
if(LAZYLEN(seen))
to_chat(M, "You notice [pick(seen)]'s bulge [pick("OwO!", "uwu!")]")
..()
@@ -301,9 +299,8 @@ datum/reagent/fermi/nanite_b_gone/reaction_obj(obj/O, reac_volume)
else
var/datum/chemical_reaction/Ferm = GLOB.chemical_reagents_list[reagent.type]
Ferm.on_reaction(holder, reagent.volume)
- for(var/mob/M in viewers(8, location))
- to_chat(M, "The solution reacts dramatically, with a meow!")
- playsound(get_turf(M), 'modular_citadel/sound/voice/merowr.ogg', 50, 1)
+ holder.my_atom.visible_message("The solution reacts dramatically, with a meow!")
+ playsound(holder.my_atom, 'modular_citadel/sound/voice/merowr.ogg', 50, 1)
holder.clear_reagents()
/datum/reagent/fermi/acidic_buffer
@@ -321,10 +318,8 @@ datum/reagent/fermi/nanite_b_gone/reaction_obj(obj/O, reac_volume)
if(LAZYLEN(holder.reagent_list) == 1)
return ..()
holder.pH = ((holder.pH * holder.total_volume)+(pH * (volume)))/(holder.total_volume + (volume))
- var/list/seen = viewers(5, get_turf(holder))
- for(var/mob/M in seen)
- to_chat(M, "The beaker fizzes as the pH changes!")
- playsound(get_turf(holder.my_atom), 'sound/FermiChem/bufferadd.ogg', 50, 1)
+ holder.my_atom.visible_message("The beaker fizzes as the pH changes!")
+ playsound(holder.my_atom, 'sound/FermiChem/bufferadd.ogg', 50, 1)
holder.remove_reagent(type, volume, ignore_pH = TRUE)
..()
@@ -342,10 +337,8 @@ datum/reagent/fermi/nanite_b_gone/reaction_obj(obj/O, reac_volume)
if(LAZYLEN(holder.reagent_list) == 1)
return ..()
holder.pH = ((holder.pH * holder.total_volume)+(pH * (volume)))/(holder.total_volume + (volume))
- var/list/seen = viewers(5, get_turf(holder))
- for(var/mob/M in seen)
- to_chat(M, "The beaker froths as the pH changes!")
- playsound(get_turf(holder.my_atom), 'sound/FermiChem/bufferadd.ogg', 50, 1)
+ holder.my_atom.visible_message("The beaker froths as the pH changes!")
+ playsound(holder.my_atom, 'sound/FermiChem/bufferadd.ogg', 50, 1)
holder.remove_reagent(type, volume, ignore_pH = TRUE)
..()
diff --git a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
index 837524d49f..a8998a51ac 100644
--- a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
+++ b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
@@ -172,9 +172,7 @@
S.rabid = 1//Make them an angery boi
S.color = "#810010"
my_atom.reagents.clear_reagents()
- var/list/seen = viewers(8, get_turf(my_atom))
- for(var/mob/M in seen)
- to_chat(M, "The cells clump up into a horrifying tumour!")
+ my_atom.visible_message("An horrifying tumoural mass forms in [my_atom]!")
/datum/chemical_reaction/fermi/breast_enlarger
name = "Sucubus milk"
@@ -211,9 +209,7 @@
/datum/chemical_reaction/fermi/breast_enlarger/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
var/obj/item/organ/genital/breasts/B = new /obj/item/organ/genital/breasts(get_turf(my_atom))
- var/list/seen = viewers(8, get_turf(my_atom))
- for(var/mob/M in seen)
- to_chat(M, "The reaction suddenly condenses, creating a pair of breasts!")
+ my_atom.visible_message("The reaction suddenly condenses, creating a pair of breasts!")
var/datum/reagent/fermi/breast_enlarger/BE = locate(/datum/reagent/fermi/breast_enlarger) in my_atom.reagents.reagent_list
B.size = ((BE.volume * BE.purity) / 10) //half as effective.
my_atom.reagents.clear_reagents()
@@ -243,9 +239,7 @@
/datum/chemical_reaction/fermi/penis_enlarger/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
var/obj/item/organ/genital/penis/P = new /obj/item/organ/genital/penis(get_turf(my_atom))
- var/list/seen = viewers(8, get_turf(my_atom))
- for(var/mob/M in seen)
- to_chat(M, "The reaction suddenly condenses, creating a penis!")
+ my_atom.visible_message("The reaction suddenly condenses, creating a penis!")
var/datum/reagent/fermi/penis_enlarger/PE = locate(/datum/reagent/fermi/penis_enlarger) in my_atom.reagents.reagent_list
P.length = ((PE.volume * PE.purity) / 10)//half as effective.
my_atom.reagents.clear_reagents()
@@ -383,9 +377,7 @@
for(var/i in 1 to amount_to_spawn)
var/obj/item/clothing/head/hattip/hat = new /obj/item/clothing/head/hattip(get_turf(my_atom))
hat.animate_atom_living()
- var/list/seen = viewers(8, get_turf(my_atom))
- for(var/mob/M in seen)
- to_chat(M, "The [my_atom] makes an off sounding pop, as a hat suddenly climbs out of it!")
+ my_atom.visible_message("The [my_atom] makes an off sounding pop, as a hat suddenly climbs out of it!")
my_atom.reagents.clear_reagents()
/datum/chemical_reaction/fermi/furranium
@@ -543,9 +535,7 @@
/datum/chemical_reaction/fermi/secretcatchem/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
var/mob/living/simple_animal/pet/cat/custom_cat/catto = new(get_turf(my_atom))
- var/list/seen = viewers(8, get_turf(my_atom))
- for(var/mob/M in seen)
- to_chat(M, "The reaction suddenly gives out a meow, condensing into a chemcat!")//meow!
+ my_atom.visible_message("The reaction suddenly gives out a meow, condensing into a chemcat!")//meow!
playsound(get_turf(my_atom), 'modular_citadel/sound/voice/merowr.ogg', 50, 1, -1)
catto.name = "Chemcat"
catto.desc = "A cute chem cat, created by a lot of compicated and confusing chemistry!"
diff --git a/modular_citadel/code/modules/reagents/objects/clothes.dm b/modular_citadel/code/modules/reagents/objects/clothes.dm
index 34af39bbe0..de4cb38360 100644
--- a/modular_citadel/code/modules/reagents/objects/clothes.dm
+++ b/modular_citadel/code/modules/reagents/objects/clothes.dm
@@ -5,7 +5,7 @@
name = "Synthetic hat"
icon = 'icons/obj/clothing/hats.dmi'
icon_state = "cowboy"
- desc = "A synthesized hat, you can't seem to take it off. And tips their hat."
+ desc = "A synthesized hat. You feel compelled to keep it on all times."
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
//item_flags = NODROP //Tips their hat!
@@ -49,9 +49,8 @@
/obj/item/clothing/head/hattip/proc/root_and_toot(obj/item/clothing/head/hattip/hat)
hat.animate_atom_living()
- var/list/seen = viewers(6, get_turf(hat))
- for(var/mob/M2 in seen)
- to_chat(M2, "[hat] exclaims, \"[pick("Whooee! Time for a hootenanny!", "Rough 'em up boys!", "Yeehaw! Freedom at last!", "Y'all about to get a good old fashioned spanking!")]\"")
+ var/mob/living/simple_animal/hostile/mimic/M = loc
+ M.say(pick("Whooee! Time for a hootenanny!", "Rough 'em up boys!", "Yeehaw! Freedom at last!", "Y'all about to get a good old fashioned spanking!"))
/obj/item/clothing/head/hattip/proc/handle_speech(datum/source, mob/speech_args)
var/message = speech_args[SPEECH_MESSAGE]
diff --git a/tgstation.dme b/tgstation.dme
index aa24a781fe..619f6767bb 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -397,6 +397,7 @@
#include "code\datums\components\edit_complainer.dm"
#include "code\datums\components\empprotection.dm"
#include "code\datums\components\explodable.dm"
+#include "code\datums\components\field_of_vision.dm"
#include "code\datums\components\footstep.dm"
#include "code\datums\components\forced_gravity.dm"
#include "code\datums\components\identification.dm"
@@ -2375,6 +2376,7 @@
#include "code\modules\mob\living\carbon\human\human_movement.dm"
#include "code\modules\mob\living\carbon\human\inventory.dm"
#include "code\modules\mob\living\carbon\human\life.dm"
+#include "code\modules\mob\living\carbon\human\login.dm"
#include "code\modules\mob\living\carbon\human\physiology.dm"
#include "code\modules\mob\living\carbon\human\say.dm"
#include "code\modules\mob\living\carbon\human\species.dm"