Files
Paradise/code/modules/mob/camera/eye.dm
asciodev 93ed0f096d Refactors AI / camera eyes and slows holopad holograms to walk speed (#25078)
* Refactor/deduplicate camera eye code

Camera Eyes previously had duplicated logic across several files. This
change uncooks the spaghetti. Additionally, half-baked support for TG's
multicam feature has been removed, as it was not functional or in use.

* lets ff now

* Camera Eye refactor fixes and finishing touches

This change completes a refactor of AI eyes, which were previously used
by xenobio consoles, syndicate and abductor camera consoles, shuttle
docking computers, holograms, and, of course, the AI. Duplicated logic
has been extracted to an abstract base mob, /mob/camera/eye, from which
new types for each of the above now derive.

Functionality is largely the same, with only a few minor cosmetic
differences (i.e. camera eyes are now appropriately named given their
type and user), as well as a quality-of-life enhancement for holograms,
slowing their movement speed to base run speed to prevent users from
accidentally zooming out of calls.

* Camera eye refactor: Fix AI acceleration toggle

The acceleration toggle was broken in the camera eye refactor, as
previously the boolean was stored on the AI rather than its eye. This
change fixes that.

* Camera eye refactor: Fix syndicate cam visibility

With the camera eye refactor, the syndicate advanced camera consoles
lost the ability to view maintenance tunnels and other areas without
active cameras, seeing static in their place instead (as all other
cameras do). This change reinstates the original behavior.

* Camera eye refactor: Convert spaces to tabs

* Camera eye refactor: Fix CRLF

* Apply suggestions from code review

General minor code quality improvements suggested by GDNgit

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>

* Apply suggestions from code review

Rename parameter names to avoid src accesses, remove an ambiguous and
unused mob_define and holopad range variable from a previous WIP, change
the for loop in /mob/camera/eye/relaymove to a for-to loop, and change
the chat message warning, sent when an AI Eye is created on an AI that
already has one, to a stack trace

* Adds toggle to AI commands for fast holograms

* Refactor ripped Hologram Eye relaymove

Previously, the relaymove proc for hologram eyes was redundant and
nearly impossible to read. It has been separated out into a few
different named procs, and has had its use of `spawn` removed.

* Remove unnecessary src access

* Fix bug involving shuttle placement outlines

The camera eye refactor that this commit is a part of introduced a bug
that prevented shuttle placement outlines from showing up on first use
of the shuttle console. This change fixes that bug.

* Unrevert some changes from #26306 lost in merge

* Remove erroneous free xray vision on advanced cams

* Autodoc camera acceleration vars

* Remove redundant null var initialization per code review

Co-authored-by: Drsmail <60036448+Drsmail@users.noreply.github.com>
Signed-off-by: asciodev <81930475+asciodev@users.noreply.github.com>

* Changed variables to camel_case, autodocs, cleanup

Changed a number of camera eye-related variables to camel_case style,
added appropriate autodoc comments, as per code review. Also removed an
unused cameranet function, modified the call signature of a cameranet
function to be more semantic, and changed a qdel-on-initialize in camera
eyes to return INITIALIZE_HINT_QDEL instead.

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>

* Remove stray qdel(src) per code review

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: asciodev <81930475+asciodev@users.noreply.github.com>

---------

Signed-off-by: asciodev <81930475+asciodev@users.noreply.github.com>
Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Co-authored-by: Drsmail <60036448+Drsmail@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
2025-01-17 18:22:43 +00:00

192 lines
7.3 KiB
Plaintext

/// Camera eyes are remote-control mobs that can move and see throughout the global cameranet.
/// They're used in AI eyes, holograms, advanced camera consoles, abductor consoles, shuttle consoles,
/// and xenobiology consoles. When created, the user with which they are initialized will be granted control,
/// and their movements will be relayed to the camera eye instead. When destroyed, the user's control of the
/// camera eye will be released; if they were previously remote controlling another object (such as another
/// camera eye) then they will be put back in control of that object; otherwise they will return to their body.
/mob/camera/eye
name = "Inactive Camera Eye"
icon = 'icons/obj/abductor.dmi'
icon_state = "camera_target"
alpha = 127
invisibility = SEE_INVISIBLE_OBSERVER
/// The list of camera chunks currently visible to the camera eye.
var/list/visible_camera_chunks = list()
/// The user controlling the eye.
var/mob/living/user
/// The thing that the user was previously remote controlling before this eye.
var/user_previous_remote_control
/// The object that created the eye.
var/origin
/// If true, speech near the camera eye will be relayed to its controller.
var/relay_speech = FALSE
/// Sets the camera eye visibility range; does not expand viewport, only affects cameranet obscuring
var/static_visibility_range = 16
/// Toggles whether this eye is detectable by AI Detectors.
var/ai_detector_visible = TRUE
/// Toggles whether the eye's icon should be visible to its user.
var/visible_icon = FALSE
/// The list of cameranets that this camera eye can see and access.
var/list/networks = list("SS13")
/// The in-memory image of the camera eye's icon.
var/image/user_image
// Camera acceleration settings
// Initially, the camera moves one turf per move. If there is no movement for
// cooldown_rate in deciseconds, the camera will reset to this movement rate.
// Every move otherwise increases sprint by acceleration_rate, until sprint
// exceeds sprint_threshold, and the movement rate increases by one per move.
// The movement rate is 1 + round(sprint / sprint_threshold).
/// The maximum sprint value - this caps acceleration
var/max_sprint = 50
/// The minimum sprint needed to increase base velocity
var/sprint_threshold = 20
/// The amount that sprint is increased per move
var/acceleration_rate = 0.5
/// Keeps track of acceleration - movement rate is 1 + round(sprint / sprint_threshold)
var/sprint = 10
/// The absolute time that sprint will reset to its initial value
var/cooldown = 0
/// The time after which sprint should be reset to its initial state, if no movements are made
var/cooldown_rate = 5
/// Toggles camera acceleration on or off.
var/acceleration = 1
/mob/camera/eye/Initialize(mapload, owner_name, camera_origin, mob/living/user)
. = ..()
name = "Camera Eye ([owner_name])"
origin = camera_origin
give_control(user)
update_visibility()
refresh_visible_icon()
if(!validate_active_cameranet())
return INITIALIZE_HINT_QDEL
/// Validates that there is an active cameranet. If strict is 0, does nothing.
/// Returns 1 if there is an active cameranet. Warns the user and returns 0 if there is not.
/mob/camera/eye/proc/validate_active_cameranet(strict = 0)
var/camera = first_active_camera()
if(strict && !camera)
to_chat(user, "<span class='warning'>ERROR: No linked and active camera network found.</span>")
return FALSE
return TRUE
/// Returns the turf of the first active camera in the global cameranet.
/mob/camera/eye/proc/first_active_camera()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
if(!C.can_use())
continue
if(length(C.network & networks))
return get_turf(C)
/// Updates what the global cameranet can see with respect to this eye and its user's client.
/mob/camera/eye/proc/update_visibility()
GLOB.cameranet.visibility(src, user.client)
/// Refreshes user_image in the user's client.images.
/mob/camera/eye/proc/refresh_visible_icon()
if(visible_icon && user.client)
user.client.images -= user_image
user_image = image(icon,loc,icon_state,FLY_LAYER)
user.client.images += user_image
/// Sets the camera eye's location to T, updates global cameranet visibility, and refreshes user_images.
/mob/camera/eye/set_loc(T)
if(user)
T = get_turf(T)
..(T)
update_visibility()
refresh_visible_icon()
/// Disables independent movement by camera eyes; camera eyes must be controlled by relaymove.
/mob/camera/eye/Move()
return FALSE
/// If `usr` is an AI, set the camera eye's location to the location of the atom clicked.
/atom/proc/move_camera_by_click()
if(is_ai(usr))
var/mob/living/silicon/ai/AI = usr
if(AI.eyeobj && (AI.client.eye == AI.eyeobj) && (AI.eyeobj.z == z))
AI.camera_follow = null
if(isturf(loc) || isturf(src))
AI.eyeobj.set_loc(src)
/// Returns the user's client, if it exists; otherwise returns null.
/mob/camera/eye/proc/get_viewer_client()
return user?.client
/// Removes obscured chunk images and user_images from the user's client.images.
/mob/camera/eye/proc/remove_images()
var/client/C = get_viewer_client()
if(!C)
return
for(var/datum/camerachunk/chunk as anything in visible_camera_chunks)
C.images -= chunk.obscured
if(visible_icon)
C.images -= user_image
/// Calls `remove_images`, changes the user's remote control from this camera eye to `user_previous_remote_control`.
/mob/camera/eye/proc/release_control()
if(!istype(user))
return
if(user.client)
user.reset_perspective(user.client.mob)
remove_images()
user.remote_control = null
if(user_previous_remote_control)
user.reset_perspective(user_previous_remote_control)
user.remote_control = user_previous_remote_control
user_previous_remote_control = null
user = null
/// Forces this eye's current user to release control, renames this eye, and grants `new_user` control of this eye.
/mob/camera/eye/proc/give_control(mob/new_user)
if(!istype(new_user))
return
release_control()
user = new_user
rename_camera(user.name)
if(istype(user.remote_control))
user_previous_remote_control = user.remote_control
user.remote_control = src
user.reset_perspective(src)
/// Renames the camera eye (only visible in observer Orbit menu)
/mob/camera/eye/proc/rename_camera(new_name)
name = "Camera Eye ([new_name])"
/// Remove this eye from all chunks containing it.
/mob/camera/eye/proc/release_chunks()
for(var/datum/camerachunk/chunk as anything in visible_camera_chunks)
chunk.remove(src)
/mob/camera/eye/Destroy()
release_control()
release_chunks()
return ..()
/// Called when the user controlling this eye attempts to move; uses camera acceleration settings.
/mob/camera/eye/relaymove(mob/user,direct)
var/initial = initial(sprint)
if(cooldown && cooldown < world.timeofday)
sprint = initial
for(var/i in 0 to sprint step sprint_threshold)
var/turf/next_step= get_turf(get_step(src, direct))
if(next_step)
set_loc(next_step)
cooldown = world.timeofday + cooldown_rate
if(acceleration)
sprint = min(sprint + acceleration_rate, max_sprint)
else
sprint = initial
/// If `relay_speech` is truthy, allows the camera eye's user to hear speech spoken at the eye's location.
/mob/camera/eye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
if(relay_speech)
user.hear_say(message_pieces, verb, italics, speaker, speech_sound, sound_vol, sound_frequency)