diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index 7008f050337..1be91011a17 100644
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -1788,8 +1788,8 @@ area/security/podbay
icon_state = "toxlab"
/area/toxins/xenobiology
- name = "\improper Xenobiology Lab"
- icon_state = "toxlab"
+ name = "Xenobiology Lab"
+ icon_state = "toxmix"
/area/toxins/xenobiology/xenoflora_storage
name = "\improper Xenoflora Storage"
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
new file mode 100644
index 00000000000..0e6e77ec9ec
--- /dev/null
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -0,0 +1,161 @@
+/obj/machinery/computer/camera_advanced
+ name = "advanced camera console"
+ desc = "Used to access the various cameras on the station."
+ icon_screen = "cameras"
+ icon_keyboard = "security_key"
+ var/mob/camera/aiEye/remote/eyeobj
+ var/mob/living/carbon/human/current_user = null
+ var/list/networks = list("SS13")
+ var/datum/action/camera_off/off_action = new
+ var/datum/action/camera_jump/jump_action = new
+
+/obj/machinery/computer/camera_advanced/proc/CreateEye()
+ eyeobj = new()
+ eyeobj.origin = src
+
+/obj/machinery/computer/camera_advanced/proc/GrantActions(mob/living/carbon/user)
+ off_action.target = user
+ off_action.Grant(user)
+ jump_action.target = user
+ jump_action.Grant(user)
+
+/obj/machinery/computer/camera_advanced/check_eye(mob/user)
+ if (get_dist(user, src) > 1 || user.eye_blind)
+ if(user == current_user)
+ off_action.Activate()
+ return 0
+ return 1
+
+/obj/machinery/computer/camera_advanced/attack_hand(mob/user)
+ if(..())
+ return
+ if(!iscarbon(user))
+ return
+ var/mob/living/carbon/L = user
+
+ if(!current_user)
+ if(!eyeobj)
+ CreateEye()
+ GrantActions(user)
+ current_user = user
+ eyeobj.user = user
+ eyeobj.name = "Camera Eye ([user.name])"
+ L.remote_view = 1
+ L.remote_control = eyeobj
+ L.client.perspective = EYE_PERSPECTIVE
+ if(!eyeobj.initialized)
+ for(var/obj/machinery/camera/C in cameranet.cameras)
+ if(!C.can_use())
+ continue
+ if(C.network&networks)
+ eyeobj.setLoc(get_turf(C))
+ break
+ eyeobj.initialized = 1
+ else
+ eyeobj.setLoc(eyeobj.loc)
+ else
+ user << "The console is already in use!"
+
+/mob/camera/aiEye/remote
+ name = "Inactive Camera Eye"
+ var/sprint = 10
+ var/cooldown = 0
+ var/acceleration = 1
+ var/mob/living/carbon/human/user = null
+ var/obj/machinery/computer/camera_advanced/origin
+ var/initialized = 0
+ var/visible_icon = 0
+ var/image/user_image = null
+
+/mob/camera/aiEye/remote/GetViewerClient()
+ if(user)
+ return user.client
+ return null
+
+/mob/camera/aiEye/remote/setLoc(T)
+ if(user)
+ if(!isturf(user.loc))
+ return
+ T = get_turf(T)
+ loc = T
+ cameranet.visibility(src)
+ if(user.client)
+ if(visible_icon)
+ user.client.images -= user_image
+ user_image = image(icon,loc,icon_state,FLY_LAYER)
+ user.client.images += user_image
+ user.client.eye = src
+
+/mob/camera/aiEye/remote/relaymove(mob/user,direct)
+ var/initial = initial(sprint)
+ var/max_sprint = 50
+
+ if(cooldown && cooldown < world.timeofday) // 3 seconds
+ sprint = initial
+
+ for(var/i = 0; i < max(sprint, initial); i += 20)
+ var/turf/step = get_turf(get_step(src, direct))
+ if(step)
+ src.setLoc(step)
+
+ cooldown = world.timeofday + 5
+ if(acceleration)
+ sprint = min(sprint + 0.5, max_sprint)
+ else
+ sprint = initial
+
+/datum/action/camera_off
+ name = "End Camera View"
+ action_type = AB_INNATE
+ button_icon_state = "camera_off"
+
+/datum/action/camera_off/Activate()
+ if(!target || !iscarbon(target))
+ return
+ var/mob/living/carbon/C = target
+ var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ C.remote_view = 0
+ remote_eye.origin.current_user = null
+ remote_eye.origin.jump_action.Remove(C)
+ remote_eye.user = null
+ if(C.client)
+ C.client.perspective = MOB_PERSPECTIVE
+ C.client.eye = src
+ C.client.images -= remote_eye.user_image
+ for(var/datum/camerachunk/chunk in remote_eye.visibleCameraChunks)
+ C.client.images -= chunk.obscured
+ C.remote_control = null
+ C.unset_machine()
+ src.Remove(C)
+
+/datum/action/camera_jump
+ name = "Jump To Camera"
+ action_type = AB_INNATE
+ button_icon_state = "camera_jump"
+
+/datum/action/camera_jump/Activate()
+ if(!target || !iscarbon(target))
+ return
+ var/mob/living/carbon/C = target
+ var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin
+
+ var/list/L = list()
+
+ for (var/obj/machinery/camera/cam in cameranet.cameras)
+ L.Add(cam)
+
+ camera_sort(L)
+
+ var/list/T = list()
+
+ for (var/obj/machinery/camera/netcam in L)
+ var/list/tempnetwork = netcam.network&origin.networks
+ if (tempnetwork.len)
+ T[text("[][]", netcam.c_tag, (netcam.can_use() ? null : " (Deactivated)"))] = netcam
+
+
+ var/camera = input("Choose which camera you want to view", "Cameras") as null|anything in T
+ var/obj/machinery/camera/final = T[camera]
+ if(final)
+ remote_eye.setLoc(get_turf(final))
\ No newline at end of file
diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm
index 71c6be4d154..99dbe5b366b 100644
--- a/code/game/objects/items/blueprints.dm
+++ b/code/game/objects/items/blueprints.dm
@@ -179,7 +179,7 @@
A.name = str
usr << "You rename the '[prevname]' to '[str]'."
interact()
- return
+ return 1
/obj/item/areaeditor/proc/set_area_machinery_title(var/area/A,var/title,var/oldtitle)
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index a3a9a2721e4..6fe1cae458d 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -939,6 +939,9 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
src << "Your psy-connection grows too faint to maintain!"
isRemoteObserve = 0
+ if(remote_view)
+ isRemoteObserve = 1
+
if(!isRemoteObserve && client && !client.adminobs)
remoteview_target = null
reset_view(null)
diff --git a/code/modules/mob/living/carbon/slime/slime.dm b/code/modules/mob/living/carbon/slime/slime.dm
index 37c2e112c24..e2a7da1ae5e 100644
--- a/code/modules/mob/living/carbon/slime/slime.dm
+++ b/code/modules/mob/living/carbon/slime/slime.dm
@@ -66,6 +66,12 @@
coretype = text2path("/obj/item/slime_extract/[sanitizedcolour]")
..()
+/mob/living/carbon/slime/Destroy()
+ for(var/obj/machinery/computer/camera_advanced/xenobio/X in machines)
+ if(src in X.stored_slimes)
+ X.stored_slimes -= src
+ return ..()
+
/mob/living/carbon/slime/regenerate_icons()
icon_state = "[colour] [is_adult ? "adult" : "baby"] slime"
overlays.len = 0
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 7a325ca2385..f8dcf7a5d0d 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -212,7 +212,7 @@
if (!( machine.check_eye(src) ))
reset_view(null)
else
- if(!client.adminobs)
+ if(!remote_view && !client.adminobs)
reset_view(null)
/mob/living/proc/update_sight()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 4f7327180c6..a4142b41d49 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -211,6 +211,8 @@
var/atom/movable/remote_control //Calls relaymove() to whatever it is
+ var/remote_view = 0 // Set to 1 to prevent view resets on Life
+
var/obj/control_object //Used by admins to possess objects. All mobs should have this var
var/datum/visibility_interface/visibility_interface = null // used by the visibility system to provide an interface for the visibility networks
diff --git a/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm b/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm
index d6e1ed34ce4..bb069890392 100644
--- a/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm
+++ b/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm
@@ -492,6 +492,20 @@
feedback_add_details("slime_cores_used","[replacetext(name," ","_")]")
var/obj/item/weapon/slimesteroid2/P = new /obj/item/weapon/slimesteroid2
P.loc = get_turf(holder.my_atom)
+
+ slime_territory
+ name = "Slime Territory"
+ id = "s_territory"
+ result = null
+ required_reagents = list("blood" = 1)
+ result_amount = 1
+ required_container = /obj/item/slime_extract/cerulean
+ required_other = 1
+ on_reaction(datum/reagents/holder)
+ feedback_add_details("slime_cores_used","[type]")
+ var/obj/item/areaeditor/blueprints/slime/P = new /obj/item/areaeditor/blueprints/slime
+ P.loc = get_turf(holder.my_atom)
+
//Sepia
slimestop
name = "Slime Stop"
diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm
index eee8c78f9b0..df6edbaa03f 100644
--- a/code/modules/reagents/reagent_containers/food/snacks.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks.dm
@@ -1597,6 +1597,12 @@
user.drop_item()
forceMove(get_turf(O))
return Expand()
+ if(istype(O, /obj/machinery/computer/camera_advanced/xenobio))
+ var/obj/machinery/computer/camera_advanced/xenobio/X = O
+ X.monkeys++
+ user << "You feed [src] to the [X]. It now has [X.monkeys] monkey cubes stored."
+ qdel(src)
+ return
..()
/obj/item/weapon/reagent_containers/food/snacks/monkeycube/attack_self(mob/user)
diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm
new file mode 100644
index 00000000000..129c97761d1
--- /dev/null
+++ b/code/modules/research/xenobiology/xenobio_camera.dm
@@ -0,0 +1,171 @@
+//Xenobio control console
+/mob/camera/aiEye/remote/xenobio
+ visible_icon = 1
+ icon = 'icons/obj/abductor.dmi'
+ icon_state = "camera_target"
+
+
+/mob/camera/aiEye/remote/xenobio/setLoc(var/t)
+ var/area/new_area = get_area(t)
+ if(new_area && new_area.name == "Xenobiology Lab" || istype(new_area, /area/toxins/xenobiology ))
+ return ..()
+ else
+ return
+
+/obj/machinery/computer/camera_advanced/xenobio
+ name = "Slime management console"
+ desc = "A computer used for remotely handling slimes."
+ networks = list("SS13")
+ off_action = new/datum/action/camera_off/xenobio
+ var/datum/action/slime_place/slime_place_action = new
+ var/datum/action/slime_pick_up/slime_up_action = new
+ var/datum/action/feed_slime/feed_slime_action = new
+ var/datum/action/monkey_recycle/monkey_recycle_action = new
+
+ var/list/stored_slimes = list()
+ var/max_slimes = 5
+ var/monkeys = 0
+
+ icon_screen = "slime_comp"
+ icon_keyboard = "rd_key"
+
+/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
+ eyeobj = new /mob/camera/aiEye/remote/xenobio()
+ eyeobj.loc = get_turf(src)
+ eyeobj.origin = src
+ eyeobj.visible_icon = 1
+ eyeobj.icon = 'icons/obj/abductor.dmi'
+ eyeobj.icon_state = "camera_target"
+
+/obj/machinery/computer/camera_advanced/xenobio/GrantActions(mob/living/carbon/user)
+ off_action.target = user
+ off_action.Grant(user)
+
+ jump_action.target = user
+ jump_action.Grant(user)
+
+ slime_up_action.target = src
+ slime_up_action.Grant(user)
+
+ slime_place_action.target = src
+ slime_place_action.Grant(user)
+
+ feed_slime_action.target = src
+ feed_slime_action.Grant(user)
+
+ monkey_recycle_action.target = src
+ monkey_recycle_action.Grant(user)
+
+
+/obj/machinery/computer/camera_advanced/xenobio/attack_hand(mob/user)
+ if(!ishuman(user)) //AIs using it might be weird
+ return
+ return ..()
+
+/datum/action/camera_off/xenobio/Activate()
+ if(!target || !ishuman(target))
+ return
+ var/mob/living/carbon/C = target
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/origin = remote_eye.origin
+ C.remote_view = 0
+ origin.current_user = null
+ origin.jump_action.Remove(C)
+ origin.slime_place_action.Remove(C)
+ origin.slime_up_action.Remove(C)
+ origin.feed_slime_action.Remove(C)
+ origin.monkey_recycle_action.Remove(C)
+ //All of this stuff below could probably be a proc for all advanced cameras, only the action removal needs to be camera specific
+ remote_eye.user = null
+ if(C.client)
+ C.client.perspective = MOB_PERSPECTIVE
+ C.client.eye = src
+ C.client.images -= remote_eye.user_image
+ for(var/datum/camerachunk/chunk in remote_eye.visibleCameraChunks)
+ C.client.images -= chunk.obscured
+ C.remote_control = null
+ C.unset_machine()
+ src.Remove(C)
+
+
+/datum/action/slime_place
+ name = "Place Slimes"
+ action_type = AB_INNATE
+ button_icon_state = "slime_down"
+
+/datum/action/slime_place/Activate()
+ if(!target || !ishuman(owner))
+ return
+ var/mob/living/carbon/human/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(cameranet.checkTurfVis(remote_eye.loc))
+ for(var/mob/living/carbon/slime/S in X.stored_slimes)
+ S.loc = remote_eye.loc
+ S.visible_message("[S] warps in!")
+ X.stored_slimes -= S
+
+/datum/action/slime_pick_up
+ name = "Pick up Slime"
+ action_type = AB_INNATE
+ button_icon_state = "slime_up"
+
+/datum/action/slime_pick_up/Activate()
+ if(!target || !ishuman(owner))
+ return
+ var/mob/living/carbon/human/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(cameranet.checkTurfVis(remote_eye.loc))
+ for(var/mob/living/carbon/slime/S in remote_eye.loc)
+ if(X.stored_slimes.len >= X.max_slimes)
+ break
+ if(!S.ckey)
+ if(S.buckled)
+ S.buckled.unbuckle_mob()
+ S.Feedstop()
+ S.visible_message("[S] vanishes in a flash of light!")
+ S.loc = X
+ X.stored_slimes += S
+
+
+/datum/action/feed_slime
+ name = "Feed Slimes"
+ action_type = AB_INNATE
+ button_icon_state = "monkey_down"
+
+/datum/action/feed_slime/Activate()
+ if(!target || !ishuman(owner))
+ return
+ var/mob/living/carbon/human/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(cameranet.checkTurfVis(remote_eye.loc))
+ if(X.monkeys >= 1)
+ var/mob/living/carbon/human/monkey/food = new /mob/living/carbon/human/monkey(remote_eye.loc)
+ food.LAssailant = C
+ X.monkeys --
+ owner << "[X] now has [X.monkeys] monkeys left."
+
+
+/datum/action/monkey_recycle
+ name = "Recycle Monkeys"
+ action_type = AB_INNATE
+ button_icon_state = "monkey_up"
+
+/datum/action/monkey_recycle/Activate()
+ if(!target || !ishuman(owner))
+ return
+ var/mob/living/carbon/human/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(cameranet.checkTurfVis(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 they are reclaimed for recycling!")
+ X.monkeys += 0.2
+ qdel(M)
\ No newline at end of file
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index a126db30def..154792c7b12 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -438,6 +438,18 @@
max_amount = 60
turf_type = /turf/simulated/floor/sepia
+/obj/item/areaeditor/blueprints/slime
+ name = "cerulean prints"
+ desc = "A one use set of blueprints made of jelly like organic material. Renaming an area to 'Xenobiology Lab' will extend the reach of the management console."
+ color = "#2956B2"
+
+/obj/item/areaeditor/blueprints/slime/edit_area()
+ var/success = ..()
+ var/area/A = get_area(src)
+ if(success)
+ for(var/turf/T in A)
+ T.color = "#2956B2"
+ qdel(src)
/turf/simulated/floor/sepia
slowdown = 2
diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi
index 75bbde4e4a3..a2847325875 100644
Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ
diff --git a/icons/obj/abductor.dmi b/icons/obj/abductor.dmi
new file mode 100644
index 00000000000..0896e9a500a
Binary files /dev/null and b/icons/obj/abductor.dmi differ
diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi
index 958c28dcaa5..09380373db8 100644
Binary files a/icons/obj/computer.dmi and b/icons/obj/computer.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 4a673c9da4a..f2e230118ac 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -502,6 +502,7 @@
#include "code\game\machinery\computer\atmos_control.dm"
#include "code\game\machinery\computer\buildandrepair.dm"
#include "code\game\machinery\computer\camera.dm"
+#include "code\game\machinery\computer\camera_advanced.dm"
#include "code\game\machinery\computer\card.dm"
#include "code\game\machinery\computer\cloning.dm"
#include "code\game\machinery\computer\communications.dm"
@@ -1835,6 +1836,7 @@
#include "code\modules\research\xenoarchaeology\tools\tools_depthscanner.dm"
#include "code\modules\research\xenoarchaeology\tools\tools_locater.dm"
#include "code\modules\research\xenoarchaeology\tools\tools_pickaxe.dm"
+#include "code\modules\research\xenobiology\xenobio_camera.dm"
#include "code\modules\research\xenobiology\xenobiology.dm"
#include "code\modules\scripting\Errors.dm"
#include "code\modules\scripting\Options.dm"