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>
This commit is contained in:
asciodev
2025-01-17 13:22:43 -05:00
committed by GitHub
parent dac57002ff
commit 93ed0f096d
107 changed files with 912 additions and 822 deletions
+2 -2
View File
@@ -387,9 +387,9 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
if(ishuman(mob))
var/mob/living/carbon/human/H = mob
H.regenerate_icons() // workaround for #13269
if(isAI(mob)) // client.mob, built in byond client var
if(is_ai(mob)) // client.mob, built in byond client var
var/mob/living/silicon/ai/ai = mob
ai.eyeobj.setLoc(old_turf)
ai.eyeobj.set_loc(old_turf)
else if(isnewplayer(mob))
to_chat(src, "<font color='red'>Error: Aghost: Can't admin-ghost whilst in the lobby. Join or observe first.</font>")
else
+3 -3
View File
@@ -157,7 +157,7 @@ GLOBAL_VAR_INIT(disable_explosions, FALSE)
"}
var/jumptoeye = ""
if(isAI(M))
if(is_ai(M))
var/mob/living/silicon/ai/A = M
if(A.client && A.eyeobj) // No point following clientless AI eyes
jumptoeye = " <b>(<A href='byond://?_src_=holder;jumpto=[A.eyeobj.UID()]'>Eye</A>)</b>"
@@ -199,7 +199,7 @@ GLOBAL_VAR_INIT(disable_explosions, FALSE)
body += "<A href='byond://?_src_=holder;corgione=[M.UID()]'>Corgize</A> | "
//AI / Cyborg
if(isAI(M))
if(is_ai(M))
body += "<B>Is an AI</B> "
else if(ishuman(M))
body += {"<A href='byond://?_src_=holder;makeai=[M.UID()]'>Make AI</A> |
@@ -800,7 +800,7 @@ GLOBAL_VAR_INIT(disable_explosions, FALSE)
if(istype(S, /mob/living/silicon/decoy) && !S.client)
continue
ai_number++
if(isAI(S))
if(is_ai(S))
messages += "<b>AI [key_name(S, TRUE)]'s laws:</b>"
else if(isrobot(S))
var/mob/living/silicon/robot/R = S
+2 -2
View File
@@ -255,7 +255,7 @@
M_job = "Carbon-based"
else if(issilicon(M)) //silicon
if(isAI(M))
if(is_ai(M))
M_job = "AI"
else if(ispAI(M))
M_job = "pAI"
@@ -298,7 +298,7 @@
M_key = replacetext(M_key, "\\", "")
var/M_eyeUID = ""
if(isAI(M))
if(is_ai(M))
var/mob/living/silicon/ai/A = M
if(A.client && A.eyeobj) // No point following clientless AI eyes
M_eyeUID = "[A.eyeobj.UID()]"
+8 -8
View File
@@ -1146,7 +1146,7 @@
if(!ismob(M))
to_chat(usr, "<span class='warning'>This can only be used on instances of type /mob</span>")
return
if(isAI(M))
if(is_ai(M))
to_chat(usr, "<span class='warning'>This cannot be used on instances of type /mob/living/silicon/ai</span>")
return
@@ -1297,7 +1297,7 @@
if(!ismob(M))
to_chat(usr, "<span class='warning'>This can only be used on instances of type /mob</span>")
return
if(isAI(M))
if(is_ai(M))
to_chat(usr, "<span class='warning'>This cannot be used on instances of type /mob/living/silicon/ai</span>")
return
@@ -1329,7 +1329,7 @@
if(!ismob(M))
to_chat(usr, "<span class='warning'>This can only be used on instances of type /mob</span>")
return
if(isAI(M))
if(is_ai(M))
to_chat(usr, "<span class='warning'>This cannot be used on instances of type /mob/living/silicon/ai</span>")
return
@@ -1361,7 +1361,7 @@
if(!ismob(M))
to_chat(usr, "<span class='warning'>This can only be used on instances of type /mob</span>")
return
if(isAI(M))
if(is_ai(M))
to_chat(usr, "<span class='warning'>This cannot be used on instances of type /mob/living/silicon/ai</span>")
return
@@ -1385,7 +1385,7 @@
if(!ismob(M))
to_chat(usr, "<span class='warning'>This can only be used on instances of type /mob</span>")
return
if(isAI(M))
if(is_ai(M))
to_chat(usr, "<span class='warning'>This cannot be used on instances of type /mob/living/silicon/ai</span>")
return
@@ -1492,7 +1492,7 @@
if(!ismob(M))
to_chat(usr, "<span class='warning'>This can only be used on instances of type /mob</span>")
return
if(isAI(M))
if(is_ai(M))
to_chat(usr, "<span class='warning'>This cannot be used on instances of type /mob/living/silicon/ai</span>")
return
@@ -2255,7 +2255,7 @@
if(!href_list["cryoafk"] && !isLivingSSD(M))
to_chat(usr, "<span class='warning'>This can only be used on living, SSD players.</span>")
return
if(isAI(M))
if(is_ai(M))
var/mob/living/silicon/ai/A = M
A.cryo_AI()
if(istype(M.loc, /obj/machinery/cryopod))
@@ -3615,7 +3615,7 @@
target = C.mob
. = ADMIN_FLW(target, "FLW")
if(isAI(target)) // AI core/eye follow links
if(is_ai(target)) // AI core/eye follow links
var/mob/living/silicon/ai/A = target
if(A.client && A.eyeobj) // No point following clientless AI eyes
. += "|[ADMIN_FLW(A.eyeobj,"EYE")]"
+1 -1
View File
@@ -23,7 +23,7 @@
return
if(ismob(M))
if(isAI(M))
if(is_ai(M))
alert("The AI can't be sent to prison you jerk!", null, null, null, null, null)
return
//strip their stuff before they teleport into a cell :downs:
@@ -44,7 +44,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
/datum/antagonist/traitor/Destroy(force, ...)
// Remove all associated malf AI abilities.
if(isAI(owner.current))
if(is_ai(owner.current))
var/mob/living/silicon/ai/A = owner.current
A.clear_zeroth_law()
var/obj/item/radio/headset/heads/ai_integrated/radio = A.get_radio()
@@ -84,7 +84,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
return ..()
/datum/antagonist/traitor/select_organization()
if(isAI(owner.current))
if(is_ai(owner.current))
return
var/chaos = pickweight(list(ORG_CHAOS_HUNTER = ORG_PROB_HUNTER, ORG_CHAOS_MILD = ORG_PROB_MILD, ORG_CHAOS_AVERAGE = ORG_PROB_AVERAGE, ORG_CHAOS_HIJACK = ORG_PROB_HIJACK))
for(var/org_type in shuffle(subtypesof(/datum/antag_org/syndicate)))
@@ -108,7 +108,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
return ..()
/datum/antagonist/traitor/give_objectives()
if(isAI(owner.current))
if(is_ai(owner.current))
forge_ai_objectives()
else
forge_human_objectives()
@@ -163,7 +163,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
antag_memory += "<b>Organization</b>: [organization.name]<br>"
if(give_codewords)
messages.Add(give_codewords())
if(isAI(owner.current))
if(is_ai(owner.current))
add_law_zero()
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
var/mob/living/silicon/ai/A = owner.current
@@ -211,7 +211,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
* Gives a traitor human their uplink, and uplink code.
*/
/datum/antagonist/traitor/proc/give_uplink()
if(isAI(owner.current))
if(is_ai(owner.current))
return FALSE
var/mob/living/carbon/human/traitor_mob = owner.current
@@ -674,7 +674,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
return data
/obj/machinery/alarm/proc/has_rcon_access(mob/user)
return user && (isAI(user) || allowed(user) || emagged || rcon_setting == RCON_YES)
return user && (is_ai(user) || allowed(user) || emagged || rcon_setting == RCON_YES)
// Intentional nulls here
/obj/machinery/alarm/ui_data(mob/user)
@@ -796,7 +796,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
return thresholds
/obj/machinery/alarm/ui_state(mob/user)
if(isAI(user))
if(is_ai(user))
var/mob/living/silicon/ai/AI = user
if(!AI.lacks_power() || AI.apc_override)
return GLOB.always_state
@@ -821,7 +821,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
return TRUE
if(user.can_admin_interact())
return TRUE
else if(isAI(user) || isrobot(user) || emagged || user.has_unlimited_silicon_privilege)
else if(is_ai(user) || isrobot(user) || emagged || user.has_unlimited_silicon_privilege)
return TRUE
else
return !locked
@@ -830,7 +830,7 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
if(buildstage != 2)
return UI_CLOSE
if(aidisabled && (isAI(user) || isrobot(user)))
if(aidisabled && (is_ai(user) || isrobot(user)))
to_chat(user, "<span class='warning'>AI control for \the [src] interface has been disabled.</span>")
return UI_CLOSE
@@ -61,7 +61,7 @@ GLOBAL_LIST_EMPTY(gas_meters)
/obj/machinery/atmospherics/meter/examine(mob/user)
. = ..()
. += "<span class='notice'>Measures the volume and temperature of the pipe under the meter.</span>"
if(get_dist(user, src) > 3 && !(isAI(user) || istype(user, /mob/dead)))
if(get_dist(user, src) > 3 && !(is_ai(user) || istype(user, /mob/dead)))
. += "<span class='boldnotice'>You are too far away to read it.</span>"
else if(stat & (NOPOWER|BROKEN))
@@ -77,7 +77,7 @@ GLOBAL_LIST_EMPTY(gas_meters)
. += "The connect error light is blinking."
/obj/machinery/atmospherics/meter/Click()
if(isAI(usr)) // ghosts can call ..() for examine
if(is_ai(usr)) // ghosts can call ..() for examine
usr.examinate(src)
return TRUE
+1 -1
View File
@@ -204,7 +204,7 @@
if(!M.anchored && (M.flags & CONDUCT))
step_towards(M,src)
for(var/mob/living/silicon/S in orange(2,src))
if(isAI(S)) continue
if(is_ai(S)) continue
step_towards(S,src)
for(var/mob/living/carbon/human/machine/M in orange(2,src))
step_towards(M,src)
+1 -1
View File
@@ -73,7 +73,7 @@
return
if(prob(shake_chance))
for(var/mob/M in range(10, src))
if(!M.stat && !isAI(M))
if(!M.stat && !is_ai(M))
shake_camera(M, 3, 1)
playsound(loc, 'sound/effects/meteorimpact.ogg', 40, 1)
+1 -1
View File
@@ -567,7 +567,7 @@
return ..()
/obj/machinery/fishtank/attack_hand(mob/user)
if(isAI(user))
if(is_ai(user))
return
user.changeNext_move(CLICK_CD_MELEE)
playsound(get_turf(src), 'sound/effects/glassknock.ogg', 80, TRUE)
+160
View File
@@ -0,0 +1,160 @@
// CAMERA NET
//
// The datum containing all the chunks.
GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
/datum/cameranet
var/name = "Camera Net"
/// The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Destroy().
var/list/cameras = list()
/// The chunks of the map, mapping the areas that the cameras can see.
var/list/chunks = list()
var/ready = FALSE
/// Returns the chunk at (x, y, z) if it exists, otherwise returns null.
/datum/cameranet/proc/chunk_generated(x, y, z)
x &= ~(CAMERA_CHUNK_SIZE - 1)
y &= ~(CAMERA_CHUNK_SIZE - 1)
var/key = "[x],[y],[z]"
return (chunks[key])
/// Returns the chunk at (x, y, z) if it exists, otherwise returns a new chunk at that location.
/datum/cameranet/proc/get_camera_chunk(x, y, z)
x &= ~(CAMERA_CHUNK_SIZE - 1)
y &= ~(CAMERA_CHUNK_SIZE - 1)
var/key = "[x],[y],[z]"
if(!chunks[key])
chunks[key] = new /datum/camerachunk(null, x, y, z)
return chunks[key]
/// Updates what the camera network can see.
/datum/cameranet/proc/visibility(list/moved_eyes, client/C)
if(!islist(moved_eyes))
moved_eyes = moved_eyes ? list(moved_eyes) : list()
var/list/chunks_pre_seen = list()
var/list/chunks_post_seen = list()
for(var/V in moved_eyes)
var/mob/camera/eye/eye = V
if(C)
chunks_pre_seen |= eye.visible_camera_chunks
// 0xf = 15
var/static_range = eye.static_visibility_range
var/x1 = max(0, eye.x - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/y1 = max(0, eye.y - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/x2 = min(world.maxx, eye.x + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/y2 = min(world.maxy, eye.y + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/list/visible_chunks = list()
for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
visible_chunks |= get_camera_chunk(x, y, eye.z)
var/list/remove = eye.visible_camera_chunks - visible_chunks
var/list/add = visible_chunks - eye.visible_camera_chunks
for(var/chunk in remove)
var/datum/camerachunk/c = chunk
c.remove(eye, FALSE)
for(var/chunk in add)
var/datum/camerachunk/c = chunk
c.add(eye, FALSE)
if(C)
chunks_post_seen |= eye.visible_camera_chunks
if(C)
var/list/remove = chunks_pre_seen - chunks_post_seen
var/list/add = chunks_post_seen - chunks_pre_seen
for(var/chunk in remove)
var/datum/camerachunk/c = chunk
C.images -= c.obscured
for(var/chunk in add)
var/datum/camerachunk/c = chunk
C.images += c.obscured
/// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
/datum/cameranet/proc/update_visibility(atom/A, opacity_check = 1)
if(!SSticker || (opacity_check && !A.opacity))
return
major_chunk_change(update = A)
/// Removes a camera from a chunk.
/datum/cameranet/proc/remove_camera(obj/machinery/camera/c)
major_chunk_change(remove = c)
/// Add a camera to a chunk.
/datum/cameranet/proc/add_camera(obj/machinery/camera/c)
major_chunk_change(add = c)
/// Refreshes the chunk location of a portable camera. Used by Cyborgs.
/datum/cameranet/proc/update_portable_camera(obj/machinery/camera/c, turf/old_loc)
major_chunk_change(add = c, old_loc = old_loc)
/// Private method for updating the chunk an atom is in, and all surrounding chunks.
/// `add` will be added as a camera to the chunk of its current location and all surrounding chunks.
/// `remove` will be removed as a camera from the chunk of its current location and all surrounding chunks.
/// `update` will not be added or removed as a camera, but its surrounding chunks will be updated.
/// These parameters are mutually exclusive.
/datum/cameranet/proc/major_chunk_change(atom/add = null, atom/remove = null, atom/update = null, turf/old_loc = null)
// 0xf = 15
if(!add && !remove && !update)
return
if(add && remove)
CRASH("Adding and removing a camera to the cameranet simultaneously is not implemented")
var/atom/c = remove
if(!c)
c = add
if(!c)
c = update
var/turf/T = get_turf(c)
if(!T)
return
// Check if the turf to add falls in the same chunk as the old_loc. If so, do nothing
if(old_loc)
if(T.x & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.x & ~(CAMERA_CHUNK_SIZE - 1) && T.y & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.y & ~(CAMERA_CHUNK_SIZE - 1))
return
// Use camera view distance here to actually know how far a camera can max watch
var/x1 = max(0, T.x - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
var/y1 = max(0, T.y - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
var/x2 = min(world.maxx, T.x + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
var/y2 = min(world.maxy, T.y + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
if(chunk_generated(x, y, T.z))
var/datum/camerachunk/chunk = get_camera_chunk(x, y, T.z)
if(remove)
// Remove the camera.
chunk.remove_camera(c)
else if(add)
// You can't have the same camera in the list twice.
chunk.add_camera(c)
chunk.has_changed()
/// Returns 1 if a mob is on a viewable turf, otherwise returns 0.
/datum/cameranet/proc/check_camera_vis(mob/living/target as mob)
// 0xf = 15
var/turf/position = get_turf(target)
return check_turf_vis(position)
/datum/cameranet/proc/check_turf_vis(turf/position)
var/datum/camerachunk/chunk = get_camera_chunk(position.x, position.y, position.z)
if(chunk)
if(chunk.changed)
chunk.has_changed(1) // Update now, no matter if it's visible or not.
if(chunk.visible_turfs[position])
return 1
return 0
@@ -1,11 +1,8 @@
// CAMERA CHUNK
//
// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
// Allows the AI Eye to stream these chunks and know what it can and cannot see.
/// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
/// Allows camera eyes to stream these chunks and know what they can and cannot see.
/datum/camerachunk
var/list/obscuredTurfs = list()
var/list/visibleTurfs = list()
var/list/obscured_turfs = list()
var/list/visible_turfs = list()
var/list/obscured = list()
var/list/active_cameras = list()
var/list/inactive_cameras = list()
@@ -17,6 +14,7 @@
var/y = 0
var/z = 0
/// Adds a camera to the chunk if it has not already been added.
/datum/camerachunk/proc/add_camera(obj/machinery/camera/cam)
if(active_cameras[cam] || inactive_cameras[cam])
return
@@ -32,6 +30,7 @@
else
inactive_cameras[cam] = cam
/// Handles each movement by a camera that has been added to the chunk.
/datum/camerachunk/proc/camera_moved(obj/machinery/camera/cam, atom/old_loc)
var/turf/T = get_turf(cam)
@@ -39,6 +38,7 @@
if(T.x + CAMERA_VIEW_DISTANCE < x || T.x - CAMERA_VIEW_DISTANCE >= x + CAMERA_CHUNK_SIZE || T.y + CAMERA_VIEW_DISTANCE < y || T.y - CAMERA_VIEW_DISTANCE >= y + CAMERA_CHUNK_SIZE || T.z != z)
remove_camera(cam)
/// Removes a camera from the chunk.
/datum/camerachunk/proc/remove_camera(obj/machinery/camera/cam)
UnregisterSignal(cam, list(COMSIG_CAMERA_OFF, COMSIG_CAMERA_ON, COMSIG_PARENT_QDELETING, COMSIG_CAMERA_MOVED))
active_cameras -= cam
@@ -55,42 +55,40 @@
active_cameras -= cam
SScamera.queue(src)
// Add an AI eye to the chunk, then update if changed.
/datum/camerachunk/proc/add(mob/camera/ai_eye/eye, add_images = TRUE)
/// Add a camera eye to the chunk, then update if changed.
/datum/camerachunk/proc/add(mob/camera/eye/eye, add_images = TRUE)
if(add_images)
var/client/client = eye.GetViewerClient()
var/client/client = eye.get_viewer_client()
if(client)
client.images += obscured
eye.visibleCameraChunks += src
eye.visible_camera_chunks += src
seenby += eye
RegisterSignal(eye, COMSIG_PARENT_QDELETING, PROC_REF(aiEye_destroyed))
RegisterSignal(eye, COMSIG_PARENT_QDELETING, PROC_REF(eye_destroyed))
if(changed)
SScamera.queue(src)
/datum/camerachunk/proc/aiEye_destroyed(mob/camera/ai_eye/eye)
/datum/camerachunk/proc/eye_destroyed(mob/camera/eye/eye)
remove(eye, FALSE)
// Remove an AI eye from the chunk, then update if changed.
/datum/camerachunk/proc/remove(mob/camera/ai_eye/eye, remove_images = TRUE)
/// Remove a camera eye from the chunk, then update if changed.
/datum/camerachunk/proc/remove(mob/camera/eye/eye, remove_images = TRUE)
if(remove_images)
var/client/client = eye.GetViewerClient()
var/client/client = eye.get_viewer_client()
if(client)
client.images -= obscured
eye.visibleCameraChunks -= src
eye.visible_camera_chunks -= src
seenby -= eye
UnregisterSignal(eye, COMSIG_PARENT_QDELETING)
// Called when a chunk has changed. I.E: A wall was deleted.
/datum/camerachunk/proc/visibilityChanged(turf/loc)
if(!visibleTurfs[loc])
/// Called when a chunk has changed. I.E: A wall was deleted.
/datum/camerachunk/proc/visibility_changed(turf/loc)
if(!visible_turfs[loc])
return
hasChanged()
has_changed()
// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will
// instead be flagged to update the next time an AI Eye moves near it.
/datum/camerachunk/proc/hasChanged(update_now = 0)
/// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will
/// instead be flagged to update the next time a camera eye moves near it.
/datum/camerachunk/proc/has_changed(update_now = 0)
if(update_now)
update()
SScamera.remove_from_queue(src)
@@ -100,48 +98,48 @@
else
changed = TRUE
// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists.
/// Gathers the visible turfs from cameras and puts them into the appropiate lists.
/datum/camerachunk/proc/update()
var/list/newVisibleTurfs = list()
var/list/new_visible_turfs = list()
for(var/obj/machinery/camera/c as anything in active_cameras)
var/turf/point = locate(src.x + (CAMERA_CHUNK_SIZE / 2), src.y + (CAMERA_CHUNK_SIZE / 2), src.z)
var/turf/T = get_turf(c)
if(get_dist(point, T) > CAMERA_VIEW_DISTANCE + (CAMERA_CHUNK_SIZE / 2))
continue // Still needed for Ais who get created on Z level 1 on the spot of the new player
// Still needed for Ais who get created on Z level 1 on the spot of the new player
continue
for(var/turf/t in c.can_see())
if(turfs[t])
newVisibleTurfs[t] = t
new_visible_turfs[t] = t
var/list/visAdded = newVisibleTurfs - visibleTurfs
var/list/visRemoved = visibleTurfs - newVisibleTurfs
var/list/vis_added = new_visible_turfs - visible_turfs
var/list/vis_removed = visible_turfs - new_visible_turfs
visibleTurfs = newVisibleTurfs
obscuredTurfs = turfs - newVisibleTurfs
visible_turfs = new_visible_turfs
obscured_turfs = turfs - new_visible_turfs
var/list/images_to_remove = list()
var/list/images_to_add = list()
for(var/turf/t as anything in visAdded)
for(var/turf/t as anything in vis_added)
if(t.obscured)
obscured -= t.obscured
images_to_remove += t.obscured
for(var/turf/t as anything in visRemoved)
for(var/turf/t as anything in vis_removed)
if(!t.obscured)
t.obscured = image('icons/effects/cameravis.dmi', t, null, BYOND_LIGHTING_LAYER + 0.1)
t.obscured.plane = BYOND_LIGHTING_PLANE + 1
obscured += t.obscured
images_to_add += t.obscured
for(var/mob/camera/ai_eye/eye as anything in seenby)
var/client/client = eye.GetViewerClient()
for(var/mob/camera/eye/eye as anything in seenby)
var/client/client = eye.get_viewer_client()
if(client)
client.images -= images_to_remove
client.images += images_to_add
changed = FALSE
// Create a new camera chunk, since the chunks are made as they are needed.
/// Create a new camera chunk, since the chunks are made as they are needed.
/datum/camerachunk/New(loc, x, y, z)
// 0xf = 15
@@ -162,11 +160,11 @@
for(var/obj/machinery/camera/c as anything in active_cameras)
for(var/turf/t in c.can_see())
if(turfs[t])
visibleTurfs[t] = t
visible_turfs[t] = t
obscuredTurfs = turfs - visibleTurfs
obscured_turfs = turfs - visible_turfs
for(var/turf/t as anything in obscuredTurfs)
for(var/turf/t as anything in obscured_turfs)
if(!t.obscured)
t.obscured = image('icons/effects/cameravis.dmi', t, "black", BYOND_LIGHTING_LAYER + 0.1)
t.obscured.plane = BYOND_LIGHTING_PLANE + 1
+191
View File
@@ -0,0 +1,191 @@
/// 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)
+14
View File
@@ -0,0 +1,14 @@
/mob/camera/eye/abductor
name = "Abductor Camera Eye"
ai_detector_visible = FALSE
/mob/camera/eye/abductor/Initialize(mapload, owner_name, camera_origin, user)
..()
set_loc(first_active_camera())
/mob/camera/eye/abductor/rename_camera(new_name)
name = "Abductor Camera Eye ([new_name])"
/// Requires the cameranet to be validated.
/mob/camera/eye/abductor/validate_active_cameranet()
..(TRUE)
+34
View File
@@ -0,0 +1,34 @@
/mob/camera/eye/ai
name = "Inactive AI Eye"
icon = 'icons/mob/ai.dmi'
icon_state = "eye"
var/mob/living/silicon/ai/ai = null
/mob/camera/eye/ai/Initialize(mapload, owner_name, camera_origin, mob/living/user)
. = ..()
ai = user
if(is_ai_eye(ai.eyeobj))
stack_trace("Tried to create an AI Eye for [user], but there is one already assigned")
return INITIALIZE_HINT_QDEL
name = "[owner_name] (AI Eye)"
/// Ensures that the user's perspective is on this eye (instead of, for example, a mech or hologram eye) and
/// updates parallax
/mob/camera/eye/ai/set_loc(T)
..()
user.reset_perspective(src)
update_parallax_contents()
/// Cancels the camera's follow target if tracking has stopped, and lights up nearby lights if the AI has
/// "Toggle Camera Lights" enabled
/mob/camera/eye/ai/relaymove(mob/user, direct)
..()
if(!ai.tracking)
ai.camera_follow = null
if(ai.camera_light_on)
ai.light_cameras()
/// Relays speech near the AI eye to the AI if the AI has surveillance, either from a malf module or a station trait
/mob/camera/eye/ai/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)
ai.relay_speech(speaker, message_pieces, verb)
@@ -0,0 +1,53 @@
/mob/camera/eye/hologram
name = "Inactive Hologram Eye"
ai_detector_visible = FALSE
acceleration = FALSE
relay_speech = TRUE
var/obj/machinery/hologram/holopad/holopad
/mob/camera/eye/hologram/Initialize(mapload, owner_name, camera_origin, mob/living/user)
..()
holopad = camera_origin
set_loc(holopad)
/mob/camera/eye/hologram/rename_camera(new_name)
name = "Hologram ([new_name])"
/// Hologram movement copies the delays and diagonal delays of regular mob movement
/// for crew, and for AI unless fast holograms are enabled
/mob/camera/eye/hologram/relaymove(mob/user, direct)
var/mob/living/silicon/ai/ai = user
if(istype(ai) && ai.fast_holograms)
return ..()
var/turf/new_location = get_turf(get_step(src, direct))
if(new_location.z != z || get_dist(new_location, src) > 1)
return
var/base_delay = GLOB.configuration.movement.base_run_speed
var/diag_delay = base_delay * SQRT_2
var/delay = (direct & (direct - 1)) ? diag_delay : base_delay
user.client.move_delay = debounced_move(normalized_delay_speed(delay))
user.last_movement = world.time
set_loc(new_location)
/mob/camera/eye/hologram/proc/debounced_move(delay_speed)
var/old_move_delay = user.client.move_delay
if(old_move_delay + world.tick_lag > world.time)
return old_move_delay + delay_speed
else
return world.time + delay_speed
/mob/camera/eye/hologram/proc/normalized_delay_speed(delay_speed)
return TICKS2DS(-round(-(DS2TICKS(delay_speed))))
/// Requires the cameranet to be validated.
/mob/camera/eye/hologram/validate_active_cameranet()
..(TRUE)
/// Moves the associated hologram, and the previously controlled object (probably an AI eye), to the new location.
/mob/camera/eye/hologram/set_loc(T)
. = ..()
var/turf/new_location = get_turf(T)
holopad.move_hologram(user, new_location)
var/mob/camera/eye/previous_eye = user_previous_remote_control
if(istype(previous_eye))
previous_eye.set_loc(new_location)
@@ -0,0 +1,30 @@
/mob/camera/eye/shuttle_docker
name = "Shuttle Docker Camera Eye"
ai_detector_visible = FALSE
simulated = FALSE
var/list/placement_images = list()
var/list/placed_images = list()
/mob/camera/eye/shuttle_docker/Initialize(mapload, owner_name, camera_origin, mob/living/user)
..()
set_loc(first_active_camera())
/mob/camera/eye/shuttle_docker/rename_camera(new_name)
name = "Shuttle Docker Camera Eye ([new_name])"
/// Prevents the shuttle docker eye from updating global cameranet chunks. Shuttle dockers don't use station cameras.
/mob/camera/eye/shuttle_docker/update_visibility()
return FALSE
/// Prevents moving the camera eye into the station, and updates the current location's landing spot validity.
/mob/camera/eye/shuttle_docker/set_loc(T)
if(isspaceturf(get_turf(T)) || istype(get_area(T), /area/space) || istype(get_area(T), /area/shuttle))
..()
var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin
console.check_landing_spot()
/mob/camera/eye/shuttle_docker/update_remote_sight(mob/living/user)
user.sight = SEE_TURFS
..()
return TRUE
+10
View File
@@ -0,0 +1,10 @@
/mob/camera/eye/syndicate
name = "Syndicate Researcher Camera Eye"
ai_detector_visible = FALSE
/mob/camera/eye/syndicate/Initialize(mapload, owner_name, camera_origin, user)
..()
set_loc(first_active_camera())
/mob/camera/eye/syndicate/rename_camera(new_name)
name = "Syndicate Researcher Camera Eye ([new_name])"
@@ -0,0 +1,22 @@
/mob/camera/eye/xenobio
name = "Xenobiology Console Camera Eye"
visible_icon = TRUE
ai_detector_visible = FALSE
var/allowed_area
/mob/camera/eye/xenobio/Initialize(mapload, owner_name, camera_origin, mob/living/user)
. = ..()
var/area/A = get_area(camera_origin)
allowed_area = A.name
/mob/camera/eye/xenobio/rename_camera(new_name)
name = "Xenobiology Console ([new_name])"
/// Prevents the camera eye from going outside of the xenobiology area
/mob/camera/eye/xenobio/set_loc(T)
var/area/new_area = get_area(T)
if(!new_area)
return
if(new_area.name != allowed_area && !new_area.xenobiology_compatible)
return
return ..()
@@ -255,7 +255,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
warningmsg = "You have committed suicide too early in the round"
else if(stat != DEAD)
warningmsg = "You are alive"
if(isAI(src))
if(is_ai(src))
warningmsg = "You are a living AI! You should probably use OOC -> Wipe Core instead."
else if(GLOB.non_respawnable_keys[ckey])
warningmsg = "You have lost your right to respawn"
@@ -656,7 +656,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/proc/ghost_follow_link(atom/target, atom/ghost)
if((!target) || (!ghost)) return
if(isAI(target)) // AI core/eye follow links
if(is_ai(target)) // AI core/eye follow links
var/mob/living/silicon/ai/A = target
. = "<a href='byond://?src=[ghost.UID()];follow=[A.UID()]'>core</a>"
if(A.client && A.eyeobj) // No point following clientless AI eyes
@@ -17,7 +17,7 @@
if(!speaker || !ismob(speaker))
return speaker_name
var/mob/speaker_mob = speaker
if(isAI(speaker_mob))
if(is_ai(speaker_mob))
//AI's can't pretend to be other mobs.
return speaker_name
if(!check_name_against || check_name_against == speaker_mob.real_name)
+1 -1
View File
@@ -624,7 +624,7 @@
continue
else if(drone_only && !isdrone(S))
continue
else if(isAI(S))
else if(is_ai(S))
message_start = list("<i><span class='game say'>[name], <a href='byond://?src=[S.UID()];track=\ref[speaker]'><span class='name'>[speaker.name]</span></a>")
else if(isrobot(S))
var/mob/living/silicon/robot/borg = S
+1 -1
View File
@@ -364,7 +364,7 @@
return (hudtype in have_hudtypes)
else if(isrobot(M) || isAI(M)) //Stand-in/Stopgap to prevent pAIs from freely altering records, pending a more advanced Records system
else if(isrobot(M) || is_ai(M)) //Stand-in/Stopgap to prevent pAIs from freely altering records, pending a more advanced Records system
return (hudtype in list(EXAMINE_HUD_SECURITY_READ, EXAMINE_HUD_SECURITY_WRITE, EXAMINE_HUD_MEDICAL_READ, EXAMINE_HUD_MEDICAL_WRITE))
else if(isobserver(M))
@@ -723,7 +723,7 @@
else if(isrobot(user))
var/mob/living/silicon/robot/U = user
rank = "[U.modtype] [U.braintype]"
else if(isAI(user))
else if(is_ai(user))
rank = "AI"
set_criminal_status(user, found_record, new_status, reason, rank)
+1 -1
View File
@@ -30,7 +30,7 @@
var/now_pushing = null
var/atom/movable/cameraFollow = null
var/atom/movable/camera_follow = null
var/on_fire = 0 //The "Are we on fire?" var
var/fire_stacks = 0 //Tracks how many stacks of fire we have on, max is usually 20
@@ -10,7 +10,7 @@
else
icon_state = "ai_dead"
if(eyeobj)
eyeobj.setLoc(get_turf(src))
eyeobj.set_loc(get_turf(src))
GLOB.shuttle_caller_list -= src
SSshuttle.autoEvac()
@@ -10,7 +10,7 @@
var/turf/T = get_turf(src)
if(stat != CONSCIOUS) //ai's fucked
cameraFollow = null
camera_follow = null
reset_perspective(null)
unset_machine()
+51 -16
View File
@@ -14,6 +14,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/proc/pick_icon,
/mob/living/silicon/ai/proc/sensor_mode,
/mob/living/silicon/ai/proc/show_laws_verb,
/mob/living/silicon/ai/proc/toggle_fast_holograms,
/mob/living/silicon/ai/proc/toggle_acceleration,
/mob/living/silicon/ai/proc/toggle_camera_light,
/mob/living/silicon/ai/proc/botcall,
@@ -104,10 +105,8 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/obj/machinery/doomsday_device/doomsday_device
var/obj/machinery/hologram/holopad/holo = null
var/mob/camera/ai_eye/eyeobj
var/sprint = 10
var/cooldown = 0
var/acceleration = 1
var/mob/camera/eye/ai/eyeobj
var/fast_holograms = TRUE
var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed.
/// If true, this AI core can use the teleporter.
@@ -121,7 +120,6 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/announce_arrivals = TRUE
var/arrivalmsg = "$name, $rank, has arrived on the station."
var/list/all_eyes = list()
var/next_text_announcement
//Used with the hotkeys on 2-5 to store locations.
@@ -228,8 +226,8 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
spawn(5)
new /obj/machinery/ai_powersupply(src)
create_eye()
eyeobj = new /mob/camera/eye/ai(loc, name, src, src)
builtInCamera = new /obj/machinery/camera/portable(src)
builtInCamera.c_tag = name
@@ -769,7 +767,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
if(controlled_mech)
to_chat(src, "<span class='warning'>You are already loaded into an onboard computer!</span>")
return
if(!GLOB.cameranet.checkCameraVis(M))
if(!GLOB.cameranet.check_camera_vis(M))
to_chat(src, "<span class='warning'>Exosuit is no longer near active cameras.</span>")
return
if(lacks_power())
@@ -826,7 +824,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
//The target must be in view of a camera or near the core.
if(turf_check in range(get_turf(src)))
call_bot(turf_check)
else if(GLOB.cameranet && GLOB.cameranet.checkTurfVis(turf_check))
else if(GLOB.cameranet && GLOB.cameranet.check_turf_vis(turf_check))
call_bot(turf_check)
else
to_chat(src, "<span class='danger'>Selected location is not visible.</span>")
@@ -882,7 +880,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C)
if(!tracking)
cameraFollow = null
camera_follow = null
if(QDELETED(C) || stat == DEAD) //C.can_use())
return FALSE
@@ -891,7 +889,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
view_core()
return
// ok, we're alive, camera is good and in our network...
eyeobj.setLoc(get_turf(C))
eyeobj.set_loc(get_turf(C))
//machine = src
return TRUE
@@ -939,7 +937,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
if(!C.can_use())
continue
if(network in C.network)
U.eyeobj.setLoc(get_turf(C))
U.eyeobj.set_loc(get_turf(C))
break
to_chat(src, "<span class='notice'>Switched to [network] camera network.</span>")
//End of code by Mord_Sith
@@ -1238,7 +1236,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/list/obj/machinery/camera/add = list()
var/list/obj/machinery/camera/remove = list()
var/list/obj/machinery/camera/visible = list()
for(var/datum/camerachunk/CC in eyeobj.visibleCameraChunks)
for(var/datum/camerachunk/CC in eyeobj.visible_camera_chunks)
for(var/obj/machinery/camera/C in CC.active_cameras)
if(!C.can_use() || get_dist(C, eyeobj) > 7)
continue
@@ -1340,7 +1338,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
//get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera
//apc_override is needed here because AIs use their own APC when depowered
var/turf/T = isturf(A) ? A : get_turf_pixel(A)
return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T)) || apc_override
return (GLOB.cameranet && GLOB.cameranet.check_turf_vis(T)) || apc_override
//AI is carded/shunted
//view(src) returns nothing for carded/shunted AIs and they have x-ray vision so just use get_dist
var/list/viewscale = getviewsize(client.view)
@@ -1433,8 +1431,45 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
else
to_chat(src, "<span class='warning'>Target is not on or near any active cameras on the station.</span>")
/mob/living/silicon/ai/proc/camera_visibility(mob/camera/ai_eye/moved_eye)
GLOB.cameranet.visibility(moved_eye, client, all_eyes)
// Return to the Core.
/mob/living/silicon/ai/proc/core()
set category = "AI Commands"
set name = "AI Core"
view_core()
/mob/living/silicon/ai/proc/view_core()
current = null
camera_follow = null
unset_machine()
if(eyeobj && loc)
eyeobj.loc = loc
else
to_chat(src, "ERROR: Eyeobj not found. Creating new eye...")
eyeobj = new /mob/camera/eye/ai(loc, name, src, src)
eyeobj.set_loc(loc)
/mob/living/silicon/ai/proc/toggle_fast_holograms()
set category = "AI Commands"
set name = "Toggle Fast Holograms"
if(usr.stat == DEAD || !is_ai_eye(eyeobj))
return
fast_holograms = !fast_holograms
to_chat(usr, "Fast holograms have been toggled [fast_holograms ? "on" : "off"].")
/mob/living/silicon/ai/proc/toggle_acceleration()
set category = "AI Commands"
set name = "Toggle Camera Acceleration"
if(usr.stat == DEAD)
return //won't work if dead
if(is_ai_eye(eyeobj))
eyeobj.acceleration = !eyeobj.acceleration
to_chat(usr, "Camera acceleration has been toggled [eyeobj.acceleration ? "on" : "off"].")
/mob/living/silicon/ai/handle_fire()
return
+1 -1
View File
@@ -28,7 +28,7 @@
else if(iscarbon(speaker)) // Nonhuman carbon mob
jobname = "No ID"
else if(isAI(speaker))
else if(is_ai(speaker))
jobname = "AI"
else if(isrobot(speaker))
jobname = "Cyborg"
@@ -1,177 +0,0 @@
// CAMERA NET
//
// The datum containing all the chunks.
GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new())
/datum/cameranet
var/name = "Camera Net" // Name to show for VV and stat()
// The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Destroy().
var/list/cameras = list()
// The chunks of the map, mapping the areas that the cameras can see.
var/list/chunks = list()
var/ready = FALSE
// Checks if a chunk has been Generated in x, y, z.
/datum/cameranet/proc/chunkGenerated(x, y, z)
x &= ~(CAMERA_CHUNK_SIZE - 1)
y &= ~(CAMERA_CHUNK_SIZE - 1)
var/key = "[x],[y],[z]"
return (chunks[key])
// Returns the chunk in the x, y, z.
// If there is no chunk, it creates a new chunk and returns that.
/datum/cameranet/proc/getCameraChunk(x, y, z)
x &= ~(CAMERA_CHUNK_SIZE - 1)
y &= ~(CAMERA_CHUNK_SIZE - 1)
var/key = "[x],[y],[z]"
if(!chunks[key])
chunks[key] = new /datum/camerachunk(null, x, y, z)
return chunks[key]
// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set.
/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes)
if(!islist(moved_eyes))
moved_eyes = moved_eyes ? list(moved_eyes) : list()
if(islist(other_eyes))
other_eyes = (other_eyes - moved_eyes)
else
other_eyes = list()
var/list/chunks_pre_seen = list()
var/list/chunks_post_seen = list()
for(var/V in moved_eyes)
var/mob/camera/ai_eye/eye = V
if(C)
chunks_pre_seen |= eye.visibleCameraChunks
// 0xf = 15
var/static_range = eye.static_visibility_range
var/x1 = max(0, eye.x - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/y1 = max(0, eye.y - static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/x2 = min(world.maxx, eye.x + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/y2 = min(world.maxy, eye.y + static_range) & ~(CAMERA_CHUNK_SIZE - 1)
var/list/visibleChunks = list()
for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
visibleChunks |= getCameraChunk(x, y, eye.z)
var/list/remove = eye.visibleCameraChunks - visibleChunks
var/list/add = visibleChunks - eye.visibleCameraChunks
for(var/chunk in remove)
var/datum/camerachunk/c = chunk
c.remove(eye, FALSE)
for(var/chunk in add)
var/datum/camerachunk/c = chunk
c.add(eye, FALSE)
if(C)
chunks_post_seen |= eye.visibleCameraChunks
if(C)
for(var/V in other_eyes)
var/mob/camera/ai_eye/eye = V
chunks_post_seen |= eye.visibleCameraChunks
var/list/remove = chunks_pre_seen - chunks_post_seen
var/list/add = chunks_post_seen - chunks_pre_seen
for(var/chunk in remove)
var/datum/camerachunk/c = chunk
C.images -= c.obscured
for(var/chunk in add)
var/datum/camerachunk/c = chunk
C.images += c.obscured
// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1)
if(!SSticker || (opacity_check && !A.opacity))
return
majorChunkChange(A, 2)
/datum/cameranet/proc/updateChunk(x, y, z)
// 0xf = 15
if(!chunkGenerated(x, y, z))
return
var/datum/camerachunk/chunk = getCameraChunk(x, y, z)
chunk.hasChanged()
// Removes a camera from a chunk.
/datum/cameranet/proc/removeCamera(obj/machinery/camera/c)
majorChunkChange(c, 0)
// Add a camera to a chunk.
/datum/cameranet/proc/addCamera(obj/machinery/camera/c)
majorChunkChange(c, 1)
// Used for Cyborg cameras. Since portable cameras can be in ANY chunk.
/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c, turf/old_loc)
majorChunkChange(c, 1, old_loc)
// Never access this proc directly!!!!
// This will update the chunk and all the surrounding chunks.
// It will also add the atom to the cameras list if you set the choice to 1.
// Setting the choice to 0 will remove the camera from the chunks.
// If you want to update the chunks around an object, without adding/removing a camera, use choice 2.
/datum/cameranet/proc/majorChunkChange(atom/c, choice, turf/old_loc = null)
// 0xf = 15
if(!c)
return
var/turf/T = get_turf(c)
if(!T)
return
if(old_loc)
// Check if the current turf falls in the same chunka as the old_loc. If so, don't do anything
if(T.x & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.x & ~(CAMERA_CHUNK_SIZE - 1) && T.y & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.y & ~(CAMERA_CHUNK_SIZE - 1))
return
// Use camera view distance here to actually know how far a camera can max watch
var/x1 = max(0, T.x - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
var/y1 = max(0, T.y - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
var/x2 = min(world.maxx, T.x + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
var/y2 = min(world.maxy, T.y + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1)
for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE)
if(chunkGenerated(x, y, T.z))
var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z)
if(choice == 0)
// Remove the camera.
chunk.remove_camera(c)
else if(choice == 1)
// You can't have the same camera in the list twice.
chunk.add_camera(c)
chunk.hasChanged()
// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.
/datum/cameranet/proc/checkCameraVis(mob/living/target as mob)
// 0xf = 15
var/turf/position = get_turf(target)
return checkTurfVis(position)
/datum/cameranet/proc/checkTurfVis(turf/position)
var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z)
if(chunk)
if(chunk.changed)
chunk.hasChanged(1) // Update now, no matter if it's visible or not.
if(chunk.visibleTurfs[position])
return 1
return 0
@@ -1,155 +0,0 @@
// AI EYE
//
// An invisible (no icon) mob that the AI controls to look around the station with.
// It streams chunks as it moves around, which will show it what the AI can and cannot see.
/mob/camera/ai_eye
name = "Inactive AI Eye"
icon = 'icons/mob/ai.dmi' //Allows ghosts to see what the AI is looking at.
icon_state = "eye"
alpha = 127
invisibility = SEE_INVISIBLE_OBSERVER
var/list/visibleCameraChunks = list()
var/mob/living/silicon/ai/ai = null
var/relay_speech = FALSE
var/use_static = TRUE
var/static_visibility_range = 16
// Decides if it is shown by AI Detector or not
var/ai_detector_visible = TRUE
// Use this when setting the aiEye's location.
// It will also stream the chunk that the new loc is in.
/mob/camera/ai_eye/setLoc(T)
if(ai)
if(!isturf(ai.loc))
return
T = get_turf(T)
..(T)
if(use_static)
ai.camera_visibility(src)
if(ai.client)
ai.client.eye = src
update_parallax_contents()
//Holopad
if(istype(ai.current, /obj/machinery/hologram/holopad))
var/obj/machinery/hologram/holopad/H = ai.current
H.move_hologram(ai, T)
/mob/camera/ai_eye/Move()
return 0
/mob/camera/ai_eye/Process_Spacemove(movement_dir)
// Nothing in space can stop us from moving.
return 1
/mob/camera/ai_eye/proc/GetViewerClient()
if(ai)
return ai.client
return null
/mob/camera/ai_eye/proc/RemoveImages()
var/client/C = GetViewerClient()
if(C && use_static)
for(var/V in visibleCameraChunks)
var/datum/camerachunk/chunk = V
C.images -= chunk.obscured
/mob/camera/ai_eye/Destroy()
if(ai)
ai.all_eyes -= src
ai = null
for(var/V in visibleCameraChunks)
var/datum/camerachunk/chunk = V
chunk.remove(src)
return ..()
/atom/proc/move_camera_by_click()
if(isAI(usr))
var/mob/living/silicon/ai/AI = usr
if(AI.eyeobj && (AI.client.eye == AI.eyeobj) && (AI.eyeobj.z == z))
AI.cameraFollow = null
if(isturf(loc) || isturf(src))
AI.eyeobj.setLoc(src)
// AI MOVEMENT
// This will move the AIEye. It will also cause lights near the eye to light up, if toggled.
// This is handled in the proc below this one.
/client/proc/AIMove(n, direct, mob/living/silicon/ai/user)
var/initial = initial(user.sprint)
var/max_sprint = 50
if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds
user.sprint = initial
for(var/i = 0; i < max(user.sprint, initial); i += 20)
var/turf/step = get_turf(get_step(user.eyeobj, direct))
if(step)
user.eyeobj.setLoc(step)
user.cooldown = world.timeofday + 5
if(user.acceleration)
user.sprint = min(user.sprint + 0.5, max_sprint)
else
user.sprint = initial
if(!user.tracking)
user.cameraFollow = null
//user.unset_machine() //Uncomment this if it causes problems.
//user.lightNearbyCamera()
if(user.camera_light_on)
user.light_cameras()
// Return to the Core.
/mob/living/silicon/ai/proc/core()
set category = "AI Commands"
set name = "AI Core"
view_core()
/mob/living/silicon/ai/proc/view_core()
current = null
cameraFollow = null
unset_machine()
if(src.eyeobj && src.loc)
src.eyeobj.loc = src.loc
else
to_chat(src, "ERROR: Eyeobj not found. Creating new eye...")
create_eye()
eyeobj.setLoc(loc)
/mob/living/silicon/ai/proc/create_eye()
if(eyeobj)
return
eyeobj = new /mob/camera/ai_eye()
all_eyes += eyeobj
eyeobj.ai = src
eyeobj.setLoc(loc)
eyeobj.name = "[name] (AI Eye)"
/mob/living/silicon/ai/proc/toggle_acceleration()
set category = "AI Commands"
set name = "Toggle Camera Acceleration"
if(usr.stat == DEAD)
return //won't work if dead
acceleration = !acceleration
to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].")
/mob/camera/ai_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)
if(istype(ai))
ai.relay_speech(speaker, message_pieces, verb)
else
var/mob/M = ai
M.hear_say(message_pieces, verb, italics, speaker, speech_sound, sound_vol, sound_frequency)
@@ -1424,7 +1424,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
. = ..()
if(camera && last_camera_update + CAMERA_UPDATE_COOLDOWN < world.time)
last_camera_update = world.time
GLOB.cameranet.updatePortableCamera(camera, OldLoc)
GLOB.cameranet.update_portable_camera(camera, OldLoc)
SEND_SIGNAL(camera, COMSIG_CAMERA_MOVED, OldLoc)
#undef CAMERA_UPDATE_COOLDOWN
+2 -2
View File
@@ -805,8 +805,8 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
reset_perspective(null)
unset_machine()
if(isliving(src))
if(src:cameraFollow)
src:cameraFollow = null
if(src:camera_follow)
src:camera_follow = null
/mob/Topic(href, href_list)
if(href_list["flavor_more"])
+2 -2
View File
@@ -461,7 +461,7 @@
if(hud_used && hud_used.action_intent)
hud_used.action_intent.icon_state = "[a_intent]"
else if(isrobot(src) || islarva(src) || isanimal(src) || isAI(src))
else if(isrobot(src) || islarva(src) || isanimal(src) || is_ai(src))
switch(input)
if(INTENT_HELP)
a_intent = INTENT_HELP
@@ -511,7 +511,7 @@
var/obj/item/multitool/P
if(isrobot(user) || ishuman(user))
P = user.get_active_hand()
else if(isAI(user))
else if(is_ai(user))
var/mob/living/silicon/ai/AI=user
P = AI.aiMulti
+11 -6
View File
@@ -91,12 +91,17 @@
if(mob.remote_control) //we're controlling something, our movement is relayed to it
return mob.remote_control.relaymove(mob, direct)
if(isAI(mob))
if(istype(mob.loc, /obj/item/aicard))
var/obj/O = mob.loc
return O.relaymove(mob, direct) // aicards have special relaymove stuff
return AIMove(n, direct, mob)
if(is_ai(mob))
var/mob/living/silicon/ai/ai = mob
var/mob/camera/eye/ai/eye = ai.eyeobj
if(istype(eye) && !istype(ai.remote_control))
ai.remote_control = eye
return eye.relaymove(mob, direct)
if(!istype(eye) && !istype(mob.loc, /obj/item/aicard))
eye = new /mob/camera/eye/ai(mob.loc, ai.name, ai, ai)
if(istype(eye))
return eye.relaymove(mob, direct)
return FALSE // If the AI is outside of its eye or a mech (e.g. carded), it can't move
if(Process_Grab())
return
+1 -1
View File
@@ -89,7 +89,7 @@
var/mob/other_mob = other
if(other_mob.universal_speak)
return TRUE
if(isAI(src) && ispAI(other_mob))
if(is_ai(src) && ispAI(other_mob))
return TRUE
if(istype(other_mob, src.type) || istype(src, other_mob.type))
return TRUE
+1 -1
View File
@@ -525,7 +525,7 @@
toner = 0
/obj/machinery/photocopier/MouseDrop_T(mob/target, mob/living/user)
if(!istype(target) || target.buckled || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || isAI(user) || target.move_resist > user.pull_force)
if(!istype(target) || target.buckled || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || is_ai(user) || target.move_resist > user.pull_force)
return
if(check_mob()) //is target mob or another mob on this photocopier already?
return
@@ -165,7 +165,7 @@
deletepicture(src)
/obj/item/camera/siliconcam/proc/getsource()
if(isAI(loc))
if(is_ai(loc))
return src
var/mob/living/silicon/robot/C = loc
+2 -2
View File
@@ -518,7 +518,7 @@
/obj/machinery/power/apc/proc/is_authenticated(mob/user as mob)
if(user.can_admin_interact())
return TRUE
if(isAI(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
if(is_ai(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
return TRUE
else
return !locked
@@ -526,7 +526,7 @@
/obj/machinery/power/apc/proc/is_locked(mob/user as mob)
if(user.can_admin_interact())
return FALSE
if(isAI(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
if(is_ai(user) || isrobot(user) || user.has_unlimited_silicon_privilege)
return FALSE
else
return locked
@@ -263,7 +263,7 @@
var/list/data = list()
data["active"] = active
if(isAI(user))
if(is_ai(user))
data["is_ai"] = TRUE
else if(isrobot(user) && !Adjacent(user))
data["is_ai"] = TRUE
+1 -1
View File
@@ -52,7 +52,7 @@
impact_light_color_override = LIGHT_COLOR_DARKRED
/obj/item/projectile/beam/laser/ai_turret/prehit(atom/target)
if(isAI(target))
if(is_ai(target))
damage = 0 //cheater cheater I don't want AI to die to stupid placement eater
nodamage = 1
if(isliving(target))
+2 -2
View File
@@ -273,7 +273,7 @@
// mouse drop another mob or self
//
/obj/machinery/disposal/MouseDrop_T(mob/living/target, mob/living/user)
if(!istype(target) || target.buckled || target.has_buckled_mobs() || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || isAI(user))
if(!istype(target) || target.buckled || target.has_buckled_mobs() || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || is_ai(user))
return
// Animals cannot put mobs other than themselves into disposals.
@@ -389,7 +389,7 @@
/obj/machinery/disposal/ui_data(mob/user)
var/list/data = list()
data["isAI"] = isAI(user)
data["is_ai"] = is_ai(user)
data["flushing"] = flush
data["mode"] = mode
data["pressure"] = round(clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 100),1)
+1 -1
View File
@@ -308,7 +308,7 @@
. = ..()
/obj/machinery/disposal/delivery_chute/Bumped(atom/movable/AM) //Go straight into the chute
if(isprojectile(AM) || isAI(AM) || QDELETED(AM))
if(isprojectile(AM) || is_ai(AM) || QDELETED(AM))
return
// We may already contain the object because thrown objects
@@ -1,26 +1,3 @@
//Xenobio control console
/mob/camera/ai_eye/remote/xenobio
visible_icon = 1
icon = 'icons/obj/abductor.dmi'
icon_state = "camera_target"
// The Xenobio Console does not trigger the AI Detector
ai_detector_visible = FALSE
/// Area that the xenobio camera eye is allowed to travel
var/allowed_area = null
/mob/camera/ai_eye/remote/xenobio/Initialize(mapload)
. = ..()
var/area/A = get_area(loc)
allowed_area = A.name
/mob/camera/ai_eye/remote/xenobio/setLoc(t)
var/area/new_area = get_area(t)
if(!new_area)
return
if(new_area.name != allowed_area && !new_area.xenobiology_compatible)
return
return ..()
/*
* # Slime Management Console
*
@@ -54,6 +31,10 @@
if(!connected_recycler)
locate_recycler()
/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
eyeobj = new /mob/camera/eye/xenobio(loc, name, src, current_user)
give_eye_control(current_user)
/obj/machinery/computer/camera_advanced/xenobio/proc/locate_recycler()
for(var/obj/machinery/monkey_recycler/recycler in GLOB.monkey_recyclers)
if(get_area(recycler) == get_area(loc))
@@ -79,14 +60,6 @@
stored_slimes -= A
return ..()
/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
eyeobj = new /mob/camera/ai_eye/remote/xenobio(get_turf(src))
eyeobj.origin = src
eyeobj.visible_icon = TRUE
eyeobj.acceleration = FALSE
eyeobj.icon = 'icons/obj/abductor.dmi'
eyeobj.icon_state = "camera_target"
/obj/machinery/computer/camera_advanced/xenobio/GrantActions(mob/living/carbon/user)
..()
@@ -223,13 +196,13 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(iswallturf(remote_eye.loc))
to_chat(owner, "You can't place slime here.")
return
else if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
else if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in X.stored_slimes)
X.release_slime(S, remote_eye.loc)
else
@@ -243,10 +216,10 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
if(length(X.stored_slimes) >= X.max_slimes)
break
@@ -265,11 +238,11 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
if(LAZYLEN(SSmobs.cubemonkeys) >= GLOB.configuration.general.monkey_cube_cap)
to_chat(owner, "<span class='warning'>Bluespace harmonics prevent the spawning of more than [GLOB.configuration.general.monkey_cube_cap] monkeys on the station at one time!</span>")
return
@@ -309,14 +282,14 @@
if(!target || !ishuman(owner))
return
var/mob/living/carbon/human/C = owner
var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
if(!recycler)
to_chat(owner, "<span class='notice'>There is no connected monkey recycler. Use a multitool to link one.</span>")
return
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/carbon/human/M in remote_eye.loc)
if(issmall(M) && M.stat)
M.visible_message("[M] vanishes as [M.p_theyre()] reclaimed for recycling!")
@@ -334,9 +307,9 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/mob/camera/eye/xenobio/remote_eye = C.remote_control
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
slime_scan(S, C)
else
@@ -351,14 +324,14 @@
return
var/mob/living/carbon/human/C = owner
var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/mob/camera/eye/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(QDELETED(X.current_potion))
to_chat(owner, "<span class='warning'>No potion loaded.</span>")
return
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
if(GLOB.cameranet.check_turf_vis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
X.current_potion.attack__legacy__attackchain(S, C)
break
@@ -416,22 +389,22 @@
// Scans slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickCtrl(mob/living/user, mob/living/simple_animal/slime/S)
if(!GLOB.cameranet.checkTurfVis(S.loc))
if(!GLOB.cameranet.check_turf_vis(S.loc))
to_chat(user, "<span class='warning'>Target is not near a camera. Cannot proceed.</span>")
return
var/mob/living/C = user
var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/mob/camera/eye/xenobio/E = C.remote_control
var/area/mobarea = get_area(S.loc)
if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
slime_scan(S, C)
//Feeds a potion to slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickAlt(mob/living/user, mob/living/simple_animal/slime/S)
if(!GLOB.cameranet.checkTurfVis(S.loc))
if(!GLOB.cameranet.check_turf_vis(S.loc))
to_chat(user, "<span class='warning'>Target is not near a camera. Cannot proceed.</span>")
return
var/mob/living/C = user
var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
if(!X.current_potion)
@@ -442,11 +415,11 @@
//Picks up slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickShift(mob/living/user, mob/living/simple_animal/slime/S)
if(!GLOB.cameranet.checkTurfVis(S.loc))
if(!GLOB.cameranet.check_turf_vis(S.loc))
to_chat(user, "<span class='warning'>Target is not near a camera. Cannot proceed.</span>")
return
var/mob/living/C = user
var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
@@ -463,11 +436,11 @@
//Place slimes
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickShift(mob/living/user, turf/T)
if(!GLOB.cameranet.checkTurfVis(T))
if(!GLOB.cameranet.check_turf_vis(T))
to_chat(user, "<span class='warning'>Target is not near a camera. Cannot proceed.</span>")
return
var/mob/living/C = user
var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/turfarea = get_area(T)
if(iswallturf(T))
@@ -479,11 +452,11 @@
//Place monkey
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickCtrl(mob/living/user, turf/T)
if(!GLOB.cameranet.checkTurfVis(T))
if(!GLOB.cameranet.check_turf_vis(T))
to_chat(user, "<span class='warning'>Target is not near a camera. Cannot proceed.</span>")
return
var/mob/living/C = user
var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
var/area/turfarea = get_area(T)
@@ -513,11 +486,11 @@
//Pick up monkey
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoMonkeyClickCtrl(mob/living/user, mob/living/carbon/human/M)
if(!GLOB.cameranet.checkTurfVis(M.loc))
if(!GLOB.cameranet.check_turf_vis(M.loc))
to_chat(user, "<span class='warning'>Target is not near a camera. Cannot proceed.</span>")
return
var/mob/living/C = user
var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/mob/camera/eye/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(M.loc)
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
+1 -1
View File
@@ -182,7 +182,7 @@
attempt_hijack_stage(user)
/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user)
var/is_ai = isAI(user)
var/is_ai = is_ai(user)
if(!Adjacent(user) && !is_ai)
return
if(!ishuman(user) && !is_ai) //No, xenomorphs, constructs and traitors in cyborgs can not hack it.
+16 -43
View File
@@ -55,8 +55,8 @@
shuttle_port = null
return
eyeobj = new /mob/camera/ai_eye/remote/shuttle_docker(get_turf(locate("landmark*Observer-Start")), src) // There should always be an observer start landmark
var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
eyeobj = new /mob/camera/eye/shuttle_docker(get_turf(locate("landmark*Observer-Start")), name, src, current_user) // There should always be an observer start landmark
var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
the_eye.setDir(shuttle_port.dir)
var/turf/origin = locate(shuttle_port.x + x_offset, shuttle_port.y + y_offset, shuttle_port.z)
for(var/V in shuttle_port.shuttle_areas)
@@ -72,11 +72,12 @@
I.plane = 0
I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
the_eye.placement_images[I] = list(x_off, y_off)
give_eye_control(current_user)
/obj/machinery/computer/camera_advanced/shuttle_docker/give_eye_control(mob/user)
..()
if(!QDELETED(user) && user.client)
var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/list/to_add = list()
to_add += the_eye.placement_images
to_add += the_eye.placed_images
@@ -89,7 +90,7 @@
/obj/machinery/computer/camera_advanced/shuttle_docker/remove_eye_control(mob/living/user)
..()
if(!QDELETED(user) && user.client)
var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/list/to_remove = list()
to_remove += the_eye.placement_images
to_remove += the_eye.placed_images
@@ -103,8 +104,8 @@
if(designating_target_loc || !current_user)
return
var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
var/landing_clear = checkLandingSpot()
var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/landing_clear = check_landing_spot()
if(designate_time && (landing_clear != SHUTTLE_DOCKER_BLOCKED))
to_chat(current_user, "<span class='warning'>Targeting transit location, please wait [DisplayTimeText(designate_time)]...</span>")
designating_target_loc = the_eye.loc
@@ -115,7 +116,7 @@
if(!wait_completed)
to_chat(current_user, "<span class='warning'>Operation aborted.</span>")
return
landing_clear = checkLandingSpot()
landing_clear = check_landing_spot()
if(landing_clear != SHUTTLE_DOCKER_LANDING_CLEAR)
switch(landing_clear)
@@ -162,7 +163,7 @@
return TRUE
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot()
var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/list/image_cache = the_eye.placement_images
the_eye.setDir(turn(the_eye.dir, -90))
for(var/i in 1 to length(image_cache))
@@ -175,10 +176,10 @@
var/Tmp = x_offset
x_offset = y_offset
y_offset = -Tmp
checkLandingSpot()
check_landing_spot()
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingSpot()
var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/check_landing_spot()
var/mob/camera/eye/shuttle_docker/the_eye = eyeobj
var/turf/eyeturf = get_turf(the_eye)
if(!eyeturf)
return SHUTTLE_DOCKER_BLOCKED
@@ -253,34 +254,6 @@
if(dock)
jumpto_ports[dock.id] = TRUE
/mob/camera/ai_eye/remote/shuttle_docker
visible_icon = FALSE
use_static = FALSE
simulated = FALSE
// The Shuttle Docker does not trigger the AI Detector
ai_detector_visible = FALSE
var/list/placement_images = list()
var/list/placed_images = list()
/mob/camera/ai_eye/remote/shuttle_docker/Initialize(mapload, obj/machinery/computer/camera_advanced/origin)
src.origin = origin
return ..()
/mob/camera/ai_eye/remote/shuttle_docker/setLoc(T)
if(isspaceturf(get_turf(T)) || isspacearea(get_area(T)) || istype(get_area(T), /area/shuttle))
..()
var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin
console.checkLandingSpot()
return
else
return
/mob/camera/ai_eye/remote/shuttle_docker/update_remote_sight(mob/living/user)
user.sight = SEE_TURFS
..()
return TRUE
/datum/action/innate/shuttledocker_rotate
name = "Rotate"
button_overlay_icon = 'icons/mob/actions/actions_mecha.dmi'
@@ -290,7 +263,7 @@
if(QDELETED(target) || !isliving(target))
return
var/mob/living/C = target
var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
origin.rotateLandingSpot()
@@ -303,7 +276,7 @@
if(QDELETED(target) || !isliving(target))
return
var/mob/living/C = target
var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
origin.placeLandingSpot(target)
@@ -315,7 +288,7 @@
if(QDELETED(target) || !isliving(target))
return
var/mob/living/C = target
var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/mob/camera/eye/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/shuttle_docker/console = remote_eye.origin
playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
@@ -337,7 +310,7 @@
var/turf/T = get_turf(L[selected])
if(T)
playsound(console, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
remote_eye.setLoc(T)
remote_eye.set_loc(T)
to_chat(target, "<span class='notice'>Jumped to [selected]</span>")
else
playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
+2 -2
View File
@@ -166,8 +166,8 @@ GLOBAL_DATUM_INIT(multispin_words, /regex, regex("like a record baby"))
var/mob/living/carbon/human/H = L
if(H.check_ear_prot() >= HEARING_PROTECTION_TOTAL)
continue
if(istype(L, /mob/camera/ai_eye))
var/mob/camera/ai_eye/ai_eye = L
if(istype(L, /mob/camera/eye/ai))
var/mob/camera/eye/ai/ai_eye = L
if(ai_eye.relay_speech && ai_eye.ai)
listeners += ai_eye.ai
else
+2 -2
View File
@@ -37,7 +37,7 @@
switch(action)
if("track")
var/mob/living/carbon/human/H = locate(params["track"]) in GLOB.human_list
if(isAI(usr))
if(is_ai(usr))
var/mob/living/silicon/ai/AI = usr
if(hassensorlevel(H, SUIT_SENSOR_TRACKING))
AI.ai_actual_track(H)
@@ -105,7 +105,7 @@
data["offsetX"] = offset_x
data["offsetY"] = offset_y
data["isAI"] = isAI(user)
data["isAI"] = is_ai(user)
data["isObserver"] = isobserver(user)
data["ignoreSensors"] = ignore_sensors
data["crewmembers"] = GLOB.crew_repository.health_data(viewing_current_z_level, ignore_sensors)
+2 -2
View File
@@ -140,7 +140,7 @@
if("notify_laws")
to_chat(owner, "<span class='danger'>Law Notice</span>")
owner.laws.show_laws(owner)
if(isAI(owner))
if(is_ai(owner))
var/mob/living/silicon/ai/AI = owner
for(var/mob/living/silicon/robot/R in AI.connected_robots)
to_chat(R, "<span class='danger'>Law Notice</span>")
@@ -178,7 +178,7 @@
package_laws(data, "inherent_laws", owner.laws.inherent_laws)
package_laws(data, "supplied_laws", owner.laws.supplied_laws)
data["isAI"] = isAI(owner)
data["isAI"] = is_ai(owner)
data["isMalf"] = is_malf(user)
data["isSlaved"] = owner.is_slaved()
data["isAdmin"] = is_admin(user)
+2 -2
View File
@@ -56,7 +56,7 @@ GLOBAL_LIST(ui_logins)
"rank" = state.rank,
"logged_in" = state.logged_in,
)
data["isAI"] = isAI(user)
data["isAI"] = is_ai(user)
data["isRobot"] = isrobot(user)
data["isAdmin"] = user.can_admin_interact()
@@ -133,7 +133,7 @@ GLOBAL_LIST(ui_logins)
else
to_chat(usr, "<span class='warning'>Access Denied</span>")
return
else if(login_type == LOGIN_TYPE_AI && isAI(usr))
else if(login_type == LOGIN_TYPE_AI && is_ai(usr))
state.name = usr.name
state.rank = "AI"
else if(login_type == LOGIN_TYPE_ROBOT && isrobot(usr))