mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-21 02:46:31 +01:00
805 lines
23 KiB
Plaintext
805 lines
23 KiB
Plaintext
//* This file is explicitly licensed under the MIT license. *//
|
|
//* Copyright (c) 2024 Citadel Station Developers *//
|
|
|
|
/**
|
|
* MOB PERRSPECTIVE SYSTEM
|
|
*
|
|
* allows managed control of client viewport/eye changes
|
|
*
|
|
* ! - - - ATTENTION - - -
|
|
* ! These datums will modify mob variables.
|
|
* ! This can result in AI mobs having inconsistent sight/whatevers
|
|
* ! from what they'd usually have after a player enters, and
|
|
* ! leaves them.
|
|
* ! Mob reset_perspective() should handle most of these cases, but be wary.
|
|
*
|
|
* ! - - - ATTENTION - - -
|
|
* ! View sizes should only be for clients.
|
|
* ! Do not read off this for stuff like AI view/target tracking.
|
|
*
|
|
* ? - - - ATTENTION - - -
|
|
* ? We track MAXIMUM view sizes.
|
|
* ? Client can resize up/down as needed.
|
|
* ? If you want to force a client to a specific size,
|
|
* ? regardless of their widescreen setting,
|
|
* ? use client.set_temporary_view.
|
|
* ? Augmented view will also ignore client prefs on widescreen, *kinda.*
|
|
*
|
|
* as of right now, perspectives will **trample** the following on every set:
|
|
* client.eye
|
|
* client.lazy_eye (unimplemented)
|
|
* client.virtual_eye (unimplemented)
|
|
* client.perspective
|
|
* client.view
|
|
* mob.see_in_dark
|
|
* mob.see_invisible
|
|
* mob.sight
|
|
*
|
|
* these will be added/removed using synchronized access,
|
|
* and therefore existing values will be left alone,
|
|
* as long as existing values are not also in the perspective:
|
|
* client.screen
|
|
* client.images
|
|
*
|
|
* this is intentional - most of mobcode uses their own screen/image synchronization code.
|
|
* perspectives will never be able to replace that without ruining a lot of lazy-load
|
|
* behavior. instead, perspective is focused on allowing using it to manage generic
|
|
* synchronization of screen/images, rather than forcing the rest of the codebase to use it.
|
|
*
|
|
* however, perspectives are designed to force synchronization of the vars it does trample,
|
|
* because there's no better way to do it (because those vars are, semantically, only relevant to our perspective),
|
|
* while screen/images can be used for embedded maps/hud/etc.
|
|
*
|
|
* ## Use Case
|
|
*
|
|
* Perspectives should for the management of an atom's semantic world-view.
|
|
* This is what they can / can not see around them.
|
|
* This should not be used for things like inventory HUDs and action buttons.
|
|
* Those are "internal" viewing / a mob's internal state.
|
|
*
|
|
* Basically, these are for what you can see, if you looked through someone's eyes.
|
|
*/
|
|
/datum/perspective
|
|
/// eye - where visual calcs go from
|
|
var/atom/movable/eye
|
|
/// client perspective var
|
|
var/perspective = EYE_PERSPECTIVE
|
|
/// images
|
|
var/list/image/images
|
|
/// screen objects
|
|
var/list/atom/movable/screens
|
|
/// sight var
|
|
var/sight = SIGHT_FLAGS_DEFAULT
|
|
/// active clients - this is not the same as mobs because a client can be looking somewhere that isn't their mob
|
|
var/list/client/clients
|
|
/// mobs that are using this - required for clean gcs
|
|
var/list/mob/mobs
|
|
/// when a client logs out of a mob, and it's using us, the mob should reset to its self_perspective
|
|
var/reset_on_logout = TRUE
|
|
/// see in dark
|
|
var/see_in_dark = 2
|
|
/// see_invisible
|
|
var/see_invisible = SEE_INVISIBLE_LIVING
|
|
|
|
//* HUDs *//
|
|
/// active atom HUD providers associated to a list of ids or paths of atom huds that's providing it.
|
|
var/list/datum/atom_hud_provider/atom_hud_providers
|
|
|
|
//? planes
|
|
/// planes
|
|
var/datum/plane_holder/mob_perspective/planes
|
|
/// trait-like list for forcing planes on
|
|
var/list/planes_visible
|
|
|
|
//? view size
|
|
/// default view; if null, world.view
|
|
var/default_view_size
|
|
/// view size increase x
|
|
var/augment_view_width = 0
|
|
/// view size increase y
|
|
var/augment_view_height = 0
|
|
/// suppression sources
|
|
var/list/view_suppression
|
|
/// cached view x
|
|
var/cached_view_width
|
|
/// cached view y
|
|
var/cached_view_height
|
|
/// view cache needs recompute
|
|
var/view_dirty = TRUE
|
|
|
|
//? vision - lighting / nightvision
|
|
// todo: most of these don't need to be on baes /datum/perspective, i think.
|
|
// alternatively, just add a way to control whether we bother using this system.
|
|
// "darksight_system"?
|
|
var/janky_should_we_use_darksight_system = FALSE
|
|
/// darksight overlay that we maintain
|
|
var/atom/movable/screen/darksight_fov/darksight_fov_overlay
|
|
/// darksight overlay that we maintain
|
|
var/atom/movable/screen/darksight_occlusion/darksight_occlusion_overlay
|
|
/// lighting plane alpha
|
|
var/hard_darkvision
|
|
/// soft darksight range
|
|
var/darkvision_range
|
|
/// soft darksight alpha
|
|
var/darkvision_alpha
|
|
/// soft darksight matrix
|
|
var/list/darkvision_matrix
|
|
/// do we use smart darkvision?
|
|
var/darkvision_smart
|
|
/// is soft darkvision in global (infinite range) mode?
|
|
var/darkvision_unlimited
|
|
/// soft darkvision fov cone
|
|
var/darkvision_fov
|
|
// todo: legacy: stuff like mesons that require hard darkvision use this to cap see_in_dark
|
|
var/darkvision_legacy_throttle
|
|
// todo: legacy: used for xray fulbright
|
|
var/legacy_forced_hard_darkvision
|
|
// todo: sigh, shadekin things
|
|
var/legacy_throttle_overridden
|
|
|
|
/datum/perspective/Destroy()
|
|
clear_clients()
|
|
clear_mobs()
|
|
clear_atom_hud_providers()
|
|
QDEL_NULL(darksight_fov_overlay)
|
|
QDEL_NULL(darksight_occlusion_overlay)
|
|
images = null
|
|
screens = null
|
|
clients = null
|
|
set_eye(null)
|
|
return ..()
|
|
|
|
/// ONLY CALL FROM CLIENT.SET_PERSPECTIVE
|
|
/// I DO NOT TRUST PEOPLE TO NOT SCREW THIS UP
|
|
/datum/perspective/proc/add_client(client/C)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(C in clients)
|
|
return
|
|
if(C.using_perspective)
|
|
CRASH("client already had perspective")
|
|
LAZYADD(clients, C)
|
|
C.using_perspective = src
|
|
SEND_SIGNAL(src, COMSIG_PERSPECTIVE_CLIENT_REGISTER, C)
|
|
apply(C)
|
|
|
|
/// ONLY CALL FROM CLIENT.SET_PERSPECTIVE
|
|
/// I DO NOT TRUST PEOPLE TO NOT SCREW THIS UP
|
|
/datum/perspective/proc/remove_client(client/C, switching = FALSE)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(!(C in clients))
|
|
return
|
|
LAZYREMOVE(clients, C)
|
|
remove(C)
|
|
// if we're not doing this as part of a switch have them immediately switch to the mob
|
|
// oh and make sure they unregister
|
|
if(C.using_perspective != src)
|
|
stack_trace("client had wrong perspective")
|
|
C.using_perspective = null
|
|
SEND_SIGNAL(src, COMSIG_PERSPECTIVE_CLIENT_UNREGISTER, C, switching)
|
|
if(!switching)
|
|
C.reset_perspective()
|
|
|
|
/**
|
|
* gets all clients viewing us
|
|
*/
|
|
/datum/perspective/proc/get_clients()
|
|
return isnull(clients)? list() : clients.Copy()
|
|
|
|
/**
|
|
* kicks all clients off us
|
|
*/
|
|
/datum/perspective/proc/clear_clients()
|
|
for(var/client/C as anything in clients)
|
|
remove_client(C)
|
|
|
|
/**
|
|
* kicks all obs off of us
|
|
*/
|
|
/datum/perspective/proc/clear_mobs()
|
|
for(var/mob/M as anything in mobs)
|
|
remove_mob(M)
|
|
|
|
/**
|
|
* registers as a mob's current perspective
|
|
* sets mob vars as necessary
|
|
*/
|
|
/datum/perspective/proc/add_mob(mob/M)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(M.using_perspective)
|
|
CRASH("mob already had perspective")
|
|
if(reset_on_logout && !M.client) // nah
|
|
return
|
|
LAZYADD(mobs, M)
|
|
M.using_perspective = src
|
|
M.sight = sight
|
|
M.see_in_dark = clamp(see_in_dark, 0, 255)
|
|
M.see_invisible = see_invisible
|
|
SEND_SIGNAL(src, COMSIG_PERSPECTIVE_MOB_ADD, M)
|
|
|
|
/**
|
|
* unregisters as a mob's current perspective
|
|
* resets mob vars to initial() values
|
|
*/
|
|
/datum/perspective/proc/remove_mob(mob/M, switching = FALSE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
M.sight = initial(M.sight)
|
|
M.see_in_dark = initial(M.see_in_dark)
|
|
M.see_invisible = initial(M.see_invisible)
|
|
LAZYREMOVE(mobs, M)
|
|
SEND_SIGNAL(src, COMSIG_PERSPECTIVE_MOB_REMOVE, M, switching)
|
|
if(M.using_perspective == src)
|
|
M.using_perspective = null
|
|
if(!switching)
|
|
M.reset_perspective()
|
|
else
|
|
CRASH("mob had wrong perspective")
|
|
|
|
/**
|
|
* applys screen objs, etc, stuff that shouldn't be updated regularly
|
|
*/
|
|
/datum/perspective/proc/apply(client/C)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
assert_planes()
|
|
assert_vision_overlays()
|
|
if(!isnull(screens))
|
|
C.screen |= screens
|
|
if(!isnull(planes))
|
|
C.screen |= planes.screens()
|
|
if(!isnull(images))
|
|
C.images |= images
|
|
update(C)
|
|
|
|
/**
|
|
* fully reloads
|
|
*
|
|
* set owner to TRUE to apply prefs like AO.
|
|
*/
|
|
/datum/perspective/proc/reload(client/C, owner)
|
|
assert_planes()
|
|
if(owner)
|
|
planes.sync_owner(C)
|
|
apply(C)
|
|
|
|
/datum/perspective/proc/remove(client/C)
|
|
if(!isnull(screens))
|
|
C.screen -= screens
|
|
if(!isnull(planes))
|
|
C.screen -= planes.screens()
|
|
if(!isnull(images))
|
|
C.images -= images
|
|
|
|
/**
|
|
* updates eye, perspective var, virtual eye, lazy eye, sight, see in dark, see invis
|
|
*/
|
|
/datum/perspective/proc/update(client/C)
|
|
SEND_SIGNAL(src, COMSIG_PERSPECTIVE_CLIENT_UPDATE, C)
|
|
var/changed = C.eye
|
|
var/new_eye = get_eye(C)
|
|
if(!new_eye)
|
|
stack_trace("got null on get_eye, this shouldn't be possible")
|
|
C.eye = C.mob
|
|
else
|
|
C.eye = new_eye
|
|
if(changed != C.eye)
|
|
C.parallax_holder?.reset(force = TRUE)
|
|
C.perspective = get_eye_mode(C)
|
|
update_view_size(C)
|
|
|
|
/**
|
|
* update all viewers
|
|
*/
|
|
/datum/perspective/proc/update_all()
|
|
for(var/client/C as anything in clients)
|
|
update(C)
|
|
|
|
/**
|
|
* works with lists too
|
|
*/
|
|
/datum/perspective/proc/add_image(image/I)
|
|
var/change = length(images)
|
|
if(!change)
|
|
images = list()
|
|
images |= I
|
|
if(images.len != change)
|
|
for(var/client/C as anything in clients)
|
|
// |=, not +=, because we don't check dupes.
|
|
C.images |= I
|
|
/**
|
|
* works with lists too
|
|
*/
|
|
/datum/perspective/proc/remove_image(image/I)
|
|
var/change = length(images)
|
|
if(!change)
|
|
return
|
|
images -= I
|
|
if(images.len != change)
|
|
for(var/client/C as anything in clients)
|
|
C.images -= I
|
|
|
|
/**
|
|
* works with lists too
|
|
*/
|
|
/datum/perspective/proc/add_screen(atom/movable/AM)
|
|
var/change = length(screens)
|
|
if(!change)
|
|
screens = list()
|
|
screens |= AM
|
|
if(screens.len != change)
|
|
for(var/client/C as anything in clients)
|
|
// |=, not +=, because we don't check dupes.
|
|
C.screen |= AM
|
|
|
|
/**
|
|
* works with lists too
|
|
*/
|
|
/datum/perspective/proc/remove_screen(atom/movable/AM)
|
|
var/change = length(screens)
|
|
if(!change)
|
|
return
|
|
screens -= AM
|
|
if(change != screens.len)
|
|
for(var/client/C as anything in clients)
|
|
C.screen -= AM
|
|
|
|
/datum/perspective/proc/SetSight(flags)
|
|
var/change = sight ^ flags
|
|
sight = flags
|
|
if(change)
|
|
for(var/client/C as anything in clients)
|
|
C.mob.sight = sight
|
|
|
|
/datum/perspective/proc/AddSight(flags)
|
|
var/change = sight ^ flags
|
|
sight |= flags
|
|
if(change)
|
|
for(var/client/C as anything in clients)
|
|
C.mob.sight = sight
|
|
|
|
/datum/perspective/proc/RemoveSight(flags)
|
|
var/change = sight ^ flags
|
|
sight &= ~(flags)
|
|
if(change)
|
|
for(var/client/C as anything in clients)
|
|
C.mob.sight = sight
|
|
|
|
/datum/perspective/proc/SetSeeInvis(see_invisible)
|
|
var/change = src.see_invisible != see_invisible
|
|
src.see_invisible = see_invisible
|
|
if(change)
|
|
for(var/client/C as anything in clients)
|
|
C.mob.see_invisible = see_invisible
|
|
|
|
//? Abstraction - see_in_dark
|
|
|
|
/datum/perspective/proc/update_see_in_dark()
|
|
var/wanted = INFINITY // show everything
|
|
// if they have hard darkvision kick it back up
|
|
if(legacy_forced_hard_darkvision || hard_darkvision < 255)
|
|
wanted = INFINITY
|
|
// if they have legacy throttle..
|
|
if(!isnull(darkvision_legacy_throttle) && !legacy_throttle_overridden)
|
|
wanted = min(darkvision_legacy_throttle, wanted)
|
|
if(wanted != see_in_dark)
|
|
see_in_dark = wanted
|
|
for(var/mob/M as anything in mobs)
|
|
M.see_in_dark = clamp(see_in_dark, 0, 255)
|
|
|
|
//* Atom HUDs *//
|
|
|
|
/**
|
|
* accepts:
|
|
* * path
|
|
* * string id
|
|
* * instance
|
|
*
|
|
* currently will runtime if it's not a valid hud!
|
|
*/
|
|
/datum/perspective/proc/add_atom_hud(datum/atom_hud/hud, source)
|
|
ASSERT(istext(source))
|
|
hud = fetch_atom_hud(hud)
|
|
if(isnull(atom_hud_providers))
|
|
atom_hud_providers = list()
|
|
var/list/datum/atom_hud_provider/providers = hud.resolve_providers()
|
|
for(var/datum/atom_hud_provider/provider as anything in providers)
|
|
var/already_there = atom_hud_providers[provider]
|
|
if(already_there)
|
|
atom_hud_providers[provider] |= source
|
|
else
|
|
atom_hud_providers[provider] = list(source)
|
|
provider.add_perspective(src)
|
|
|
|
/**
|
|
* accepts:
|
|
* * path
|
|
* * string id
|
|
* * instance
|
|
*
|
|
* currently will runtime if it's not a valid hud!
|
|
*/
|
|
/datum/perspective/proc/remove_atom_hud(datum/atom_hud/hud, source)
|
|
ASSERT(istext(source))
|
|
if(!length(atom_hud_providers))
|
|
return
|
|
if(!hud)
|
|
// remove all of source
|
|
for(var/datum/atom_hud_provider/provider as anything in atom_hud_providers)
|
|
if(!(source in atom_hud_providers[provider]))
|
|
continue
|
|
atom_hud_providers[provider] -= source
|
|
if(!length(atom_hud_providers[provider]))
|
|
atom_hud_providers -= provider
|
|
provider.remove_perspective(src)
|
|
return
|
|
hud = fetch_atom_hud(hud)
|
|
var/list/datum/atom_hud_provider/providers = hud.resolve_providers()
|
|
for(var/datum/atom_hud_provider/provider as anything in providers)
|
|
if(!length(atom_hud_providers[provider]))
|
|
continue
|
|
atom_hud_providers[provider] -= source
|
|
if(!length(atom_hud_providers[provider]))
|
|
atom_hud_providers -= provider
|
|
provider.remove_perspective(src)
|
|
|
|
// todo: add_atom_hud_provider, remove_atom_hud_provider
|
|
|
|
/**
|
|
* clears all atom hud providers
|
|
*
|
|
* * seriously don't use this unless you know what you're doing
|
|
* * "why are huds broken" --> 9 / 10 times it's because someone used this when they shouldn't
|
|
* * remember that /datum/perspective does NOT state track HUD registration for you; you have to ensure things are added/removed as necessary
|
|
* * hint: if it's not on /silicon/robot, /simple_mob, or /dead/observer (or you don't know what those words are), you shouldn't be using this!
|
|
*/
|
|
/datum/perspective/proc/clear_atom_hud_providers()
|
|
for(var/datum/atom_hud_provider/provider as anything in atom_hud_providers)
|
|
provider.remove_perspective(src)
|
|
atom_hud_providers = null
|
|
|
|
//? Eye
|
|
|
|
/datum/perspective/proc/set_eye_mode(perspective)
|
|
var/change = src.perspective != perspective
|
|
if(!change)
|
|
return
|
|
src.perspective = perspective
|
|
for(var/client/C as anything in clients)
|
|
C.perspective = get_eye_mode(C)
|
|
|
|
/datum/perspective/proc/set_eye(atom/movable/AM)
|
|
var/change = src.eye != AM
|
|
if(!change)
|
|
return
|
|
if(!isnull(src.eye))
|
|
detach_from_eye(src.eye)
|
|
src.eye = AM
|
|
if(!isnull(src.eye))
|
|
attach_to_eye(src.eye)
|
|
for(var/client/C as anything in clients)
|
|
var/changed = C.eye
|
|
C.eye = get_eye(C)
|
|
if(changed != C.eye)
|
|
C.parallax_holder?.reset(force = TRUE)
|
|
C.perspective = get_eye_mode(C)
|
|
|
|
/datum/perspective/proc/attach_to_eye(atom/movable/AM)
|
|
RegisterSignal(AM, COMSIG_ATOM_DIR_CHANGE, PROC_REF(eye_dir_changed))
|
|
|
|
/datum/perspective/proc/detach_from_eye(atom/movable/AM)
|
|
UnregisterSignal(AM, COMSIG_ATOM_DIR_CHANGE)
|
|
|
|
/datum/perspective/proc/eye_dir_changed(atom/movable/source, old_dir, new_dir)
|
|
darksight_fov_overlay?.dir = new_dir
|
|
|
|
/datum/perspective/proc/get_eye(client/C)
|
|
return eye
|
|
|
|
/**
|
|
* get perspective var for a client
|
|
*/
|
|
/datum/perspective/proc/get_eye_mode(client/C)
|
|
// necessary for smooth transitions when calling update_perspective
|
|
// we default to eye perspectivie
|
|
// also this is honestly pointless but theoretically mob perspective should always be used while on mob
|
|
// this is stupid and i'm stupid
|
|
return eye == C.mob? MOB_PERSPECTIVE : perspective
|
|
|
|
//? lighting
|
|
|
|
/datum/perspective/proc/push_vision_stack(list/datum/vision/holders)
|
|
// reset to default
|
|
hard_darkvision = 255
|
|
darkvision_range = SOFT_DARKSIGHT_RANGE_DEFAULT
|
|
darkvision_alpha = SOFT_DARKSIGHT_ALPHA_DEFAULT
|
|
darkvision_matrix = construct_rgba_color_matrix()
|
|
darkvision_smart = TRUE
|
|
darkvision_unlimited = FALSE
|
|
darkvision_legacy_throttle = INFINITY
|
|
darkvision_fov = SOFT_DARKSIGHT_FOV_DEFAULT
|
|
legacy_throttle_overridden = FALSE
|
|
// push holders
|
|
for(var/datum/vision/holder as anything in holders)
|
|
holder.push(src)
|
|
// ensure darkvision matrix is full color format - we have a bug where not doing so can break shit after the first push (?!)
|
|
darkvision_matrix = color_matrix_expand(darkvision_matrix)
|
|
darkvision_unlimited = darkvision_range >= SOFT_DARKSIGHT_UNLIMITED_THRESHOLD
|
|
// update
|
|
update_vision()
|
|
|
|
/datum/perspective/proc/update_vision()
|
|
update_see_in_dark()
|
|
update_planes()
|
|
update_vision_overlays()
|
|
|
|
/datum/perspective/proc/assert_vision_overlays()
|
|
. = TRUE
|
|
if(isnull(darksight_fov_overlay))
|
|
darksight_fov_overlay = new
|
|
add_screen(darksight_fov_overlay)
|
|
if(isnull(darksight_occlusion_overlay))
|
|
darksight_occlusion_overlay = new
|
|
add_screen(darksight_occlusion_overlay)
|
|
if(!.)
|
|
return
|
|
update_vision_overlays()
|
|
|
|
/datum/perspective/proc/update_vision_overlays()
|
|
if(!isnull(darksight_fov_overlay))
|
|
// handle fov
|
|
var/state_to_use = "fade-omni-super"
|
|
var/color_to_use = null
|
|
switch(darkvision_fov)
|
|
if(SOFT_DARKSIGHT_FOV_270)
|
|
state_to_use = "fade-270-hard"
|
|
if(SOFT_DARKSIGHT_FOV_180)
|
|
state_to_use = "fade-180-hard"
|
|
if(SOFT_DARKSIGHT_FOV_90)
|
|
state_to_use = "fade-90-hard"
|
|
else
|
|
state_to_use = "fade-omni-super"
|
|
color_to_use = list(
|
|
0, 0, 0, -0.5,
|
|
0, 0, 0, -0.5,
|
|
0, 0, 0, -0.5,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 1,
|
|
)
|
|
darksight_fov_overlay.icon_state = state_to_use
|
|
darksight_fov_overlay.color = color_to_use
|
|
if(view_dirty)
|
|
recompute_view_size()
|
|
// todo: this should take shifting into account, for things like binoculars.
|
|
var/needed = max(cached_view_height, cached_view_width) / 15
|
|
var/matrix/transforming = matrix()
|
|
if(needed > 1)
|
|
transforming.Scale(needed, needed)
|
|
darksight_fov_overlay.transform = transforming
|
|
if(!isnull(darksight_occlusion_overlay))
|
|
// now, handle occlusion
|
|
var/matrix/cropping = matrix()
|
|
var/factor = ((darkvision_range / (WORLD_ICON_SIZE)) / ((15 / 2) + 1))
|
|
cropping.Scale(factor)
|
|
darksight_occlusion_overlay.transform = cropping
|
|
|
|
/datum/perspective/proc/legacy_force_set_hard_darkvision(amt)
|
|
. = legacy_forced_hard_darkvision == amt
|
|
legacy_forced_hard_darkvision = amt
|
|
if(.)
|
|
update_see_in_dark()
|
|
update_planes()
|
|
|
|
//? plane holder
|
|
|
|
/datum/perspective/proc/assert_planes()
|
|
if(!isnull(planes))
|
|
return
|
|
planes = new /datum/plane_holder/mob_perspective
|
|
update_planes()
|
|
|
|
/datum/perspective/proc/update_planes()
|
|
if(isnull(planes))
|
|
return
|
|
var/atom/movable/screen/plane_master/darkvision_plate = planes.by_plane_type(/atom/movable/screen/plane_master/darkvision)
|
|
if(!isnull(darkvision_plate))
|
|
darkvision_plate.color = darkvision_matrix?.Copy()
|
|
var/atom/movable/screen/plane_master/lighting/lighting_plane = planes?.by_plane_type(/atom/movable/screen/plane_master/lighting)
|
|
var/wanted_alpha = isnull(legacy_forced_hard_darkvision)? (isnull(hard_darkvision)? 255 : hard_darkvision) : legacy_forced_hard_darkvision
|
|
lighting_plane.alpha = wanted_alpha
|
|
var/lit_factor = darkvision_alpha / 255
|
|
darkvision_plate.alpha = round((lit_factor * (wanted_alpha / 255)) * 255, 1)
|
|
|
|
/**
|
|
* sets a plane visible if it wasn't already
|
|
* source should be a trait source
|
|
*
|
|
* @params
|
|
* * key - path to plane
|
|
* * source - trait source enum
|
|
*/
|
|
/datum/perspective/proc/set_plane_visible(key, source)
|
|
LAZYINITLIST(planes_visible)
|
|
var/was = planes_visible[key]
|
|
if(islist(was))
|
|
planes_visible[key] |= source
|
|
else
|
|
planes_visible[key] = list(source)
|
|
show_plane(key)
|
|
|
|
/**
|
|
* removes a plane's visibility if it was forced to be visible
|
|
* source should be a trait source
|
|
*
|
|
* @params
|
|
* * key - path to plane (optional); if not specified, all planes from that source is removed.
|
|
* * source - trait source enum.
|
|
*/
|
|
/datum/perspective/proc/unset_plane_visible(key, source)
|
|
if(isnull(key))
|
|
for(key in planes_visible)
|
|
if(source in planes_visible[key])
|
|
planes_visible[key] -= source
|
|
if(!length(planes_visible[key]))
|
|
planes_visible -= key
|
|
hide_plane(key)
|
|
return
|
|
var/was = planes_visible[key]
|
|
if(islist(was))
|
|
was -= source
|
|
if(!length(was))
|
|
planes_visible -= key
|
|
hide_plane(key)
|
|
|
|
/datum/perspective/proc/is_plane_visible(var/atom/movable/screen/plane_master/key, source)
|
|
if(!initial(key.default_invisible))
|
|
return TRUE
|
|
if(isnull(source))
|
|
return planes_visible[key]
|
|
return source in planes_visible[key]
|
|
|
|
/datum/perspective/proc/show_plane(key, force)
|
|
assert_planes()
|
|
var/atom/movable/screen/plane_master/plane = planes.by_plane_type(key)
|
|
if(isnull(plane))
|
|
return
|
|
plane.alpha = plane.default_alpha
|
|
|
|
/datum/perspective/proc/hide_plane(key, force)
|
|
assert_planes()
|
|
var/atom/movable/screen/plane_master/plane = planes.by_plane_type(key)
|
|
if(isnull(plane))
|
|
return
|
|
if(!plane.default_invisible && !force)
|
|
return
|
|
plane.alpha = 0
|
|
|
|
//? view size
|
|
|
|
/**
|
|
* Sets our default size.
|
|
*
|
|
* DO NOT use this in place of augmented.
|
|
* User widescreen preferences WILL override this!
|
|
*
|
|
* Ideally this shouldn't be used at all.
|
|
*/
|
|
/datum/perspective/proc/set_default_view(size)
|
|
if(size == default_view_size)
|
|
return
|
|
// assert it y'know, works
|
|
if(isnum(size))
|
|
default_view_size = size
|
|
else if(isnull(size))
|
|
default_view_size = size
|
|
else if(istext(size))
|
|
var/list/split = splittext(size, "x")
|
|
ASSERT(split.len == 2)
|
|
// if the above runtimes, let it so we know who fucked up
|
|
default_view_size = size
|
|
else
|
|
CRASH("What?")
|
|
view_dirty = TRUE
|
|
update_view_size()
|
|
|
|
/**
|
|
* Sets augmented size ontop of default size.
|
|
*
|
|
* Currently only supports positive numbers. Numbers apply equally
|
|
* to both directions of a dimension.
|
|
*/
|
|
/datum/perspective/proc/set_augmented_view(width, height)
|
|
if(!isnum(height) || !isnum(width))
|
|
CRASH("invalid")
|
|
// no negative augment values for now
|
|
augment_view_height = max(0, height)
|
|
augment_view_width = max(0, width)
|
|
view_dirty = TRUE
|
|
update_view_size()
|
|
|
|
/datum/perspective/proc/update_view_size(client/C)
|
|
if(view_dirty)
|
|
recompute_view_size()
|
|
update_vision_overlays()
|
|
if(C)
|
|
C.request_viewport_update()
|
|
else
|
|
for(var/client/_C as anything in clients)
|
|
update_view_size(_C)
|
|
|
|
/datum/perspective/proc/recompute_view_size()
|
|
view_dirty = FALSE
|
|
if(isnum(default_view_size))
|
|
cached_view_height = default_view_size + augment_view_width
|
|
cached_view_width = default_view_size + augment_view_height
|
|
return
|
|
else if(isnull(default_view_size))
|
|
cached_view_width = GLOB.max_client_view_x + augment_view_width
|
|
cached_view_height = GLOB.max_client_view_y + augment_view_height
|
|
return
|
|
var/list/parsed = splittext(default_view_size, "x")
|
|
if(length(parsed) != 2)
|
|
cached_view_width = GLOB.max_client_view_x + augment_view_width
|
|
cached_view_height = GLOB.max_client_view_y + augment_view_height
|
|
return
|
|
cached_view_width = parsed[1] + augment_view_width
|
|
cached_view_height = parsed[2] + augment_view_height
|
|
|
|
/datum/perspective/proc/suppress_view(source)
|
|
var/was = LAZYLEN(view_suppression)
|
|
LAZYDISTINCTADD(view_suppression, source)
|
|
if(!was && LAZYLEN(view_suppression))
|
|
view_dirty = TRUE
|
|
update_view_size()
|
|
|
|
/datum/perspective/proc/unsuppress_view(source)
|
|
var/was = LAZYLEN(view_suppression)
|
|
LAZYREMOVE(view_suppression, source)
|
|
if(was && !LAZYLEN(view_suppression))
|
|
view_dirty = TRUE
|
|
update_view_size()
|
|
|
|
/datum/perspective/proc/ensure_view_cached()
|
|
if(view_dirty)
|
|
recompute_view_size()
|
|
|
|
//! remotes
|
|
/datum/perspective/proc/considered_remote(mob/M)
|
|
return eye == M
|
|
|
|
/**
|
|
* used for self-perspectives - eye should always be the owner
|
|
*/
|
|
/datum/perspective/self
|
|
|
|
/datum/perspective/self/get_eye(client/C)
|
|
return get_top_atom(eye)
|
|
|
|
/datum/perspective/self/proc/get_top_atom(atom/movable/where)
|
|
if(isturf(where))
|
|
return where // already on turf
|
|
var/turf/T = get_turf(where)
|
|
if(!T)
|
|
return where // either the perspective owner's mob or someone fucked up royally
|
|
// we know we're on a turf now (assuming get_turf() didn't break but that'd be a byond thing) so
|
|
while(!isturf(where.loc))
|
|
where = where.loc
|
|
return where
|
|
|
|
/**
|
|
* temporary perspectives generated - automatically deletes when last mob is gone
|
|
*/
|
|
/datum/perspective/self/temporary
|
|
|
|
/datum/perspective/self/temporary/remove_mob(mob/M, switching)
|
|
. = ..()
|
|
if(!length(mobs))
|
|
qdel(src)
|
|
|
|
/**
|
|
* always remote - you usually want to override this
|
|
*/
|
|
/datum/perspective/remote
|
|
|
|
/datum/perspective/remote/considered_remote(mob/M)
|
|
return TRUE
|