diff --git a/code/modules/vehicles/blackfoot/blackfoot.dm b/code/modules/vehicles/blackfoot/blackfoot.dm index 0c5c8a278e54..f51764476b3f 100644 --- a/code/modules/vehicles/blackfoot/blackfoot.dm +++ b/code/modules/vehicles/blackfoot/blackfoot.dm @@ -43,7 +43,6 @@ hardpoints_allowed = list( /obj/item/hardpoint/locomotion/blackfoot_thrusters, /obj/item/hardpoint/primary/blackfoot_launchers, - /obj/item/hardpoint/support/sensor_array, /obj/item/hardpoint/secondary/doorgun, ) diff --git a/code/modules/vehicles/hardpoints/support/sensor_array.dm b/code/modules/vehicles/hardpoints/support/sensor_array.dm index ad612ef3331c..8dbcf5549896 100644 --- a/code/modules/vehicles/hardpoints/support/sensor_array.dm +++ b/code/modules/vehicles/hardpoints/support/sensor_array.dm @@ -1,8 +1,18 @@ #define SENSOR_MODE "sensor" #define NIGHTVISION_MODE "nightvision" +#define RADAR_MODE_OFF -1 +#define RADAR_MODE_PASSIVE 0 +#define RADAR_MODE_ACTIVE 1 + +#define RADAR_CONTACT_BLANK "contact" +#define RADAR_CONTACT_NEUTRAL "neutral" +#define RADAR_CONTACT_UNKNOWN "unknown" +#define RADAR_CONTACT_FRIENDLY "friendly" +#define RADAR_CONTACT_HOSTILE "hostile" + /obj/item/hardpoint/support/sensor_array - name = "\improper AQ-133 Acquisition System" + name = "\improper AQ-133 Acquisition System (WIP)" desc = "A short-range Air-to-Ground LIDAR target acquisition system designed to actively track and profile non-IFF signatures in a localized range of detection." icon = 'icons/obj/vehicles/hardpoints/blackfoot.dmi' icon_state = "radar" @@ -12,13 +22,34 @@ health = 250 var/active = FALSE - + var/interface_active = FALSE + var/volume = 25 + var/map_zoom = 200 + var/minimap_shown = TRUE + var/locking_mode + var/radar_mode = RADAR_MODE_OFF /// Range of the wallhacks var/sensor_radius = 45 /// weakrefs of xenos temporarily added to the marine minimap var/list/minimap_added = list() /// current mode, can be either nvg (gives nightvision to the pilot) or sensor (shows xenos on tacmap) var/mode = SENSOR_MODE + var/datum/flattened_tacmap/map + /// List of actively tracked radar contacts. Used to check if a contact blip is being seen for the first time + var/list/stored_contacts = list() + + var/static/list/radar_modes = list( + RADAR_MODE_OFF, + RADAR_MODE_PASSIVE, + RADAR_MODE_ACTIVE) + + var/contact_alert = TRUE + var/proximity_alert = TRUE + +// to do list +// +// d + /obj/item/hardpoint/support/sensor_array/get_icon_image(x_offset, y_offset, new_dir) var/obj/vehicle/multitile/blackfoot/blackfoot_owner = owner @@ -62,6 +93,110 @@ if(NIGHTVISION_MODE) deactivate_nightvision(user) +/obj/item/hardpoint/support/sensor_array/tgui_interact(mob/user, datum/tgui/ui) + . = ..() + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "VehicleRadar", "AQ-133 'Reaper' Targeting System", 900, 600) + ui.open() + +/obj/item/hardpoint/support/sensor_array/ui_status(mob/user) + if(!(isatom(src))) + return UI_INTERACTIVE + + if(user in owner.interior.get_passengers()) + return UI_INTERACTIVE + else + return UI_CLOSE + +/obj/item/hardpoint/support/sensor_array/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + switch (action) + if("power") + interface_active = !interface_active + if(interface_active) + playsound(src, 'sound/machines/tcomms_on.ogg', 10, FALSE) + if("mode") + minimap_shown = !minimap_shown + if("zoom_in") + return + if("zoom_out") + return + if("zone_lock") + return + if("target_lock") + return + if("volume") + var/volume_change = params["volume"] + switch(volume_change) + if("up") + volume = volume + 5 + if("down") + volume = volume - 5 + if("mute") + volume = 0 + volume = clamp(volume, 0, 25) + if("switch_radar_mode") + radar_mode = next_in_list(radar_mode, radar_modes) + if("proximity_alert") + proximity_alert = !proximity_alert + if("contact_alert") + contact_alert = !contact_alert + if("manual_pulse") + tgui_interact(ui.user) + if("automatic_pulse") + return + if("pulse_control") + var/pulse_speed = params["pulse_change"] + switch(pulse_speed) + if("up") + pulse_speed = pulse_speed + 5 + if("down") + pulse_speed = pulse_speed - 5 + if("mute") + pulse_speed = 0 + pulse_speed = clamp(pulse_speed, 0, 25) + + + var/click_sound = pick( + 'sound/machines/terminal_button01.ogg', + 'sound/machines/terminal_button04.ogg', + 'sound/machines/terminal_button05.ogg') + + playsound(src, click_sound, volume, FALSE) + +/obj/item/hardpoint/support/sensor_array/ui_static_data(mob/user) + var/list/data = list() + + data["blackfoot_icon"] = 'icons/ui_icons/map_blips_extra_large.dmi' + data["radar_blip_icons"] = 'icons/ui_icons/map_blips_vehicle_radar.dmi' + for(var/datum/space_level/map as anything in SSmapping.z_list) + if(map.z_value == 2) + data["map_size_x"] = map.bounds[MAP_MAXX] + data["map_size_y"] = map.bounds[MAP_MAXY] + + return data + +/obj/item/hardpoint/support/sensor_array/ui_data(mob/user) + var/list/data = list() + + map = get_unannounced_tacmap_data_png(FACTION_MARINE) + if(map) + data["radar_map"] = map.flat_tacmap + data["interface_active"] = interface_active + data["minimap_shown"] = minimap_shown + data["blackfoot_dir"] = owner.dir + data["blackfoot_x"] = owner.x + data["blackfoot_y"] = owner.y + data["radar_mode"] = radar_mode + + if(radar_mode != RADAR_MODE_OFF) + data = gather_radar_contact_data(data) + + return data + /obj/item/hardpoint/support/sensor_array/proc/on_update_sight(mob/user) SIGNAL_HANDLER @@ -82,6 +217,49 @@ STOP_PROCESSING(SSobj, src) active = FALSE +/obj/item/hardpoint/support/sensor_array/proc/gather_radar_contact_data(list/data) + + for(var/mob/living/carbon/xenomorph/current_xeno as anything in GLOB.living_xeno_list) + var/turf/xeno_turf = get_turf(current_xeno) + var/area/xeno_area = get_area(current_xeno) + + if(!is_ground_level(xeno_turf.z)) + continue + + //var/datum/weakref/xeno_weakref = WEAKREF(current_xeno) + + if(get_dist(src, current_xeno) >= sensor_radius) + if(current_xeno in stored_contacts) + stored_contacts -= current_xeno + continue + + if(xeno_area.ceiling > CEILING_GLASS) + continue + + data["contact_data"] += list(list( + "name" = current_xeno.name, + "icon" = RADAR_CONTACT_HOSTILE, + "position_x" = current_xeno.x, + "position_y" = current_xeno.y, + "contact_ref" = REF(current_xeno) + )) + + if(!(current_xeno in stored_contacts) && contact_alert) + stored_contacts += current_xeno + notify_radar_contact(current_xeno) + + return data + +/obj/item/hardpoint/support/sensor_array/proc/notify_radar_contact(mob/contact) + var/obj/vehicle/multitile/blackfoot/blackfoot_owner = owner + + if(!blackfoot_owner) + return + + var/mob/user = blackfoot_owner.seats[VEHICLE_DRIVER] + + playsound_client(user.client, 'sound/vehicles/vtol/radar_new_contact.ogg', src, volume, FALSE) + /obj/item/hardpoint/support/sensor_array/proc/activate_mode(mode) var/obj/vehicle/multitile/blackfoot/blackfoot_owner = owner @@ -95,6 +273,7 @@ switch(mode) if(SENSOR_MODE) + tgui_interact(user) START_PROCESSING(SSslowobj, src) if(NIGHTVISION_MODE) var/matrix_color = NV_COLOR_GREEN @@ -151,5 +330,9 @@ current_xeno.add_minimap_marker() minimap_added -= xeno_weakref +#undef RADAR_MODE_OFF +#undef RADAR_MODE_PASSIVE +#undef RADAR_MODE_ACTIVE + #undef SENSOR_MODE #undef NIGHTVISION_MODE diff --git a/icons/ui_icons/map_blips_extra_large.dmi b/icons/ui_icons/map_blips_extra_large.dmi new file mode 100644 index 000000000000..1e76b06e8e6e Binary files /dev/null and b/icons/ui_icons/map_blips_extra_large.dmi differ diff --git a/icons/ui_icons/map_blips_vehicle_radar.dmi b/icons/ui_icons/map_blips_vehicle_radar.dmi new file mode 100644 index 000000000000..c136cdcf85cb Binary files /dev/null and b/icons/ui_icons/map_blips_vehicle_radar.dmi differ diff --git a/maps/map_files/USS_Almayer/USS_Almayer.dmm b/maps/map_files/USS_Almayer/USS_Almayer.dmm index 7f901a21416c..1c89d5339946 100644 --- a/maps/map_files/USS_Almayer/USS_Almayer.dmm +++ b/maps/map_files/USS_Almayer/USS_Almayer.dmm @@ -7318,10 +7318,6 @@ pixel_x = -6; pixel_y = -3 }, -/obj/item/hardpoint/support/sensor_array{ - pixel_x = 6; - pixel_y = -6 - }, /turf/open/floor/almayer/plating/northeast, /area/almayer/shipboard/operations) "arZ" = ( diff --git a/sound/vehicles/vtol/radar_new_contact.ogg b/sound/vehicles/vtol/radar_new_contact.ogg new file mode 100644 index 000000000000..c3b05d534677 Binary files /dev/null and b/sound/vehicles/vtol/radar_new_contact.ogg differ diff --git a/tgui/packages/tgui/interfaces/VehicleRadar.tsx b/tgui/packages/tgui/interfaces/VehicleRadar.tsx new file mode 100644 index 000000000000..7f6ce94fb13f --- /dev/null +++ b/tgui/packages/tgui/interfaces/VehicleRadar.tsx @@ -0,0 +1,538 @@ +import { resolveAsset } from '../assets'; +import { useBackend } from '../backend'; +import { Box, Flex, Icon, Table } from '../components'; +import { DmIcon } from '../components'; +import { Image } from '../components'; +import { Window } from '../layouts'; + +type RadarData = { + radar_map: any; + interface_active: boolean; + volume: number; + map_zoom: number; + minimap_shown: boolean; + locking_mode: string; + blackfoot_icon: any; + blackfoot_x: number; + blackfoot_y: number; + radar_mode: number; + map_size_x: number; + map_size_y: number; + contact_data: ContactData[]; + radar_blip_icons: string; + blackfoot_dir: number; +}; + +type ContactData = { + name: string; + icon: string; + position_x: number; + position_y: number; + contact_ref: string; +}; + +const HexScrew = () => { + return ( + + + + + + + ); +}; + +const Sector = () => { + const { act, data } = useBackend(); + return ( + + + + + + + + + + + + + ); +}; + +const SwitchButtonHolder = () => { + return ( + + + + + + + + + + + + ); +}; + +export const VehicleRadar = (props) => { + const { act, data } = useBackend(); + + const topProps = props.topButtons ?? []; + const topButtons = Array.from({ length: 5 }).map((_, i) => topProps[i] ?? {}); + + return ( + + + + + + + + + + + + + + + + + + + + + + {data.interface_active ? : } + + + + + + + + + + + + + + + + +
+
+
+ ); +}; + +const LeftButtonsPanel = (props) => { + const { act, data } = useBackend(); + + const topProps = props.topButtons ?? []; + const topButtons = Array.from({ length: 5 }).map((_, i) => topProps[i] ?? {}); + + return ( + + + act('power')}> + POWR + + + {data.interface_active ? ( + <> + + act('mode')}> + MODE + + + + act('zoom_in')}> + + + + + + act('zoom_out')}> + - + + + + act('zone_lock')}> + {data.radar_mode >= 0 && 'ZLCK'} + + + + act('target_lock')}> + {data.radar_mode >= 1 && 'TLCK'} + + + + ) : ( + Array.from({ length: 5 }).map((_, s) => { + return ( + + + + ); + }) + )} + + ); +}; + +const RightButtonsPanel = (props) => { + const { act, data } = useBackend(); + + const topProps = props.topButtons ?? []; + const topButtons = Array.from({ length: 5 }).map((_, i) => topProps[i] ?? {}); + + return ( + + {data.interface_active && data.radar_mode >= 0 ? ( + <> + + act('blink')}> + MPUL + + + + APUL + + + PUL+ + + + PUL- + + + CLRT + + + PROX + + + ) : ( + Array.from({ length: 6 }).map((_, s) => { + return ( + + + + ); + }) + )} + + ); +}; + +const TopButtonsPanel = (props) => { + const { act, data } = useBackend(); + + return ( + + + + + + + + + + + ACTIVE + PASSIVE + OFF + + act('switch_radar_mode')}> + + + + + + + + + + + ); +}; + +const BottomButtonsPanel = (props) => { + const { act, data } = useBackend(); + + return ( + + + act('volume', { volume: 'mute' })} + > + MUTE + + + + + + act('volume', { volume: 'up' })} + > + + + + act('volume', { volume: 'down' })} + > + + + + + + + ); +}; + +const ScreenOff = (props) => { + const { act, data } = useBackend(); + + return ; +}; + +const VehicleRadarDisplay = (props) => { + const { act, data } = useBackend(); + + let { contact_data } = data; + + return ( + + + + + + + + + {data.radar_mode >= 0 && + contact_data && + contact_data.map((contact, index) => { + return ( + + + + ); + })} + + {data.radar_mode >= 0 && + Array.from({ length: 4 }).map((_, s) => { + return Array.from({ length: 12 }).map((_, i) => ( + + + + )); + })} + + + + + + + + + + REAPER AQ-133 + + Strategic target acquisition system + + + Developed by UA Northridge + + Property of USCMC Aerospace Command + + + ); +}; + +const ButtonOverlay = (props) => { + const { act, data } = useBackend(); + + return ( + + + hi! + + + + + + + + +
+ + hi! + + + X + + {data.blackfoot_x} + + + + 4:00:02 + + + + +
+ + + + + + hi! + + + PASSIVE RADAR + + + + + + 4:00:02 + + + + +
+
+ + + ); +}; diff --git a/tgui/packages/tgui/styles/interfaces/DropshipWeapons.scss b/tgui/packages/tgui/styles/interfaces/DropshipWeapons.scss index 4eee235f5e3f..9e96c82d3f27 100644 --- a/tgui/packages/tgui/styles/interfaces/DropshipWeapons.scss +++ b/tgui/packages/tgui/styles/interfaces/DropshipWeapons.scss @@ -1,10 +1,6 @@ @use '../base.scss'; @use 'SidePanel'; -.Test { - border: 1px solid red; -} - .WeaponsConsoleBackground { background-color: hsl(0, 0%, 2%); width: 100%; diff --git a/tgui/packages/tgui/styles/interfaces/VehicleRadar.scss b/tgui/packages/tgui/styles/interfaces/VehicleRadar.scss new file mode 100644 index 000000000000..5549b4e167bf --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/VehicleRadar.scss @@ -0,0 +1,264 @@ +@use '../base.scss'; + +@keyframes switch_in { + from { + visibility: hidden; + position: absolute; + } + to { + visibility: visible; + } +} + +@keyframes switch_out { + from { + visibility: visible; + } + to { + visibility: collapse; + } +} + +.Backdrop { + background-color: #040404; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + padding: 5px; +} + +.LeftButtons { + border-top: 2px inset #242424; + position: absolute; + width: 40px; + height: 340px; + margin-top: 25px; + padding-right: 3px; + color: rgb(31, 230, 24); + .ButtonHolderL { + border: 2.3px inset #242424; + border-right: 2px solid #0e0e0e; + background-color: #0e0e0e; + width: 41px; + height: 55px; + font-family: monospace; + text-align: center; + padding-top: 10px; + padding-left: 1px; + z-index: 5; + .ButtonL { + border: 2px outset #757575bd; + border-radius: 2px; + width: 35px; + height: 30px; + font-family: monospace; + text-align: center; + padding-top: 5px; + } + } +} + +.RightButtons { + position: absolute; + width: 40px; + height: 340px; + margin-top: 25px; + padding-left: 3px; + left: 419px; + color: rgb(31, 230, 24); + .ButtonHolderR { + border: 2.3px inset #242424; + border-left: 2px solid #0e0e0e; + background-color: #0e0e0e; + width: 41px; + height: 55px; + font-family: monospace; + text-align: center; + padding-top: 10px; + padding-right: 1px; + .ButtonR { + border: 2px outset #757575bd; + border-radius: 2px; + width: 35px; + height: 30px; + font-family: monospace; + text-align: center; + padding-top: 5px; + } + } +} + +.TopButtons { + border: 2.3px inset #242424; + border-bottom: 2px solid #36363657; + border-top: 2px solid #0e0e0e; + background-color: #0e0e0e; + width: 310px; + height: 37px; + margin-left: 25px; + padding-left: 4px; + z-index: 3; + position: absolute; +} + +.BottomButtons { + width: 310px; + height: 37px; + margin-top: 7px; + padding-left: 250px; + position: absolute; + .ButtonB { + border: 2px outset #757575bd; + border-radius: 3px; + width: 35px; + height: 30px; + font-family: monospace; + text-align: center; + padding-top: 6px; + padding-left: 1px; + margin-top: 1px; + margin-right: 5px; + color: #6b6b6b; + } +} + +.SwitchButtonHolder { + border: 2px outset #757575bd; + border-radius: 5px; + width: 60px; + height: 32px; + font-family: monospace; + text-align: center; + padding: 2px; + .SwitchButton { + width: 45%; + height: 100%; + font-family: monospace; + padding-left: 5%; + padding-right: 5%; + .Icon { + font-size: large; + padding-top: 3px; + color: #363636; + } + } + .SwitchButtonDivider { + width: 1px; + margin-left: 2.5%; + margin-right: 2.5%; + height: 100%; + font-family: monospace; + border: 1.5px inset #363636; + text-align: left; + } +} + +.RadialSwitch { + width: 30px; + height: 30px; + border-radius: 15px; + border: 2px outset #757575bd; + .RadialSwitchNotch { + width: 45%; + height: 1px; + margin-top: 11.5px; + border: 1px solid #636363bd; + } +} + +.RadarPanel { + background-color: #131313; + width: 450px; + height: 450px; + outline: 4px solid #242424; + + padding: 45px; + border-radius: 5px; + z-index: 1; + .RadarPanelBlank { + z-index: 2; + background: radial-gradient(ellipse at center, #042208, transparent); + background-color: rgb(0, 14, 3); + border: 1px ridge rgb(5, 160, 0); + border-radius: 15px; + width: 100%; + height: 100%; + padding: 10px; + overflow: hidden; + animation: switch_out 1.2s steps(1, end) forwards; + .ScreenDivider { + border-top: 1.5px solid rgba(0, 128, 0, 0.61); + } + } + .RadarPanelOutline { + width: 100%; + height: 100%; + outline: 3px inset #757575; + outline-offset: 2px; + border-radius: 15px; + } +} + +.RadarMap { + z-index: 2; + background-color: rgb(0, 172, 37); + background-repeat: no-repeat; + border: 1px ridge rgb(0, 0, 0); + border-radius: 15px; + width: 100%; + height: 100%; + + overflow: hidden; + background-size: 250%; + animation: switch_in 1.2s steps(1, end) forwards; +} + +.RadarSector { + --s: 0deg; /* starting angle */ + --p: 30deg; /* filling angle */ + aspect-ratio: 1; + border-radius: 50%; + border: 2px solid rgb(0, 0, 0); + /* switch #0000 and #000 to get the opposite shape */ + mask: conic-gradient( + from var(--s), + #0000, + #000 1deg var(--p), + #0000 calc(var(--p) + 1deg) + ); +} + +.ContactBlip { + position: absolute; + z-index: 5; + animation: switch_in 1.2s steps(1, end) forwards; +} + +.ScreenOff { + outline: 3px inset #3a3a3a; + outline-offset: 2px; + background: radial-gradient( + ellipse at center, + #000e0169, + rgba(0, 22, 0, 0.178) + ); + background-color: rgb(0, 0, 0); + border: 1px ridge rgb(1, 43, 0); + border-radius: 15px; + width: 100%; + height: 100%; + padding: 10px; + overflow: hidden; +} + +.UIHighlight { + background-color: rgba(236, 236, 236, 0.527); + overflow: hidden; +} + +.UIElement { + text-align: left; + color: black; +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index 0425b5403f98..2a8382355c4b 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -62,6 +62,7 @@ @include meta.load-css('./interfaces/ChemMaster.scss'); @include meta.load-css('./interfaces/BugReportForm.scss'); @include meta.load-css('./interfaces/DropshipWeapons.scss'); +@include meta.load-css('./interfaces/VehicleRadar.scss'); @include meta.load-css('./interfaces/ElevatorControl.scss'); @include meta.load-css('./interfaces/ExperimentConfigure.scss'); @include meta.load-css('./interfaces/HairPicker.scss');