diff --git a/code/__DEFINES/dcs/signals/signals_clothing.dm b/code/__DEFINES/dcs/signals/signals_clothing.dm new file mode 100644 index 00000000000..242a076615f --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_clothing.dm @@ -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" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index ca6cdae1200..244ef15c3f1 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -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" diff --git a/code/__DEFINES/fov.dm b/code/__DEFINES/fov.dm new file mode 100644 index 00000000000..1a10b84be91 --- /dev/null +++ b/code/__DEFINES/fov.dm @@ -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 diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index bfa6c686b35..1862341c13d 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -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 ------------ diff --git a/code/__DEFINES/~skyrat_defines/signals.dm b/code/__DEFINES/~skyrat_defines/signals.dm index cb5bd681f23..70143539adb 100644 --- a/code/__DEFINES/~skyrat_defines/signals.dm +++ b/code/__DEFINES/~skyrat_defines/signals.dm @@ -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" diff --git a/code/_onclick/hud/rendering/plane_master.dm b/code/_onclick/hud/rendering/plane_master.dm index a952ad59853..8453f31e4d1 100644 --- a/code/_onclick/hud/rendering/plane_master.dm +++ b/code/_onclick/hud/rendering/plane_master.dm @@ -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 diff --git a/code/_onclick/hud/rendering/plane_master_controller.dm b/code/_onclick/hud/rendering/plane_master_controller.dm index 1b363a754c1..8ae132fd078 100644 --- a/code/_onclick/hud/rendering/plane_master_controller.dm +++ b/code/_onclick/hud/rendering/plane_master_controller.dm @@ -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, diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 16b5f54da8b..adcef045fbe 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -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 diff --git a/code/datums/components/clothing_fov_visor.dm b/code/datums/components/clothing_fov_visor.dm new file mode 100644 index 00000000000..2ea793bd5fd --- /dev/null +++ b/code/datums/components/clothing_fov_visor.dm @@ -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() diff --git a/code/datums/components/fov_handler.dm b/code/datums/components/fov_handler.dm new file mode 100644 index 00000000000..7ed738ba8c6 --- /dev/null +++ b/code/datums/components/fov_handler.dm @@ -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 diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm index e47c03a813c..a529025c967 100644 --- a/code/datums/elements/footstep.dm +++ b/code/datums/elements/footstep.dm @@ -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]), diff --git a/code/datums/elements/item_fov.dm b/code/datums/elements/item_fov.dm new file mode 100644 index 00000000000..57d93f4f537 --- /dev/null +++ b/code/datums/elements/item_fov.dm @@ -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() diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 0a7a9a8b423..71f1eebacbc 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -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 = "[name]" + ///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) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 54d818ac86e..a72fbaf7352 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -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) diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm index a69a082c18e..f3f1f01cc71 100644 --- a/code/game/objects/effects/effect_system/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/effects_smoke.dm @@ -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 diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm index f0c25219c13..65654533e9e 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -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 diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index a9a5b582306..7d0fec987ce 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -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() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 076a5e0d940..fb6f32d32ae 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -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) diff --git a/code/modules/admin/verbs/fov.dm b/code/modules/admin/verbs/fov.dm new file mode 100644 index 00000000000..f74ba6f8058 --- /dev/null +++ b/code/modules/admin/verbs/fov.dm @@ -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() diff --git a/code/modules/client/preferences/ambient_occlusion.dm b/code/modules/client/preferences/ambient_occlusion.dm index a81efca00bd..33c87c188d7 100644 --- a/code/modules/client/preferences/ambient_occlusion.dm +++ b/code/modules/client/preferences/ambient_occlusion.dm @@ -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 diff --git a/code/modules/client/preferences/fov_darkness.dm b/code/modules/client/preferences/fov_darkness.dm new file mode 100644 index 00000000000..b0146e10b40 --- /dev/null +++ b/code/modules/client/preferences/fov_darkness.dm @@ -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 diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index fa3d9b97726..8d88762f991 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -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)]" diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 59a4d35d8e1..ef6c93a79bb 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -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." diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm index abb9bebce45..74bae5e91af 100644 --- a/code/modules/mining/equipment/explorer_gear.dm +++ b/code/modules/mining/equipment/explorer_gear.dm @@ -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) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 9c3623379de..e9db404eb9c 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -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() diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index d52027bd65a..aebd1fe71cd 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -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)) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index ba6394b3df0..d991d8e3dca 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) 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 diff --git a/code/modules/mob/living/living_fov.dm b/code/modules/mob/living/living_fov.dm new file mode 100644 index 00000000000..96bcff79191 --- /dev/null +++ b/code/modules/mob/living/living_fov.dm @@ -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" diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index bbe97d6aaef..7d34fab011a 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -26,3 +26,5 @@ changeling.regain_powers() med_hud_set_status() + + update_fov_client() diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 3e0df35925e..0a5124a9e2a 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -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 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 11ab84a2743..27fbb7aaaa3 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -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) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 235e529658a..0553b5a6664 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -449,6 +449,7 @@ client.perspective = EYE_PERSPECTIVE client.eye = loc return 1 + SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE) /** * Examine a mob diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 7d75a7323e6..e1995fe115a 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -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() diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index e4d495add93..a32ede8a9d7 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -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() diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm index 8f652cc92d2..aa14db17c2a 100644 --- a/code/modules/vehicles/_vehicle.dm +++ b/code/modules/vehicles/_vehicle.dm @@ -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 diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index 1c68ac359cf..0df887e66f6 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -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) diff --git a/config/game_options.txt b/config/game_options.txt index 04ab833bf54..e9d801b8dc9 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -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 diff --git a/icons/effects/fov/field_of_view.dmi b/icons/effects/fov/field_of_view.dmi new file mode 100644 index 00000000000..8086773d140 Binary files /dev/null and b/icons/effects/fov/field_of_view.dmi differ diff --git a/icons/effects/fov/fov_effects.dmi b/icons/effects/fov/fov_effects.dmi new file mode 100644 index 00000000000..d5b7de38a45 Binary files /dev/null and b/icons/effects/fov/fov_effects.dmi differ diff --git a/modular_skyrat/master_files/code/modules/client/preferences/face_cursor_combat_mode.dm b/modular_skyrat/master_files/code/modules/client/preferences/face_cursor_combat_mode.dm new file mode 100644 index 00000000000..1bb5fb99fe6 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/client/preferences/face_cursor_combat_mode.dm @@ -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 diff --git a/modular_skyrat/master_files/code/modules/client/preferences/out_of_combat_fov_darkness.dm b/modular_skyrat/master_files/code/modules/client/preferences/out_of_combat_fov_darkness.dm new file mode 100644 index 00000000000..78a1c6708d6 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/client/preferences/out_of_combat_fov_darkness.dm @@ -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 diff --git a/tgstation.dme b/tgstation.dme index 5f95dd0dfe2..b0f3ba02cf6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/fov_darkness.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/fov_darkness.tsx new file mode 100644 index 00000000000..1315cc88fba --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/fov_darkness.tsx @@ -0,0 +1,8 @@ +import { Feature, FeatureNumberInput } from "../base"; + +export const fov_darkness: Feature = { + 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, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/skyrat/face_cursor_combat_mode.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/skyrat/face_cursor_combat_mode.tsx new file mode 100644 index 00000000000..283a9225dfc --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/skyrat/face_cursor_combat_mode.tsx @@ -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, +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/skyrat/out_of_combat_fov_darkness.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/skyrat/out_of_combat_fov_darkness.tsx new file mode 100644 index 00000000000..235869413a0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/skyrat/out_of_combat_fov_darkness.tsx @@ -0,0 +1,7 @@ +import { Feature, FeatureNumberInput } from "../../base"; + +export const out_of_combat_fov_darkness: Feature = { + name: "Out of Combat Field of View Darkness", + category: "GAMEPLAY", + component: FeatureNumberInput, +};