diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index aa906283888..ce100c807a5 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -60614,8 +60614,7 @@
},
/area/shuttle/syndicate)
"cIn" = (
-/obj/structure/table/reinforced,
-/obj/item/folder/red,
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -60650,10 +60649,11 @@
pixel_x = -32
},
/obj/item/clipboard,
-/obj/item/toy/figure/syndie,
/obj/machinery/light{
dir = 8
},
+/obj/item/folder/red,
+/obj/item/toy/figure/syndie,
/turf/open/floor/plasteel/vault{
dir = 8
},
diff --git a/_maps/map_files/Cerestation/cerestation.dmm b/_maps/map_files/Cerestation/cerestation.dmm
index 86ac838e9fc..811996dbdae 100644
--- a/_maps/map_files/Cerestation/cerestation.dmm
+++ b/_maps/map_files/Cerestation/cerestation.dmm
@@ -1,4 +1,4 @@
-//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
"aaa" = (
/turf/open/space,
/area/space)
@@ -72742,10 +72742,11 @@
pixel_x = -32
},
/obj/item/clipboard,
-/obj/item/toy/figure/syndie,
/obj/machinery/light{
dir = 8
},
+/obj/item/folder/red,
+/obj/item/toy/figure/syndie,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -72784,10 +72785,10 @@
},
/area/shuttle/syndicate)
"fXq" = (
+/turf/open/space,
/obj/machinery/porta_turret/syndicate{
dir = 9
},
-/turf/open/space,
/turf/closed/wall/mineral/plastitanium{
dir = 8;
icon_state = "diagonalWall3"
@@ -73544,10 +73545,10 @@
/turf/open/floor/plasteel/floorgrime,
/area/maintenance/asteroid/disposal/south)
"qah" = (
+/turf/open/space,
/obj/machinery/porta_turret/syndicate{
dir = 10
},
-/turf/open/space,
/turf/closed/wall/mineral/plastitanium{
icon_state = "diagonalWall3"
},
@@ -75054,8 +75055,7 @@
/turf/open/floor/plasteel,
/area/hallway/secondary/exit)
"HmY" = (
-/obj/structure/table/reinforced,
-/obj/item/folder/red,
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -76501,10 +76501,10 @@
/turf/open/floor/plating,
/area/maintenance/asteroid/disposal/south/west)
"ZfX" = (
+/turf/open/space,
/obj/machinery/porta_turret/syndicate{
dir = 5
},
-/turf/open/space,
/turf/closed/wall/mineral/plastitanium{
dir = 1;
icon_state = "diagonalWall3"
@@ -76532,10 +76532,10 @@
},
/area/shuttle/syndicate)
"ZYy" = (
+/turf/open/space,
/obj/machinery/porta_turret/syndicate{
dir = 6
},
-/turf/open/space,
/turf/closed/wall/mineral/plastitanium{
dir = 4;
icon_state = "diagonalWall3"
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 6c678d3cd49..da4fe635d8e 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -1714,8 +1714,7 @@
},
/area/shuttle/syndicate)
"adC" = (
-/obj/structure/table/reinforced,
-/obj/item/folder/red,
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -1840,10 +1839,11 @@
pixel_x = -32
},
/obj/item/clipboard,
-/obj/item/toy/figure/syndie,
/obj/machinery/light{
dir = 8
},
+/obj/item/folder/red,
+/obj/item/toy/figure/syndie,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -138762,13 +138762,13 @@ bZC
duO
cJx
cHS
-cMI
-cMI
-cMI
+cHX
+cHX
+cHX
cRJ
-cMI
-cMI
-cMI
+cHX
+cHX
+cHX
cHS
cJA
cFm
@@ -139276,13 +139276,13 @@ cBp
erO
cJz
cHS
-cMI
-cMI
-cMI
-cMI
-cMI
-cMI
-cMI
+cHX
+cHX
+cHX
+cHX
+cHX
+cHX
+cHX
cHS
cZL
dbc
@@ -139535,8 +139535,8 @@ cJA
cHS
cHS
cOs
-cMI
-cMI
+cHX
+cHX
cTv
cOs
cHS
@@ -155472,7 +155472,7 @@ cPm
cLL
cSA
cUj
-cVW
+cUu
cXJ
cZi
dap
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 0eb1286d501..4511bfe5820 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -78644,8 +78644,7 @@
/turf/open/floor/plating,
/area/science/xenobiology)
"cTE" = (
-/obj/structure/table/reinforced,
-/obj/item/folder/red,
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -78668,10 +78667,11 @@
pixel_x = -32
},
/obj/item/clipboard,
-/obj/item/toy/figure/syndie,
/obj/machinery/light{
dir = 8
},
+/obj/item/folder/red,
+/obj/item/toy/figure/syndie,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -136448,8 +136448,8 @@ cgD
chW
cjl
ckK
-bCC
-bCC
+dbu
+dbu
ckM
aaf
bKK
@@ -136704,9 +136704,9 @@ ccY
ccY
ccY
cjm
-bCC
+dbu
cme
-bCC
+dbu
cjo
aaa
bKK
@@ -136962,8 +136962,8 @@ cgE
chX
cjn
ckL
-bCC
-bCC
+dbu
+dbu
bxc
aaf
aai
diff --git a/_maps/map_files/OmegaStation/OmegaStation.dmm b/_maps/map_files/OmegaStation/OmegaStation.dmm
index 2fc2c473e87..028c958e3e4 100644
--- a/_maps/map_files/OmegaStation/OmegaStation.dmm
+++ b/_maps/map_files/OmegaStation/OmegaStation.dmm
@@ -37022,8 +37022,7 @@
},
/area/shuttle/syndicate)
"bmZ" = (
-/obj/structure/table/reinforced,
-/obj/item/folder/red,
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -37062,10 +37061,11 @@
pixel_x = -32
},
/obj/item/clipboard,
-/obj/item/toy/figure/syndie,
/obj/machinery/light{
dir = 8
},
+/obj/item/folder/red,
+/obj/item/toy/figure/syndie,
/turf/open/floor/plasteel/vault{
dir = 8
},
diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm
index 272e5829d1f..7f55c286c77 100644
--- a/_maps/map_files/PubbyStation/PubbyStation.dmm
+++ b/_maps/map_files/PubbyStation/PubbyStation.dmm
@@ -46,8 +46,7 @@
},
/area/shuttle/syndicate)
"aah" = (
-/obj/structure/table/reinforced,
-/obj/item/folder/red,
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -82,10 +81,11 @@
pixel_x = -32
},
/obj/item/clipboard,
-/obj/item/toy/figure/syndie,
/obj/machinery/light{
dir = 8
},
+/obj/item/folder/red,
+/obj/item/toy/figure/syndie,
/turf/open/floor/plasteel/vault{
dir = 8
},
@@ -80202,9 +80202,9 @@ bva
bva
bva
aht
-crn
+bIZ
crA
-crn
+bIZ
aaa
bva
bva
@@ -103021,7 +103021,7 @@ aNR
aNR
aTu
aUB
-cpd
+aEl
aaa
aaa
aEl
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm
new file mode 100644
index 00000000000..32324ea233d
--- /dev/null
+++ b/code/modules/shuttle/navigation_computer.dm
@@ -0,0 +1,244 @@
+/obj/machinery/computer/camera_advanced/shuttle_docker
+ name = "navigation computer"
+ desc = "Used to designate a precise transit location for a spacecraft."
+ z_lock = ZLEVEL_STATION
+ jump_action = null
+ var/datum/action/innate/shuttledocker_rotate/rotate_action = new
+ var/datum/action/innate/shuttledocker_place/place_action = new
+ var/shuttleId = ""
+ var/shuttlePortId = ""
+ var/shuttlePortName = ""
+ var/list/jumpto_ports = list() //hashset of ports to jump to and ignore for collision purposes
+ var/list/blacklisted_turfs
+ var/obj/docking_port/stationary/my_port
+ var/view_range = 7
+ var/x_offset = 0
+ var/y_offset = 0
+ var/space_turfs_only = TRUE
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/GrantActions(mob/living/user)
+ if(jumpto_ports.len)
+ jump_action = new /datum/action/innate/camera_jump/shuttle_docker
+ ..()
+
+ if(rotate_action)
+ rotate_action.target = user
+ rotate_action.Grant(user)
+ actions += rotate_action
+
+ if(place_action)
+ place_action.target = user
+ place_action.Grant(user)
+ actions += place_action
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/CreateEye()
+ var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
+ if(QDELETED(M))
+ return
+ eyeobj = new /mob/camera/aiEye/remote/shuttle_docker()
+ var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
+ the_eye.origin = src
+ the_eye.dir = M.dir
+ var/area/A = get_area(M)
+ if(QDELETED(A))
+ return
+ var/turf/origin = locate(M.x + x_offset, M.y + y_offset, M.z)
+ for(var/turf/T in A)
+ if(T.z != origin.z)
+ continue
+ var/image/I = image('icons/effects/alphacolors.dmi', origin, "red")
+ I.layer = ABOVE_NORMAL_TURF_LAYER
+ I.plane = 0
+ I.mouse_opacity = 0
+ var/x_off = T.x - origin.x
+ var/y_off = T.y - origin.y
+ I.pixel_x = x_off * 32
+ I.pixel_y = y_off * 32
+ the_eye.placement_images[I] = list(x_off, y_off)
+ generateBlacklistedTurfs()
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/give_eye_control(mob/user)
+ ..()
+ if(!QDELETED(user) && user.client)
+ var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
+ user.client.images += the_eye.placement_images
+ user.client.images += the_eye.placed_images
+ user.client.view = view_range
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/remove_eye_control(mob/living/user)
+ ..()
+ if(!QDELETED(user) && user.client)
+ var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
+ user.client.images -= the_eye.placement_images
+ user.client.images -= the_eye.placed_images
+ user.client.view = world.view
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/placeLandingSpot()
+ if(!checkLandingSpot())
+ return FALSE
+ var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
+ if(!my_port)
+ my_port = new /obj/docking_port/stationary
+ my_port.name = shuttlePortName
+ my_port.id = shuttlePortId
+ var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
+ my_port.height = M.height
+ my_port.width = M.width
+ my_port.dheight = M.dheight
+ my_port.dwidth = M.dwidth
+ my_port.dir = the_eye.dir
+ my_port.loc = locate(eyeobj.x - x_offset, eyeobj.y - y_offset, eyeobj.z)
+ if(current_user && current_user.client)
+ current_user.client.images -= the_eye.placed_images
+
+ for(var/V in the_eye.placed_images)
+ qdel(V)
+ the_eye.placed_images = list()
+
+ for(var/V in the_eye.placement_images)
+ var/turf/T = locate(eyeobj.x + the_eye.placement_images[V][1], eyeobj.y + the_eye.placement_images[V][2], eyeobj.z)
+ var/image/I = image('icons/effects/alphacolors.dmi', T, "blue")
+ I.layer = ABOVE_OPEN_TURF_LAYER
+ I.plane = 0
+ I.mouse_opacity = 0
+ the_eye.placed_images += I
+
+ if(current_user && current_user.client)
+ current_user.client.images += the_eye.placed_images
+ return TRUE
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot()
+ var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
+ the_eye.dir = turn(the_eye.dir, -90)
+ for(var/V in the_eye.placement_images)
+ var/image/I = V
+ var/list/coords = the_eye.placement_images[V]
+ var/Tmp = coords[1]
+ coords[1] = coords[2]
+ coords[2] = -Tmp
+
+ I.pixel_x = coords[1] * 32
+ I.pixel_y = coords[2] * 32
+ var/Tmp = x_offset
+ x_offset = y_offset
+ y_offset = -Tmp
+ checkLandingSpot()
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingTurf(turf/T)
+ return T && (!blacklisted_turfs || !blacklisted_turfs[T]) && (!space_turfs_only || isspaceturf(T))
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/generateBlacklistedTurfs()
+ blacklisted_turfs = list()
+ for(var/V in SSshuttle.stationary)
+ if(!V)
+ continue
+ var/obj/docking_port/stationary/S = V
+ if(z_lock && (S.z != z_lock))
+ continue
+ if((S.id == shuttlePortId) || jumpto_ports[S.id])
+ continue
+ for(var/T in S.return_turfs())
+ blacklisted_turfs[T] = TRUE
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingSpot()
+ var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
+ var/turf/eyeturf = get_turf(the_eye)
+ if(!eyeturf)
+ return
+ var/landing_spot_clear = TRUE
+ for(var/V in the_eye.placement_images)
+ var/image/I = V
+ I.loc = eyeturf
+ var/list/coords = the_eye.placement_images[V]
+ var/turf/T = locate(eyeturf.x + coords[1], eyeturf.y + coords[2], eyeturf.z)
+ if(checkLandingTurf(T))
+ I.icon_state = "green"
+ else
+ I.icon_state = "red"
+ landing_spot_clear = FALSE
+ return landing_spot_clear
+
+/mob/camera/aiEye/remote/shuttle_docker
+ visible_icon = FALSE
+ use_static = FALSE
+ var/list/placement_images = list()
+ var/list/placed_images = list()
+
+/mob/camera/aiEye/remote/shuttle_docker/setLoc(T)
+ ..()
+ var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin
+ console.checkLandingSpot()
+
+/mob/camera/aiEye/remote/shuttle_docker/update_remote_sight(mob/living/user)
+ user.sight = BLIND|SEE_TURFS
+ user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
+ user.sync_lighting_plane_alpha()
+ return TRUE
+
+/datum/action/innate/shuttledocker_rotate
+ name = "Rotate"
+ icon_icon = 'icons/mob/actions/actions_mecha.dmi'
+ button_icon_state = "mech_cycle_equip_off"
+
+/datum/action/innate/shuttledocker_rotate/Activate()
+ if(QDELETED(target) || !isliving(target))
+ return
+ var/mob/living/C = target
+ var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
+ origin.rotateLandingSpot()
+
+/datum/action/innate/shuttledocker_place
+ name = "Place"
+ icon_icon = 'icons/mob/actions/actions_mecha.dmi'
+ button_icon_state = "mech_zoom_off"
+
+/datum/action/innate/shuttledocker_place/Activate()
+ if(QDELETED(target) || !isliving(target))
+ return
+ var/mob/living/C = target
+ var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
+ if(origin.placeLandingSpot())
+ to_chat(target, "Transit location designated")
+ else
+ to_chat(target, "Invalid transit location")
+
+/datum/action/innate/camera_jump/shuttle_docker
+ name = "Jump to Location"
+ button_icon_state = "camera_jump"
+
+/datum/action/innate/camera_jump/shuttle_docker/Activate()
+ if(QDELETED(target) || !isliving(target))
+ return
+ var/mob/living/C = target
+ var/mob/camera/aiEye/remote/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)
+
+ var/list/L = list()
+ for(var/V in SSshuttle.stationary)
+ if(!V)
+ continue
+ var/obj/docking_port/stationary/S = V
+ if(console.z_lock && (S.z != console.z_lock))
+ continue
+ if(console.jumpto_ports[S.id])
+ L[S.name] = S
+
+ playsound(console, 'sound/machines/terminal_prompt.ogg', 25, 0)
+ var/selected = input("Choose location to jump to", "Locations", null) as null|anything in L
+ if(QDELETED(src) || QDELETED(target) || !isliving(target))
+ return
+ playsound(src, "terminal_type", 25, 0)
+ if(selected)
+ var/turf/T = get_turf(L[selected])
+ if(T)
+ playsound(console, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
+ remote_eye.setLoc(T)
+ to_chat(target, "Jumped to [selected]")
+ C.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static)
+ C.clear_fullscreen("flash", 3)
+ else
+ playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 61981d3867b..d0d5952a0c8 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -71,6 +71,12 @@
_y + (-dwidth+width-1)*sin + (-dheight+height-1)*cos
)
+//returns turfs within our projected rectangle in no particular order
+/obj/docking_port/proc/return_turfs()
+ var/list/L = return_coords()
+ var/turf/T0 = locate(L[1],L[2],z)
+ var/turf/T1 = locate(L[3],L[4],z)
+ return block(T0,T1)
//returns turfs within our projected rectangle in a specific order.
//this ensures that turfs are copied over in the same order, regardless of any rotation
diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm
index 0a610cc6361..f9d90dd3426 100644
--- a/code/modules/shuttle/syndicate.dm
+++ b/code/modules/shuttle/syndicate.dm
@@ -8,7 +8,7 @@
light_color = LIGHT_COLOR_RED
req_access = list(ACCESS_SYNDICATE)
shuttleId = "syndicate"
- possible_destinations = "syndicate_away;syndicate_z5;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s"
+ possible_destinations = "syndicate_away;syndicate_z5;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s;syndicate_custom"
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
/obj/machinery/computer/shuttle/syndicate/recall
@@ -42,5 +42,18 @@
return 0
..()
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate
+ name = "syndicate shuttle navigation computer"
+ desc = "Used to designate a precise transit location for the syndicate shuttle."
+ icon_screen = "syndishuttle"
+ icon_keyboard = "syndie_key"
+ z_lock = ZLEVEL_STATION
+ shuttleId = "syndicate"
+ shuttlePortId = "syndicate_custom"
+ shuttlePortName = "custom location"
+ jumpto_ports = list("syndicate_ne" = 1, "syndicate_nw" = 1, "syndicate_n" = 1, "syndicate_se" = 1, "syndicate_sw" = 1, "syndicate_s" = 1)
+ view_range = 13
+ x_offset = -4
+ y_offset = -2
#undef SYNDICATE_CHALLENGE_TIMER
\ No newline at end of file
diff --git a/tgstation.dme b/tgstation.dme
index 0c345008035..0f4b25199b0 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2048,6 +2048,7 @@
#include "code\modules\shuttle\emergency.dm"
#include "code\modules\shuttle\ferry.dm"
#include "code\modules\shuttle\manipulator.dm"
+#include "code\modules\shuttle\navigation_computer.dm"
#include "code\modules\shuttle\on_move.dm"
#include "code\modules\shuttle\ripple.dm"
#include "code\modules\shuttle\shuttle.dm"