diff --git a/aurorastation.dme b/aurorastation.dme
index aae86ad7c2d..141e14ac2d5 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -111,6 +111,7 @@
#include "code\_helpers\mouse.dm"
#include "code\_helpers\names.dm"
#include "code\_helpers\overlay.dm"
+#include "code\_helpers\overmap.dm"
#include "code\_helpers\sanitize_values.dm"
#include "code\_helpers\spatial_info.dm"
#include "code\_helpers\spawn_sync.dm"
@@ -2634,6 +2635,9 @@
#include "code\modules\overmap\README.dm"
#include "code\modules\overmap\sectors.dm"
#include "code\modules\overmap\spacetravel.dm"
+#include "code\modules\overmap\contacts\_contacts.dm"
+#include "code\modules\overmap\contacts\contact_sensors.dm"
+#include "code\modules\overmap\contacts\tracker.dm"
#include "code\modules\overmap\events\event.dm"
#include "code\modules\overmap\exoplanets\exoplanet.dm"
#include "code\modules\overmap\exoplanets\exoplanet_skybox.dm"
diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index a9da582b60e..af1dded3b97 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -15,6 +15,7 @@
#define INVISIBILITY_LIGHTING 20
#define INVISIBILITY_LEVEL_ONE 35
#define INVISIBILITY_LEVEL_TWO 45
+#define INVISIBILITY_OVERMAP 50
#define INVISIBILITY_OBSERVER 60
#define INVISIBILITY_EYE 61
#define INVISIBILITY_SYSTEM 99
diff --git a/code/__defines/overmap.dm b/code/__defines/overmap.dm
index 1ecfe46d2d1..7c05d82e139 100644
--- a/code/__defines/overmap.dm
+++ b/code/__defines/overmap.dm
@@ -12,4 +12,6 @@
#define OVERMAP_WEAKNESS_MINING 4
#define OVERMAP_WEAKNESS_EXPLOSIVE 8
+#define SENSOR_COEFFICENT 1000
+
#define waypoint_sector(waypoint) map_sectors["[waypoint.z]"]
diff --git a/code/_helpers/overmap.dm b/code/_helpers/overmap.dm
new file mode 100644
index 00000000000..07c4bdd45b4
--- /dev/null
+++ b/code/_helpers/overmap.dm
@@ -0,0 +1,9 @@
+var/datum/overmap_sectors = list()
+var/datum/overmaps_by_name = list()
+var/datum/overmaps_by_z = list()
+
+/proc/get_empty_zlevel(var/base_turf_type)
+ if(base_turf_type && base_turf_type != world.turf)
+ for(var/turf/T as anything in block(locate(1, 1, .),locate(world.maxx, world.maxy, .)))
+ T.ChangeTurf(base_turf_type)
+ return world.maxz
diff --git a/code/_helpers/visual_filters.dm b/code/_helpers/visual_filters.dm
index 306876122d8..d9465d0cdb1 100644
--- a/code/_helpers/visual_filters.dm
+++ b/code/_helpers/visual_filters.dm
@@ -9,6 +9,10 @@
/proc/cmp_filter_data_priority(list/A, list/B)
return A["priority"] - B["priority"]
+// Defining this for future proofing and ease of searching for erroneous usage.
+/image/proc/add_filter(filter_name, priority, list/params)
+ filters += filter(arglist(params))
+
/atom/movable/proc/add_filter(filter_name, priority, list/params)
LAZYINITLIST(filter_data)
var/list/p = params.Copy()
@@ -46,13 +50,13 @@
var/list/monkeypatched_params = params.Copy()
monkeypatched_params.Insert(1, null)
var/index = filter_data.Find(filter_name)
-
+
// First, animate ourselves.
- monkeypatched_params[1] = filters[index]
+ monkeypatched_params[1] = filters[index]
animate(arglist(monkeypatched_params))
// If we're being copied by Z-Mimic, update mimics too.
if (bound_overlay)
for (var/atom/movable/AM as anything in get_above_oo())
monkeypatched_params[1] = AM.filters[index]
- animate(arglist(monkeypatched_params))
\ No newline at end of file
+ animate(arglist(monkeypatched_params))
diff --git a/code/modules/overmap/_defines.dm b/code/modules/overmap/_defines.dm
index 22d0efab087..bfc5beaa54b 100644
--- a/code/modules/overmap/_defines.dm
+++ b/code/modules/overmap/_defines.dm
@@ -8,6 +8,7 @@ var/global/list/map_sectors = list()
icon_state = "start"
requires_power = 0
base_turf = /turf/unsimulated/map
+ dynamic_lighting = 0
/turf/unsimulated/map
icon = 'icons/turf/space.dmi'
diff --git a/code/modules/overmap/contacts/_contacts.dm b/code/modules/overmap/contacts/_contacts.dm
new file mode 100644
index 00000000000..b773d511bd8
--- /dev/null
+++ b/code/modules/overmap/contacts/_contacts.dm
@@ -0,0 +1,111 @@
+#define SENSOR_TIME_DELAY 0.2 SECONDS
+/datum/overmap_contact
+
+ var/name = "Unknown" // Contact name.
+ var/image/marker // Image overlay attached to the contact.
+ var/pinged = FALSE // Used to animate overmap effects.
+ var/list/images = list() // Our list of images to cast to users.
+ var/image/radar // Radar image for sonar esque effect
+
+ // The sensor console holding this data.
+ var/obj/machinery/computer/ship/sensors/owner
+
+ // The actual overmap effect associated with this.
+ var/obj/effect/overmap/effect
+
+/datum/overmap_contact/New(var/obj/machinery/computer/ship/sensors/creator, var/obj/effect/overmap/source)
+ // Update local tracking information.
+ owner = creator
+ effect = source
+ name = effect.name
+ owner.contact_datums[effect] = src
+
+ marker = new(loc = effect)
+ update_marker_icon()
+ marker.alpha = 0 // Marker fades in on detection.
+ marker.appearance_flags |= RESET_TRANSFORM
+
+ images += marker
+
+ radar = image(loc = effect, icon = 'icons/obj/overmap.dmi', icon_state = "sensor_range")
+ radar.color = source.color
+ radar.tag = "radar"
+ radar.add_filter("blur", 1, list("blur", size = 1))
+
+/datum/overmap_contact/proc/update_marker_icon()
+
+ marker.appearance = effect
+ marker.appearance_flags |= RESET_TRANSFORM
+ // Pixel offsets are included in appearance but since this marker's loc
+ // is the effect, it's already offset and we don't want to double it.
+ marker.pixel_x = 0
+ marker.pixel_y = 0
+
+ marker.transform = effect.transform
+ marker.dir = effect.dir
+ marker.overlays.Cut()
+
+/datum/overmap_contact/proc/ping_radar(var/range = 0)
+
+ radar.transform = null
+ radar.alpha = 255
+
+ if(range > 1)
+ images |= radar
+
+ var/matrix/M = matrix()
+ M.Scale(range*2.6)
+ animate(radar, transform = M, alpha = 0, time = (SENSOR_TIME_DELAY*range), 1, SINE_EASING)
+ else
+ images -= radar
+
+/datum/overmap_contact/proc/show()
+ if(!owner)
+ return
+ var/list/showing = owner.linked?.navigation_viewers || owner.viewers
+ if(length(showing))
+ for(var/datum/weakref/W in showing)
+ var/mob/M = W.resolve()
+ if(istype(M) && M.client)
+ M.client.images |= images
+
+
+/datum/overmap_contact/proc/ping()
+ if(pinged)
+ return
+ pinged = TRUE
+ effect.opacity = 1
+ show()
+ animate(marker, alpha=255, 0.5 SECOND, 1, LINEAR_EASING)
+ addtimer(CALLBACK(src, PROC_REF(unping)), 1 SECOND)
+
+/datum/overmap_contact/proc/unping()
+ animate(marker, alpha=75, 2 SECOND, 1, LINEAR_EASING)
+ pinged = FALSE
+
+/datum/overmap_contact/Destroy()
+ if(owner)
+ // If we have a lock on what was lost, remove the lock from the targeting consoles
+ if(owner.connected.targeting == effect)
+ for(var/obj/machinery/computer/ship/targeting/console in owner.connected.consoles)
+ owner.connected.detarget(effect, console)
+
+ // Removes the client images from the client of the mobs that are looking at this contact
+ var/list/showing = owner.linked?.navigation_viewers || owner.viewers
+ if(length(showing))
+ for(var/datum/weakref/W in showing)
+ var/mob/M = W.resolve()
+ if(istype(M) && M.client)
+ M.client.images -= images
+
+ // Removes the effect from the contact datums of the owner, and null the owner
+ if(effect)
+ owner.contact_datums -= effect
+ owner = null
+
+ // Remove the effect opacity and null the effect
+ effect.opacity = 0
+ effect = null
+
+ QDEL_NULL_LIST(images)
+ . = ..()
diff --git a/code/modules/overmap/contacts/contact_sensors.dm b/code/modules/overmap/contacts/contact_sensors.dm
new file mode 100644
index 00000000000..ccb6a1cc551
--- /dev/null
+++ b/code/modules/overmap/contacts/contact_sensors.dm
@@ -0,0 +1,292 @@
+#define SENSORS_DISTANCE_COEFFICIENT 5
+/obj/machinery/computer/ship/sensors
+ var/list/objects_in_view = list() // Associative list of objects in view -> identification process
+ var/list/contact_datums = list() // Associate an /obj/effect -> /datum/overmap_contact
+ var/list/trackers = list()
+ var/list/datalink_contacts = list() // A list of the datalink contacts we're receiving from the datalinks
+ var/tmp/muted = FALSE
+
+/obj/machinery/computer/ship/sensors/Destroy()
+ objects_in_view.Cut()
+ trackers.Cut()
+
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ qdel(record)
+ contact_datums.Cut()
+ . = ..()
+
+/obj/machinery/computer/ship/sensors/attempt_hook_up(obj/effect/overmap/visitable/ship/sector)
+ . = ..()
+ if(. && linked && !contact_datums[linked])
+ var/datum/overmap_contact/record = new(src, linked)
+ contact_datums[linked] = record
+ record.marker.alpha = 255
+
+/obj/machinery/computer/ship/sensors/proc/reveal_contacts(var/mob/user)
+ if(user && user.client)
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record)
+ user.client.images |= record.marker
+
+/obj/machinery/computer/ship/sensors/proc/hide_contacts(var/mob/user)
+ if(user && user.client)
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record)
+ user.client.images -= record.marker
+
+/obj/machinery/computer/ship/sensors/process()
+ ..()
+ update_sound()
+ if(!linked)
+ return
+
+ // Update our own marker icon regardless of power or sensor connections.
+ var/sensor_range = 0
+
+ var/obj/machinery/shipsensors/sensors = get_sensors()
+ if(sensors?.use_power)
+ sensor_range = round(sensors.range,1)
+ var/datum/overmap_contact/self_record = contact_datums[linked]
+ self_record.update_marker_icon()
+ self_record.ping_radar(sensor_range)
+ self_record.show()
+
+ // Update our 'sensor range' (ie. overmap lighting)
+ if(!sensors || !sensors.use_power || (stat & (NOPOWER|BROKEN)))
+ datalink_remove_all_ships_datalink()
+ for(var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if(record.effect == linked)
+ continue
+ qdel(record) // Immediately cut records if power is lost.
+
+ objects_in_view.Cut()
+ return
+
+ // What can we see?
+ var/list/objects_in_current_view = list()
+
+ // Find all sectors with a tracker on their z-level. Only works on ships when they are in space.
+ for(var/obj/item/ship_tracker/tracker in trackers)
+ if(tracker.enabled)
+ var/obj/effect/overmap/visitable/tracked_effect = overmap_sectors["[GET_Z(tracker)]"]
+ if(tracked_effect && istype(tracked_effect) && tracked_effect != linked && tracked_effect.requires_contact)
+ objects_in_current_view[tracked_effect] = TRUE
+ objects_in_view[tracked_effect] = 100
+
+
+ // Handle datalinked view
+ datalink_process()
+
+
+ for(var/obj/effect/overmap/contact in view(sensor_range, linked))
+ if(contact == linked)
+ continue
+ if(!contact.requires_contact) // Only some effects require contact for visibility.
+ continue
+ objects_in_current_view[contact] = TRUE
+
+ if(contact.instant_contact) // Instantly identify the object in range.
+ objects_in_view[contact] = 100
+ else if(!(contact in objects_in_view))
+ objects_in_view[contact] = 0
+
+ for(var/obj/effect/overmap/contact in objects_in_view) //Update everything.
+
+ // Are we already aware of this object?
+ var/datum/overmap_contact/record = contact_datums[contact]
+
+ // Fade out and remove anything that is out of range.
+ if(QDELETED(contact) || !objects_in_current_view[contact]) // Object has exited sensor range.
+ if(record)
+ animate(record.marker, alpha=0, 2 SECOND, 1, LINEAR_EASING)
+ QDEL_IN(record, 2 SECOND) // Need to restart the search if you've lost contact with the object.
+ if(contact.scannable) // Scannable objects are the only ones that give off notifications to prevent spam
+ visible_message(SPAN_NOTICE("\The [src] states, \"Contact lost with [record.name].\""))
+ playsound(loc, "sound/machines/sensors/contact_lost.ogg", 30, 1)
+ objects_in_view -= contact
+ continue
+
+ // Generate contact information for this overmap object.
+ var/bearing = round(90 - Atan2(contact.x - linked.x, contact.y - linked.y),5)
+ if(bearing < 0)
+ bearing += 360
+ if(!record) // Begin attempting to identify ship.
+ // The chance of detection decreases with distance to the target ship.
+ if(contact.scannable && prob((SENSORS_DISTANCE_COEFFICIENT * contact.sensor_visibility)/max(get_dist(linked, contact), 0.5)))
+ var/bearing_variability = round(30/sensors.sensor_strength, 5)
+ var/bearing_estimate = round(rand(bearing-bearing_variability, bearing+bearing_variability), 5)
+ if(bearing_estimate < 0)
+ bearing_estimate += 360
+ // Give the player an idea of where the ship is in relation to the ship.
+ if(objects_in_view[contact] <= 0)
+ if(!muted)
+ visible_message(SPAN_NOTICE("\The [src] states, \"Unknown contact designation '[contact.unknown_id]' detected nearby, bearing [bearing_estimate], error +/- [bearing_variability]. Beginning trace.\""))
+ objects_in_view[contact] = round(sensors.sensor_strength**2)
+ else
+ objects_in_view[contact] += round(sensors.sensor_strength**2)
+ if(!muted)
+ visible_message(SPAN_NOTICE("\The [src] states, \"Contact '[contact.unknown_id]' tracing [objects_in_view[contact]]% complete, bearing [bearing_estimate], error +/- [bearing_variability].\""))
+ playsound(loc, "sound/machines/sensors/contactgeneric.ogg", 10, 1) //Let players know there's something nearby.
+ if(objects_in_view[contact] >= 100) // Identification complete.
+ record = new /datum/overmap_contact(src, contact)
+ contact_datums[contact] = record
+ if(contact.scannable)
+ playsound(loc, "sound/machines/sensors/newcontact.ogg", 30, 1)
+ visible_message(SPAN_NOTICE("\The [src] states, \"New contact identified, designation [record.name], bearing [bearing].\""))
+ record.show()
+ animate(record.marker, alpha=255, 2 SECOND, 1, LINEAR_EASING)
+ continue
+ // Update identification information for this record.
+ record.update_marker_icon()
+
+ var/time_delay = max((SENSOR_TIME_DELAY * get_dist(linked, contact)),1)
+ if(!record.pinged)
+ addtimer(CALLBACK(record, PROC_REF(ping)), time_delay)
+
+/obj/machinery/computer/ship/sensors/attackby(var/obj/item/I, var/mob/user)
+ . = ..()
+ var/obj/item/device/multitool/P = I
+ if(!istype(P))
+ return
+ var/obj/item/ship_tracker/tracker = P.get_buffer()
+ if(!tracker || !istype(tracker))
+ return
+
+ if(tracker in trackers)
+ trackers -= tracker
+ destroyed_event.unregister(tracker, src, PROC_REF(remove_tracker))
+ to_chat(user, SPAN_NOTICE("You unlink the tracker in \the [P]'s buffer from \the [src]."))
+ return
+ trackers += tracker
+ destroyed_event.register(tracker, src, PROC_REF(remove_tracker))
+ to_chat(user, SPAN_NOTICE("You link the tracker in \the [P]'s buffer to \the [src]."))
+
+/obj/machinery/computer/ship/sensors/proc/remove_tracker(var/obj/item/ship_tracker/tracker)
+ trackers -= tracker
+
+/obj/machinery/computer/ship/sensors/proc/datalink_process()
+ for(var/obj/effect/overmap/visitable/datalink_ship in src.connected.datalinked) // Get ships that are datalinked with us
+ for(var/obj/machinery/computer/ship/sensors/sensor_console in datalink_ship.consoles) // Pick one sensor console
+
+
+ var/list/diff_datalink_contacts = list()
+
+ // If it's not a known datalinked ship already, initalize its list of supplied contacts
+ if(!length(datalink_contacts[datalink_ship]))
+ datalink_contacts[datalink_ship] = list()
+
+ datalink_process_all_contacts_of_console(sensor_console, datalink_ship)
+
+ // If it's a known datalink, compute the lost contacts to remove
+ if(datalink_contacts[datalink_ship])
+ diff_datalink_contacts = datalink_contacts[datalink_ship] - (sensor_console.objects_in_view + list(datalink_ship))
+
+ for(var/obj/effect/overmap/datalink_contact in diff_datalink_contacts)
+ datalink_remove_contact(datalink_contact, datalink_ship)
+ continue
+
+ // Handle the datalinked ship's own contact
+ var/datum/overmap_contact/ship_contact_record = contact_datums[datalink_ship]
+
+ if(!ship_contact_record)
+ datalink_add_contact(datalink_ship, datalink_ship, force = TRUE)
+ ship_contact_record = contact_datums[datalink_ship]
+
+ ship_contact_record.ping()
+ ship_contact_record.update_marker_icon()
+
+/obj/machinery/computer/ship/sensors/proc/datalink_process_all_contacts_of_console(var/obj/machinery/computer/ship/sensors/sensor_console, var/obj/effect/overmap/visitable/datalink_ship)
+ for(var/obj/effect/overmap/datalink_contact in sensor_console.objects_in_view) // Pick one contact that the sensor console has
+ if(datalink_contact != src.connected)
+ if(!(datalink_contact in datalink_contacts[datalink_ship])) // This is a new datalink contact
+ if(!(contact_datums[datalink_contact]))
+ datalink_add_contact(datalink_contact, datalink_ship)
+ else
+ var/datum/overmap_contact/datalink_contact_record = contact_datums[datalink_contact]
+
+ if(datalink_contact_record && !QDELING(datalink_contact_record))
+ datalink_contact_record.ping()
+
+
+/obj/machinery/computer/ship/sensors/proc/datalink_remove_all_contacts_of_console(var/obj/machinery/computer/ship/sensors/sensor_console, var/obj/effect/overmap/visitable/datalink_ship)
+ for(var/obj/effect/overmap/datalink_contact in sensor_console.objects_in_view)
+ if(datalink_contact != src.connected)
+ datalink_remove_contact(datalink_contact, datalink_ship)
+
+
+/obj/machinery/computer/ship/sensors/proc/datalink_add_contact(var/obj/effect/overmap/datalink_contact, var/obj/effect/overmap/visitable/datalink_ship, var/force = FALSE)
+ if(!(datalink_contact in datalink_contacts[datalink_ship]) && !(objects_in_view[datalink_contact]) || force) // This is a new datalink contact
+
+ var/datum/overmap_contact/datalink_contact_record = contact_datums[datalink_contact] // Is it already in the contact_datums?
+ if(!datalink_contact_record) // If not, create it
+ datalink_contact_record = new /datum/overmap_contact(src, datalink_contact)
+ contact_datums[datalink_contact] = datalink_contact_record // And add it with its associated effect
+
+
+ if(!QDELING(datalink_contact_record))
+ // Show the new contact
+ datalink_contact_record.show()
+ datalink_contact_record.ping()
+ animate(datalink_contact_record.marker, alpha=255, 2 SECOND, 1, LINEAR_EASING)
+ datalink_contact_record.update_marker_icon()
+
+ // Add it to the contact datum
+ if(!datalink_contacts[datalink_ship])
+ datalink_contacts[datalink_ship] = list()
+ datalink_contacts[datalink_ship] |= list(datalink_contact)
+
+
+/obj/machinery/computer/ship/sensors/proc/datalink_remove_contact(var/obj/effect/overmap/datalink_contact, var/obj/effect/overmap/visitable/datalink_ship)
+ var/datum/overmap_contact/datalink_contact_record = contact_datums[datalink_contact] // Retrieve the contact record
+ if(!datalink_contact_record)
+ return
+ animate(datalink_contact_record.marker, alpha=0, 2 SECOND, 1, LINEAR_EASING)
+ QDEL_IN(datalink_contact_record, 2 SECOND)
+
+ if(datalink_contacts[datalink_ship])
+ datalink_contacts[datalink_ship] -= list(datalink_contact)
+
+
+/obj/machinery/computer/ship/sensors/proc/datalink_add_ship_datalink(var/obj/effect/overmap/visitable/datalink_ship)
+ datalink_ship.datalinked |= src.connected
+ src.connected.datalinked |= datalink_ship
+
+/obj/machinery/computer/ship/sensors/proc/datalink_remove_ship_datalink(var/obj/effect/overmap/visitable/datalink_ship)
+ if(datalink_contacts[datalink_ship])
+ for(var/obj/machinery/computer/ship/sensors/sensor_console in datalink_ship.consoles)
+ // Remove the two ships from each other's datalinked list
+ src.connected.datalinked -= datalink_ship
+ datalink_ship.datalinked -= src.connected
+
+ // Removes the ship contact itself from the contacts
+
+ var/datum/overmap_contact/ship_contact_record = contact_datums[datalink_ship]
+
+ if(ship_contact_record)
+ animate(ship_contact_record.marker, alpha=0, 2 SECOND, 1, LINEAR_EASING)
+ QDEL_IN(ship_contact_record, 2 SECOND)
+ //contact_datums[datalink_ship] -= ship_contact_record
+
+ // Remove all contacts
+ datalink_remove_all_contacts_of_console(sensor_console, datalink_ship)
+
+
+ datalink_contacts.Remove(datalink_ship)
+
+ // Recurse the function on the other ship's instance
+ sensor_console.datalink_remove_ship_datalink(src.connected)
+ visible_message(SPAN_NOTICE("\The [src] states, \"A datalink contact was sewered! Recalibration...\""))
+
+
+/obj/machinery/computer/ship/sensors/proc/datalink_remove_all_ships_datalink()
+ for(var/obj/effect/overmap/visitable/datalink_ship in linked.datalinked)
+ for(var/obj/machinery/computer/ship/sensors/sensor_console in datalink_ship.consoles)
+ sensor_console.datalink_remove_ship_datalink(linked)
+ break
+
+
+#undef SENSORS_DISTANCE_COEFFICIENT
diff --git a/code/modules/overmap/contacts/tracker.dm b/code/modules/overmap/contacts/tracker.dm
new file mode 100644
index 00000000000..05dc5634172
--- /dev/null
+++ b/code/modules/overmap/contacts/tracker.dm
@@ -0,0 +1,23 @@
+/obj/item/ship_tracker
+ name = "long range tracker"
+ desc = "A complex device that transmits conspicuous signals, easily locked onto by modern sensors hardware."
+ icon = 'icons/obj/ship_tracker.dmi'
+ icon_state = "disabled"
+
+ origin_tech = "{'magnets':3, 'programming':2}"
+ var/enabled = FALSE
+
+/obj/item/ship_tracker/Initialize()
+ . = ..()
+
+/obj/item/ship_tracker/attack_self(var/mob/user)
+ enabled = !enabled
+ to_chat(user, SPAN_NOTICE("You [enabled ? "enable" : "disable"] \the [src]"))
+ update_icon()
+
+/obj/item/ship_tracker/proc/on_update_icon()
+ icon_state = enabled ? "enabled" : "disabled"
+
+/obj/item/ship_tracker/examine(var/mob/user)
+ . = ..()
+ to_chat(user, "It appears to be [enabled ? "enabled" : "disabled"]")
diff --git a/code/modules/overmap/events/event.dm b/code/modules/overmap/events/event.dm
index ceedb282f95..391b94884d6 100644
--- a/code/modules/overmap/events/event.dm
+++ b/code/modules/overmap/events/event.dm
@@ -168,14 +168,18 @@
/obj/effect/overmap/event
name = "event"
icon = 'icons/obj/overmap.dmi'
- icon_state = "event"
- opacity = 1
+ icon_state = "blank"
+ opacity = 0
var/list/events
var/list/event_icon_states = list("event")
var/difficulty = EVENT_LEVEL_MODERATE
var/list/victims //basically cached events on which Z level
var/can_be_destroyed = TRUE //Can this event be destroyed by ship guns?
+ // Events must be detected by sensors, but are otherwise instantly visible.
+ requires_contact = TRUE
+ instant_contact = TRUE
+
// Vars that determine movability, current moving direction, and moving speed //
/// Whether this event can move or not
var/movable_event = FALSE
@@ -194,6 +198,8 @@
/// Ticks up each process until move speed is matched, at which point the event will move
var/ship_delay_counter = 0
+ var/list/colors = list() //Pick a color from this list on init
+
/obj/effect/overmap/event/Initialize()
. = ..()
icon_state = pick(event_icon_states)
@@ -202,6 +208,8 @@
start_moving()
else if(prob(movable_event_chance))
make_movable()
+ if(LAZYLEN(colors))
+ color = pick(colors)
/obj/effect/overmap/event/proc/make_movable()
movable_event = TRUE
@@ -258,6 +266,7 @@
events = list(/datum/event/meteor_wave/overmap)
event_icon_states = list("meteor1", "meteor2", "meteor3", "meteor4")
difficulty = EVENT_LEVEL_MAJOR
+ colors = list("#fc1100", "#b5251b", "#be1e12")
/obj/effect/overmap/event/electric
name = "electrical storm"
@@ -266,6 +275,7 @@
event_icon_states = list("electrical1", "electrical2")
difficulty = EVENT_LEVEL_MAJOR
can_be_destroyed = FALSE
+ colors = list("#f5ed0c", "#d9d323", "#faf450")
/obj/effect/overmap/event/dust
name = "dust cloud"
@@ -280,6 +290,7 @@
event_icon_states = list("ion1", "ion2", "ion3", "ion4")
difficulty = EVENT_LEVEL_MAJOR
can_be_destroyed = FALSE
+ colors = list("#02faee", "#34d1c9", "#1b9ce7")
/obj/effect/overmap/event/carp
name = "carp shoal"
@@ -288,10 +299,12 @@
difficulty = EVENT_LEVEL_MODERATE
event_icon_states = list("carp")
movable_event_chance = 5
+ colors = list("#c25bc7", "#ea50f2", "#f67efc")
/obj/effect/overmap/event/carp/major
name = "carp school"
difficulty = EVENT_LEVEL_MAJOR
+ colors = list("#a709db", "#c228c7", "#c444e4")
/obj/effect/overmap/event/gravity
name = "dark matter influx"
diff --git a/code/modules/overmap/overmap_object.dm b/code/modules/overmap/overmap_object.dm
index dcf597f8cbc..63d0a5d7c32 100644
--- a/code/modules/overmap/overmap_object.dm
+++ b/code/modules/overmap/overmap_object.dm
@@ -8,6 +8,14 @@
var/known = 0 //shows up on nav computers automatically
var/scannable //if set to TRUE will show up on ship sensors for detailed scans
+ var/unknown_id // A unique identifier used when this entity is scanned. Assigned in Initialize().
+ var/requires_contact = TRUE //whether or not the effect must be identified by ship sensors before being seen.
+ var/instant_contact = FALSE //do we instantly identify ourselves to any ship in sensors range?
+
+ var/sensor_visibility = 10 //how likely it is to increase identification process each scan.
+ var/vessel_mass = 10000 // metric tonnes, very rough number, affects acceleration provided by engines
+
+
var/image/targeted_overlay
//Overlay of how this object should look on other skyboxes
@@ -49,6 +57,9 @@
H.get_known_sectors()
update_icon()
+ if(requires_contact)
+ invisibility = INVISIBILITY_OVERMAP // Effects that require identification have their images cast to the client via sensors.
+
/obj/effect/overmap/Crossed(var/obj/effect/overmap/visitable/other)
if(istype(other))
for(var/obj/effect/overmap/visitable/O in loc)
diff --git a/code/modules/overmap/sectors.dm b/code/modules/overmap/sectors.dm
index a4483848f87..dcfd4422016 100644
--- a/code/modules/overmap/sectors.dm
+++ b/code/modules/overmap/sectors.dm
@@ -8,6 +8,7 @@ var/global/area/overmap/map_overmap // Global object used to locate the overmap
scannable = TRUE
var/designation //Actual name of the object.
var/class //Imagine a ship or station's class. "NTCC" Odin, "SCCV" Horizon, ...
+ unknown_id = "Bogey"
var/obfuscated_name = "unidentified object"
var/obfuscated_desc = "This object is not displaying its IFF signature."
var/obfuscated = FALSE //Whether we hide our name and class or not.
@@ -40,6 +41,11 @@ var/global/area/overmap/map_overmap // Global object used to locate the overmap
var/comms_name = "shipboard"
/// Whether away ship comms have access to the common channel / PUB_FREQ
var/use_common = FALSE
+ var/list/navigation_viewers // list of weakrefs to people viewing the overmap via this ship
+ var/list/consoles
+
+ var/list/datalink_requests = list()// A list of datalink requests that we received
+ var/list/datalinked = list()// Other effects that we are datalinked with
/obj/effect/overmap/visitable/Initialize()
. = ..()
diff --git a/code/modules/overmap/ships/computers/sensors.dm b/code/modules/overmap/ships/computers/sensors.dm
index 340b035ecab..0035f057b84 100644
--- a/code/modules/overmap/ships/computers/sensors.dm
+++ b/code/modules/overmap/ships/computers/sensors.dm
@@ -9,6 +9,16 @@
circuit = /obj/item/circuitboard/ship/sensors
linked_type = /obj/effect/overmap/visitable
+ var/working_sound = 'sound/machines/sensors/dradis.ogg'
+ var/datum/sound_token/sound_token
+ var/sound_id
+
+ var/datum/weakref/sensor_ref
+ var/list/last_scan
+
+/obj/machinery/computer/ship/sensors/proc/get_sensors()
+ return sensors
+
/obj/machinery/computer/ship/sensors/attempt_hook_up(var/obj/effect/overmap/visitable/sector)
. = ..()
if(!.)
@@ -27,6 +37,21 @@
identification = IB
break
+/obj/machinery/computer/ship/sensors/proc/update_sound()
+ if(!working_sound)
+ return
+ if(!sound_id)
+ sound_id = "[type]_[sequential_id(/obj/machinery/computer/ship/sensors)]"
+
+ var/obj/machinery/shipsensors/sensors = get_sensors()
+ if(linked && sensors?.use_power && !(sensors.stat & NOPOWER))
+ var/volume = 10
+ if(!sound_token)
+ sound_token = sound_player.PlayLoopingSound(src, sound_id, working_sound, volume = volume, range = 10)
+ sound_token.SetVolume(volume)
+ else if(sound_token)
+ QDEL_NULL(sound_token)
+
/obj/machinery/computer/ship/sensors/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
if(!linked)
display_reconnect_dialog(user, "sensors")
@@ -35,6 +60,7 @@
var/data[0]
data["viewing"] = viewing_overmap(user)
+ data["muted"] = muted
if(sensors)
data["on"] = sensors.use_power
data["range"] = sensors.range
@@ -62,8 +88,22 @@
distress_beacons.Add(list(list("caller" = vessel.name, "sender" = "[job_string][H.name]", "bearing" = bearing)))
if(length(distress_beacons))
data["distress_beacons"] = distress_beacons
+
+
var/list/contacts = list()
- for(var/obj/effect/overmap/O in view(7,linked))
+ var/list/potential_contacts = list()
+
+ for(var/obj/effect/overmap/nearby in view(7,linked))
+ if(nearby.requires_contact) // Some ships require.
+ continue
+ potential_contacts |= nearby
+
+ // Effects that require contact are only added to the contacts if they have been identified.
+ // Allows for coord tracking out of range of the player's view.
+ for(var/obj/effect/overmap/visitable/identified_contact in contact_datums)
+ potential_contacts |= identified_contact
+
+ for(var/obj/effect/overmap/O in potential_contacts)
if(linked == O)
continue
if(!O.scannable)
@@ -74,6 +114,21 @@
contacts.Add(list(list("name"=O.name, "ref"="\ref[O]", "bearing"=bearing)))
if(length(contacts))
data["contacts"] = contacts
+
+ // Add datalink requests
+ if(length(connected.datalink_requests))
+ var/list/local_datalink_requests = list()
+ for(var/obj/effect/overmap/visitable/requestor in connected.datalink_requests)
+ local_datalink_requests.Add(list(list("name"=requestor.name, "ref"="\ref[requestor]")))
+ data["datalink_requests"] = local_datalink_requests
+
+ if(length(connected.datalinked))
+ var/list/local_datalinked = list()
+ for(var/obj/effect/overmap/visitable/datalinked_ship in connected.datalinked)
+ local_datalinked.Add(list(list("name"=datalinked_ship.name, "ref"="\ref[datalinked_ship]")))
+ data["datalinked"] = local_datalinked
+
+ data["last_scan"] = last_scan
else
data["status"] = "MISSING"
data["range"] = "N/A"
@@ -168,11 +223,43 @@
if (href_list["scan"])
var/obj/effect/overmap/O = locate(href_list["scan"])
- if(istype(O) && !QDELETED(O) && (O in view(7,linked)))
- playsound(loc, "sound/machines/dotprinter.ogg", 30, 1)
- new/obj/item/paper/(get_turf(src), O.get_scan_data(usr), "paper (Sensor Scan - [O])")
+ if(istype(O) && !QDELETED(O))
+ if((O in view(7,linked))|| (O in contact_datums))
+ playsound(loc, "sound/machines/dotprinter.ogg", 30, 1)
+ LAZYSET(last_scan, "data", O.get_scan_data(usr))
+ LAZYSET(last_scan, "location", "[O.x],[O.y]")
+ LAZYSET(last_scan, "name", "[O]")
+ to_chat(usr, SPAN_NOTICE("Successfully scanned [O]."))
+ new/obj/item/paper/(get_turf(src), O.get_scan_data(usr), "paper (Sensor Scan - [O])")
+ return TOPIC_HANDLED
+
+ if (href_list["request_datalink"])
+ var/obj/effect/overmap/visitable/O = locate(href_list["request_datalink"])
+ if(istype(O) && !QDELETED(O))
+ if((O in view(7,linked)) || (O in contact_datums))
+
+ for(var/obj/machinery/computer/ship/sensors/sensor_console in O.consoles)
+ sensor_console.connected.datalink_requests |= src.connected
+ return TOPIC_HANDLED
+
+ if (href_list["accept_datalink_requests"])
+ var/obj/effect/overmap/visitable/O = locate(href_list["accept_datalink_requests"])
+ for(var/obj/machinery/computer/ship/sensors/sensor_console in src.connected.consoles)
+ sensor_console.datalink_add_ship_datalink(O)
+ break
+ src.connected.datalink_requests -= O // Remove the request
return TOPIC_HANDLED
+ if (href_list["decline_datalink_requests"])
+ var/obj/effect/overmap/visitable/O = locate(href_list["decline_datalink_requests"])
+ src.connected.datalink_requests -= O // Remove the request
+
+ if (href_list["remove_datalink"])
+ var/obj/effect/overmap/visitable/O = locate(href_list["remove_datalink"])
+ for(var/obj/machinery/computer/ship/sensors/rescinder_sensor_console in src.connected.consoles) // Get sensor console from the rescinder
+ rescinder_sensor_console.datalink_remove_ship_datalink(O)
+ return TOPIC_HANDLED
+
if (href_list["play_message"])
var/caller = href_list["play_message"]
var/datum/distress_beacon/beacon = SSdistress.active_distress_beacons[caller]
@@ -192,18 +279,6 @@
security_announcement.Announce("No fire is incoming at the current moment, resume damage control.", "Space clear!", sound('sound/misc/announcements/security_level_old.ogg'), 0)
return TOPIC_HANDLED
-/obj/machinery/computer/ship/sensors/process()
- ..()
- if(!linked)
- return
- if(sensors && sensors.use_power && sensors.powered())
- var/sensor_range = round(sensors.range*1.5) + 1
- linked.set_light(sensor_range, sensor_range+1, light_color)
- linked.handle_sensor_state_change(TRUE)
- else
- linked.set_light(0)
- linked.handle_sensor_state_change(FALSE)
-
/obj/machinery/shipsensors
name = "sensors suite"
desc = "Long range gravity scanner with various other sensors, used to detect irregularities in surrounding space. Can only run in vacuum to protect delicate quantum BS elements."
@@ -216,6 +291,7 @@
var/heat_reduction = 0.5 // mitigates this much heat per tick - can sustain range 2
var/heat = 0
var/range = 1
+ var/sensor_strength = 5//used for detecting ships via contacts
idle_power_usage = 5000
var/base_icon_state
diff --git a/code/modules/overmap/ships/computers/ship.dm b/code/modules/overmap/ships/computers/ship.dm
index 78d67d49e22..cbb572b6917 100644
--- a/code/modules/overmap/ships/computers/ship.dm
+++ b/code/modules/overmap/ships/computers/ship.dm
@@ -56,6 +56,8 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
if(user.eyeobj)
moved_event.register(user.eyeobj, src, PROC_REF(unlook))
LAZYDISTINCTADD(viewers, WEAKREF(user))
+ if(linked)
+ LAZYDISTINCTADD(linked.navigation_viewers, WEAKREF(user))
/obj/machinery/computer/ship/proc/unlook(var/mob/user)
user.reset_view()
@@ -78,9 +80,15 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
moved_event.unregister(E.owner, src, PROC_REF(unlook))
LAZYREMOVE(viewers, WEAKREF(E.owner))
LAZYREMOVE(viewers, WEAKREF(user))
+ if(linked)
+ LAZYREMOVE(linked.navigation_viewers, WEAKREF(user))
+
+ if(linked)
+ for(var/obj/machinery/computer/ship/sensors/sensor in linked.consoles)
+ sensor.hide_contacts(user)
/obj/machinery/computer/ship/proc/viewing_overmap(mob/user)
- return (WEAKREF(user) in viewers)
+ return (WEAKREF(user) in viewers) || (linked && (WEAKREF(user) in linked.navigation_viewers))
/obj/machinery/computer/ship/CouldNotUseTopic(mob/user)
. = ..()
@@ -102,6 +110,8 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
return 0
/obj/machinery/computer/ship/Destroy()
+ if(linked)
+ linked = null
if(connected)
LAZYREMOVE(connected.consoles, src)
. = ..()
@@ -114,6 +124,8 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
var/M = W.resolve()
if(M)
unlook(M)
+ if(linked)
+ LAZYREMOVE(linked.navigation_viewers, W)
. = ..()
/obj/machinery/computer/ship/on_user_login(mob/M)
diff --git a/code/modules/overmap/ships/ship.dm b/code/modules/overmap/ships/ship.dm
index 624d83dbee3..278039905fd 100644
--- a/code/modules/overmap/ships/ship.dm
+++ b/code/modules/overmap/ships/ship.dm
@@ -14,10 +14,14 @@ var/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
name = "generic ship"
desc = "Space faring vessel."
icon_state = "ship"
+ requires_contact = TRUE
obfuscated_name = "unidentified vessel"
var/moving_state = "ship_moving"
- var/vessel_mass = 10000 //tonnes, arbitrary number, affects acceleration provided by engines
+ var/list/known_ships = list() //List of ships known at roundstart - put types here.
+ var/base_sensor_visibility
+
+ vessel_mass = 10000 //tonnes, arbitrary number, affects acceleration provided by engines
var/vessel_size = SHIP_SIZE_LARGE //arbitrary number, affects how likely are we to evade meteors
var/max_speed = 1/(1 SECOND) //"speed of light" for the ship, in turfs/tick.
var/min_speed = 1/(2 MINUTES) // Below this, we round speed to 0 to avoid math errors.
@@ -35,8 +39,6 @@ var/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
var/thrust_limit = 1 //global thrust limit for all engines, 0..1
var/halted = 0 //admin halt or other stop.
- var/list/consoles
-
comms_support = TRUE
/obj/effect/overmap/visitable/ship/Initialize()
@@ -44,6 +46,7 @@ var/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
glide_size = world.icon_size
min_speed = round(min_speed, SHIP_MOVE_RESOLUTION)
max_speed = round(max_speed, SHIP_MOVE_RESOLUTION)
+ base_sensor_visibility = round((vessel_mass/SENSOR_COEFFICENT),1)
SSshuttle.ships += src
/obj/effect/overmap/visitable/ship/find_z_levels(var/fore_direction)
@@ -68,8 +71,11 @@ var/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
/obj/effect/overmap/visitable/ship/get_scan_data(mob/user)
. = ..()
+ . += "
Mass: [vessel_mass] tons."
if(!is_still())
. += "
Heading: [dir2angle(get_heading())], speed [get_speed() * 1000]"
+ if(instant_contact)
+ . += "
It is broadcasting a distress signal."
//Projected acceleration based on information from engines
/obj/effect/overmap/visitable/ship/proc/get_acceleration()
@@ -165,6 +171,7 @@ var/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
if(newloc && loc != newloc)
Move(newloc)
handle_wraparound()
+ sensor_visibility = min(round(base_sensor_visibility + get_speed_sensor_increase(), 1), 100)
/obj/effect/overmap/visitable/ship/update_icon()
pixel_x = position[1] * (world.icon_size/2)
@@ -306,6 +313,9 @@ var/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
for(var/obj/machinery/computer/ship/targeting/TR in consoles)
TR.visible_message(SPAN_NOTICE("[icon2html(src, viewers(get_turf(TR)))] Hit confirmed on [hit_data["target_name"]] in [hit_data["target_area"]] at coordinates [hit_data["coordinates"]]."), range = 2)
+/obj/effect/overmap/visitable/ship/proc/get_speed_sensor_increase()
+ return min(get_speed() * 1000, 50) //Engines should never increase sensor visibility by more than 50.
+
#undef MOVING
#undef SANITIZE_SPEED
#undef CHANGE_SPEED_BY
diff --git a/html/changelogs/fluffyghost-sensorsoverhaul.yml b/html/changelogs/fluffyghost-sensorsoverhaul.yml
new file mode 100644
index 00000000000..161573d748f
--- /dev/null
+++ b/html/changelogs/fluffyghost-sensorsoverhaul.yml
@@ -0,0 +1,42 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+# balance
+# admin
+# backend
+# security
+# refactor
+#################################
+
+# Your name.
+author: FluffyGhost
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "The ship sensors now work as a sonar or radar, obscuring the view when the first object is pinged, otherwise the things not in range will not be seen."
+ - rscadd: "The ships now need to be scanned before they appear and are identified, which takes a little of time."
diff --git a/icons/obj/overmap.dmi b/icons/obj/overmap.dmi
index 284426e6d67..fb4ab0437fc 100644
Binary files a/icons/obj/overmap.dmi and b/icons/obj/overmap.dmi differ
diff --git a/icons/obj/ship_tracker.dmi b/icons/obj/ship_tracker.dmi
new file mode 100644
index 00000000000..7c9295da12d
Binary files /dev/null and b/icons/obj/ship_tracker.dmi differ
diff --git a/nano/templates/shipsensors.tmpl b/nano/templates/shipsensors.tmpl
index dc34e1ce0d4..df594acef93 100644
--- a/nano/templates/shipsensors.tmpl
+++ b/nano/templates/shipsensors.tmpl
@@ -112,6 +112,7 @@
{{:helper.link('Scan', 'search' ,{ 'scan' : value.ref }, null, null)}} |
+ {{:helper.link('Datalink', 'search' ,{ 'request_datalink' : value.ref }, null, null)}} |
{{:value.name}}, bearing {{:value.bearing}} |
@@ -120,6 +121,36 @@
{{/if}}
+Datalinks
+
+{{if data.datalink_requests}}
+
+ {{for data.datalink_requests}}
+
+
+
{{:helper.link('Accept', 'search' ,{ 'accept_datalink_requests' : value.ref }, null, null)}} |
+ {{:helper.link('Decline', 'search' ,{ 'decline_datalink_requests' : value.ref }, null, null)}} |
+ {{:value.name}} |
+
+
+ {{/for}}
+
+{{/if}}
+{{if data.datalinked}}
+
Connected Datalinks
+
+ {{for data.datalinked}}
+
+
+
{{:helper.link('Rescind', 'search' ,{ 'remove_datalink' : value.ref }, null, null)}} |
+ {{:value.name}} |
+
+
+ {{/for}}
+
+{{/if}}
+
+
{{if data.id_name == 'Horizon'}}
Announce Inbound fire
diff --git a/sound/machines/sensors/contact_lost.ogg b/sound/machines/sensors/contact_lost.ogg
new file mode 100644
index 00000000000..4a1b7753658
Binary files /dev/null and b/sound/machines/sensors/contact_lost.ogg differ
diff --git a/sound/machines/sensors/contactgeneric.ogg b/sound/machines/sensors/contactgeneric.ogg
new file mode 100644
index 00000000000..ef4e04b9825
Binary files /dev/null and b/sound/machines/sensors/contactgeneric.ogg differ
diff --git a/sound/machines/sensors/dradis.ogg b/sound/machines/sensors/dradis.ogg
new file mode 100644
index 00000000000..aa53495a6fd
Binary files /dev/null and b/sound/machines/sensors/dradis.ogg differ
diff --git a/sound/machines/sensors/newcontact.ogg b/sound/machines/sensors/newcontact.ogg
new file mode 100644
index 00000000000..dd52c2d34dc
Binary files /dev/null and b/sound/machines/sensors/newcontact.ogg differ