[MIRROR] Field of View and Blindness improvements [bounty + upstream push] [MDB IGNORE] (#10060)

* Field of View and Blindness improvements [bounty + upstream push]

* Update death.dm

* almost done

* Update fov_handler.dm

* Face mouse when in combat mode, fix

* Fixes the category for the fov admin verb. #63401

* Fixes objects with bad planes and FoV bugs #63412

* pain

* there we go

* face pref

Co-authored-by: Azarak <azarak10@gmail.com>
This commit is contained in:
SkyratBot
2021-12-22 15:56:01 +01:00
committed by GitHub
parent 5604c5d160
commit c0706f4a41
45 changed files with 600 additions and 14 deletions

View File

@@ -0,0 +1,3 @@
// /obj/item/clothing
/// (/obj/item/clothing, visor_state) - When a clothing gets it's visor toggled.
#define COMSIG_CLOTHING_VISOR_TOGGLE "clothing_visor_toggle"

View File

@@ -31,6 +31,10 @@
#define COMSIG_MOB_CLIENT_BLOCK_PRE_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE
/// From base of /client/Move()
#define COMSIG_MOB_CLIENT_MOVED "mob_client_moved"
/// From base of /client/proc/change_view() (mob/source, new_size)
#define COMSIG_MOB_CLIENT_CHANGE_VIEW "mob_client_change_view"
/// From base of /mob/proc/reset_perspective() (mob/source)
#define COMSIG_MOB_RESET_PERSPECTIVE "mob_reset_perspective"
///from mind/transfer_to. Sent to the receiving mob.
#define COMSIG_MOB_MIND_TRANSFERRED_INTO "mob_mind_transferred_into"

11
code/__DEFINES/fov.dm Normal file
View File

@@ -0,0 +1,11 @@
/// Field of vision defines.
#define FOV_90_DEGREES 90
#define FOV_180_DEGREES 180
#define FOV_270_DEGREES 270
/// Base mask dimensions. They're like a client's view, only change them if you modify the mask to different dimensions.
#define BASE_FOV_MASK_X_DIMENSION 15
#define BASE_FOV_MASK_Y_DIMENSION 15
/// Range at which FOV effects treat nearsightness as blind and play
#define NEARSIGHTNESS_FOV_BLINDNESS 2

View File

@@ -4,6 +4,9 @@
//NEVER HAVE ANYTHING BELOW THIS PLANE ADJUST IF YOU NEED MORE SPACE
#define LOWEST_EVER_PLANE -200
#define FIELD_OF_VISION_BLOCKER_PLANE -199
#define FIELD_OF_VISION_BLOCKER_RENDER_TARGET "*FIELD_OF_VISION_BLOCKER_RENDER_TARGET"
#define CLICKCATCHER_PLANE -99
#define PLANE_SPACE -95
@@ -19,8 +22,10 @@
#define FLOOR_PLANE -7
#define GAME_PLANE -4
#define GAME_PLANE_FOV_HIDDEN -3
#define ABOVE_GAME_PLANE -2
#define MOUSE_TRANSPARENT_PLANE -3 //SKYRAT EDIT ADDITION - Pollution port
#define MOUSE_TRANSPARENT_PLANE -1 //SKYRAT EDIT ADDITION - Pollution port
#define SPACE_LAYER 1.8
//#define TURF_LAYER 2 //For easy recordkeeping; this is a byond define
@@ -151,6 +156,7 @@
#define BLIND_LAYER 4
#define CRIT_LAYER 5
#define CURSE_LAYER 6
#define FOV_EFFECTS_LAYER 10000 //Blindness effects are not layer 4, they lie to you
///--------------- FULLSCREEN RUNECHAT BUBBLES ------------

View File

@@ -70,3 +70,6 @@
#define COMSIG_START_FISHING "start_fishing"
//when someone pulls back their fishing rod
#define COMSIG_FINISH_FISHING "finish_fishing"
/// From mob/living/*/set_combat_mode(): (new_state)
#define COMSIG_LIVING_COMBAT_MODE_TOGGLE "living_combat_mode_toggle"

View File

@@ -72,6 +72,23 @@
if(istype(mymob) && mymob.client?.prefs?.read_preference(/datum/preference/toggle/ambient_occlusion))
add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA"))
/atom/movable/screen/plane_master/game_world_fov_hidden
name = "game world fov hidden plane master"
plane = GAME_PLANE_FOV_HIDDEN
render_relay_plane = GAME_PLANE
appearance_flags = PLANE_MASTER //should use client color
blend_mode = BLEND_OVERLAY
/atom/movable/screen/plane_master/game_world_fov_hidden/Initialize()
. = ..()
add_filter("vision_cone", 1, alpha_mask_filter(render_source = FIELD_OF_VISION_BLOCKER_RENDER_TARGET, flags = MASK_INVERSE))
/atom/movable/screen/plane_master/game_world_above
name = "above game world plane master"
plane = ABOVE_GAME_PLANE
render_relay_plane = GAME_PLANE
appearance_flags = PLANE_MASTER //should use client color
blend_mode = BLEND_OVERLAY
/atom/movable/screen/plane_master/massive_obj
name = "massive object plane master"
@@ -235,3 +252,10 @@
name = "fullscreen alert plane"
plane = FULLSCREEN_PLANE
render_relay_plane = RENDER_PLANE_NON_GAME
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/atom/movable/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

View File

@@ -85,6 +85,8 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
controlled_planes = list(
FLOOR_PLANE,
GAME_PLANE,
GAME_PLANE_FOV_HIDDEN,
ABOVE_GAME_PLANE,
MASSIVE_OBJ_PLANE,
GHOST_PLANE,
POINT_PLANE,

View File

@@ -377,3 +377,5 @@
integer = FALSE // It is in hours, but just in case one wants to specify minutes.
/datum/config_entry/flag/sdql_spells
/datum/config_entry/flag/native_fov

View File

@@ -0,0 +1,60 @@
/// An element to add a FOV trait to the wearer, removing it when an item is unequipped, but only as long as the visor is up.
/datum/component/clothing_fov_visor
/// What's the FOV angle of the trait we're applying to the wearer
var/fov_angle
/// Keeping track of the visor of our clothing.
var/visor_up = FALSE
/// Because of clothing code not being too good, we need keep track whether we are worn.
var/is_worn = FALSE
/datum/component/clothing_fov_visor/Initialize(fov_angle)
. = ..()
if(!isclothing(parent))
return COMPONENT_INCOMPATIBLE
var/obj/item/clothing/clothing_parent = parent
src.fov_angle = fov_angle
src.visor_up = clothing_parent.up //Initial values could vary, so we need to get it.
/datum/component/clothing_fov_visor/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
RegisterSignal(parent, COMSIG_CLOTHING_VISOR_TOGGLE, .proc/on_visor_toggle)
/datum/component/clothing_fov_visor/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED, COMSIG_CLOTHING_VISOR_TOGGLE))
return ..()
/// On dropping the item, remove the FoV trait if visor was down.
/datum/component/clothing_fov_visor/proc/on_drop(datum/source, mob/living/dropper)
SIGNAL_HANDLER
is_worn = FALSE
if(visor_up)
return
dropper.remove_fov_trait(source.type, fov_angle)
dropper.update_fov()
/// On equipping the item, add the FoV trait if visor isn't up.
/datum/component/clothing_fov_visor/proc/on_equip(obj/item/source, mob/living/equipper, slot)
SIGNAL_HANDLER
if(!(source.slot_flags & slot)) //If EQUIPPED TO HANDS FOR EXAMPLE
return
is_worn = TRUE
if(visor_up)
return
equipper.add_fov_trait(source.type, fov_angle)
equipper.update_fov()
/// On toggling the visor, we may want to add or remove FOV trait from the wearer.
/datum/component/clothing_fov_visor/proc/on_visor_toggle(datum/source, visor_state)
SIGNAL_HANDLER
visor_up = visor_state
if(!is_worn)
return
var/obj/item/clothing/clothing_parent = parent
var/mob/living/wearer = clothing_parent.loc //This has to be the case due to equip/dropped keeping track.
if(visor_up)
wearer.remove_fov_trait(source.type, fov_angle)
wearer.update_fov()
else
wearer.add_fov_trait(source.type, fov_angle)
wearer.update_fov()

View File

@@ -0,0 +1,142 @@
/// Component which handles Field of View masking for clients. FoV attributes are at /mob/living
/datum/component/fov_handler
/// Currently applied x size of the fov masks
var/current_fov_x = BASE_FOV_MASK_X_DIMENSION
/// Currently applied y size of the fov masks
var/current_fov_y = BASE_FOV_MASK_Y_DIMENSION
/// Whether we are applying the masks now
var/applied_mask = FALSE
/// The angle of the mask we are applying
var/fov_angle = FOV_180_DEGREES
/// The blocker mask applied to a client's screen
var/atom/movable/screen/fov_blocker/blocker_mask
/// The shadow mask applied to a client's screen
var/atom/movable/screen/fov_shadow/visual_shadow
/datum/component/fov_handler/Initialize(fov_type = FOV_180_DEGREES)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
var/mob/living/mob_parent = parent
var/client/parent_client = mob_parent.client
if(!parent_client) //Love client volatility!!
qdel(src) //no QDEL hint for components, and we dont want this to print a warning regarding bad component application
return
blocker_mask = new
visual_shadow = new
//visual_shadow.alpha = parent_client?.prefs.read_preference(/datum/preference/numeric/fov_darkness) //SKYRAT EDIT REMOVAL
update_visual_shadow_alpha() //SKYRAT EDIT ADDITION
set_fov_angle(fov_type)
on_dir_change(mob_parent, mob_parent.dir, mob_parent.dir)
update_fov_size()
update_mask()
/datum/component/fov_handler/Destroy()
if(applied_mask)
remove_mask()
if(blocker_mask) // In a case of early deletion due to volatile client
QDEL_NULL(blocker_mask)
if(visual_shadow) // In a case of early deletion due to volatile client
QDEL_NULL(visual_shadow)
return ..()
/datum/component/fov_handler/proc/set_fov_angle(new_angle)
fov_angle = new_angle
blocker_mask.icon_state = "[fov_angle]"
visual_shadow.icon_state = "[fov_angle]_v"
/// Updates the size of the FOV masks by comparing them to client view size.
/datum/component/fov_handler/proc/update_fov_size()
SIGNAL_HANDLER
var/mob/parent_mob = parent
var/client/parent_client = parent_mob.client
if(!parent_client) //Love client volatility!!
return
var/list/view_size = getviewsize(parent_client.view)
if(view_size[1] == current_fov_x && view_size[2] == current_fov_y)
return
current_fov_x = BASE_FOV_MASK_X_DIMENSION
current_fov_y = BASE_FOV_MASK_Y_DIMENSION
var/matrix/new_matrix = new
var/x_shift = view_size[1] - current_fov_x
var/y_shift = view_size[2] - current_fov_y
var/x_scale = view_size[1] / current_fov_x
var/y_scale = view_size[2] / current_fov_y
current_fov_x = view_size[1]
current_fov_y = view_size[2]
visual_shadow.transform = blocker_mask.transform = new_matrix.Scale(x_scale, y_scale)
visual_shadow.transform = blocker_mask.transform = new_matrix.Translate(x_shift * 16, y_shift * 16)
/// Updates the mask application to client by checking `stat` and `eye`
/datum/component/fov_handler/proc/update_mask()
SIGNAL_HANDLER
var/mob/parent_mob = parent
var/client/parent_client = parent_mob.client
if(!parent_client) //Love client volatility!!
return
var/user_living = parent_mob != DEAD
var/atom/top_most_atom = get_atom_on_turf(parent_mob)
var/user_extends_eye = parent_client.eye != top_most_atom
var/should_apply_mask = user_living && !user_extends_eye
if(should_apply_mask == applied_mask)
return
if(should_apply_mask)
add_mask()
else
remove_mask()
/datum/component/fov_handler/proc/remove_mask()
var/mob/parent_mob = parent
var/client/parent_client = parent_mob.client
if(!parent_client) //Love client volatility!!
return
applied_mask = FALSE
parent_client.screen -= blocker_mask
parent_client.screen -= visual_shadow
/datum/component/fov_handler/proc/add_mask()
var/mob/parent_mob = parent
var/client/parent_client = parent_mob.client
if(!parent_client) //Love client volatility!!
return
applied_mask = TRUE
parent_client.screen += blocker_mask
parent_client.screen += visual_shadow
/// When a direction of the user changes, so do the masks
/datum/component/fov_handler/proc/on_dir_change(mob/source, old_dir, new_dir)
SIGNAL_HANDLER
blocker_mask.dir = new_dir
visual_shadow.dir = new_dir
/// When a mob logs out, delete the component
/datum/component/fov_handler/proc/mob_logout(mob/source)
SIGNAL_HANDLER
qdel(src)
/datum/component/fov_handler/RegisterWithParent()
. = ..()
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/on_dir_change)
RegisterSignal(parent, COMSIG_LIVING_DEATH, .proc/update_mask)
RegisterSignal(parent, COMSIG_LIVING_REVIVE, .proc/update_mask)
RegisterSignal(parent, COMSIG_MOB_CLIENT_CHANGE_VIEW, .proc/update_fov_size)
RegisterSignal(parent, COMSIG_MOB_RESET_PERSPECTIVE, .proc/update_mask)
RegisterSignal(parent, COMSIG_MOB_LOGOUT, .proc/mob_logout)
RegisterSignal(parent, COMSIG_LIVING_COMBAT_MODE_TOGGLE, .proc/update_visual_shadow_alpha) //SKYRAT EDIT ADDITION
/datum/component/fov_handler/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, list(COMSIG_MOB_RESET_PERSPECTIVE, COMSIG_ATOM_DIR_CHANGE, COMSIG_LIVING_DEATH, COMSIG_LIVING_REVIVE, COMSIG_MOB_LOGOUT))
UnregisterSignal(parent, COMSIG_LIVING_COMBAT_MODE_TOGGLE) //SKYRAT EDIT ADDITION
//SKYRAT EDIT ADDITION BEGIN
/// When toggling combat mode, we update the alpha of the shadow mask
/datum/component/fov_handler/proc/update_visual_shadow_alpha()
SIGNAL_HANDLER
var/mob/living/parent_mob = parent
var/pref_to_read = parent_mob.combat_mode ? /datum/preference/numeric/fov_darkness : /datum/preference/numeric/out_of_combat_fov_darkness
var/target_alpha = parent_mob.client.prefs.read_preference(pref_to_read)
visual_shadow.alpha = target_alpha
//SKYRAT EDIT ADDITION END

View File

@@ -117,7 +117,7 @@
return
playsound(source_loc, pick(footstep_sounds[turf_footstep][1]), footstep_sounds[turf_footstep][2] * volume, TRUE, footstep_sounds[turf_footstep][3] + e_range, falloff_distance = 1, vary = sound_vary)
/datum/element/footstep/proc/play_humanstep(mob/living/carbon/human/source)
/datum/element/footstep/proc/play_humanstep(mob/living/carbon/human/source, atom/oldloc, direction)
SIGNAL_HANDLER
if (SHOULD_DISABLE_FOOTSTEPS(source))
@@ -134,6 +134,7 @@
if(!source_loc)
return
play_fov_effect(source, 5, "footstep", direction, ignore_self = TRUE)
if ((source.wear_suit?.body_parts_covered | source.w_uniform?.body_parts_covered | source.shoes?.body_parts_covered) & FEET)
// we are wearing shoes
playsound(source_loc, pick(GLOB.footstep[source_loc.footstep][1]),

View File

@@ -0,0 +1,34 @@
/// An element to unconditonally add a FOV trait to the wearer, removing it when an item is unequipped
/datum/element/item_fov
element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
id_arg_index = 2
/// Angle of the FoV we will apply when someone wears the clothing this element is attached to.
var/fov_angle
/datum/element/item_fov/Attach(datum/target, fov_angle)
. = ..()
if(!isitem(target))
return ELEMENT_INCOMPATIBLE
src.fov_angle = fov_angle
RegisterSignal(target, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
RegisterSignal(target, COMSIG_ITEM_DROPPED, .proc/on_drop)
/datum/element/item_fov/Detach(datum/target)
UnregisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
return ..()
/// On dropping the item, remove the FoV trait.
/datum/element/item_fov/proc/on_drop(datum/source, mob/living/dropper)
SIGNAL_HANDLER
dropper.remove_fov_trait(source.type, fov_angle)
dropper.update_fov()
/// On equipping the item, add the FoV trait.
/datum/element/item_fov/proc/on_equip(obj/item/source, mob/living/equipper, slot)
SIGNAL_HANDLER
if(!(source.slot_flags & slot)) //If EQUIPPED TO HANDS FOR EXAMPLE
return
equipper.add_fov_trait(source.type, fov_angle)
equipper.update_fov()

View File

@@ -2115,6 +2115,14 @@
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
active_hud.screentip_text.maptext = "<span class='maptext' style='text-align: center; font-size: 32px; color: [active_hud.screentip_color]'>[name]</span>"
///SKYRAT EDIT ADDITION BEGIN
// Face directions on combat mode. No procs, no typechecks, just a var for speed
var/mob/user_mob = usr
if(user_mob.face_mouse)
user_mob.face_atom(src)
///SKYRAT EDIT ADDITION END
/// Gets a merger datum representing the connected blob of objects in the allowed_types argument
/atom/proc/GetMergeGroup(id, list/allowed_types)
RETURN_TYPE(/datum/merger)

View File

@@ -81,6 +81,9 @@
/// The degree of pressure protection that mobs in list/contents have from the external environment, between 0 and 1
var/contents_pressure_protection = 0
/// Whether a user will face atoms on entering them with a mouse. Despite being a mob variable, it is here for performances //SKYRAT EDIT ADDITION
var/face_mouse = FALSE //SKYRAT EDIT ADDITION
/atom/movable/Initialize(mapload)
. = ..()
@@ -446,7 +449,7 @@
if(!direction)
direction = get_dir(src, newloc)
if(set_dir_on_move)
if(set_dir_on_move && !face_mouse)
setDir(direction)
var/is_multi_tile_object = bound_width > 32 || bound_height > 32
@@ -573,7 +576,7 @@
moving_diagonally = SECOND_DIAG_STEP
. = step(src, SOUTH)
if(moving_diagonally == SECOND_DIAG_STEP)
if(!. && set_dir_on_move)
if(!. && set_dir_on_move && !face_mouse)
setDir(first_step_dir)
else if (!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
@@ -613,7 +616,7 @@
last_move = direct
if(set_dir_on_move)
if(set_dir_on_move && !face_mouse)
setDir(direct)
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s)
. = FALSE
@@ -1079,7 +1082,7 @@
return
/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect)
/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect, fov_effect = TRUE)
if(!no_effect && (visual_effect_icon || used_item))
do_item_attack_animation(attacked_atom, visual_effect_icon, used_item)
@@ -1103,6 +1106,9 @@
pixel_x_diff = -8
turn_dir = -1
if(fov_effect)
play_fov_effect(attacked_atom, 5, "attack")
var/matrix/initial_transform = matrix(transform)
var/matrix/rotated_transform = transform.Turn(15 * turn_dir)
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform=rotated_transform, time = 1, easing=BACK_EASING|EASE_IN, flags = ANIMATION_PARALLEL)

View File

@@ -9,6 +9,7 @@
pixel_x = -32
pixel_y = -32
opacity = FALSE
plane = ABOVE_GAME_PLANE
layer = FLY_LAYER
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT

View File

@@ -3,6 +3,7 @@
icon = 'icons/obj/guns/projectiles.dmi'
icon_state = "nothing"
layer = ABOVE_MOB_LAYER
plane = GAME_PLANE_FOV_HIDDEN
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
appearance_flags = 0

View File

@@ -186,6 +186,7 @@
/obj/structure/chair/comfy/Initialize(mapload)
armrest = GetArmrest()
armrest.layer = ABOVE_MOB_LAYER
armrest.plane = ABOVE_GAME_PLANE
return ..()
/obj/structure/chair/comfy/proc/GetArmrest()

View File

@@ -203,6 +203,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/cmd_sdql_spell_menu,
/client/proc/adventure_manager,
/client/proc/load_circuit,
/client/proc/cmd_admin_toggle_fov,
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
GLOBAL_PROTECT(admin_verbs_possess)

View File

@@ -0,0 +1,17 @@
/client/proc/cmd_admin_toggle_fov()
set name = "Enable/Disable Field of View"
set category = "Debug"
if(!check_rights(R_ADMIN) || !check_rights(R_DEBUG))
return
var/on_off = CONFIG_GET(flag/native_fov)
message_admins("[key_name_admin(usr)] has [on_off ? "disabled" : "enabled"] the Native Field of View configuration..")
log_admin("[key_name(usr)] has [on_off ? "disabled" : "enabled"] the Native Field of View configuration.")
CONFIG_SET(flag/native_fov, !on_off)
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Field of View", "[on_off ? "Enabled" : "Disabled"]"))
for(var/mob/living/mob in GLOB.player_list)
mob.update_fov()

View File

@@ -5,6 +5,7 @@
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/ambient_occlusion/apply_to_client(client/client, value)
/// Backdrop for the game world plane.
var/atom/movable/screen/plane_master/game_world/plane_master = locate() in client?.screen
if (!plane_master)
return

View File

@@ -0,0 +1,17 @@
/datum/preference/numeric/fov_darkness
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "fov_darkness"
savefile_identifier = PREFERENCE_PLAYER
minimum = 0
maximum = 255
/datum/preference/numeric/fov_darkness/create_default_value()
return 255
/datum/preference/numeric/fov_darkness/apply_to_client_updated(client/client, value)
if(client.mob)
var/datum/component/fov_handler/fov_component = client.mob.GetComponent(/datum/component/fov_handler)
if(!fov_component)
return
fov_component.visual_shadow.alpha = value

View File

@@ -246,7 +246,7 @@
QDEL_NULL(moth_snack)
return ..()
/obj/item/clothing/dropped(mob/user)
/obj/item/clothing/dropped(mob/living/user)
..()
if(!istype(user))
return
@@ -254,6 +254,7 @@
for(var/trait in clothing_traits)
REMOVE_TRAIT(user, trait, "[CLOTHING_TRAIT] [REF(src)]")
if(LAZYLEN(user_vars_remembered))
for(var/variable in user_vars_remembered)
if(variable in user.vars)
@@ -261,7 +262,7 @@
user.vars[variable] = user_vars_remembered[variable]
user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null.
/obj/item/clothing/equipped(mob/user, slot)
/obj/item/clothing/equipped(mob/living/user, slot)
. = ..()
if (!istype(user))
return
@@ -463,6 +464,7 @@ BLIND // can't see anything
/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags
up = !up
SEND_SIGNAL(src, COMSIG_CLOTHING_VISOR_TOGGLE, up)
clothing_flags ^= visor_flags
flags_inv ^= visor_flags_inv
flags_cover ^= initial(flags_cover)
@@ -474,6 +476,7 @@ BLIND // can't see anything
/obj/item/clothing/head/helmet/space/plasmaman/visor_toggling() //handles all the actual toggling of flags
up = !up
SEND_SIGNAL(src, COMSIG_CLOTHING_VISOR_TOGGLE, up)
clothing_flags ^= visor_flags
flags_inv ^= visor_flags_inv
icon_state = "[initial(icon_state)]"

View File

@@ -18,6 +18,7 @@
/obj/item/clothing/mask/gas/Initialize(mapload)
. = ..()
init_fov()
if(!max_filters || !starting_filter_type)
return
@@ -75,6 +76,10 @@
has_filter = FALSE
return filtered_breath
/// Initializes the FoV component for the gas mask
/obj/item/clothing/mask/gas/proc/init_fov()
AddComponent(/datum/component/clothing_fov_visor, FOV_90_DEGREES)
/**
* Getter for overall filter durability, takes into consideration all filters filter_status
*/
@@ -173,6 +178,9 @@
species_exception = list(/datum/species/golem/bananium)
var/list/clownmask_designs = list()
/obj/item/clothing/mask/gas/clown_hat/init_fov()
return
/obj/item/clothing/mask/gas/clown_hat/plasmaman
starting_filter_type = /obj/item/gas_filter/plasmaman
@@ -219,6 +227,9 @@
resistance_flags = FLAMMABLE
species_exception = list(/datum/species/golem/bananium)
/obj/item/clothing/mask/gas/sexyclown/init_fov()
return
/obj/item/clothing/mask/gas/mime
name = "mime mask"
desc = "The traditional mime's mask. It has an eerie facial posture."
@@ -232,6 +243,9 @@
species_exception = list(/datum/species/golem)
var/list/mimemask_designs = list()
/obj/item/clothing/mask/gas/mime/init_fov()
return
/obj/item/clothing/mask/gas/mime/plasmaman
starting_filter_type = /obj/item/gas_filter/plasmaman
@@ -284,6 +298,9 @@
resistance_flags = FLAMMABLE
species_exception = list(/datum/species/golem)
/obj/item/clothing/mask/gas/sexymime/init_fov()
return
/obj/item/clothing/mask/gas/cyborg
name = "cyborg visor"
desc = "Beep boop."

View File

@@ -46,6 +46,9 @@
armor = list(MELEE = 10, BULLET = 5, LASER = 5, ENERGY = 5, BOMB = 0, BIO = 50, FIRE = 20, ACID = 40, WOUND = 5)
resistance_flags = FIRE_PROOF
/obj/item/clothing/mask/gas/explorer/init_fov()
return
/obj/item/clothing/mask/gas/explorer/attack_self(mob/user)
adjustmask(user)

View File

@@ -10,6 +10,7 @@
faction += "[REF(src)]"
GLOB.mob_living_list += src
SSpoints_of_interest.make_point_of_interest(src)
update_fov()
/mob/living/ComponentInitialize()
. = ..()
@@ -753,7 +754,6 @@
adjustToxLoss(-20, TRUE, TRUE) //slime friendly
updatehealth()
grab_ghost()
SEND_SIGNAL(src, COMSIG_LIVING_REVIVE, full_heal, admin_revive)
if(full_heal)
fully_heal(admin_revive = admin_revive)
if(stat == DEAD && can_be_revived()) //in some cases you can't revive (e.g. no brain)
@@ -776,6 +776,8 @@
else if(admin_revive)
updatehealth()
get_up(TRUE)
// The signal is called after everything else so components can properly check the updated values
SEND_SIGNAL(src, COMSIG_LIVING_REVIVE, full_heal, admin_revive)
/mob/living/proc/remove_CC()

View File

@@ -77,6 +77,7 @@
return
. = combat_mode
combat_mode = new_mode
SEND_SIGNAL(src, COMSIG_LIVING_COMBAT_MODE_TOGGLE, new_mode) //SKYRAT EDIT ADDITION
if(hud_used?.action_intent)
hud_used.action_intent.update_appearance()
//SKYRAT EDIT ADDITION BEGIN
@@ -92,6 +93,7 @@
set_combat_indicator(TRUE)
else
set_combat_indicator(FALSE)
face_mouse = (client?.prefs?.read_preference(/datum/preference/toggle/face_cursor_combat_mode) && combat_mode) ? TRUE : FALSE
//SKYRAT EDIT ADDITION END
if(silent || !(client?.prefs.toggles & SOUND_COMBATMODE))

View File

@@ -4,6 +4,7 @@
see_in_dark = 2
hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD)
pressure_resistance = 10
plane = GAME_PLANE //SKYRAT EDIT CHANGE
hud_type = /datum/hud/living
@@ -171,3 +172,10 @@
var/body_position_pixel_x_offset = 0
///The x amount a mob's sprite should be offset due to the current position they're in
var/body_position_pixel_y_offset = 0
/// FOV view that is applied from either nativeness or traits
var/fov_view
/// Native FOV that will be applied if a config is enabled
var/native_fov = FOV_180_DEGREES //SKYRAT EDIT CHANGE
/// Lazy list of FOV traits that will apply a FOV view when handled.
var/list/fov_traits

View File

@@ -0,0 +1,136 @@
/// Is `observed_atom` in a mob's field of view? This takes blindness, nearsightness and FOV into consideration
/mob/living/proc/in_fov(atom/observed_atom, ignore_self = FALSE)
if(ignore_self && observed_atom == src)
return TRUE
if(is_blind())
return FALSE
. = FALSE
var/turf/my_turf = get_turf(src) //Because being inside contents of something will cause our x,y to not be updated
// If turf doesn't exist, then we wouldn't get a fov check called by `play_fov_effect` or presumably other new stuff that might check this.
// ^ If that case has changed and you need that check, add it.
var/rel_x = observed_atom.x - my_turf.x
var/rel_y = observed_atom.y - my_turf.y
if(fov_view && observed_atom.plane == GAME_PLANE_FOV_HIDDEN) //SKYRAT EDIT CHANGE
if(rel_x >= -1 && rel_x <= 1 && rel_y >= -1 && rel_y <= 1) //Cheap way to check inside that 3x3 box around you
return TRUE //Also checks if both are 0 to stop division by zero
// Get the vector length so we can create a good directional vector
var/vector_len = sqrt(abs(rel_x) ** 2 + abs(rel_y) ** 2)
/// Getting a direction vector
var/dir_x
var/dir_y
switch(dir)
if(SOUTH)
dir_x = 0
dir_y = -vector_len
if(NORTH)
dir_x = 0
dir_y = vector_len
if(EAST)
dir_x = vector_len
dir_y = 0
if(WEST)
dir_x = -vector_len
dir_y = 0
///Calculate angle
var/angle = arccos((dir_x * rel_x + dir_y * rel_y) / (sqrt(dir_x**2 + dir_y**2) * sqrt(rel_x**2 + rel_y**2)))
/// Calculate vision angle and compare
var/vision_angle = (360 - fov_view) / 2
if(angle < vision_angle)
. = TRUE
else
. = TRUE
// Handling nearsightnedness
if(. && HAS_TRAIT(src, TRAIT_NEARSIGHT))
//Checking if our dude really is suffering from nearsightness! (very nice nearsightness code)
if(iscarbon(src))
var/mob/living/carbon/carbon_me = src
if(carbon_me.glasses)
var/obj/item/clothing/glasses/glass = carbon_me.glasses
if(glass.vision_correction)
return
if((rel_x >= NEARSIGHTNESS_FOV_BLINDNESS || rel_x <= -NEARSIGHTNESS_FOV_BLINDNESS) || (rel_y >= NEARSIGHTNESS_FOV_BLINDNESS || rel_y <= -NEARSIGHTNESS_FOV_BLINDNESS))
return FALSE
/// Updates the applied FOV value and applies the handler to client if able
/mob/living/proc/update_fov()
var/highest_fov
if(CONFIG_GET(flag/native_fov))
highest_fov = native_fov
for(var/trait_type in fov_traits)
var/fov_type = fov_traits[trait_type]
if(fov_type > highest_fov)
highest_fov = fov_type
fov_view = highest_fov
update_fov_client()
/// Updates the FOV for the client.
/mob/living/proc/update_fov_client()
if(!client)
return
var/datum/component/fov_handler/fov_component = GetComponent(/datum/component/fov_handler)
if(fov_view)
if(!fov_component)
AddComponent(/datum/component/fov_handler, fov_view)
else
fov_component.set_fov_angle(fov_view)
else if(fov_component)
qdel(fov_component)
/// Adds a trait which limits a user's FOV
/mob/living/proc/add_fov_trait(source, type)
LAZYINITLIST(fov_traits)
fov_traits[source] = type
update_fov()
/// Removes a trait which limits a user's FOV
/mob/living/proc/remove_fov_trait(source, type)
if(!fov_traits) //Clothing equip/unequip is bad code and invokes this several times
return
fov_traits -= source
UNSETEMPTY(fov_traits)
update_fov()
/// Plays a visual effect representing a sound cue for people with vision obstructed by FOV or blindness
/proc/play_fov_effect(atom/center, range, icon_state, dir = SOUTH, ignore_self = FALSE, angle = 0)
var/turf/anchor_point = get_turf(center)
var/image/fov_image
for(var/mob/living/living_mob in get_hearers_in_view(range, center))
var/client/mob_client = living_mob.client
if(!mob_client)
continue
if(HAS_TRAIT(living_mob, TRAIT_DEAF)) //Deaf people can't hear sounds so no sound indicators
continue
if(living_mob.in_fov(center, ignore_self))
continue
if(!fov_image) //Make the image once we found one recipient to receive it
fov_image = image(icon = 'icons/effects/fov/fov_effects.dmi', icon_state = icon_state, loc = anchor_point)
fov_image.plane = FULLSCREEN_PLANE
fov_image.layer = FOV_EFFECTS_LAYER
fov_image.dir = dir
fov_image.appearance_flags = RESET_COLOR | RESET_TRANSFORM
if(angle)
var/matrix/matrix = new
matrix.Turn(angle)
fov_image.transform = matrix
fov_image.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
mob_client.images += fov_image
addtimer(CALLBACK(GLOBAL_PROC, .proc/remove_image_from_client, fov_image, mob_client), 30)
/atom/movable/screen/fov_blocker
icon = 'icons/effects/fov/field_of_view.dmi'
icon_state = "90"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
plane = FIELD_OF_VISION_BLOCKER_PLANE
screen_loc = "BOTTOM,LEFT"
/atom/movable/screen/fov_shadow
icon = 'icons/effects/fov/field_of_view.dmi'
icon_state = "90_v"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
plane = ABOVE_LIGHTING_PLANE
screen_loc = "BOTTOM,LEFT"

View File

@@ -26,3 +26,5 @@
changeling.regain_powers()
med_hud_set_status()
update_fov_client()

View File

@@ -30,6 +30,7 @@
mob_size = MOB_SIZE_LARGE
radio = /obj/item/radio/headset/silicon/ai
can_buckle_to = FALSE
native_fov = null
var/battery = 200 //emergency power if the AI's APC is off
var/list/network = list("ss13")
var/obj/machinery/camera/current

View File

@@ -263,6 +263,7 @@
eyeblur = 0
damage_type = BRUTE
pass_flags = PASSTABLE
plane = GAME_PLANE
var/explode_hit_objects = TRUE
/obj/projectile/colossus/can_hit_target(atom/target, direct_target = FALSE, ignore_loc = FALSE, cross_failed = FALSE)

View File

@@ -449,6 +449,7 @@
client.perspective = EYE_PERSPECTIVE
client.eye = loc
return 1
SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
/**
* Examine a mob

View File

@@ -501,6 +501,7 @@
return FALSE
//SKYRAT EDIT ADDITION END
m_intent = MOVE_INTENT_RUN
plane = (m_intent == MOVE_INTENT_WALK) ? GAME_PLANE_FOV_HIDDEN : GAME_PLANE //SKYRAT EDIT ADDITION
if(hud_used?.static_inventory)
for(var/atom/movable/screen/mov_intent/selector in hud_used.static_inventory)
selector.update_appearance()

View File

@@ -12,6 +12,7 @@
wound_bonus = CANT_WOUND // can't wound by default
generic_canpass = FALSE
blocks_emissive = EMISSIVE_BLOCK_GENERIC
plane = GAME_PLANE_FOV_HIDDEN
//The sound this plays on impact.
var/hitsound = 'sound/weapons/pierce.ogg'
var/hitsound_wall = ""
@@ -689,6 +690,7 @@
trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, SSprojectiles.global_pixel_speed)
last_projectile_move = world.time
fired = TRUE
play_fov_effect(starting, 6, "gunfire", dir = NORTH, angle = Angle)
SEND_SIGNAL(src, COMSIG_PROJECTILE_FIRE)
if(hitscan)
process_hitscan()

View File

@@ -5,6 +5,7 @@
icon_state = "fuckyou"
max_integrity = 300
armor = list(MELEE = 30, BULLET = 30, LASER = 30, ENERGY = 0, BOMB = 30, BIO = 0, FIRE = 60, ACID = 60)
plane = GAME_PLANE_FOV_HIDDEN
density = TRUE
anchored = FALSE
blocks_emissive = EMISSIVE_BLOCK_GENERIC

View File

@@ -702,7 +702,7 @@
if(dir != direction && !strafe || forcerotate || keyheld)
if(dir != direction && !(mecha_flags & QUIET_TURNS) && !step_silent)
playsound(src,turnsound,40,TRUE)
setDir(direction)
set_dir_mecha(direction)
return TRUE
set_glide_size(DELAY_TO_GLIDE_SIZE(movedelay))
@@ -711,7 +711,7 @@
if(phasing)
use_power(phasing_energy_drain)
if(strafe)
setDir(olddir)
set_dir_mecha(olddir)
/obj/vehicle/sealed/mecha/Bump(atom/obstacle)
@@ -976,7 +976,7 @@
newoccupant.update_mouse_pointer()
add_fingerprint(newoccupant)
log_message("[newoccupant] moved in as pilot.", LOG_MECHA)
setDir(dir_in)
set_dir_mecha(dir_in)
playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE)
if(!internal_damage)
SEND_SOUND(newoccupant, sound('sound/mecha/nominal.ogg',volume=50))
@@ -1023,7 +1023,7 @@
brain_mob.reset_perspective(src)
brain_mob.remote_control = src
brain_mob.update_mouse_pointer()
setDir(dir_in)
set_dir_mecha(dir_in)
log_message("[brain_obj] moved in as pilot.", LOG_MECHA)
if(!internal_damage)
SEND_SOUND(brain_obj, sound('sound/mecha/nominal.ogg',volume=50))
@@ -1090,7 +1090,7 @@
remove_occupant(ejector)
mmi.set_mecha(null)
mmi.update_appearance()
setDir(dir_in)
set_dir_mecha(dir_in)
return ..()
@@ -1253,3 +1253,9 @@
for(var/occupant in occupants)
remove_action_type_from_mob(/datum/action/vehicle/sealed/mecha/mech_toggle_lights, occupant)
return COMPONENT_BLOCK_LIGHT_EATER
/// Sets the direction of the mecha and all of its occcupents, required for FOV. Alternatively one could make a recursive contents registration and register topmost direction changes in the fov component
/obj/vehicle/sealed/mecha/proc/set_dir_mecha(new_dir)
setDir(new_dir)
for(var/mob/living/occupant as anything in occupants)
occupant.setDir(new_dir)

View File

@@ -502,3 +502,6 @@ MAXFINE 2000
## Warning: SDQL is a powerful tool and can break many things or expose security sensitive information.
## Giving players access to it has major security concerns, be careful and deliberate when using this feature.
#SDQL_SPELLS
## Whether native FoV is enabled for all people.
#NATIVE_FOV

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,5 @@
/datum/preference/toggle/face_cursor_combat_mode
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
default_value = TRUE
savefile_key = "face_cursor_combat_mode"
savefile_identifier = PREFERENCE_PLAYER

View File

@@ -0,0 +1,10 @@
/datum/preference/numeric/out_of_combat_fov_darkness
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "out_of_combat_fov_darkness"
savefile_identifier = PREFERENCE_PLAYER
minimum = 0
maximum = 255
/datum/preference/numeric/out_of_combat_fov_darkness/create_default_value()
return 0

View File

@@ -73,6 +73,7 @@
#include "code\__DEFINES\food.dm"
#include "code\__DEFINES\footsteps.dm"
#include "code\__DEFINES\forensics.dm"
#include "code\__DEFINES\fov.dm"
#include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\icon_smoothing.dm"
#include "code\__DEFINES\id_cards.dm"
@@ -179,6 +180,7 @@
#include "code\__DEFINES\dcs\signals\signals_area.dm"
#include "code\__DEFINES\dcs\signals\signals_bot.dm"
#include "code\__DEFINES\dcs\signals\signals_circuit.dm"
#include "code\__DEFINES\dcs\signals\signals_clothing.dm"
#include "code\__DEFINES\dcs\signals\signals_container.dm"
#include "code\__DEFINES\dcs\signals\signals_customizable.dm"
#include "code\__DEFINES\dcs\signals\signals_cytology.dm"
@@ -672,6 +674,7 @@
#include "code\datums\components\caltrop.dm"
#include "code\datums\components\chasm.dm"
#include "code\datums\components\clickbox.dm"
#include "code\datums\components\clothing_fov_visor.dm"
#include "code\datums\components\codeword_hearing.dm"
#include "code\datums\components\combustible_flooder.dm"
#include "code\datums\components\connect_loc_behalf.dm"
@@ -694,6 +697,7 @@
#include "code\datums\components\explodable.dm"
#include "code\datums\components\faction_granter.dm"
#include "code\datums\components\forensics.dm"
#include "code\datums\components\fov_handler.dm"
#include "code\datums\components\fullauto.dm"
#include "code\datums\components\gas_leaker.dm"
#include "code\datums\components\geiger_sound.dm"
@@ -906,6 +910,7 @@
#include "code\datums\elements\forced_gravity.dm"
#include "code\datums\elements\haunted.dm"
#include "code\datums\elements\honkspam.dm"
#include "code\datums\elements\item_fov.dm"
#include "code\datums\elements\item_scaling.dm"
#include "code\datums\elements\kneecapping.dm"
#include "code\datums\elements\kneejerk.dm"
@@ -1839,6 +1844,7 @@
#include "code\modules\admin\verbs\diagnostics.dm"
#include "code\modules\admin\verbs\ert.dm"
#include "code\modules\admin\verbs\fix_air.dm"
#include "code\modules\admin\verbs\fov.dm"
#include "code\modules\admin\verbs\fps.dm"
#include "code\modules\admin\verbs\getlogs.dm"
#include "code\modules\admin\verbs\ghost_pool_protection.dm"
@@ -2272,6 +2278,7 @@
#include "code\modules\client\preferences\buttons_locked.dm"
#include "code\modules\client\preferences\clothing.dm"
#include "code\modules\client\preferences\darkened_flash.dm"
#include "code\modules\client\preferences\fov_darkness.dm"
#include "code\modules\client\preferences\fps.dm"
#include "code\modules\client\preferences\gender.dm"
#include "code\modules\client\preferences\ghost.dm"
@@ -2951,6 +2958,7 @@
#include "code\modules\mob\living\living.dm"
#include "code\modules\mob\living\living_defense.dm"
#include "code\modules\mob\living\living_defines.dm"
#include "code\modules\mob\living\living_fov.dm"
#include "code\modules\mob\living\living_movement.dm"
#include "code\modules\mob\living\living_say.dm"
#include "code\modules\mob\living\living_update_icons.dm"
@@ -4177,11 +4185,13 @@
#include "modular_skyrat\master_files\code\modules\client\preferences\cursed_shit.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\delete_sparks.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\erp_preferences.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\face_cursor_combat_mode.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\flavor_text.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\laugh.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\loadout_override_preference.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\looc.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\mutant_parts.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\out_of_combat_fov_darkness.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\scream.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\skin_tone.dm"
#include "modular_skyrat\master_files\code\modules\client\preferences\tgui_prefs_migration.dm"

View File

@@ -0,0 +1,8 @@
import { Feature, FeatureNumberInput } from "../base";
export const fov_darkness: Feature<number> = {
name: "Field of view darkness",
category: "GAMEPLAY",
description: "The density of darkness of field of vision cones you may have by wearing restrictive eye cover.",
component: FeatureNumberInput,
};

View File

@@ -0,0 +1,12 @@
import { multiline } from "common/string";
import { CheckboxInput, FeatureToggle } from "../../base";
export const face_cursor_combat_mode: FeatureToggle = {
name: "Face cursor with combat mode",
category: "GAMEPLAY",
description: multiline`
When toggled, you will now face towards the cursor
with combat mode enabled.
`,
component: CheckboxInput,
};

View File

@@ -0,0 +1,7 @@
import { Feature, FeatureNumberInput } from "../../base";
export const out_of_combat_fov_darkness: Feature<number> = {
name: "Out of Combat Field of View Darkness",
category: "GAMEPLAY",
component: FeatureNumberInput,
};