diff --git a/aurorastation.dme b/aurorastation.dme
index 19a57b5831d..5dff3a36396 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -1592,6 +1592,10 @@
#include "code\modules\clothing\wrists\watches.dm"
#include "code\modules\clothing\wrists\wrists.dm"
#include "code\modules\clothing\wrists\xeno\unathi.dm"
+#include "code\modules\compass\_compass.dm"
+#include "code\modules\compass\compass_holder.dm"
+#include "code\modules\compass\compass_waypoint.dm"
+#include "code\modules\compass\~compass.dm"
#include "code\modules\cooking\trays.dm"
#include "code\modules\cooking\machinery\gibber.dm"
#include "code\modules\cooking\machinery\icecream.dm"
diff --git a/code/__defines/_layers.dm b/code/__defines/_layers.dm
index a018e75bff0..e5a4670dc33 100644
--- a/code/__defines/_layers.dm
+++ b/code/__defines/_layers.dm
@@ -19,6 +19,7 @@
#define ABOVE_ALL_MOB_LAYER 4.5
#define LIGHTING_LAYER 11
#define EFFECTS_ABOVE_LIGHTING_LAYER 12 // For overlays you want to be above light.
+#define UNDER_HUD_LAYER 19
#define HUD_LAYER 20 //Above lighting, but below obfuscation. For in-game HUD effects (whereas SCREEN_LAYER is for abstract/OOC things like inventory slots)
#define OBFUSCATION_LAYER 21 //Where images covering the view for eyes are put
#define SCREEN_LAYER 22 //Mob HUD/effects layer
diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm
index a402670dcce..6fe43923af6 100644
--- a/code/_helpers/game.dm
+++ b/code/_helpers/game.dm
@@ -116,9 +116,9 @@
turfs += T
return turfs
-// Will recursively loop through an atom's locs until it finds the atom loc above a turf
-/proc/recursive_loc_turf_check(var/atom/O, var/recursion_limit = 3)
- if(recursion_limit <= 0 || isturf(O.loc))
+// Will recursively loop through an atom's locs until it finds the atom loc above a turf or its target_atom
+/proc/recursive_loc_turf_check(var/atom/O, var/recursion_limit = 3, var/atom/target_atom)
+ if(recursion_limit <= 0 || isturf(O.loc) || O == target_atom)
return O
else
O = O.loc
diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index 2ff74f50b2e..4995a5ddc96 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -1027,6 +1027,10 @@ var/global/known_proc = new /proc/get_type_ref_bytes
colour += temp_col
return "#[colour]"
+/proc/color_square(red, green, blue, hex)
+ var/color = hex ? hex : "#[num2hex(red, 2)][num2hex(green, 2)][num2hex(blue, 2)]"
+ return "___"
+
// call to generate a stack trace and print to runtime logs
/proc/crash_with(msg)
CRASH(msg)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 13f63b251f5..0fb7554d806 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -339,7 +339,7 @@
// Called whenever an object is moved around inside the mob's contents.
// Linker proc: mob/proc/prepare_for_slotmove, which is referenced in proc/handle_item_insertion and obj/item/attack_hand.
// This shit exists so that dropped() could almost exclusively be called when an item is dropped.
-/obj/item/proc/on_slotmove(var/mob/user)
+/obj/item/proc/on_slotmove(var/mob/user, slot)
if(zoom)
zoom(user)
diff --git a/code/modules/compass/_compass.dm b/code/modules/compass/_compass.dm
new file mode 100644
index 00000000000..1ceb975b0c2
--- /dev/null
+++ b/code/modules/compass/_compass.dm
@@ -0,0 +1,15 @@
+#define COMPASS_INTERVAL 3
+#define COMPASS_PERIOD 15
+#define COMPASS_LABEL_OFFSET 150
+
+/*
+ This folder contains an abstract type (/obj/compass_holder) which contains a set of
+ waypoints (/datum/compass_waypoint) and generates a circular compass with markers for
+ mobs that have the object in their screen list. See GPS for an example implementation.
+*/
+
+/image/compass_marker
+ maptext_height = 64
+ maptext_width = 128
+ maptext_x = -48
+ maptext_y = -32
\ No newline at end of file
diff --git a/code/modules/compass/compass_holder.dm b/code/modules/compass/compass_holder.dm
new file mode 100644
index 00000000000..2eca5759b8d
--- /dev/null
+++ b/code/modules/compass/compass_holder.dm
@@ -0,0 +1,125 @@
+/obj/compass_holder
+ name = null
+ icon = null
+ icon_state = null
+ screen_loc = "CENTER,CENTER"
+
+ var/show_heading = FALSE
+ var/numeric_directions = FALSE
+
+ var/image/compass_heading_marker
+ var/list/compass_static_labels
+ var/list/compass_waypoints
+ var/list/compass_waypoint_markers
+
+ var/static/list/angle_step_to_dir = list(
+ "N",
+ "NE",
+ "E",
+ "SE",
+ "S",
+ "SW",
+ "W",
+ "NW",
+ "N"
+ )
+
+/obj/compass_holder/Initialize(mapload, ...)
+ . = ..()
+ if(show_heading)
+ compass_heading_marker = new /image/compass_marker
+ compass_heading_marker.maptext = "
△"
+ compass_heading_marker.filters = filter(type="drop_shadow", color = "#00ffffaa", size = 2, offset = 1,x = 0, y = 0)
+ compass_heading_marker.layer = UNDER_HUD_LAYER
+
+ for(var/i in 0 to (360/(COMPASS_PERIOD))-1)
+ var/image/I = new /image/compass_marker
+ I.loc = src
+ var/str
+ var/str_col
+ if(i % COMPASS_INTERVAL == 0)
+ var/angle = (i * COMPASS_PERIOD)
+ if(numeric_directions)
+ str = "[angle]"
+ else
+ str = angle_step_to_dir[clamp(round(angle/45)+1, 1, length(angle_step_to_dir))]
+ str_col = "#ffffffaa"
+ else
+ str = "〡"
+ str_col = "#aaaaaa88"
+ I.maptext = "[str]"
+ var/matrix/M = matrix()
+ M.Translate(0, COMPASS_LABEL_OFFSET)
+ M.Turn(COMPASS_PERIOD * i)
+ I.transform = M
+ I.filters = filter(type="drop_shadow", color = "#77777777", size = 2, offset = 1,x = 0, y = 0)
+ I.layer = UNDER_HUD_LAYER
+ LAZYADD(compass_static_labels, I)
+
+ rebuild_overlay_lists(TRUE)
+
+/obj/compass_holder/Destroy()
+ QDEL_NULL_LIST(compass_waypoints)
+ . = ..()
+
+/obj/compass_holder/proc/get_heading()
+ var/atom/A = loc?.loc // is there a get_holder_recursive() equivalent on Polaris?
+ if(istype(A))
+ . = dir2angle(A.dir)
+ else
+ . = 0
+
+/obj/compass_holder/update_icon()
+ var/set_overlays = (compass_static_labels | compass_waypoint_markers)
+ if(show_heading)
+ set_overlays |= compass_heading_marker
+ overlays = set_overlays
+
+/obj/compass_holder/proc/clear_waypoint(var/id)
+ LAZYREMOVE(compass_waypoints, id)
+ rebuild_overlay_lists(TRUE)
+
+/obj/compass_holder/proc/set_waypoint(var/id, var/label, var/heading_x, var/heading_y, var/heading_z, var/label_color)
+ var/datum/compass_waypoint/wp = LAZYACCESS(compass_waypoints, id)
+ if(!wp)
+ wp = new /datum/compass_waypoint()
+ wp.set_values(label, heading_x, heading_y, heading_z, label_color)
+ LAZYSET(compass_waypoints, id, wp)
+ rebuild_overlay_lists(TRUE)
+
+/obj/compass_holder/proc/recalculate_heading(var/rebuild_icon = TRUE)
+ if(show_heading)
+ var/matrix/M = matrix()
+ M.Translate(0, round(COMPASS_LABEL_OFFSET - 35))
+ M.Turn(get_heading())
+ compass_heading_marker.transform = M
+ if(rebuild_icon)
+ update_icon()
+
+/obj/compass_holder/proc/show_waypoint(var/id)
+ var/datum/compass_waypoint/wp = compass_waypoints[id]
+ wp.hidden = FALSE
+
+/obj/compass_holder/proc/hide_waypoint(var/id)
+ var/datum/compass_waypoint/wp = compass_waypoints[id]
+ wp.hidden = TRUE
+
+/obj/compass_holder/proc/hide_waypoints(var/rebuild_overlays = FALSE)
+ for(var/id in compass_waypoints)
+ hide_waypoint(id)
+ if(rebuild_overlays)
+ rebuild_overlay_lists(TRUE)
+
+/obj/compass_holder/proc/rebuild_overlay_lists(var/update_icon = FALSE)
+ compass_waypoint_markers = null
+ var/turf/T = get_turf(src)
+ if(istype(T))
+ for(var/id in compass_waypoints)
+ var/datum/compass_waypoint/wp = compass_waypoints[id]
+ if(!wp.hidden)
+ wp.recalculate_heading(T.x, T.y)
+ LAZYADD(compass_waypoint_markers, wp.compass_overlay)
+ if(show_heading)
+ recalculate_heading(FALSE)
+ if(update_icon)
+ update_icon()
diff --git a/code/modules/compass/compass_waypoint.dm b/code/modules/compass/compass_waypoint.dm
new file mode 100644
index 00000000000..be9f2f7b4f9
--- /dev/null
+++ b/code/modules/compass/compass_waypoint.dm
@@ -0,0 +1,26 @@
+/datum/compass_waypoint
+ var/name
+ var/x
+ var/y
+ var/z
+ var/color
+ var/hidden = FALSE
+ var/image/compass_overlay
+
+/datum/compass_waypoint/proc/set_values(var/_name, var/_x, var/_y, var/_z, var/_color)
+ name = _name
+ x = _x
+ y = _y
+ z = _z
+ color = _color
+ compass_overlay = new /image/compass_marker
+ compass_overlay.loc = src
+ compass_overlay.maptext = "|\n[name]"
+ compass_overlay.filters = filter(type="drop_shadow", color = "[color]" + "aa", size = 2, offset = 1,x = 0, y = 0)
+ compass_overlay.layer = HUD_LAYER
+
+/datum/compass_waypoint/proc/recalculate_heading(var/cx, var/cy)
+ var/matrix/M = matrix()
+ M.Translate(0, (name ? COMPASS_LABEL_OFFSET-4 : COMPASS_LABEL_OFFSET))
+ M.Turn(arctan(cy-y, cx-x)+180)
+ compass_overlay.transform = M
\ No newline at end of file
diff --git a/code/modules/compass/~compass.dm b/code/modules/compass/~compass.dm
new file mode 100644
index 00000000000..7546d7a1e69
--- /dev/null
+++ b/code/modules/compass/~compass.dm
@@ -0,0 +1,3 @@
+#undef COMPASS_INTERVAL
+#undef COMPASS_PERIOD
+#undef COMPASS_LABEL_OFFSET
\ No newline at end of file
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 55160b38afb..bfc2163d539 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -38,8 +38,7 @@
//This is an UNSAFE proc. It merely handles the actual job of equipping. All the checks on whether you can or can't eqip need to be done before! Use mob_can_equip() for that task.
//In most cases you will want to use equip_to_slot_if_possible()
/mob/proc/equip_to_slot(obj/item/W, slot, redraw_mob, assisted_equip)
- W.on_slotmove(src)
- return
+ W.on_slotmove(src, slot)
//This is just a commonly used configuration for the equip_to_slot_if_possible() proc, used to equip people when the rounds tarts and when events happen and such.
/mob/proc/equip_to_slot_or_del(obj/item/W as obj, slot)
diff --git a/code/modules/telesci/gps.dm b/code/modules/telesci/gps.dm
index ea86386e881..2a12bca4360 100644
--- a/code/modules/telesci/gps.dm
+++ b/code/modules/telesci/gps.dm
@@ -10,19 +10,28 @@ var/list/GPS_list = list()
origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2)
var/gps_prefix = "COM"
var/gpstag = "COM0"
- var/emped = 0
- var/held_by = null
- var/implanted_into = null
+ var/compass_color = "#193A7A"
+
+ var/emped = FALSE
+
+ var/mob/held_by = null
+ var/mob/implanted_into = null
var/turf/locked_location
+
var/list/tracking = list()
+ var/list/tracking_compass
+ var/obj/compass_holder/compass
var/list/static/gps_count = list()
+ var/process_interval = 6 SECONDS
+ var/last_process = 0
+
/obj/item/device/gps/Initialize()
. = ..()
+ compass = new(src)
gpstag = next_initial_tag()
name = "global positioning system ([gpstag])"
update_position()
- add_overlay("working")
if(ismob(loc))
if(ishuman(loc))
@@ -32,10 +41,14 @@ var/list/GPS_list = list()
else
implanted_into = loc
else if(issilicon(loc))
+ held_by = loc
implanted_into = loc
else if(istype(loc, /obj/item/robot_module))
+ held_by = loc.loc
implanted_into = loc.loc
+ update_icon()
+
if(held_by)
moved_event.register(held_by, src, /obj/item/device/gps/proc/update_position)
if(implanted_into)
@@ -45,6 +58,8 @@ var/list/GPS_list = list()
for(var/gps in GPS_list)
tracking += GPS_list[gps]["tag"]
+ START_PROCESSING(SSprocessing, src)
+
/obj/item/device/gps/Destroy()
GPS_list -= GPS_list[gpstag]
moved_event.unregister(src, src)
@@ -54,29 +69,64 @@ var/list/GPS_list = list()
if(implanted_into)
moved_event.unregister(implanted_into, src)
implanted_into = null
+ STOP_PROCESSING(SSprocessing, src)
return ..()
+/obj/item/device/gps/update_icon()
+ cut_overlays()
+ if(emped)
+ add_overlay("emp")
+ else if(held_by || implanted_into)
+ add_overlay("working")
+ else
+ add_overlay("confused")
+
/obj/item/device/gps/pickup(var/mob/user)
..()
+ if(held_by)
+ moved_event.unregister(held_by, src)
held_by = user
moved_event.register(user, src, /obj/item/device/gps/proc/update_position)
+ update_icon()
/obj/item/device/gps/dropped(var/mob/user)
..()
- held_by = null
- moved_event.unregister(user, src)
+ if(isturf(loc))
+ held_by = null
+ moved_event.unregister(user, src)
+ if(user.client)
+ user.client.screen -= compass
+ update_icon()
+
+/obj/item/device/gps/on_slotmove(mob/user, slot)
+ . = ..()
+ if(user.client && !(slot == slot_r_hand || slot == slot_l_hand))
+ user.client.screen -= compass
+
+/obj/item/device/gps/equipped(mob/user, slot)
+ . = ..()
+ if(user.client && (slot == slot_r_hand || slot == slot_l_hand))
+ user.client.screen |= compass
+
+/obj/item/device/gps/on_module_activate(mob/living/silicon/robot/R)
+ ..()
+ if(R.client)
+ R.client.screen |= compass
+
+/obj/item/device/gps/on_module_deactivate(mob/living/silicon/robot/R)
+ ..()
+ if(R.client)
+ R.client.screen -= compass
/obj/item/device/gps/emp_act(severity)
- emped = 1
- cut_overlay("working")
- add_overlay("emp")
- addtimer(CALLBACK(src, .proc/post_emp), 300)
+ emped = TRUE
+ addtimer(CALLBACK(src, .proc/post_emp), 30 SECONDS)
+ update_icon()
update_position()
/obj/item/device/gps/proc/post_emp()
- emped = 0
- cut_overlay("emp")
- add_overlay("working")
+ emped = FALSE
+ update_icon()
update_position()
/obj/item/device/gps/vueui_data_change(var/list/data, var/mob/user, var/datum/vueui/ui)
@@ -95,6 +145,7 @@ var/list/GPS_list = list()
tracking_list[tracking_tag] = (GPS_list[tracking_tag])
data["tracking_list"] = tracking_list
+ data["compass_list"] = tracking_compass
return data // should update constantly
@@ -109,6 +160,11 @@ var/list/GPS_list = list()
ui.auto_update_content = TRUE
ui.open()
+/obj/item/device/gps/process()
+ if(held_by || implanted_into || (world.time < last_process + process_interval))
+ return
+ update_position(FALSE)
+
/obj/item/device/gps/Topic(href, href_list)
..()
@@ -144,6 +200,7 @@ var/list/GPS_list = list()
if(GPS_list[new_tag])
tracking |= new_tag
+ update_compass(TRUE)
else
to_chat(usr, "Could not locate GPS tag.")
@@ -151,29 +208,61 @@ var/list/GPS_list = list()
if(href_list["remove_tag"])
tracking -= href_list["remove_tag"]
-
+ update_compass(TRUE)
return TRUE
if(href_list["add_all"])
tracking.Cut()
for(var/gps in GPS_list)
tracking += GPS_list[gps]["tag"]
-
+ update_compass(TRUE)
return TRUE
if(href_list["clear_all"])
tracking.Cut()
tracking |= gpstag // always want to track ourselves
+ update_compass(TRUE)
+ return TRUE
+ if(href_list["compass"])
+ var/tracking_tag = href_list["compass"]
+ if(LAZYISIN(tracking_compass, tracking_tag))
+ LAZYREMOVE(tracking_compass, tracking_tag)
+ else
+ LAZYADD(tracking_compass, tracking_tag)
+ update_compass(TRUE)
return TRUE
return FALSE
-/obj/item/device/gps/proc/update_position()
+/obj/item/device/gps/proc/update_position(var/check_held_by = TRUE)
var/turf/T = get_turf(src)
+ if(check_held_by && held_by && (held_by.x != T.x || held_by.y != T.y || held_by.z != T.z) && held_by != recursive_loc_turf_check(src, 3, held_by))
+ moved_event.unregister(held_by, src)
+ held_by = null
+ update_icon()
+ return
var/area/gpsarea = get_area(src)
- GPS_list[gpstag] = list("tag" = gpstag, "pos_x" = T.x, "pos_y" = T.y, "pos_z" = T.z, "area" = "[gpsarea.name]", "emped" = emped)
+ GPS_list[gpstag] = list("tag" = gpstag, "pos_x" = T.x, "pos_y" = T.y, "pos_z" = T.z, "area" = "[gpsarea.name]", "emped" = emped, "compass_color" = compass_color)
SSvueui.check_uis_for_change(src)
+ if(check_held_by && held_by && (held_by.get_active_hand() == src || held_by.get_inactive_hand() == src))
+ update_compass(TRUE)
+
+/obj/item/device/gps/proc/update_compass(var/update_compass_icon)
+ compass.hide_waypoints(FALSE)
+ if(LAZYLEN(tracking_compass))
+ for(var/tracking_tag in tracking_compass - gpstag)
+ if(!GPS_list[tracking_tag])
+ continue
+ if(!(tracking_tag in tracking))
+ continue
+ if(GPS_list[tracking_tag]["pos_x"] == GPS_list[gpstag]["pos_x"] && GPS_list[tracking_tag]["pos_y"] == GPS_list[gpstag]["pos_y"])
+ continue
+ compass.set_waypoint(tracking_tag, tracking_tag, GPS_list[tracking_tag]["pos_x"], GPS_list[tracking_tag]["pos_y"], GPS_list[tracking_tag]["pos_z"], GPS_list[tracking_tag]["compass_color"])
+ var/turf/origin = get_turf(src)
+ if(!emped && !GPS_list[tracking_tag]["emped"] && origin.z == GPS_list[tracking_tag]["pos_z"])
+ compass.show_waypoint(tracking_tag)
+ compass.rebuild_overlay_lists(update_compass_icon)
/obj/item/device/gps/proc/next_initial_tag()
if(!LAZYACCESS(gps_count, gps_prefix))
@@ -187,20 +276,24 @@ var/list/GPS_list = list()
/obj/item/device/gps/science
icon_state = "gps-s"
gps_prefix = "SCI"
+ compass_color = "#993399"
gpstag = "SCI0"
/obj/item/device/gps/engineering
icon_state = "gps-e"
gps_prefix = "ENG"
+ compass_color = "#A66300"
gpstag = "ENG0"
/obj/item/device/gps/mining
icon_state = "gps-m"
gps_prefix = "MIN"
+ compass_color = "#5F4519"
gpstag = "MIN0"
desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life."
/obj/item/device/gps/janitor
icon_state = "gps-j"
gps_prefix = "JAN"
+ compass_color = "#6eaa2c"
gpstag = "JAN0"
\ No newline at end of file
diff --git a/html/changelogs/geeves-gps_stuff.yml b/html/changelogs/geeves-gps_stuff.yml
new file mode 100644
index 00000000000..24f76f8744a
--- /dev/null
+++ b/html/changelogs/geeves-gps_stuff.yml
@@ -0,0 +1,8 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "GPSs now have an idle mode, where they will update their transmitted location every six seconds. This mode activates if they're not in someone's possession."
+ - tweak: "You can now keep your GPS in your bag, webbing, etc. It will keep actively updating as you move. If you drop the bag, webbing, etc., it'll swap into idle mode after you move again."
+ - rscadd: "Ported the GPS compass, selecting a GPS to track on the C-Track column will add it to the compass. The GPS must be in your active hand/slot to show you the compass."
\ No newline at end of file
diff --git a/icons/obj/telescience.dmi b/icons/obj/telescience.dmi
index 73b42fee305..ddcb0eb7ad3 100644
Binary files a/icons/obj/telescience.dmi and b/icons/obj/telescience.dmi differ
diff --git a/vueui/src/components/view/devices/gps/gps.vue b/vueui/src/components/view/devices/gps/gps.vue
index dd3fcf41b82..a6252716d13 100644
--- a/vueui/src/components/view/devices/gps/gps.vue
+++ b/vueui/src/components/view/devices/gps/gps.vue
@@ -14,12 +14,15 @@
Location |
Area |
Remove |
+ C-Track |
| {{ gps.tag }} |
{{ gps.pos_x }}, {{ gps.pos_y }}, {{ gps.pos_z }} |
{{ gps.area }} |
- Untrack
+ Untrack |
+ |
+ Compass |
|