diff --git a/code/__DEFINES/shuttle.dm b/code/__DEFINES/shuttle.dm
new file mode 100644
index 00000000000..22af9a38670
--- /dev/null
+++ b/code/__DEFINES/shuttle.dm
@@ -0,0 +1,16 @@
+//Docking error flags
+#define DOCKING_SUCCESS 0
+#define DOCKING_BLOCKED (1<<0)
+#define DOCKING_IMMOBILIZED (1<<1)
+#define DOCKING_AREA_EMPTY (1<<2)
+#define DOCKING_NULL_DESTINATION (1<<3)
+#define DOCKING_NULL_SOURCE (1<<4)
+
+//Rotation params
+#define ROTATE_DIR 1
+#define ROTATE_SMOOTH 2
+#define ROTATE_OFFSET 4
+
+#define SHUTTLE_DOCKER_LANDING_CLEAR 1
+#define SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT 2
+#define SHUTTLE_DOCKER_BLOCKED 3
\ No newline at end of file
diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm
index c0c22872a32..ec9fcb5fb31 100644
--- a/code/__HELPERS/maths.dm
+++ b/code/__HELPERS/maths.dm
@@ -120,4 +120,24 @@ var/gaussian_next
/proc/round_down(num)
if(round(num) != num)
return round(num--)
- else return num
\ No newline at end of file
+ else return num
+
+// Returns a list where [1] is all x values and [2] is all y values that overlap between the given pair of rectangles
+/proc/get_overlap(x1, y1, x2, y2, x3, y3, x4, y4)
+ var/list/region_x1 = list()
+ var/list/region_y1 = list()
+ var/list/region_x2 = list()
+ var/list/region_y2 = list()
+
+ // These loops create loops filled with x/y values that the boundaries inhabit
+ // ex: list(5, 6, 7, 8, 9)
+ for(var/i in min(x1, x2) to max(x1, x2))
+ region_x1["[i]"] = TRUE
+ for(var/i in min(y1, y2) to max(y1, y2))
+ region_y1["[i]"] = TRUE
+ for(var/i in min(x3, x4) to max(x3, x4))
+ region_x2["[i]"] = TRUE
+ for(var/i in min(y3, y4) to max(y3, y4))
+ region_y2["[i]"] = TRUE
+
+ return list(region_x1 & region_x2, region_y1 & region_y2)
\ No newline at end of file
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index acb86875a5a..3256f6375e9 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -14,6 +14,7 @@ GLOBAL_LIST_INIT(janitorial_equipment, list()) //list of janitorial equipment
GLOBAL_LIST_INIT(crafting_recipes, list()) //list of all crafting recipes
GLOBAL_LIST_INIT(prisoncomputer_list, list())
GLOBAL_LIST_INIT(cell_logs, list())
+GLOBAL_LIST_INIT(navigation_computers, list())
GLOBAL_LIST_INIT(all_areas, list())
GLOBAL_LIST_INIT(machines, list())
diff --git a/code/controllers/subsystem/shuttles.dm b/code/controllers/subsystem/shuttles.dm
index 64ae30f2952..30021df4fb1 100644
--- a/code/controllers/subsystem/shuttles.dm
+++ b/code/controllers/subsystem/shuttles.dm
@@ -39,6 +39,8 @@ SUBSYSTEM_DEF(shuttle)
var/list/supply_packs = list()
var/datum/round_event/shuttle_loan/shuttle_loan
var/sold_atoms = ""
+ var/list/hidden_shuttle_turfs = list() //all turfs hidden from navigation computers associated with a list containing the image hiding them and the type of the turf they are pretending to be
+ var/list/hidden_shuttle_turf_images = list() //only the images from the above list
/datum/controller/subsystem/shuttle/Initialize(start_timeofday)
ordernum = rand(1,9000)
@@ -49,7 +51,7 @@ SUBSYSTEM_DEF(shuttle)
WARNING("No /obj/docking_port/mobile/emergency/backup placed on the map!")
if(!supply)
WARNING("No /obj/docking_port/mobile/supply placed on the map!")
-
+
initial_load()
for(var/typepath in subtypesof(/datum/supply_packs))
@@ -259,4 +261,54 @@ SUBSYSTEM_DEF(shuttle)
return O
+/datum/controller/subsystem/shuttle/proc/get_dock_overlap(x0, y0, x1, y1, z)
+ . = list()
+ var/list/stationary_cache = stationary
+ for(var/i in 1 to stationary_cache.len)
+ var/obj/docking_port/port = stationary_cache[i]
+ if(!port || port.z != z)
+ continue
+ var/list/bounds = port.return_coords()
+ var/list/overlap = get_overlap(x0, y0, x1, y1, bounds[1], bounds[2], bounds[3], bounds[4])
+ var/list/xs = overlap[1]
+ var/list/ys = overlap[2]
+ if(xs.len && ys.len)
+ .[port] = overlap
+
+/datum/controller/subsystem/shuttle/proc/update_hidden_docking_ports(list/remove_turfs, list/add_turfs)
+ var/list/remove_images = list()
+ var/list/add_images = list()
+
+ if(remove_turfs)
+ for(var/T in remove_turfs)
+ var/list/L = hidden_shuttle_turfs[T]
+ if(L)
+ remove_images += L[1]
+ hidden_shuttle_turfs -= remove_turfs
+
+ if(add_turfs)
+ for(var/V in add_turfs)
+ var/turf/T = V
+ var/image/I
+ if(remove_images.len)
+ //we can just reuse any images we are about to delete instead of making new ones
+ I = remove_images[1]
+ remove_images.Cut(1, 2)
+ I.loc = T
+ else
+ I = image(loc = T)
+ add_images += I
+ I.appearance = T.appearance
+ I.override = TRUE
+ hidden_shuttle_turfs[T] = list(I, T.type)
+
+ hidden_shuttle_turf_images -= remove_images
+ hidden_shuttle_turf_images += add_images
+
+ for(var/V in GLOB.navigation_computers)
+ var/obj/machinery/computer/camera_advanced/shuttle_docker/C = V
+ C.update_hidden_docking_ports(remove_images, add_images)
+
+ QDEL_LIST(remove_images)
+
#undef CALL_SHUTTLE_REASON_LENGTH
\ No newline at end of file
diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm
index 80e56f0ca82..67f1527b199 100644
--- a/code/game/machinery/computer/computer.dm
+++ b/code/game/machinery/computer/computer.dm
@@ -35,25 +35,24 @@
/obj/machinery/computer/ex_act(severity)
- switch(severity)
- if(1.0)
- qdel(src)
- return
- if(2.0)
- if(prob(25))
+ if(!(resistance_flags & INDESTRUCTIBLE))
+ switch(severity)
+ if(1.0)
qdel(src)
return
- if(prob(50))
- for(var/x in verbs)
- verbs -= x
- set_broken()
- if(3.0)
- if(prob(25))
- for(var/x in verbs)
- verbs -= x
- set_broken()
- else
- return
+ if(2.0)
+ if(prob(25))
+ qdel(src)
+ return
+ if(prob(50))
+ for(var/x in verbs)
+ verbs -= x
+ set_broken()
+ if(3.0)
+ if(prob(25))
+ for(var/x in verbs)
+ verbs -= x
+ set_broken()
/obj/machinery/computer/bullet_act(var/obj/item/projectile/Proj)
if(prob(Proj.damage))
@@ -97,8 +96,9 @@
set_light(light_range_on, light_power_on)
/obj/machinery/computer/proc/set_broken()
- stat |= BROKEN
- update_icon()
+ if(!(resistance_flags & INDESTRUCTIBLE))
+ stat |= BROKEN
+ update_icon()
/obj/machinery/computer/proc/decode(text)
// Adds line breaks
@@ -114,7 +114,7 @@
return ..()
/obj/machinery/computer/attackby(obj/I, mob/user, params)
- if(istype(I, /obj/item/screwdriver) && circuit)
+ if(istype(I, /obj/item/screwdriver) && circuit && !(resistance_flags & INDESTRUCTIBLE))
var/obj/item/screwdriver/S = I
playsound(src.loc, S.usesound, 50, 1)
if(do_after(user, 20 * S.toolspeed, target = src))
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm
new file mode 100644
index 00000000000..8b5c73e32cb
--- /dev/null
+++ b/code/modules/shuttle/navigation_computer.dm
@@ -0,0 +1,348 @@
+/obj/machinery/computer/camera_advanced/shuttle_docker
+ name = "navigation computer"
+ desc = "Used to designate a precise transit location for a spacecraft."
+ 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 = "custom location"
+ var/list/jumpto_ports = list() //list of ports to jump to
+ var/access_station = TRUE //can we park near the station?
+ var/access_sats = TRUE //can we park near telecomms/construction site?
+ var/access_mining = TRUE //can we park near the mining asteroid?
+ var/access_derelict = FALSE //can we explore the derelict zlevel?
+ var/obj/docking_port/stationary/my_port //the custom docking port placed by this console
+ var/obj/docking_port/mobile/shuttle_port //the mobile docking port of the connected shuttle
+ var/view_range = 7
+ var/x_offset = 0
+ var/y_offset = 0
+ var/space_turfs_only = TRUE
+ var/see_hidden = FALSE
+ var/designate_time = 0
+ var/turf/designating_target_loc
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/Initialize()
+ . = ..()
+ GLOB.navigation_computers += src
+ if(access_station)
+ jumpto_ports += list("nav_z1" = 1)
+ if(access_sats)
+ jumpto_ports += list("nav_z3" = 1, "nav_z4" = 1)
+ if(access_mining)
+ jumpto_ports += list("nav_z5" = 1)
+ if(access_derelict)
+ jumpto_ports += list("nav_z6" = 1)
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/Destroy()
+ . = ..()
+ GLOB.navigation_computers -= src
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/attack_hand(mob/user)
+ if(!shuttle_port && !SSshuttle.getShuttle(shuttleId))
+ to_chat(user,"Warning: Shuttle connection severed!")
+ return
+ return ..()
+
+/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
+ ..()
+ /* //technically working but some icons are buggy as shit and either don't rotate or rotate wrong :
+ //namely shuttle walls, shuttle windows, shuttle engines and buckled mobs
+ 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()
+ shuttle_port = SSshuttle.getShuttle(shuttleId)
+ if(QDELETED(shuttle_port))
+ shuttle_port = null
+ return
+
+ eyeobj = new /mob/camera/aiEye/remote/shuttle_docker(null, src)
+ var/mob/camera/aiEye/remote/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)
+ var/area/A = V
+ for(var/turf/T in A)
+ if(T.z != origin.z)
+ continue
+ var/image/I = image('icons/effects/alphacolors.dmi', origin, "red")
+ var/x_off = T.x - origin.x
+ var/y_off = T.y - origin.y
+ I.loc = locate(origin.x + x_off, origin.y + y_off, origin.z) //we have to set this after creating the image because it might be null, and images created in nullspace are immutable.
+ I.layer = ABOVE_NORMAL_TURF_LAYER
+ I.plane = 0
+ I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ the_eye.placement_images[I] = list(x_off, y_off)
+
+/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
+ var/list/to_add = list()
+ to_add += the_eye.placement_images
+ to_add += the_eye.placed_images
+ if(!see_hidden)
+ to_add += SSshuttle.hidden_shuttle_turf_images
+
+ user.client.images += to_add
+ user.client.SetView(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
+ var/list/to_remove = list()
+ to_remove += the_eye.placement_images
+ to_remove += the_eye.placed_images
+ if(!see_hidden)
+ to_remove += SSshuttle.hidden_shuttle_turf_images
+
+ user.client.images -= to_remove
+ user.client.SetView(7)
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/placeLandingSpot()
+ if(designating_target_loc || !current_user)
+ return
+
+ var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
+ var/landing_clear = checkLandingSpot()
+ if(designate_time && (landing_clear != SHUTTLE_DOCKER_BLOCKED))
+ to_chat(current_user, "Targeting transit location, please wait [DisplayTimeText(designate_time)]...")
+ designating_target_loc = the_eye.loc
+ var/wait_completed = do_after(current_user, designate_time, FALSE, designating_target_loc, TRUE, CALLBACK(src, /obj/machinery/computer/camera_advanced/shuttle_docker/proc/canDesignateTarget))
+ designating_target_loc = null
+ if(!current_user)
+ return
+ if(!wait_completed)
+ to_chat(current_user, "Operation aborted.")
+ return
+ landing_clear = checkLandingSpot()
+
+ if(landing_clear != SHUTTLE_DOCKER_LANDING_CLEAR)
+ switch(landing_clear)
+ if(SHUTTLE_DOCKER_BLOCKED)
+ to_chat(current_user, "Invalid transit location")
+ if(SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT)
+ to_chat(current_user, "Unknown object detected in landing zone. Please designate another location.")
+ return
+
+ if(!my_port)
+ my_port = new()
+ my_port.name = shuttlePortName
+ my_port.id = shuttlePortId
+ my_port.height = shuttle_port.height
+ my_port.width = shuttle_port.width
+ my_port.dheight = shuttle_port.dheight
+ my_port.dwidth = shuttle_port.dwidth
+ my_port.hidden = shuttle_port.hidden
+ my_port.register()
+ my_port.setDir(the_eye.dir)
+ my_port.forceMove(locate(eyeobj.x - x_offset, eyeobj.y - y_offset, eyeobj.z))
+ if(current_user.client)
+ current_user.client.images -= the_eye.placed_images
+
+ QDEL_LIST(the_eye.placed_images)
+
+ for(var/V in the_eye.placement_images)
+ var/image/I = V
+ var/image/newI = image('icons/effects/alphacolors.dmi', the_eye.loc, "blue")
+ newI.loc = I.loc //It is highly unlikely that any landing spot including a null tile will get this far, but better safe than sorry.
+ newI.layer = ABOVE_OPEN_TURF_LAYER
+ newI.plane = 0
+ newI.mouse_opacity = 0
+ the_eye.placed_images += newI
+
+ if(current_user.client)
+ current_user.client.images += the_eye.placed_images
+ to_chat(current_user, "Transit location designated")
+ return
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/canDesignateTarget()
+ if(!designating_target_loc || !current_user || (eyeobj.loc != designating_target_loc) || (stat & (NOPOWER|BROKEN)) )
+ return FALSE
+ return TRUE
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot()
+ var/mob/camera/aiEye/remote/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 image_cache.len)
+ var/image/pic = image_cache[i]
+ var/list/coords = image_cache[pic]
+ var/Tmp = coords[1]
+ coords[1] = coords[2]
+ coords[2] = -Tmp
+ pic.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z)
+ var/Tmp = x_offset
+ x_offset = y_offset
+ y_offset = -Tmp
+ checkLandingSpot()
+
+/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 SHUTTLE_DOCKER_BLOCKED
+
+ . = SHUTTLE_DOCKER_LANDING_CLEAR
+ var/list/bounds = shuttle_port.return_coords(the_eye.x - x_offset, the_eye.y - y_offset, the_eye.dir)
+ var/list/overlappers = SSshuttle.get_dock_overlap(bounds[1], bounds[2], bounds[3], bounds[4], the_eye.z)
+ var/list/image_cache = the_eye.placement_images
+ for(var/i in 1 to image_cache.len)
+ var/image/I = image_cache[i]
+ var/list/coords = image_cache[I]
+ var/turf/T = locate(eyeturf.x + coords[1], eyeturf.y + coords[2], eyeturf.z)
+ I.loc = T
+ switch(checkLandingTurf(T, overlappers))
+ if(SHUTTLE_DOCKER_LANDING_CLEAR)
+ I.icon_state = "green"
+ if(SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT)
+ I.icon_state = "green"
+ if(. == SHUTTLE_DOCKER_LANDING_CLEAR)
+ . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
+ else
+ I.icon_state = "red"
+ . = SHUTTLE_DOCKER_BLOCKED
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingTurf(turf/T, list/overlappers)
+ // Too close to the map edge is never allowed
+ if(!T || T.x <= 10 || T.y <= 10 || T.x >= world.maxx - 10 || T.y >= world.maxy - 10)
+ return SHUTTLE_DOCKER_BLOCKED
+ // If it's one of our shuttle areas assume it's ok to be there
+ if(shuttle_port.shuttle_areas[T.loc])
+ return SHUTTLE_DOCKER_LANDING_CLEAR
+ . = SHUTTLE_DOCKER_LANDING_CLEAR
+ // See if the turf is hidden from us
+ var/list/hidden_turf_info
+ if(!see_hidden)
+ hidden_turf_info = SSshuttle.hidden_shuttle_turfs[T]
+ if(hidden_turf_info)
+ . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
+
+ if(space_turfs_only)
+ var/turf_type = hidden_turf_info ? hidden_turf_info[2] : T.type
+ if(!ispath(turf_type, /turf/space))
+ return SHUTTLE_DOCKER_BLOCKED
+
+ if(istype(T.loc.type, /area/syndicate_depot))
+ return SHUTTLE_DOCKER_BLOCKED
+
+ // Checking for overlapping dock boundaries
+ for(var/i in 1 to overlappers.len)
+ var/obj/docking_port/port = overlappers[i]
+ if(port == my_port || locate(port) in jumpto_ports)
+ continue
+ var/port_hidden = !see_hidden && port.hidden
+ var/list/overlap = overlappers[port]
+ var/list/xs = overlap[1]
+ var/list/ys = overlap[2]
+ if(xs["[T.x]"] && ys["[T.y]"])
+ if(port_hidden)
+ . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
+ else
+ return SHUTTLE_DOCKER_BLOCKED
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/update_hidden_docking_ports(list/remove_images, list/add_images)
+ if(!see_hidden && current_user && current_user.client)
+ current_user.client.images -= remove_images
+ current_user.client.images += add_images
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+ if(port && (shuttleId == initial(shuttleId) || override))
+ shuttleId = port.id
+ shuttlePortId = "[port.id]_custom"
+ if(dock)
+ jumpto_ports[dock.id] = TRUE
+
+/mob/camera/aiEye/remote/shuttle_docker
+ visible_icon = FALSE
+ use_static = FALSE
+ simulated = FALSE
+ var/list/placement_images = list()
+ var/list/placed_images = list()
+
+/mob/camera/aiEye/remote/shuttle_docker/Initialize(mapload, obj/machinery/computer/camera_advanced/origin)
+ src.origin = origin
+ return ..()
+
+/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 = 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
+ origin.placeLandingSpot(target)
+
+/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.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]")
+ else
+ playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
\ No newline at end of file
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index 4a587ac1253..44edebdca50 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -57,19 +57,4 @@
/obj/machinery/door/airlock/postDock(obj/docking_port/stationary/S1)
. = ..()
if(!S1.lock_shuttle_doors && id_tag == "s_docking_airlock")
- INVOKE_ASYNC(src, .proc/unlock)
-
-// Shuttle Rotation //
-/atom/proc/shuttleRotate(rotation)
- //rotate our direction
- dir = angle2dir(rotation+dir2angle(dir))
-
- //rotate the pixel offsets too.
- if(pixel_x || pixel_y)
- if(rotation < 0)
- rotation += 360
- for(var/turntimes=rotation/90;turntimes>0;turntimes--)
- var/oldPX = pixel_x
- var/oldPY = pixel_y
- pixel_x = oldPY
- pixel_y = (oldPX*(-1))
+ INVOKE_ASYNC(src, .proc/unlock)
\ No newline at end of file
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index fe00b6b3611..75aea7725a3 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -24,6 +24,7 @@
var/timid = FALSE
var/list/ripples = list()
+ var/hidden = FALSE //are we invisible to shuttle navigation computers?
//these objects are indestructable
/obj/docking_port/Destroy(force)
@@ -76,6 +77,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
@@ -200,6 +207,7 @@
icon_state = "pinonclose"
var/area/shuttle/areaInstance
+ var/list/shuttle_areas
var/timer //used as a timer (if you want time left to complete move, use timeLeft proc)
var/mode = SHUTTLE_IDLE //current shuttle mode (see global defines)
@@ -227,12 +235,16 @@
highlight("#0f0")
#endif
-
-
-
/obj/docking_port/mobile/Initialize()
if(!timid)
register()
+ shuttle_areas = list()
+ var/list/all_turfs = return_ordered_turfs(x, y, z, dir)
+ for(var/i in 1 to all_turfs.len)
+ var/turf/curT = all_turfs[i]
+ var/area/cur_area = curT.loc
+ if(istype(cur_area, areaInstance))
+ shuttle_areas[cur_area] = TRUE
..()
/obj/docking_port/mobile/register()
@@ -255,6 +267,7 @@
areaInstance = null
destination = null
previous = null
+ shuttle_areas = null
return ..()
//this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact
@@ -821,11 +834,10 @@
/obj/machinery/computer/shuttle/ert
name = "specops shuttle console"
- //circuit = /obj/item/circuitboard/ert
req_access = list(access_cent_general)
shuttleId = "specops"
possible_destinations = "specops_home;specops_away"
-
+ resistance_flags = INDESTRUCTIBLE
/obj/machinery/computer/shuttle/white_ship
name = "White Ship Console"
@@ -834,12 +846,6 @@
shuttleId = "whiteship"
possible_destinations = "whiteship_away;whiteship_home;whiteship_z4"
-/obj/machinery/computer/shuttle/vox
- name = "skipjack control console"
- req_access = list(access_vox)
- shuttleId = "skipjack"
- possible_destinations = "skipjack_away;skipjack_ne;skipjack_nw;skipjack_se;skipjack_sw;skipjack_z5"
-
/obj/machinery/computer/shuttle/engineering
name = "Engineering Shuttle Console"
desc = "Used to call and send the engineering shuttle."
@@ -857,28 +863,11 @@
desc = "Used to call and send the administration shuttle."
shuttleId = "admin"
possible_destinations = "admin_home;admin_away"
-
-/obj/machinery/computer/shuttle/sst
- name = "Syndicate Strike Time Shuttle Console"
- desc = "Used to call and send the SST shuttle."
- icon_keyboard = "syndie_key"
- icon_screen = "syndishuttle"
- req_access = list(access_syndicate)
- shuttleId = "sst"
- possible_destinations = "sst_home;sst_away"
-
-/obj/machinery/computer/shuttle/sit
- name = "Syndicate Infiltration Team Shuttle Console"
- desc = "Used to call and send the SIT shuttle."
- icon_keyboard = "syndie_key"
- icon_screen = "syndishuttle"
- req_access = list(access_syndicate)
- shuttleId = "sit"
- possible_destinations = "sit_arrivals;sit_engshuttle;sit_away"
-
+ resistance_flags = INDESTRUCTIBLE
/obj/machinery/computer/shuttle/trade
name = "Freighter Console"
+ resistance_flags = INDESTRUCTIBLE
/obj/machinery/computer/shuttle/trade/sol
req_access = list(access_trade_sol)
diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/shuttle_rotate.dm
new file mode 100644
index 00000000000..f2f35b1139c
--- /dev/null
+++ b/code/modules/shuttle/shuttle_rotate.dm
@@ -0,0 +1,112 @@
+/*
+All shuttleRotate procs go here
+If ever any of these procs are useful for non-shuttles, rename it to proc/rotate and move it to be a generic atom proc
+*/
+
+/************************************Base proc************************************/
+
+/atom/proc/shuttleRotate(rotation, params=ROTATE_DIR|ROTATE_SMOOTH|ROTATE_OFFSET)
+ if(params & ROTATE_DIR)
+ //rotate our direction
+ setDir(angle2dir(rotation+dir2angle(dir)))
+
+ //resmooth if need be.
+ if(smooth && (params & ROTATE_SMOOTH))
+ queue_smooth(src)
+
+ //rotate the pixel offsets too.
+ if((pixel_x || pixel_y) && (params & ROTATE_OFFSET))
+ if(rotation < 0)
+ rotation += 360
+ for(var/turntimes=rotation/90;turntimes>0;turntimes--)
+ var/oldPX = pixel_x
+ var/oldPY = pixel_y
+ pixel_x = oldPY
+ pixel_y = (oldPX*(-1))
+
+/************************************Turf rotate procs************************************/
+
+/************************************Mob rotate procs************************************/
+
+//override to avoid rotating pixel_xy on mobs
+/mob/shuttleRotate(rotation, params)
+ params = NONE
+ . = ..()
+ if(!buckled)
+ setDir(angle2dir(rotation+dir2angle(dir)))
+
+/mob/dead/observer/shuttleRotate(rotation, params)
+ . = ..()
+ update_icons()
+
+/************************************Structure rotate procs************************************/
+
+/obj/structure/cable/shuttleRotate(rotation, params)
+ params &= ~ROTATE_DIR
+ . = ..()
+ if(d1)
+ d1 = angle2dir(rotation+dir2angle(d1))
+ if(d2)
+ d2 = angle2dir(rotation+dir2angle(d2))
+
+ //d1 should be less than d2 for cable icons to work
+ if(d1 > d2)
+ var/temp = d1
+ d1 = d2
+ d2 = temp
+ update_icon()
+
+//Fixes dpdir on shuttle rotation
+/obj/structure/disposalpipe/shuttleRotate(rotation, params)
+ . = ..()
+ var/new_dpdir = 0
+ for(var/D in list(NORTH, SOUTH, EAST, WEST))
+ if(dpdir & D)
+ new_dpdir = new_dpdir | angle2dir(rotation+dir2angle(D))
+ dpdir = new_dpdir
+
+/obj/structure/alien/weeds/shuttleRotate(rotation, params)
+ params &= ~ROTATE_OFFSET
+ return ..()
+
+//prevents shuttles attempting to rotate this since it messes up sprites
+/obj/structure/table/shuttleRotate(rotation, params)
+ params = NONE
+ return ..()
+
+/obj/structure/table_frame/shuttleRotate(rotation, params)
+ params = NONE
+ return ..()
+
+/************************************Machine rotate procs************************************/
+
+/*/obj/machinery/atmospherics/shuttleRotate(rotation, params)
+ var/list/real_node_connect = getNodeConnects()
+ for(var/i in 1 to device_type)
+ real_node_connect[i] = angle2dir(rotation+dir2angle(real_node_connect[i]))
+ . = ..()
+ SetInitDirections()
+ var/list/supposed_node_connect = getNodeConnects()
+ var/list/nodes_copy = nodes.Copy()
+ for(var/i in 1 to device_type)
+ var/new_pos = supposed_node_connect.Find(real_node_connect[i])
+ nodes[new_pos] = nodes_copy[i]
+*/
+//prevents shuttles attempting to rotate this since it messes up sprites
+/obj/machinery/gateway/shuttleRotate(rotation, params)
+ params = NONE
+ return ..()
+
+//prevents shuttles attempting to rotate this since it messes up sprites
+/obj/machinery/gravity_generator/shuttleRotate(rotation, params)
+ params = NONE
+ return ..()
+/*
+/obj/machinery/door/airlock/shuttleRotate(rotation, params)
+ . = ..()
+ if(cyclelinkeddir && (params & ROTATE_DIR))
+ cyclelinkeddir = angle2dir(rotation+dir2angle(cyclelinkeddir))
+ // If we update the linked airlock here, the partner airlock might
+ // not be present yet, so don't do that. Just assume we're still
+ // partnered with the same airlock as before.
+*/
\ No newline at end of file
diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm
index 14fde8c1be0..532fedc5497 100644
--- a/code/modules/shuttle/syndicate.dm
+++ b/code/modules/shuttle/syndicate.dm
@@ -7,7 +7,8 @@
req_access = list(access_syndicate)
circuit = /obj/item/circuitboard/shuttle/syndicate
shuttleId = "syndicate"
- possible_destinations = "syndicate_away;syndicate_z5;syndicate_z3;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s"
+ possible_destinations = "syndicate_away;syndicate_z5;syndicate_z3;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s;syndicate_custom"
+ resistance_flags = INDESTRUCTIBLE
var/challenge = FALSE
var/moved = FALSE
@@ -40,4 +41,55 @@
return 0
..()
-#undef SYNDICATE_CHALLENGE_TIMER
+/obj/machinery/computer/shuttle/sst
+ name = "Syndicate Strike Time Shuttle Console"
+ desc = "Used to call and send the SST shuttle."
+ icon_keyboard = "syndie_key"
+ icon_screen = "syndishuttle"
+ req_access = list(access_syndicate)
+ shuttleId = "sst"
+ possible_destinations = "sst_home;sst_away;sst_custom"
+ resistance_flags = INDESTRUCTIBLE
+
+/obj/machinery/computer/shuttle/sit
+ name = "Syndicate Infiltration Team Shuttle Console"
+ desc = "Used to call and send the SIT shuttle."
+ icon_keyboard = "syndie_key"
+ icon_screen = "syndishuttle"
+ req_access = list(access_syndicate)
+ shuttleId = "sit"
+ possible_destinations = "sit_arrivals;sit_engshuttle;sit_away;sit_custom"
+ resistance_flags = INDESTRUCTIBLE
+
+/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"
+ shuttleId = "syndicate"
+ shuttlePortId = "syndicate_custom"
+ view_range = 13
+ x_offset = -5
+ y_offset = -1
+ see_hidden = TRUE
+ resistance_flags = INDESTRUCTIBLE
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/SST
+ name = "SST shuttle navigation computer"
+ desc = "Used to designate a precise transit location for the SST shuttle."
+ shuttleId = "sst"
+ shuttlePortId = "sst_custom"
+ view_range = 13
+ x_offset = 0
+ y_offset = 0
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/SIT
+ name = "SIT shuttle navigation computer"
+ desc = "Used to designate a precise transit location for the SIT shuttle."
+ shuttleId = "sit"
+ shuttlePortId = "sit_custom"
+ view_range = 13
+ x_offset = 0
+ y_offset = 0
+
+#undef SYNDICATE_CHALLENGE_TIMER
\ No newline at end of file
diff --git a/code/modules/shuttle/vox.dm b/code/modules/shuttle/vox.dm
new file mode 100644
index 00000000000..7b3be543e20
--- /dev/null
+++ b/code/modules/shuttle/vox.dm
@@ -0,0 +1,19 @@
+/obj/machinery/computer/shuttle/vox
+ name = "skipjack control console"
+ req_access = list(access_vox)
+ shuttleId = "skipjack"
+ possible_destinations = "skipjack_away;skipjack_ne;skipjack_nw;skipjack_se;skipjack_sw;skipjack_z5;skipjack_custom"
+ resistance_flags = INDESTRUCTIBLE
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/vox
+ name = "skipjack navigation computer"
+ desc = "Used to designate a precise transit location for the skipjack."
+ icon_screen = "shuttle"
+ icon_keyboard = "tech_key"
+ shuttleId = "skipjack"
+ shuttlePortId = "skipjack_custom"
+ view_range = 13
+ x_offset = -10
+ y_offset = -10
+ resistance_flags = INDESTRUCTIBLE
+ access_derelict = TRUE
diff --git a/code/modules/space_management/space_level.dm b/code/modules/space_management/space_level.dm
index e53264e5d57..67fd1d6dc69 100644
--- a/code/modules/space_management/space_level.dm
+++ b/code/modules/space_management/space_level.dm
@@ -27,6 +27,7 @@
flags = traits
build_space_destination_arrays()
set_linkage(transition_type)
+ set_navbeacon()
/datum/space_level/Destroy()
if(linkage == CROSSLINKED)
@@ -134,6 +135,14 @@
if(SELFLOOPING)
link_to_self() // `link_to_self` is defined in space_transitions.dm
+//create docking ports for navigation consoles to jump to
+/datum/space_level/proc/set_navbeacon()
+ var/obj/docking_port/stationary/D = new /obj/docking_port/stationary(src)
+ D.name = name
+ D.id = "nav_z[zpos]"
+ D.register()
+ D.forceMove(locate(122, 122, zpos))
+
var/list/atmos_machine_typecache = typecacheof(/obj/machinery/atmospherics)
var/list/cable_typecache = typecacheof(/obj/structure/cable)
var/list/maploader_typecache = typecacheof(/obj/effect/landmark/map_loader)
diff --git a/paradise.dme b/paradise.dme
index 912ff79c344..020aa9d41ae 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -54,6 +54,7 @@
#include "code\__DEFINES\reagents.dm"
#include "code\__DEFINES\role_preferences.dm"
#include "code\__DEFINES\rolebans.dm"
+#include "code\__DEFINES\shuttle.dm"
#include "code\__DEFINES\sight.dm"
#include "code\__DEFINES\snpc.dm"
#include "code\__DEFINES\sound.dm"
@@ -2265,12 +2266,15 @@
#include "code\modules\security_levels\security levels.dm"
#include "code\modules\shuttle\assault_pod.dm"
#include "code\modules\shuttle\emergency.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"
#include "code\modules\shuttle\shuttle_manipulator.dm"
+#include "code\modules\shuttle\shuttle_rotate.dm"
#include "code\modules\shuttle\supply.dm"
#include "code\modules\shuttle\syndicate.dm"
+#include "code\modules\shuttle\vox.dm"
#include "code\modules\space_management\heap_space_level.dm"
#include "code\modules\space_management\level_check.dm"
#include "code\modules\space_management\level_traits.dm"