From c43484eeed688b8ee6857ecf5c4d984d3dd5aec7 Mon Sep 17 00:00:00 2001 From: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> Date: Wed, 30 Aug 2023 01:08:39 +0200 Subject: [PATCH] Overwatch Console TGUI Refactor (#4178) # About the pull request This redoes the whole console in TGUI. Adds 1 new feature: Saving coordinates. You can now save up to 3 sets of coordinates, and attach comments. For easy dialling of supply drops and OBs # Explain why it's good for the game HTML ugly. TGUI Pretty. Also gives a neat CIC buff See screenshots below. # Testing Photographs and Procedure
Screenshots & Videos ![image](https://github.com/cmss13-devs/cmss13/assets/5618080/35899d6b-4532-47fc-8bb9-5279793eb40d) ![image](https://github.com/cmss13-devs/cmss13/assets/5618080/88bfaf83-1c31-4f95-971b-16b59463094b) ![image](https://github.com/cmss13-devs/cmss13/assets/5618080/1ec3028f-8b94-4660-9c79-7361509b82d4) ![image](https://github.com/cmss13-devs/cmss13/assets/5618080/b7f44c14-43a7-4ccc-a614-64bf566b3e22)
# Changelog :cl: refactor: Refactors the overwatch console in TGUI /:cl: --------- Co-authored-by: harryob --- code/modules/cm_marines/overwatch.dm | 1020 ++++++++--------- .../tgui/interfaces/OverwatchConsole.js | 756 ++++++++++++ 2 files changed, 1231 insertions(+), 545 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/OverwatchConsole.js diff --git a/code/modules/cm_marines/overwatch.dm b/code/modules/cm_marines/overwatch.dm index 070cf1f6c1cf..26de42ad99ec 100644 --- a/code/modules/cm_marines/overwatch.dm +++ b/code/modules/cm_marines/overwatch.dm @@ -1,3 +1,5 @@ +#define MAX_SAVED_COORDINATES 3 + #define HIDE_ALMAYER 2 #define HIDE_GROUND 1 #define HIDE_NONE 0 @@ -28,10 +30,18 @@ var/datum/tacmap/tacmap var/minimap_type = MINIMAP_FLAG_USCM + + ///List of saved coordinates, format of ["x", "y", "comment"] + var/list/saved_coordinates = list() + ///Currently selected UI theme + var/ui_theme = "crtblue" + + /obj/structure/machinery/computer/overwatch/Initialize() . = ..() tacmap = new(src, minimap_type) + /obj/structure/machinery/computer/overwatch/Destroy() QDEL_NULL(tacmap) return ..() @@ -57,437 +67,345 @@ to_chat(user, SPAN_WARNING("You don't have the training to use [src].")) return - user.set_interaction(src) - var/dat = "" + tgui_interact(user) - if(!operator) - dat += "
Operator: ----------
" - else - dat += "
Operator: [operator.name]
" - dat += " Stop Overwatch
" - dat += "
" - - switch(state) - if(0) // Base menu - dat += get_base_menu_text() - if(1) //Info screen. - dat += get_info_screen_text() - if(2) - dat += get_supply_drop_menu_text() - if(3) - dat += get_orbital_bombardment_control_text() - - show_browser(user, dat, "Overwatch Console", "overwatch", "size=550x550") - return +/obj/structure/machinery/computer/overwatch/get_examine_text(mob/user) + . = ..() -/obj/structure/machinery/computer/overwatch/proc/get_base_menu_text() - var/dat = "" + . += SPAN_NOTICE("Alt-Click this machine to change the UI theme.") - if(!current_squad) //No squad has been set yet. Pick one. - dat += "Current Squad: ----------
" - return dat; +/obj/structure/machinery/computer/overwatch/clicked(mob/user, list/mods) - dat += "Current Squad: [current_squad.name] Squad " - dat += "Message Squad

" - dat += "Toggle Tactical Map" - dat += "

" - if(current_squad.squad_leader) - dat += "Squad Leader: [current_squad.squad_leader.name] " - dat += "MSG " - dat += "CHANGE SQUAD LEADER

" - else - dat += "Squad Leader: NONE ASSIGN SQUAD LEADER

" + if(!ishuman(user)) + return ..() + if(mods["alt"]) //Changing UI theme + var/list/possible_options = list("Blue"= "crtblue", "Green" = "crtgreen", "Yellow" = "crtyellow", "Red" = "crtred") + var/chosen_theme = tgui_input_list(user, "Choose a UI theme:", "UI Theme", list("Blue", "Green", "Yellow", "Red")) + if(possible_options[chosen_theme]) + ui_theme = possible_options[chosen_theme] + return TRUE + . = ..() - dat += "Primary Objective: " - if(current_squad.primary_objective) - dat += "Check Set
" - else - dat += "NONE! Set
" - dat += "Secondary Objective: " - if(current_squad.secondary_objective) - dat += "Check Set
" - else - dat += "NONE! Set
" - dat += "
" - dat += "Report a marine for insubordination
" - dat += "Transfer a marine to another squad

" - - dat += "Supply Drop Control
" - dat += "Orbital Bombardment Control
" - dat += "Squad Monitor
" - dat += "
" - dat += "Refresh" - - return dat - -/obj/structure/machinery/computer/overwatch/proc/get_info_screen_text() - var/dat = "" - - dat += {" - - "} +/obj/structure/machinery/computer/overwatch/ui_static_data(mob/user) + var/list/data = list() + data["mapRef"] = tacmap.map_holder.map_ref + return data + +/obj/structure/machinery/computer/overwatch/tgui_interact(mob/user, datum/tgui/ui) + + if(!tacmap.map_holder) + var/level = SSmapping.levels_by_trait(tacmap.targeted_ztrait) + if(!level[1]) + return + tacmap.map_holder = SSminimaps.fetch_tacmap_datum(level[1], tacmap.allowed_flags) + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + user.client.register_map_obj(tacmap.map_holder.map) + ui = new(user, src, "OverwatchConsole", "Overwatch Console") + ui.open() + +/obj/structure/machinery/computer/overwatch/ui_data(mob/user) + var/list/data = list() + + data["theme"] = ui_theme if(!current_squad) - dat += "No Squad selected!
" - else - var/leader_text = "" - var/leader_count = 0 - var/tl_text = "" - var/tl_count = 0 - var/spec_text = "" - var/spec_count = 0 - var/medic_text = "" - var/medic_count = 0 - var/engi_text = "" - var/engi_count = 0 - var/smart_text = "" - var/smart_count = 0 - var/marine_text = "" - var/marine_count = 0 - var/misc_text = "" - var/living_count = 0 - - var/conscious_text = "" - var/unconscious_text = "" - var/dead_text = "" - - var/SL_z //z level of the Squad Leader - if(current_squad.squad_leader) - var/turf/SL_turf = get_turf(current_squad.squad_leader) - SL_z = SL_turf.z - - - for(var/X in current_squad.marines_list) - if(!X) - continue //just to be safe - var/mob_name = "unknown" - var/mob_state = "" - var/role = "unknown" - var/act_sl = "" - var/fteam = "" - var/dist = "???" - var/area_name = "???" - var/mob/living/carbon/human/H - - var/is_filtered = FALSE - if(X && ("\ref[X]" in marine_filter)) - is_filtered = TRUE - - if(ishuman(X)) - H = X - mob_name = H.real_name - var/area/A = get_area(H) - var/turf/M_turf = get_turf(H) - if(!M_turf) - continue - if(A) - area_name = sanitize_area(A.name) - - switch(z_hidden) - if(HIDE_ALMAYER) - if(is_mainship_level(M_turf.z)) - continue - if(HIDE_GROUND) - if(is_ground_level(M_turf.z)) - continue - - if(H.job) - role = H.job - else if(istype(H.wear_id, /obj/item/card/id)) //decapitated marine is mindless, - var/obj/item/card/id/ID = H.wear_id //we use their ID to get their role. - if(ID.rank) role = ID.rank - - if(current_squad.squad_leader) - if(H == current_squad.squad_leader) - dist = "N/A" - if(current_squad.name == SQUAD_SOF) - if(H.job == JOB_MARINE_RAIDER_CMD) - act_sl = " (direct command)" - else if(H.job != JOB_MARINE_RAIDER_SL) - act_sl = " (acting TL)" - else if(H.job != JOB_SQUAD_LEADER) - act_sl = " (acting SL)" - else if(M_turf && (M_turf.z == SL_z)) - dist = "[get_dist(H, current_squad.squad_leader)] ([dir2text_short(get_dir(current_squad.squad_leader, H))])" - - if(is_filtered && marine_filter_enabled) - continue - - switch(H.stat) - if(CONSCIOUS) - mob_state = "Conscious" - living_count++ - conscious_text += "[mob_name][role][act_sl][mob_state][area_name][dist][is_filtered ? "Show" : "Hide"]" - - if(UNCONSCIOUS) - mob_state = "Unconscious" - living_count++ - unconscious_text += "[mob_name][role][act_sl][mob_state][area_name][dist][is_filtered ? "Show" : "Hide"]" - - if(DEAD) - if(dead_hidden) - continue - mob_state = SET_CLASS("DEAD", INTERFACE_RED) - dead_text += "[mob_name][role][act_sl][mob_state][area_name][dist][is_filtered ? "Show" : "Hide"]" - - if(!istype(H.head, /obj/item/clothing/head/helmet/marine)) - mob_state += SET_CLASS(" (NO HELMET)", INTERFACE_ORANGE) - - if(!H.key || !H.client) - if(H.stat != DEAD) - mob_state += " (SSD)" - - - if(H.assigned_fireteam) - fteam = " [H.assigned_fireteam]" - - else //listed marine was deleted or gibbed, all we have is their name - if(dead_hidden) - continue - if(z_hidden) //gibbed marines are neither on the colony nor on the almayer - continue - for(var/datum/data/record/t in GLOB.data_core.general) - if(t.fields["name"] == X) - role = t.fields["real_rank"] - break - mob_state = SET_CLASS("DEAD", INTERFACE_RED) - mob_name = X - - dead_text += "[mob_name][role][act_sl][mob_state][area_name][dist][is_filtered ? "Show" : "Hide"]" - - - var/marine_infos = "[mob_name][role][act_sl][fteam][mob_state][area_name][dist][is_filtered ? "Show" : "Hide"]" - switch(role) - if(JOB_SQUAD_LEADER) - leader_text += marine_infos - leader_count++ - if(JOB_SQUAD_TEAM_LEADER) - tl_text += marine_infos - tl_count++ - if(JOB_SQUAD_SPECIALIST) - spec_text += marine_infos - spec_count++ - if(JOB_SQUAD_MEDIC) - medic_text += marine_infos - medic_count++ - if(JOB_SQUAD_ENGI) - engi_text += marine_infos - engi_count++ - if(JOB_SQUAD_SMARTGUN) - smart_text += marine_infos - smart_count++ - if(JOB_SQUAD_MARINE) - marine_text += marine_infos - marine_count++ - else - misc_text += marine_infos - - dat += "[leader_count ? "Squad Leader Deployed" : SET_CLASS("No Squad Leader Deployed!", INTERFACE_RED)]
" - dat += "Fireteam Leaders: [tl_count ? "[tl_count]" : SET_CLASS("No Fireteam Leaders Deployed!", INTERFACE_RED)]
" - dat += "[spec_count ? "Squad Specialist Deployed" : SET_CLASS("No Specialist Deployed!", INTERFACE_RED)]
" - dat += "[smart_count ? "Squad Smartgunner Deployed" : SET_CLASS("No Smartgunner Deployed!", INTERFACE_RED)]
" - dat += "Squad Hospital Corpsmen: [medic_count] Deployed | Squad Combat Technicians: [engi_count] Deployed
" - dat += "Squad Riflemen: [marine_count] Deployed
" - dat += "Total: [current_squad.marines_list.len] Deployed
" - dat += "Marines alive: [living_count]


" - dat += "
Search:
" - dat += "" - dat += "" - if(!living_marines_sorting) - dat += leader_text + tl_text + spec_text + medic_text + engi_text + smart_text + marine_text + misc_text - else - dat += conscious_text + unconscious_text + dead_text - dat += "
NameRoleStateLocationSL DistanceFilter
" - dat += "

" - dat += "Refresh
" - dat += "Change Sorting Method
" - dat += "[dead_hidden ? "Show Dead Marines" : "Hide Dead Marines" ]
" - dat += "[marine_filter_enabled ? "Disable Marine Filter" : "Enable Marine Filter"]
" - dat += "Change Locations Ignored
" - dat += "
Back" - return dat - -/obj/structure/machinery/computer/overwatch/proc/get_supply_drop_menu_text() - var/dat = "Supply Drop Control

" - if(!current_squad) - dat += "No squad selected!" - else - dat += "Current Supply Drop Status: " - var/cooldown_left = COOLDOWN_TIMELEFT(current_squad, next_supplydrop) - if(cooldown_left > 0) - dat += "Launch tubes resetting ([round(cooldown_left/10)] seconds)
" - else - dat += SET_CLASS("Ready!", INTERFACE_GREEN) - dat += "
" - dat += "Launch Pad Status: " - var/obj/structure/closet/crate/C = locate() in current_squad.drop_pad.loc - if(C) - dat += SET_CLASS("Supply crate loaded", INTERFACE_GREEN) - dat += "
" - else - dat += "Empty
" - dat += "Longitude: [x_supply] Change
" - dat += "Latitude: [y_supply] Change

" - dat += "LAUNCH!" - dat += "

" - dat += "Refresh
" - dat += "Back" - return dat - -/obj/structure/machinery/computer/overwatch/proc/get_orbital_bombardment_control_text() - var/dat = "Orbital Bombardment Control

" - if(!current_squad) - dat += "No squad selected!" - else - dat += "Current Cannon Status: " - var/cooldown_left = COOLDOWN_TIMELEFT(almayer_orbital_cannon, ob_firing_cooldown) - if(almayer_orbital_cannon.is_disabled) - dat += "Cannon is disabled!
" - else if(cooldown_left > 0) - dat += "Cannon on cooldown ([round(cooldown_left/10)] seconds)
" - else if(!almayer_orbital_cannon.chambered_tray) - dat += SET_CLASS("No ammo chambered in the cannon.", INTERFACE_RED) - dat += "
" - else - dat += SET_CLASS("Ready!", INTERFACE_GREEN) - dat += "
" - dat += "Longitude: [x_bomb] Change
" - dat += "Latitude: [y_bomb] Change

" - dat += "FIRE!" - dat += "

" - dat += "Refresh
" - dat += "Back" - return dat - -/obj/structure/machinery/computer/overwatch/Topic(href, href_list) - if(..()) - return - if(!href_list["operation"]) - return - - if((usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) || (ishighersilicon(usr))) - usr.set_interaction(src) - - switch(href_list["operation"]) - // main interface - if("mapview") - tacmap.tgui_interact(usr) - if("back") - state = 0 - if("monitor") - state = 1 - if("supplies") - state = 2 - if("bombs") - state = 3 - if("change_operator") - if(operator != usr) - if(operator && ishighersilicon(operator)) - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("AI override in progress. Access denied.")]") - return - if(!current_squad || current_squad.assume_overwatch(usr)) - operator = usr - if(ishighersilicon(usr)) - to_chat(usr, "[icon2html(src, usr)] [SPAN_BOLDNOTICE("Overwatch system AI override protocol successful.")]") - current_squad?.send_squad_message("Attention. [operator.name] has engaged overwatch system control override.", displayed_icon = src) - else - var/mob/living/carbon/human/H = operator - var/obj/item/card/id/ID = H.get_idcard() - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Basic overwatch systems initialized. Welcome, [ID ? "[ID.rank] ":""][operator.name]. Please select a squad.")]") - current_squad?.send_squad_message("Attention. Your Overwatch officer is now [ID ? "[ID.rank] ":""][operator.name].", displayed_icon = src) + data["squad_list"] = list() + for(var/datum/squad/current_squad in RoleAuthority.squads) + if(current_squad.active && !current_squad.overwatch_officer && current_squad.faction == faction && current_squad.name != "Root") + data["squad_list"] += current_squad.name + return data + + data["current_squad"] = current_squad.name + + data["primary_objective"] = current_squad.primary_objective + data["secondary_objective"] = current_squad.secondary_objective + + data["marines"] = list() + + var/leader_count = 0 + var/ftl_count = 0 + var/spec_count = 0 + var/medic_count = 0 + var/engi_count = 0 + var/smart_count = 0 + var/marine_count = 0 + + var/leaders_alive = 0 + var/ftl_alive = 0 + var/spec_alive= 0 + var/medic_alive= 0 + var/engi_alive = 0 + var/smart_alive = 0 + var/marines_alive = 0 + + var/specialist_type + + var/SL_z //z level of the Squad Leader + if(current_squad.squad_leader) + var/turf/SL_turf = get_turf(current_squad.squad_leader) + SL_z = SL_turf.z + + for(var/marine in current_squad.marines_list) + if(!marine) + continue //just to be safe + var/mob_name = "unknown" + var/mob_state = "" + var/has_helmet = TRUE + var/role = "unknown" + var/acting_sl = "" + var/fteam = "" + var/distance = "???" + var/area_name = "???" + var/is_squad_leader = FALSE + var/mob/living/carbon/human/marine_human + + + if(ishuman(marine)) + marine_human = marine + if(istype(marine_human.loc, /obj/structure/machinery/cryopod)) //We don't care much for these + continue + mob_name = marine_human.real_name + var/area/current_area = get_area(marine_human) + var/turf/current_turf = get_turf(marine_human) + if(!current_turf) + continue + if(current_area) + area_name = sanitize_area(current_area.name) + + switch(z_hidden) + if(HIDE_ALMAYER) + if(is_mainship_level(current_turf.z)) + continue + if(HIDE_GROUND) + if(is_ground_level(current_turf.z)) + continue + + if(marine_human.job) + role = marine_human.job + else if(istype(marine_human.wear_id, /obj/item/card/id)) //decapitated marine is mindless, + var/obj/item/card/id/ID = marine_human.wear_id //we use their ID to get their role. + if(ID.rank) + role = ID.rank + + + if(current_squad.squad_leader) + if(marine_human == current_squad.squad_leader) + distance = "N/A" + if(current_squad.name == SQUAD_SOF) + if(marine_human.job == JOB_MARINE_RAIDER_CMD) + acting_sl = " (direct command)" + else if(marine_human.job != JOB_MARINE_RAIDER_SL) + acting_sl = " (acting TL)" + else if(marine_human.job != JOB_SQUAD_LEADER) + acting_sl = " (acting SL)" + is_squad_leader = TRUE + else if(current_turf && (current_turf.z == SL_z)) + distance = "[get_dist(marine_human, current_squad.squad_leader)] ([dir2text_short(get_dir(current_squad.squad_leader, marine_human))])" + + + switch(marine_human.stat) + if(CONSCIOUS) + mob_state = "Conscious" + + if(UNCONSCIOUS) + mob_state = "Unconscious" + + if(DEAD) + mob_state = "Dead" + + if(!istype(marine_human.head, /obj/item/clothing/head/helmet/marine)) + has_helmet = FALSE + + if(!marine_human.key || !marine_human.client) + if(marine_human.stat != DEAD) + mob_state += " (SSD)" + + + if(marine_human.assigned_fireteam) + fteam = " [marine_human.assigned_fireteam]" + + else //listed marine was deleted or gibbed, all we have is their name + for(var/datum/data/record/marine_record as anything in GLOB.data_core.general) + if(marine_record.fields["name"] == marine) + role = marine_record.fields["real_rank"] + break + mob_state = "Dead" + mob_name = marine + + + switch(role) + if(JOB_SQUAD_LEADER) + leader_count++ + if(mob_state != "Dead") + leaders_alive++ + if(JOB_SQUAD_TEAM_LEADER) + ftl_count++ + if(mob_state != "Dead") + ftl_alive++ + if(JOB_SQUAD_SPECIALIST) + spec_count++ + if(marine_human) + if(istype(marine_human.wear_id, /obj/item/card/id)) //decapitated marine is mindless, + var/obj/item/card/id/ID = marine_human.wear_id //we use their ID to get their role. + if(ID.assignment) + if(specialist_type) + specialist_type = "MULTIPLE" + else + var/list/spec_type = splittext(ID.assignment, "(") + if(islist(spec_type) && (length(spec_type) > 1)) + specialist_type = splittext(spec_type[2], ")")[1] + else if(!specialist_type) + specialist_type = "UNKNOWN" + if(mob_state != "Dead") + spec_alive++ + if(JOB_SQUAD_MEDIC) + medic_count++ + if(mob_state != "Dead") + medic_alive++ + if(JOB_SQUAD_ENGI) + engi_count++ + if(mob_state != "Dead") + engi_alive++ + if(JOB_SQUAD_SMARTGUN) + smart_count++ + if(mob_state != "Dead") + smart_alive++ + if(JOB_SQUAD_MARINE) + marine_count++ + if(mob_state != "Dead") + marines_alive++ + + var/marine_data = list(list("name" = mob_name, "state" = mob_state, "has_helmet" = has_helmet, "role" = role, "acting_sl" = acting_sl, "fteam" = fteam, "distance" = distance, "area_name" = area_name,"ref" = REF(marine))) + data["marines"] += marine_data + if(is_squad_leader) + if(!data["squad_leader"]) + data["squad_leader"] = marine_data[1] + + data["total_deployed"] = leader_count + ftl_count + spec_count + medic_count + engi_count + smart_count + marine_count + data["living_count"] = leaders_alive + ftl_alive + spec_alive + medic_alive + engi_alive + smart_alive + marines_alive + + data["leader_count"] = leader_count + data["ftl_count"] = ftl_count + data["spec_count"] = spec_count + data["medic_count"] = medic_count + data["engi_count"] = engi_count + data["smart_count"] = smart_count + + data["leaders_alive"] = leaders_alive + data["ftl_alive"] = ftl_alive + data["spec_alive"] = spec_alive + data["medic_alive"] = medic_alive + data["engi_alive"] = engi_alive + data["smart_alive"] = smart_alive + data["specialist_type"] = specialist_type ? specialist_type : "NONE" + + data["z_hidden"] = z_hidden + + data["saved_coordinates"] = list() + for(var/i in 1 to length(saved_coordinates)) + data["saved_coordinates"] += list(list("x" = saved_coordinates[i]["x"], "y" = saved_coordinates[i]["y"], "comment" = saved_coordinates[i]["comment"], "index" = i)) + + var/has_supply_pad = FALSE + var/obj/structure/closet/crate/supply_crate + if(current_squad.drop_pad) + supply_crate = locate() in current_squad.drop_pad.loc + has_supply_pad = TRUE + data["can_launch_crates"] = has_supply_pad + data["has_crate_loaded"] = supply_crate + data["supply_cooldown"] = COOLDOWN_TIMELEFT(current_squad, next_supplydrop) + data["ob_cooldown"] = COOLDOWN_TIMELEFT(almayer_orbital_cannon, ob_firing_cooldown) + data["ob_loaded"] = almayer_orbital_cannon.chambered_tray + + data["operator"] = operator.name + + return data + +/obj/structure/machinery/computer/overwatch/ui_state(mob/user) + return GLOB.not_incapacitated_and_adjacent_strict_state + +/obj/structure/machinery/computer/overwatch/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + var/mob/user = usr + + if((user.contents.Find(src) || (in_range(src, user) && istype(loc, /turf))) || (ishighersilicon(user))) + user.set_interaction(src) + + switch(action) + if("pick_squad") + if(current_squad) + return + var/datum/squad/selected_squad + for(var/datum/squad/searching_squad in RoleAuthority.squads) + if(searching_squad.active && !searching_squad.overwatch_officer && searching_squad.faction == faction && searching_squad.name != "Root" && searching_squad.name == params["squad"]) + selected_squad = searching_squad + break + + if(!selected_squad) + return + + if(selected_squad.assume_overwatch(user)) + current_squad = selected_squad + operator = user + current_squad.send_squad_message("Attention - Your squad has been selected for Overwatch. Check your Status pane for objectives.", displayed_icon = src) + current_squad.send_squad_message("Your Overwatch officer is: [operator.name].", displayed_icon = src) + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Tactical data for squad '[current_squad]' loaded. All tactical functions initialized.")]") + return TRUE if("logout") if(current_squad?.release_overwatch()) - if(ishighersilicon(usr)) + if(ishighersilicon(user)) current_squad.send_squad_message("Attention. [operator.name] has released overwatch system control. Overwatch functions deactivated.", displayed_icon = src) - to_chat(usr, "[icon2html(src, usr)] [SPAN_BOLDNOTICE("Overwatch system control override disengaged.")]") + to_chat(user, "[icon2html(src, user)] [SPAN_BOLDNOTICE("Overwatch system control override disengaged.")]") else - var/mob/living/carbon/human/H = operator - var/obj/item/card/id/ID = H.get_idcard() + var/mob/living/carbon/human/human_operator = operator + var/obj/item/card/id/ID = human_operator.get_idcard() current_squad.send_squad_message("Attention. [ID ? "[ID.rank] ":""][operator ? "[operator.name]":"sysadmin"] is no longer your Overwatch officer. Overwatch functions deactivated.", displayed_icon = src) visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Overwatch systems deactivated. Goodbye, [ID ? "[ID.rank] ":""][operator ? "[operator.name]":"sysadmin"].")]") operator = null current_squad = null - if(cam && !ishighersilicon(usr)) - usr.reset_view(null) - usr.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) + if(cam && !ishighersilicon(user)) + user.reset_view(null) + user.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) cam = null - state = 0 - if("pick_squad") - if(operator == usr) - if(current_squad) - to_chat(usr, SPAN_WARNING("[icon2html(src, usr)] You are already selecting a squad.")) - else - var/list/squad_list = list() - for(var/datum/squad/S in RoleAuthority.squads) - if(S.active && !S.overwatch_officer && S.faction == faction && S.name != "Root") - squad_list += S.name - - var/name_sel = tgui_input_list(usr, "Which squad would you like to claim for Overwatch?", "Claim Squad", squad_list) - if(!name_sel) - return - if(operator != usr) - return - if(current_squad) - to_chat(usr, SPAN_WARNING("[icon2html(src, usr)] You are already selecting a squad.")) - return - var/datum/squad/selected = get_squad_by_name(name_sel) - if(selected) - //Link everything together, squad, console, and officer - if(selected.assume_overwatch(usr)) - current_squad = selected - current_squad.send_squad_message("Attention - Your squad has been selected for Overwatch. Check your Status pane for objectives.", displayed_icon = src) - current_squad.send_squad_message("Your Overwatch officer is: [operator.name].", displayed_icon = src) - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Tactical data for squad '[current_squad]' loaded. All tactical functions initialized.")]") - attack_hand(usr) - else - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("Invalid input. Aborting.")]") + ui.close() + return TRUE + if("message") - if(current_squad && operator == usr) - var/input = sanitize_control_chars(stripped_input(usr, "Please write a message to announce to the squad:", "Squad Message")) + if(current_squad) + var/input = sanitize_control_chars(stripped_input(user, "Please write a message to announce to the squad:", "Squad Message")) if(input) current_squad.send_message(input, 1) //message, adds username current_squad.send_maptext(input, "Squad Message:") visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Message '[input]' sent to all Marines of squad '[current_squad]'.")]") - log_overwatch("[key_name(usr)] sent '[input]' to squad [current_squad].") + log_overwatch("[key_name(user)] sent '[input]' to squad [current_squad].") + if("sl_message") - if(current_squad && operator == usr) - var/input = sanitize_control_chars(stripped_input(usr, "Please write a message to announce to the squad leader:", "SL Message")) + if(current_squad) + var/input = sanitize_control_chars(stripped_input(user, "Please write a message to announce to the squad leader:", "SL Message")) if(input) current_squad.send_message(input, 1, 1) //message, adds username, only to leader current_squad.send_maptext(input, "Squad Leader Message:", 1) visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Message '[input]' sent to Squad Leader [current_squad.squad_leader] of squad '[current_squad]'.")]") - log_overwatch("[key_name(usr)] sent '[input]' to Squad Leader [current_squad.squad_leader] of squad [current_squad].") + log_overwatch("[key_name(user)] sent '[input]' to Squad Leader [current_squad.squad_leader] of squad [current_squad].") + if("check_primary") if(current_squad) //This is already checked, but ehh. if(current_squad.primary_objective) - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Reminding primary objectives of squad '[current_squad]'.")]") - to_chat(usr, "[icon2html(src, usr)] Primary Objective: [current_squad.primary_objective]") + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Reminding '[current_squad]' of primary objectives: [current_squad.primary_objective].")]") + current_squad.send_message("Your primary objective is '[current_squad.primary_objective]'. See Status pane for details.") + current_squad.send_maptext(current_squad.primary_objective, "Primary Objective:") + if("check_secondary") if(current_squad) //This is already checked, but ehh. if(current_squad.secondary_objective) - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Reminding secondary objectives of squad '[current_squad]'.")]") - to_chat(usr, "[icon2html(src, usr)] Secondary Objective: [current_squad.secondary_objective]") + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Reminding '[current_squad]' of secondary objectives: [current_squad.secondary_objective].")]") + current_squad.send_message("Your secondary objective is '[current_squad.secondary_objective]'. See Status pane for details.") + current_squad.send_maptext(current_squad.secondary_objective, "Secondary Objective:") + if("set_primary") var/input = sanitize_control_chars(stripped_input(usr, "What will be the squad's primary objective?", "Primary Objective")) if(current_squad && input) @@ -496,6 +414,8 @@ current_squad.send_maptext(input, "Primary Objective Updated:") visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Primary objective of squad '[current_squad]' set to '[input]'.")]") log_overwatch("[key_name(usr)] set [current_squad]'s primary objective to '[input]'.") + return TRUE + if("set_secondary") var/input = sanitize_control_chars(stripped_input(usr, "What will be the squad's secondary objective?", "Secondary Objective")) if(input) @@ -504,108 +424,174 @@ current_squad.send_maptext(input, "Secondary Objective Updated:") visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Secondary objective of squad '[current_squad]' set to '[input]'.")]") log_overwatch("[key_name(usr)] set [current_squad]'s secondary objective to '[input]'.") - if("supply_x") - var/input = tgui_input_real_number(usr,"What longitude should be targetted? (Increments towards the east)", "X Coordinate", 0) - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Longitude is now [input].")]") - x_supply = input - if("supply_y") - var/input = tgui_input_real_number(usr,"What latitude should be targetted? (Increments towards the north)", "Y Coordinate", 0) - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Latitude is now [input].")]") - y_supply = input - if("bomb_x") - var/input = tgui_input_real_number(usr,"What longitude should be targetted? (Increments towards the east)", "X Coordinate", 0) - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Longitude is now [input].")]") - x_bomb = input - if("bomb_y") - var/input = tgui_input_real_number(usr,"What latitude should be targetted? (Increments towards the north)", "Y Coordinate", 0) - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Latitude is now [input].")]") - y_bomb = input - if("refresh") - attack_hand(usr) - if("change_sort") - living_marines_sorting = !living_marines_sorting - if(living_marines_sorting) - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Marines are now sorted by health status.")]") - else - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Marines are now sorted by rank.")]") - if("hide_dead") - dead_hidden = !dead_hidden - if(dead_hidden) - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Dead marines are now not shown.")]") - else - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Dead marines are now shown again.")]") - if("choose_z") + return TRUE + if("replace_lead") + if(!params["ref"]) + return + change_lead(user, params["ref"]) + + if("insubordination") + mark_insubordination() + if("transfer_marine") + transfer_squad() + + if("change_locations_ignored") switch(z_hidden) if(HIDE_NONE) z_hidden = HIDE_ALMAYER - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Marines on the Almayer are now hidden.")]") + to_chat(user, "[icon2html(src, usr)] [SPAN_NOTICE("Marines on the Almayer are now hidden.")]") if(HIDE_ALMAYER) z_hidden = HIDE_GROUND - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Marines on the ground are now hidden.")]") + to_chat(user, "[icon2html(src, usr)] [SPAN_NOTICE("Marines on the ground are now hidden.")]") else z_hidden = HIDE_NONE - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("No location is ignored anymore.")]") - - if("toggle_marine_filter") - if(marine_filter_enabled) - marine_filter_enabled = FALSE - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("All marines will now be shown regardless of filter.")]") + to_chat(user, "[icon2html(src, usr)] [SPAN_NOTICE("No location is ignored anymore.")]") + if("tacmap_unpin") + tacmap.tgui_interact(user) + if("dropbomb") + if(!params["x"] || !params["y"]) + return + x_bomb = text2num(params["x"]) + y_bomb = text2num(params["y"]) + if(almayer_orbital_cannon.is_disabled) + to_chat(user, "[icon2html(src, usr)] [SPAN_WARNING("Orbital bombardment cannon disabled!")]") + else if(!COOLDOWN_FINISHED(almayer_orbital_cannon, ob_firing_cooldown)) + to_chat(user, "[icon2html(src, usr)] [SPAN_WARNING("Orbital bombardment cannon not yet ready to fire again! Please wait [COOLDOWN_TIMELEFT(almayer_orbital_cannon, ob_firing_cooldown)/10] seconds.")]") else - marine_filter_enabled = TRUE - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Individual Marine Filter is now enabled.")]") - if("filter_marine") - if (current_squad) - var/squaddie = href_list["squaddie"] - if(!(squaddie in marine_filter)) - marine_filter += squaddie - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Marine now hidden.")]") - else - marine_filter -= squaddie - to_chat(usr, "[icon2html(src, usr)] [SPAN_NOTICE("Marine will now be shown.")]") - if("change_lead") - change_lead() - if("insubordination") - mark_insubordination() - if("squad_transfer") - transfer_squad() + handle_bombard(user) + if("dropsupply") + if(!params["x"] || !params["y"]) + return + x_supply = text2num(params["x"]) + y_supply = text2num(params["y"]) if(current_squad) if(!COOLDOWN_FINISHED(current_squad, next_supplydrop)) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("Supply drop not yet ready to launch again!")]") + to_chat(user, "[icon2html(src, user)] [SPAN_WARNING("Supply drop not yet ready to launch again!")]") else handle_supplydrop() - if("dropbomb") - if(almayer_orbital_cannon.is_disabled) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("Orbital bombardment cannon disabled!")]") - else if(!COOLDOWN_FINISHED(almayer_orbital_cannon, ob_firing_cooldown)) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("Orbital bombardment cannon not yet ready to fire again! Please wait [COOLDOWN_TIMELEFT(almayer_orbital_cannon, ob_firing_cooldown)/10] seconds.")]") - else - handle_bombard(usr) - if("back") - state = 0 - if("use_cam") - if(isRemoteControlling(usr)) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("Unable to override console camera viewer. Track with camera instead. ")]") + + if("save_coordinates") + if(!params["x"] || !params["y"]) + return + if(length(saved_coordinates) >= MAX_SAVED_COORDINATES) + popleft(saved_coordinates) + saved_coordinates += list(list("x" = text2num(params["x"]), "y" = text2num(params["y"]))) + return TRUE + if("change_coordinate_comment") + if(!params["index"] || !params["comment"]) + return + var/index = text2num(params["index"]) + if(length(saved_coordinates) + 1 < index) + return + saved_coordinates[index]["comment"] = params["comment"] + return TRUE + + if("watch_camera") + if(isRemoteControlling(user)) + to_chat(user, "[icon2html(src, user)] [SPAN_WARNING("Unable to override console camera viewer. Track with camera instead. ")]") + return + if(!params["target_ref"]) return if(current_squad) - var/mob/cam_target = locate(href_list["cam_target"]) + var/mob/cam_target = locate(params["target_ref"]) var/obj/structure/machinery/camera/new_cam = get_camera_from_target(cam_target) if(!new_cam || !new_cam.can_use()) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("Searching for helmet cam. No helmet cam found for this marine! Tell your squad to put their helmets on!")]") + to_chat(user, "[icon2html(src, user)] [SPAN_WARNING("Searching for helmet cam. No helmet cam found for this marine! Tell your squad to put their helmets on!")]") else if(cam && cam == new_cam)//click the camera you're watching a second time to stop watching. visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Stopping helmet cam view of [cam_target].")]") - usr.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) + user.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) cam = null - usr.reset_view(null) - else if(usr.client.view != world_view_size) - to_chat(usr, SPAN_WARNING("You're too busy peering through binoculars.")) + user.reset_view(null) + else if(user.client.view != world_view_size) + to_chat(user, SPAN_WARNING("You're too busy peering through binoculars.")) else if(cam) - usr.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) + user.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) cam = new_cam - usr.reset_view(cam) - usr.RegisterSignal(cam, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/mob, reset_observer_view_on_deletion)) - attack_hand(usr) //The above doesn't ever seem to work. + user.reset_view(cam) + user.RegisterSignal(cam, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/mob, reset_observer_view_on_deletion)) + if("change_operator") + if(operator != user) + if(operator && ishighersilicon(operator)) + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("AI override in progress. Access denied.")]") + return + if(!current_squad || current_squad.assume_overwatch(user)) + operator = user + if(ishighersilicon(user)) + to_chat(user, "[icon2html(src, usr)] [SPAN_BOLDNOTICE("Overwatch system AI override protocol successful.")]") + current_squad?.send_squad_message("Attention. [operator.name] has engaged overwatch system control override.", displayed_icon = src) + else + var/mob/living/carbon/human/H = operator + var/obj/item/card/id/ID = H.get_idcard() + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Basic overwatch systems initialized. Welcome, [ID ? "[ID.rank] ":""][operator.name]. Please select a squad.")]") + current_squad?.send_squad_message("Attention. Your Overwatch officer is now [ID ? "[ID.rank] ":""][operator.name].", displayed_icon = src) + + +/obj/structure/machinery/computer/overwatch/proc/change_lead(mob/user, sl_ref) + if(!user) + return + + if(!current_squad) + return + + var/mob/living/carbon/human/selected_sl = locate(sl_ref) in current_squad.marines_list + if(!selected_sl) + return + if(!istype(selected_sl)) + return + + if(!istype(selected_sl) || !selected_sl.mind || selected_sl.stat == DEAD) //marines_list replaces mob refs of gibbed marines with just a name string + to_chat(user, "[icon2html(src, usr)] [SPAN_WARNING("[selected_sl] is KIA!")]") + return + if(selected_sl == current_squad.squad_leader) + to_chat(user, "[icon2html(src, usr)] [SPAN_WARNING("[selected_sl] is already the Squad Leader!")]") + return + if(jobban_isbanned(selected_sl, JOB_SQUAD_LEADER)) + to_chat(user, "[icon2html(src, usr)] [SPAN_WARNING("[selected_sl] is unfit to lead!")]") + return + if(current_squad.squad_leader) + current_squad.send_message("Attention: [current_squad.squad_leader] is [current_squad.squad_leader.stat == DEAD ? "stepping down" : "demoted"]. A new Squad Leader has been set: [selected_sl.real_name].") + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Squad Leader [current_squad.squad_leader] of squad '[current_squad]' has been [current_squad.squad_leader.stat == DEAD ? "replaced" : "demoted and replaced"] by [selected_sl.real_name]! Logging to enlistment files.")]") + var/old_lead = current_squad.squad_leader + current_squad.demote_squad_leader(current_squad.squad_leader.stat != DEAD) + SStracking.start_tracking(current_squad.tracking_id, old_lead) + else + current_squad.send_message("Attention: A new Squad Leader has been set: [selected_sl.real_name].") + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("[selected_sl.real_name] is the new Squad Leader of squad '[current_squad]'! Logging to enlistment file.")]") + + to_chat(selected_sl, "[icon2html(src, selected_sl)] Overwatch: You've been promoted to \'[selected_sl.job == JOB_SQUAD_LEADER ? "SQUAD LEADER" : "ACTING SQUAD LEADER"]\' for [current_squad.name]. Your headset has access to the command channel (:v).") + to_chat(user, "[icon2html(src, usr)] [selected_sl.real_name] is [current_squad]'s new leader!") + + if(selected_sl.assigned_fireteam) + if(selected_sl == current_squad.fireteam_leaders[selected_sl.assigned_fireteam]) + current_squad.unassign_ft_leader(selected_sl.assigned_fireteam, TRUE, FALSE) + current_squad.unassign_fireteam(selected_sl, FALSE) + + current_squad.squad_leader = selected_sl + current_squad.update_squad_leader() + current_squad.update_free_mar() + + SStracking.set_leader(current_squad.tracking_id, selected_sl) + SStracking.start_tracking("marine_sl", selected_sl) + + if(selected_sl.job == JOB_SQUAD_LEADER)//a real SL + selected_sl.comm_title = "SL" + else //an acting SL + selected_sl.comm_title = "aSL" + ADD_TRAIT(selected_sl, TRAIT_LEADERSHIP, TRAIT_SOURCE_SQUAD_LEADER) + + var/obj/item/device/radio/headset/almayer/marine/sl_headset = selected_sl.get_type_in_ears(/obj/item/device/radio/headset/almayer/marine) + if(sl_headset) + sl_headset.keys += new /obj/item/device/encryptionkey/squadlead/acting(sl_headset) + sl_headset.recalculateChannels() + if(istype(selected_sl.wear_id, /obj/item/card/id)) + var/obj/item/card/id/ID = selected_sl.wear_id + ID.access += ACCESS_MARINE_LEADER + selected_sl.hud_set_squad() + selected_sl.update_inv_head() //updating marine helmet leader overlays + selected_sl.update_inv_wear_suit() + /obj/structure/machinery/computer/overwatch/check_eye(mob/user) if(user.is_mob_incapacitated(TRUE) || get_dist(user, src) > 1 || user.blinded) //user can't see - not sure why canmove is here. @@ -621,6 +607,14 @@ cam = null user.reset_view(null) +/obj/structure/machinery/computer/overwatch/ui_close(mob/user) + ..() + if(!isRemoteControlling(user)) + if(cam) + user.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) + cam = null + user.reset_view(null) + //returns the helmet camera the human is wearing /obj/structure/machinery/computer/overwatch/proc/get_camera_from_target(mob/living/carbon/human/H) if(current_squad) @@ -648,73 +642,9 @@ to_chat(M, SPAN_HIGHDANGER("Orbital bombardment launch command detected!")) to_chat(M, SPAN_DANGER("Launch command informs [ob_type] warhead. Estimated impact area: [ob_area.name]")) -/obj/structure/machinery/computer/overwatch/proc/change_lead() - if(!usr || usr != operator) - return - if(!current_squad) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("No squad selected!")]") - return - var/sl_candidates = list() - for(var/mob/living/carbon/human/H in current_squad.marines_list) - if(istype(H) && H.stat != DEAD && H.mind && !jobban_isbanned(H, JOB_SQUAD_LEADER)) - sl_candidates += H - var/new_lead = tgui_input_list(usr, "Choose a new Squad Leader", "Replace SL", sl_candidates) - if(!new_lead || new_lead == "Cancel") - return - var/mob/living/carbon/human/H = new_lead - if(!istype(H) || !H.mind || H.stat == DEAD) //marines_list replaces mob refs of gibbed marines with just a name string - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("[H] is KIA!")]") - return - if(H == current_squad.squad_leader) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("[H] is already the Squad Leader!")]") - return - if(jobban_isbanned(H, JOB_SQUAD_LEADER)) - to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("[H] is unfit to lead!")]") - return - if(current_squad.squad_leader) - current_squad.send_message("Attention: [current_squad.squad_leader] is [current_squad.squad_leader.stat == DEAD ? "stepping down" : "demoted"]. A new Squad Leader has been set: [H.real_name].") - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Squad Leader [current_squad.squad_leader] of squad '[current_squad]' has been [current_squad.squad_leader.stat == DEAD ? "replaced" : "demoted and replaced"] by [H.real_name]! Logging to enlistment files.")]") - var/old_lead = current_squad.squad_leader - current_squad.demote_squad_leader(current_squad.squad_leader.stat != DEAD) - SStracking.start_tracking(current_squad.tracking_id, old_lead) - else - current_squad.send_message("Attention: A new Squad Leader has been set: [H.real_name].") - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("[H.real_name] is the new Squad Leader of squad '[current_squad]'! Logging to enlistment file.")]") - - to_chat(H, "[icon2html(src, H)] Overwatch: You've been promoted to \'[H.job == JOB_SQUAD_LEADER ? "SQUAD LEADER" : "ACTING SQUAD LEADER"]\' for [current_squad.name]. Your headset has access to the command channel (:v).") - to_chat(usr, "[icon2html(src, usr)] [H.real_name] is [current_squad]'s new leader!") - - if(H.assigned_fireteam) - if(H == current_squad.fireteam_leaders[H.assigned_fireteam]) - current_squad.unassign_ft_leader(H.assigned_fireteam, TRUE, FALSE) - current_squad.unassign_fireteam(H, FALSE) - - current_squad.squad_leader = H - current_squad.update_squad_leader() - current_squad.update_free_mar() - - SStracking.set_leader(current_squad.tracking_id, H) - SStracking.start_tracking("marine_sl", H) - - if(H.job == JOB_SQUAD_LEADER)//a real SL - H.comm_title = "SL" - else //an acting SL - H.comm_title = "aSL" - ADD_TRAIT(H, TRAIT_LEADERSHIP, TRAIT_SOURCE_SQUAD_LEADER) - - var/obj/item/device/radio/headset/almayer/marine/R = H.get_type_in_ears(/obj/item/device/radio/headset/almayer/marine) - if(R) - R.keys += new /obj/item/device/encryptionkey/squadlead/acting(R) - R.recalculateChannels() - if(istype(H.wear_id, /obj/item/card/id)) - var/obj/item/card/id/ID = H.wear_id - ID.access += ACCESS_MARINE_LEADER - H.hud_set_squad() - H.update_inv_head() //updating marine helmet leader overlays - H.update_inv_wear_suit() /obj/structure/machinery/computer/overwatch/proc/mark_insubordination() - if(!usr || usr != operator) + if(!usr) return if(!current_squad) to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("No squad selected!")]") @@ -748,7 +678,7 @@ return /obj/structure/machinery/computer/overwatch/proc/transfer_squad() - if(!usr || usr != operator) + if(!usr) return if(!current_squad) to_chat(usr, "[icon2html(src, usr)] [SPAN_WARNING("No squad selected!")]") @@ -877,7 +807,7 @@ /obj/structure/machinery/computer/overwatch/proc/handle_supplydrop() SHOULD_NOT_SLEEP(TRUE) - if(!usr || usr != operator) + if(!usr) return if(busy) diff --git a/tgui/packages/tgui/interfaces/OverwatchConsole.js b/tgui/packages/tgui/interfaces/OverwatchConsole.js new file mode 100644 index 000000000000..7beceef9e72d --- /dev/null +++ b/tgui/packages/tgui/interfaces/OverwatchConsole.js @@ -0,0 +1,756 @@ +import { useBackend, useLocalState, useSharedState } from '../backend'; +import { Button, Section, Stack, Tabs, Table, Box, Input, NumberInput, LabeledControls, Divider, Collapsible } from '../components'; +import { Window } from '../layouts'; + +export const OverwatchConsole = (props, context) => { + const { act, data } = useBackend(context); + + return ( + + + {(!data.current_squad && ) || } + + + ); +}; + +const HomePanel = (props, context) => { + const { act, data } = useBackend(context); + + // Buttons don't seem to support hexcode colors, so we'll have to do this manually, sadly + const squadColorMap = { + 'alpha': 'red', + 'bravo': 'yellow', + 'charlie': 'purple', + 'delta': 'blue', + 'echo': 'green', + 'foxtrot': 'brown', + }; + + return ( +
+ + {data.squad_list.map((squad, index) => { + return ( + + + + ); + })} + +
+ ); +}; + +const SquadPanel = (props, context) => { + const { act, data } = useBackend(context); + + const [category, setCategory] = useLocalState(context, 'selected', 'monitor'); + let hello = 2; + + return ( + <> + + + + + + + + + + setCategory('monitor')}> + Squad Monitor + + {!!data.can_launch_crates && ( + setCategory('supply')}> + Supply Drop + + )} + setCategory('ob')}> + Orbital Bombardment + + act('tacmap_unpin')}> + Tactical Map + + + {category === 'monitor' && } + {category === 'supply' && data.can_launch_crates && } + {category === 'ob' && } + + ); +}; + +const MainDashboard = (props, context) => { + const { act, data } = useBackend(context); + + let { current_squad, primary_objective, secondary_objective } = data; + + return ( +
+ + + + }> + + + PRIMARY ORDERS + SECONDARY ORDERS + + + + {primary_objective ? primary_objective : 'NONE'} + + + {secondary_objective ? secondary_objective : 'NONE'} + + +
+ + + {primary_objective && ( + + )} + + {secondary_objective && ( + + )} + + + + + + +
+ ); +}; + +const RoleTable = (props, context) => { + const { act, data } = useBackend(context); + + const { + squad_leader, + leaders_alive, + ftl_alive, + ftl_count, + specialist_type, + spec_alive, + smart_alive, + smart_count, + spec_count, + medic_count, + medic_alive, + engi_alive, + engi_count, + living_count, + total_deployed, + } = data; + + return ( + + + + Squad Leader + + + Fire Team Leaders + + + Specialist + + + Smartgunner + + + Hospital Corpsmen + + + Combat Technicians + + + Total/Living + + + + {(squad_leader && ( + + {squad_leader.name ? squad_leader.name : 'NONE'} + + {squad_leader.state !== 'Dead' ? 'ALIVE' : 'DEAD'} + + + )) || ( + + NONE + NOT DEPLOYED + + )} + + + {ftl_count} DEPLOYED + {ftl_alive} ALIVE + + + {specialist_type ? specialist_type : 'NONE'} + + {spec_count ? (spec_alive ? 'ALIVE' : 'DEAD') : 'NOT DEPLOYED'} + + + + + {smart_count ? smart_count + ' DEPLOYED' : 'NONE'} + + + {smart_count ? (smart_alive ? 'ALIVE' : 'DEAD') : 'N/A'} + + + + {medic_count} DEPLOYED + {medic_alive} ALIVE + + + {engi_count} DEPLOYED + {engi_alive} ALIVE + + + {total_deployed} TOTAL + {living_count} ALIVE + + +
+ ); +}; + +const SquadMonitor = (props, context) => { + const { act, data } = useBackend(context); + + const sortByRole = (a, b) => { + a = a.role; + b = b.role; + const roleValues = { + 'Squad Leader': 10, + 'Fireteam Leader': 9, + 'Weapons Specialist': 8, + 'Smartgunner': 7, + 'Hospital Corpsman': 6, + 'Combat Technician': 5, + 'Rifleman': 4, + }; + let valueA = roleValues[a]; + let valueB = roleValues[b]; + if (a.includes('Weapons Specialist')) { + valueA = roleValues['Weapons Specialist']; + } + if (b.includes('Weapons Specialist')) { + valueB = roleValues['Weapons Specialist']; + } + if (!valueA && !valueB) return 0; // They're both unknown + if (!valueA) return 1; // B is defined but A is not + if (!valueB) return -1; // A is defined but B is not + + if (valueA > valueB) return -1; // A is more important + if (valueA < valueB) return 1; // B is more important + + return 0; // They're equal + }; + + let { marines, squad_leader } = data; + + const [hidden_marines, setHiddenMarines] = useLocalState( + context, + 'hidden_marines', + [] + ); + + const [showHiddenMarines, setShowHiddenMarines] = useLocalState( + context, + 'showhidden', + false + ); + const [showDeadMarines, setShowDeadMarines] = useLocalState( + context, + 'showdead', + false + ); + + const [marineSearch, setMarineSearch] = useLocalState( + context, + 'marinesearch', + null + ); + + let determine_status_color = (status) => { + let conscious = status.includes('Conscious'); + let unconscious = status.includes('Unconscious'); + + let state_color = 'red'; + if (conscious) { + state_color = 'green'; + } else if (unconscious) { + state_color = 'yellow'; + } + return state_color; + }; + + let toggle_marine_hidden = (ref) => { + if (!hidden_marines.includes(ref)) { + setHiddenMarines([...hidden_marines, ref]); + } else { + let array_copy = [...hidden_marines]; + let index = array_copy.indexOf(ref); + if (index > -1) { + array_copy.splice(index, 1); + } + setHiddenMarines(array_copy); + } + }; + + let location_filter; + if (data.z_hidden === 2) { + location_filter = 'groundside'; + } else if (data.z_hidden === 1) { + location_filter = 'shipside'; + } else { + location_filter = 'all'; + } + + return ( +
+ + {(showDeadMarines && ( + + )) || ( + + )} + {(showHiddenMarines && ( + + )) || ( + + )} + + + + }> + setMarineSearch(value)} + /> + + + Name + Role + + State + + Location + + SL Dist. + + + + {squad_leader && ( + + + {(squad_leader.has_helmet && ( + + )) || {squad_leader.name} (NO HELMET)} + + {squad_leader.role} + + {squad_leader.state} + + {squad_leader.area_name} + + {squad_leader.distance} + + + + )} + {marines && + marines + .sort(sortByRole) + .filter((marine) => { + if (marineSearch) { + const searchableString = String(marine.name).toLowerCase(); + return searchableString.match(new RegExp(marineSearch, 'i')); + } + return marine; + }) + .map((marine, index) => { + if (squad_leader) { + if (marine.ref === squad_leader.ref) { + return; + } + } + if (hidden_marines.includes(marine.ref) && !showHiddenMarines) { + return; + } + if (marine.state === 'Dead' && !showDeadMarines) { + return; + } + + return ( + + + {(marine.has_helmet && ( + + )) || {marine.name} (NO HELMET)} + + {marine.role} + + {marine.state} + + {marine.area_name} + + {marine.distance} + + + {(hidden_marines.includes(marine.ref) && ( +
+
+ ); +}; + +const SupplyDrop = (props, context) => { + const { act, data } = useBackend(context); + + const [supplyX, setSupplyX] = useSharedState(context, 'supplyx', 0); + const [supplyY, setSupplyY] = useSharedState(context, 'supply', 0); + + let crate_status = 'Crate Loaded'; + let crate_color = 'green'; + if (data.supply_cooldown) { + crate_status = 'Cooldown - ' + data.supply_cooldown / 10 + ' seconds'; + crate_color = 'yellow'; + } else if (!data.has_crate_loaded) { + crate_status = 'No crate loaded'; + crate_color = 'red'; + } + + return ( +
+ + + + + setSupplyX(value)} + width="75px" + /> + + + setSupplyY(value)} + width="75px" + /> + + + + {crate_status} + + + + + + + + + + + + + +
+ ); +}; + +const OrbitalBombardment = (props, context) => { + const { act, data } = useBackend(context); + + const [OBX, setOBX] = useSharedState(context, 'obx', 0); + const [OBY, setOBY] = useSharedState(context, 'oby', 0); + + let ob_status = 'Ready'; + let ob_color = 'green'; + if (data.ob_cooldown) { + ob_status = 'Cooldown - ' + data.ob_cooldown / 10 + ' seconds'; + ob_color = 'yellow'; + } else if (!data.ob_loaded) { + ob_status = 'Not chambered'; + ob_color = 'red'; + } + + return ( +
+ + + + + setOBX(value)} + width="75px" + /> + + + setOBY(value)} + width="75px" + /> + + + + + {ob_status} + + + + + + + + + + + + + +
+ ); +}; + +const SavedCoordinates = (props, context) => { + const { act, data } = useBackend(context); + + const [OBX, setOBX] = useSharedState(context, 'obx', 0); + const [OBY, setOBY] = useSharedState(context, 'oby', 0); + const [supplyX, setSupplyX] = useSharedState(context, 'supplyx', 0); + const [supplyY, setSupplyY] = useSharedState(context, 'supply', 0); + + const { forOB, forSupply } = props; + + let transferCoords = (x, y) => { + if (forSupply) { + setSupplyX(x); + setSupplyY(y); + } else if (forOB) { + setOBX(x); + setOBY(y); + } + }; + + console.log(props); + + return ( + + + Max 3 stored coordinates. Will overwrite oldest first. + + + + + LONG. + + + LAT. + + COMMENT + + + {data.saved_coordinates.map((coords, index) => ( + + {coords.x} + {coords.y} + + + act('change_coordinate_comment', { + comment: value, + index: coords.index, + }) + } + /> + + +
+
+ ); +};