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}} + +
+
+ + + + + {{/for}} +
{{:helper.link('Accept', 'search' ,{ 'accept_datalink_requests' : value.ref }, null, null)}}{{:helper.link('Decline', 'search' ,{ 'decline_datalink_requests' : value.ref }, null, null)}}{{:value.name}}
+{{/if}} +{{if data.datalinked}} +

Connected Datalinks

+ + {{for data.datalinked}} + +
+
+ + + + {{/for}} +
{{:helper.link('Rescind', 'search' ,{ 'remove_datalink' : value.ref }, null, null)}}{{:value.name}}
+{{/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