From b98f57b5edec7bd5b1057d89e40c19243ae2b3a8 Mon Sep 17 00:00:00 2001 From: forest2001 <41653574+realforest2001@users.noreply.github.com> Date: Mon, 24 Jul 2023 19:29:33 +0100 Subject: [PATCH] Project ARES - Apollo Controller (#3783) # About the pull request Further to #3298 develops the Apollo Maintenance Controller to be functional properly. At present it only functions for maintenance tickets, but will eventually allow for proper use of access tickets, and remote access of the console by working joes (hopefully) # Explain why it's good for the game # Testing Photographs and Procedure
Screenshots & Videos Put screenshots and videos here with an empty line between the screenshots and the `
` tags.
# Changelog :cl: add: Adds functional maintenance ticket control to the Apollo Console add: Adds a unique ID system for each ticket. Credit to Ben10083 for the idea. add: Adds procs for vents to spew gas within a radius. Not currently usable without proccall. code: Starts work on Access Tickets. code: START of contribs from Ben10083 add: ARES now announces when Bioscan fails. code: new procs to see if ARES can talk, or log something. Bioscan proc renamed for consistency. admin: Admins can now force an ARES announcement or communication if subsystem is offline. add: new emergency protocol from ARES; call General Quarters, which sets the ship to immediate Red Alert. add: New APOLLO maintenance ticket categories; Janitorial and Support. Fire now a priority ticket qol: Claimed APOLLO tickets can be unclaimed qol: APOLLO tickets can be rejected/completed if unclaimed by any Working Joe code: END of contribs from Ben10083 /:cl: --------- Co-authored-by: Ben10083 Co-authored-by: Ben <91219575+Ben10083@users.noreply.github.com> Co-authored-by: harryob --- code/__DEFINES/ARES.dm | 13 +- code/__DEFINES/atmospherics.dm | 4 + code/__DEFINES/typecheck/mobs_generic.dm | 1 + code/defines/procs/announcement.dm | 6 +- code/game/bioscans.dm | 21 +- code/game/machinery/ARES/ARES_procs.dm | 202 +++++++++- code/game/machinery/ARES/ARES_records.dm | 14 +- .../game/machinery/ARES/ARES_step_triggers.dm | 14 +- .../objects/effects/effect_system/smoke.dm | 76 +++- .../objects/structures/pipes/vents/vents.dm | 29 ++ code/modules/admin/tabs/event_tab.dm | 35 +- code/modules/cm_tech/droppod/lz_effect.dm | 5 +- code/modules/mob/language/languages.dm | 15 +- code/span_macros.dm | 1 + tgui/packages/tgui/index.js | 1 + .../packages/tgui/interfaces/AresInterface.js | 27 +- tgui/packages/tgui/interfaces/WorkingJoe.js | 353 ++++++++++++++++-- .../tgui/styles/themes/crt/crt-red.scss | 37 ++ 18 files changed, 752 insertions(+), 102 deletions(-) create mode 100644 tgui/packages/tgui/styles/themes/crt/crt-red.scss diff --git a/code/__DEFINES/ARES.dm b/code/__DEFINES/ARES.dm index 05a42738c499..ec84a6ab5992 100644 --- a/code/__DEFINES/ARES.dm +++ b/code/__DEFINES/ARES.dm @@ -43,11 +43,20 @@ #define APOLLO_ACCESS_DEBUG 5 /// Ticket statuses, both for Access and Maintenance +/// Pending assignment/rejection #define TICKET_PENDING "pending" +/// Assigned to a WJ #define TICKET_ASSIGNED "assigned" -#define TICKET_REJECTED "rejected" +/// Cancelled by reporter #define TICKET_CANCELLED "cancelled" -#define TICKET_COMPLETED "complete" +/// Rejected by WJs +#define TICKET_REJECTED "rejected" +/// Completed by WJs +#define TICKET_COMPLETED "completed" + +/// Checks for if buttons can be used, these may yet be removed and internalised to the UI programming +#define TICKET_OPEN "OPEN" +#define TICKET_CLOSED "CLOSED" /// Cooldowns #define COOLDOWN_ARES_SENSOR 60 SECONDS diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 5869650da273..fcc3e7784cb5 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -37,3 +37,7 @@ /// This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage var/MAX_EXPLOSION_RANGE = 14 + +/// Used in /obj/structure/pipes/vents/proc/create_gas +#define VENT_GAS_SMOKE "Smoke" +#define VENT_GAS_CN20 "CN20 Nerve Gas" diff --git a/code/__DEFINES/typecheck/mobs_generic.dm b/code/__DEFINES/typecheck/mobs_generic.dm index 49e4463435a6..1d848decda4e 100644 --- a/code/__DEFINES/typecheck/mobs_generic.dm +++ b/code/__DEFINES/typecheck/mobs_generic.dm @@ -1,4 +1,5 @@ #define isAI(A) (istype(A, /mob/living/silicon/ai)) +#define isARES(A) (istype(A, /mob/living/silicon/decoy/ship_ai)) #define isSilicon(A) (istype(A, /mob/living/silicon)) #define isRemoteControlling(M) (M && M.client && M.client.remote_control) #define isRemoteControllingOrAI(M) ((M && M.client && M.client.remote_control) || (istype(M, /mob/living/silicon/ai))) diff --git a/code/defines/procs/announcement.dm b/code/defines/procs/announcement.dm index 323fb526d527..5223d63b8e59 100644 --- a/code/defines/procs/announcement.dm +++ b/code/defines/procs/announcement.dm @@ -46,7 +46,7 @@ targets.Remove(H) var/datum/ares_link/link = GLOB.ares_link - if(link.interface && !(link.interface.inoperable())) + if(ares_can_log()) switch(logging) if(ARES_LOG_MAIN) link.log_ares_announcement(title, message) @@ -99,7 +99,7 @@ INVOKE_ASYNC(AI, TYPE_PROC_REF(/mob/living/silicon/decoy/ship_ai, say), message) var/datum/ares_link/link = GLOB.ares_link - if(link.interface && !(link.interface.inoperable())) + if(ares_can_log()) switch(logging) if(ARES_LOG_MAIN) link.log_ares_announcement("[MAIN_AI_SYSTEM] Comms Update", message) @@ -151,7 +151,7 @@ targets.Remove(T) var/datum/ares_link/link = GLOB.ares_link - if(link.interface && !(link.interface.inoperable())) + if(ares_can_log()) link.log_ares_announcement("[title] Shipwide Update", message) announcement_helper(message, title, targets, sound_to_play) diff --git a/code/game/bioscans.dm b/code/game/bioscans.dm index 5f07b307751a..62c801a02d29 100644 --- a/code/game/bioscans.dm +++ b/code/game/bioscans.dm @@ -110,7 +110,7 @@ GLOBAL_DATUM_INIT(bioscan_data, /datum/bioscan_data, new) /// This will do something after Project ARES. -/datum/bioscan_data/proc/can_ares_bioscan() +/datum/bioscan_data/proc/ares_can_bioscan() var/datum/ares_link/link = GLOB.ares_link if(!istype(link)) return FALSE @@ -120,8 +120,15 @@ GLOBAL_DATUM_INIT(bioscan_data, /datum/bioscan_data, new) /// The announcement to all Humans. Slightly off for the planet and elsewhere, accurate for the ship. /datum/bioscan_data/proc/ares_bioscan(forced = FALSE, variance = 2) - if(!forced && !can_ares_bioscan()) - message_admins("BIOSCAN: An ARES bioscan has failed.") + var/datum/ares_link/link = GLOB.ares_link + if(!forced && !ares_can_bioscan()) + message_admins("An ARES Bioscan has failed.") + var/name = "[MAIN_AI_SYSTEM] Bioscan Status" + var/input = "Bioscan failed. \n\nInvestigation into Bioscan subsystem recommended." + if(ares_can_log()) + link.log_ares_bioscan(name, input) + if(ares_can_interface()) + marine_announcement(input, name, 'sound/misc/interference.ogg', logging = ARES_LOG_NONE) return //Adjust the randomness there so everyone gets the same thing var/fake_xenos_on_planet = max(0, xenos_on_planet + rand(-variance, variance)) @@ -130,10 +137,12 @@ GLOBAL_DATUM_INIT(bioscan_data, /datum/bioscan_data, new) log_game("BIOSCAN: ARES bioscan completed. [input]") - var/datum/ares_link/link = GLOB.ares_link - link.log_ares_bioscan(name, input) - if(forced || (link.p_interface && !link.p_interface.inoperable())) + if(forced || ares_can_log()) + link.log_ares_bioscan(name, input) //if interface is down, bioscan still logged, just have to go read it. + if(forced || ares_can_interface()) marine_announcement(input, name, 'sound/AI/bioscan.ogg', logging = ARES_LOG_NONE) + else + message_admins("An ARES Bioscan has succeeded, but was not announced.") /// The announcement to all Xenos. Slightly off for the human ship, accurate otherwise. /datum/bioscan_data/proc/qm_bioscan(variance = 2) diff --git a/code/game/machinery/ARES/ARES_procs.dm b/code/game/machinery/ARES/ARES_procs.dm index 79c49818595c..8b5f77fa6d84 100644 --- a/code/game/machinery/ARES/ARES_procs.dm +++ b/code/game/machinery/ARES/ARES_procs.dm @@ -1,4 +1,18 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) +GLOBAL_LIST_INIT(maintenance_categories, list( + "Broken Light", + "Shattered Glass", + "Minor Structural Damage", + "Major Structural Damage", + "Janitorial", + "Chemical Spill", + "Fire", + "Communications Failure", + "Power Generation Failure", + "Electrical Fault", + "Support", + "Other" + )) /datum/ares_link var/link_id = MAIN_SHIP_DEFAULT_NAME @@ -35,14 +49,12 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) speaker = "Unknown" var/datum/ares_link/link = GLOB.ares_link if(!link.p_apollo || link.p_apollo.inoperable()) - return + return FALSE if(!link.p_interface || link.p_interface.inoperable()) - return + return FALSE link.apollo_log.Add("[worldtime2text()]: [speaker], '[message]'") /datum/ares_link/proc/log_ares_bioscan(title, input) - if(!p_bioscan || p_bioscan.inoperable() || !interface) - return FALSE interface.records_bioscan.Add(new /datum/ares_record/bioscan(title, input)) /datum/ares_link/proc/log_ares_bombardment(mob/living/user, ob_name, coordinates) @@ -61,6 +73,32 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) interface.records_security.Add(new /datum/ares_record/security(title, details)) // ------ End ARES Logging Procs ------ // +/proc/ares_apollo_talk(broadcast_message) + var/datum/language/apollo/apollo = GLOB.all_languages[LANGUAGE_APOLLO] + for(var/mob/living/silicon/decoy/ship_ai/ai in ai_mob_list) + if(ai.stat == DEAD) + return FALSE + apollo.broadcast(ai, broadcast_message) + for(var/mob/listener in (GLOB.human_mob_list + GLOB.dead_mob_list)) + if(listener.hear_apollo())//Only plays sound to mobs and not observers, to reduce spam. + playsound_client(listener.client, sound('sound/misc/interference.ogg'), listener, vol = 45) + +/proc/ares_can_interface() + var/obj/structure/machinery/ares/processor/interface/processor = GLOB.ares_link.p_interface + if(!istype(GLOB.ares_link)) + return FALSE + if(processor && !processor.inoperable()) + return TRUE + return FALSE //interface processor not found or is broken + +/proc/ares_can_log() + var/obj/structure/machinery/computer/ares_console/interface = GLOB.ares_link.interface + if(!istype(GLOB.ares_link)) + return FALSE + if(interface && !interface.inoperable()) + return TRUE + return FALSE //ares interface not found or is broken + // ------ ARES Interface Procs ------ // /obj/structure/machinery/computer/proc/get_ares_access(obj/item/card/id/card) if(ACCESS_ARES_DEBUG in card.access) @@ -463,6 +501,19 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) current_menu = "read_deleted" // -- Emergency Buttons -- // + if("general_quarters") + if(security_level == SEC_LEVEL_RED || security_level == SEC_LEVEL_DELTA) + to_chat(usr, SPAN_WARNING("Alert level is already red or above, General Quarters cannot be called.")) + playsound(src, 'sound/machines/buzz-two.ogg', 15, 1) + return FALSE + set_security_level(2, no_sound = TRUE, announce = FALSE) + shipwide_ai_announcement("ATTENTION! GENERAL QUARTERS. ALL HANDS, MAN YOUR BATTLESTATIONS.", MAIN_AI_SYSTEM, 'sound/effects/GQfullcall.ogg') + log_game("[key_name(usr)] has called for general quarters via ARES.") + message_admins("[key_name_admin(usr)] has called for general quarters via ARES.") + var/datum/ares_link/link = GLOB.ares_link + link.log_ares_security("General Quarters", "[last_login] has called for general quarters via ARES.") + . = TRUE + if("evacuation_start") if(security_level < SEC_LEVEL_RED) to_chat(usr, SPAN_WARNING("The ship must be under red alert in order to enact evacuation procedures.")) @@ -551,7 +602,7 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) return APOLLO_ACCESS_AUTHED if(ACCESS_MARINE_AI_TEMP in card.access) return APOLLO_ACCESS_TEMP - if((ACCESS_MARINE_COMMAND in card.access ) || (ACCESS_MARINE_ENGINEERING in card.access) || (ACCESS_WY_CORPORATE in card.access)) + if((ACCESS_MARINE_SENIOR in card.access ) || (ACCESS_MARINE_ENGINEERING in card.access) || (ACCESS_WY_CORPORATE in card.access)) return APOLLO_ACCESS_REPORTER else return APOLLO_ACCESS_REQUEST @@ -577,7 +628,8 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) set name = "Eject ID Card" set src in oview(1) - if(!usr || usr.stat || usr.lying) return + if(!usr || usr.stat || usr.lying) + return FALSE if(authenticator_id) authenticator_id.loc = get_turf(src) @@ -604,11 +656,14 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) /obj/structure/machinery/computer/working_joe/attackby(obj/object, mob/user) if(istype(object, /obj/item/card/id)) + if(!ticket_console) + to_chat(user, SPAN_WARNING("This console doesn't have an ID port!")) + return FALSE if(!operable()) to_chat(user, SPAN_NOTICE("You try to insert [object] but [src] remains silent.")) - return + return FALSE var/obj/item/card/id/idcard = object - if((ACCESS_MARINE_AI in idcard.access) || (ACCESS_ARES_DEBUG in idcard.access)) + if((idcard.assignment == JOB_WORKING_JOE) || (ACCESS_ARES_DEBUG in idcard.access)) if(!authenticator_id) if(user.drop_held_item()) object.forceMove(src) @@ -620,7 +675,7 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) target_id = object else to_chat(user, "Both slots are full already. Remove a card first.") - return + return FALSE else if(!target_id) if(user.drop_held_item()) @@ -628,7 +683,7 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) target_id = object else to_chat(user, "Both slots are full already. Remove a card first.") - return + return FALSE else ..() @@ -689,26 +744,42 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) for(var/datum/ares_ticket/maintenance/maint_ticket as anything in link.tickets_maintenance) if(!istype(maint_ticket)) continue + var/lock_status = TICKET_OPEN + switch(maint_ticket.ticket_status) + if(TICKET_REJECTED, TICKET_CANCELLED, TICKET_COMPLETED) + lock_status = TICKET_CLOSED + var/list/current_maint = list() + current_maint["id"] = maint_ticket.ticket_id current_maint["time"] = maint_ticket.ticket_time - current_maint["title"] = maint_ticket.ticket_name + current_maint["priority_status"] = maint_ticket.ticket_priority + current_maint["category"] = maint_ticket.ticket_name current_maint["details"] = maint_ticket.ticket_details current_maint["status"] = maint_ticket.ticket_status current_maint["submitter"] = maint_ticket.ticket_submitter current_maint["assignee"] = maint_ticket.ticket_assignee + current_maint["lock_status"] = lock_status current_maint["ref"] = "\ref[maint_ticket]" logged_maintenance += list(current_maint) data["maintenance_tickets"] = logged_maintenance var/list/logged_access = list() - for(var/datum/ares_ticket/access_ticket/access_ticket as anything in link.tickets_access) + for(var/datum/ares_ticket/access/access_ticket as anything in link.tickets_access) + var/lock_status = TICKET_OPEN + switch(access_ticket.ticket_status) + if(TICKET_REJECTED, TICKET_CANCELLED, TICKET_COMPLETED) + lock_status = TICKET_CLOSED + var/list/current_ticket = list() + current_ticket["id"] = access_ticket.ticket_id current_ticket["time"] = access_ticket.ticket_time + current_ticket["priority_status"] = access_ticket.ticket_priority current_ticket["title"] = access_ticket.ticket_name current_ticket["details"] = access_ticket.ticket_details current_ticket["status"] = access_ticket.ticket_status current_ticket["submitter"] = access_ticket.ticket_submitter current_ticket["assignee"] = access_ticket.ticket_assignee + current_ticket["lock_status"] = lock_status current_ticket["ref"] = "\ref[access_ticket]" logged_access += list(current_ticket) data["access_tickets"] = logged_access @@ -758,7 +829,7 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) authentication = get_ares_access(idcard) last_login = idcard.registered_name else - to_chat(usr, SPAN_WARNING("You require an ID card to access this terminal!")) + to_chat(operator, SPAN_WARNING("You require an ID card to access this terminal!")) playsound(src, 'sound/machines/buzz-two.ogg', 15, 1) return FALSE if(authentication) @@ -796,20 +867,113 @@ GLOBAL_DATUM_INIT(ares_link, /datum/ares_link, new) current_menu = "maint_claim" if("new_report") - var/name = tgui_input_text(usr, "What is the type of maintenance item you wish to report?\n\nExample:\n 'Broken light in Aft Hallway.'", "Ticket Name", encode = FALSE) - if(!name) + var/priority_report = FALSE + var/maint_type = tgui_input_list(operator, "What is the type of maintenance item you wish to report?", "Report Category", GLOB.maintenance_categories, 30 SECONDS) + switch(maint_type) + if("Major Structural Damage", "Fire", "Communications Failure", "Power Generation Failure") + priority_report = TRUE + + if(!maint_type) return FALSE - var/details = tgui_input_text(usr, "What are the details for this report?", "Ticket Details", encode = FALSE) + var/details = tgui_input_text(operator, "What are the details for this report?", "Ticket Details", encode = FALSE) if(!details) return FALSE - var/confirm = tgui_alert(usr, "Please confirm the submission of your maintenance report. \n\n [name] \n\n [details] \n\n Is this correct?", "Confirmation", list("Yes", "No")) + + if((authentication >= APOLLO_ACCESS_REPORTER) && !priority_report) + var/is_priority = tgui_alert(operator, "Is this a priority report?", "Priority designation", list("Yes", "No")) + if(is_priority == "Yes") + priority_report = TRUE + + var/confirm = alert(operator, "Please confirm the submission of your maintenance report. \n\n Priority: [priority_report ? "Yes" : "No"] \n Category: '[maint_type]' \n Details: '[details]' \n\n Is this correct?", "Confirmation", "Yes", "No") if(confirm == "Yes") if(link) - var/datum/ares_ticket/maintenance/maint_ticket = new(last_login, name, details) + var/datum/ares_ticket/maintenance/maint_ticket = new(last_login, maint_type, details, priority_report) link.tickets_maintenance += maint_ticket - log_game("ARES: Maintenance Ticket created by [key_name(operator)] as [last_login] with Header '[name]' and Details of '[details]'.") + if(priority_report) + ares_apollo_talk("Priority Maintenance Report: [maint_type] - ID [maint_ticket.ticket_id]. Seek and resolve.") + log_game("ARES: Maintenance Ticket '\ref[maint_ticket]' created by [key_name(operator)] as [last_login] with Category '[maint_type]' and Details of '[details]'.") return TRUE return FALSE + if("claim_ticket") + var/datum/ares_ticket/ticket = locate(params["ticket"]) + if(!istype(ticket)) + return FALSE + var/claim = TRUE + var/assigned = ticket.ticket_assignee + if(assigned) + if(assigned == last_login) + var/prompt = tgui_alert(usr, "You already claimed this ticket! Do you wish to drop your claim?", "Unclaim ticket", list("Yes", "No")) + if(prompt != "Yes") + return FALSE + /// set ticket back to pending + ticket.ticket_assignee = null + ticket.ticket_status = TICKET_PENDING + return claim + var/choice = tgui_alert(usr, "This ticket has already been claimed by [assigned]! Do you wish to override their claim?", "Claim Override", list("Yes", "No")) + if(choice != "Yes") + claim = FALSE + if(claim) + ticket.ticket_assignee = last_login + ticket.ticket_status = TICKET_ASSIGNED + return claim + + if("cancel_ticket") + var/datum/ares_ticket/ticket = locate(params["ticket"]) + if(!istype(ticket)) + return FALSE + if(ticket.ticket_submitter != last_login) + to_chat(usr, SPAN_WARNING("You cannot cancel a ticket that does not belong to you!")) + return FALSE + to_chat(usr, SPAN_WARNING("[ticket.ticket_type] [ticket.ticket_id] has been cancelled.")) + ticket.ticket_status = TICKET_CANCELLED + if(ticket.ticket_priority) + ares_apollo_talk("Priority [ticket.ticket_type] [ticket.ticket_id] has been cancelled.") + return TRUE + + if("mark_ticket") + var/datum/ares_ticket/ticket = locate(params["ticket"]) + if(!istype(ticket)) + return FALSE + if(ticket.ticket_assignee != last_login && ticket.ticket_assignee) //must be claimed by you or unclaimed.) + to_chat(usr, SPAN_WARNING("You cannot update a ticket that is not assigned to you!")) + return FALSE + var/choice = tgui_alert(usr, "What do you wish to mark the ticket as?", "Mark", list(TICKET_COMPLETED, TICKET_REJECTED), 20 SECONDS) + switch(choice) + if(TICKET_COMPLETED) + ticket.ticket_status = TICKET_COMPLETED + if(TICKET_REJECTED) + ticket.ticket_status = TICKET_REJECTED + else + return FALSE + if(ticket.ticket_priority) + ares_apollo_talk("Priority [ticket.ticket_type] [ticket.ticket_id] has been [choice] by [last_login].") + to_chat(usr, SPAN_NOTICE("[ticket.ticket_type] [ticket.ticket_id] marked as [choice].")) + return TRUE + + if("new_access") + var/priority_report = FALSE + var/ticket_holder = tgui_input_text(operator, "Who is the ticket for?", "Ticket Holder", encode = FALSE) + if(!ticket_holder) + return FALSE + var/details = tgui_input_text(operator, "What is the purpose of this access ticket?", "Ticket Details", encode = FALSE) + if(!details) + return FALSE + + if(authentication >= APOLLO_ACCESS_AUTHED) + var/is_priority = tgui_alert(operator, "Is this a priority request?", "Priority designation", list("Yes", "No")) + if(is_priority == "Yes") + priority_report = TRUE + + var/confirm = alert(operator, "Please confirm the submission of your access ticket request. \n\n Priority: [priority_report ? "Yes" : "No"] \n Holder: '[ticket_holder]' \n Details: '[details]' \n\n Is this correct?", "Confirmation", "Yes", "No") + if(confirm != "Yes" || !link) + return FALSE + var/datum/ares_ticket/access/access_ticket = new(last_login, ticket_holder, details, priority_report) + link.tickets_access += access_ticket + if(priority_report) + ares_apollo_talk("Priority Access Request: [ticket_holder] - ID [access_ticket.ticket_id]. Seek and resolve.") + log_game("ARES: Access Ticket '\ref[access_ticket]' created by [key_name(operator)] as [last_login] with Holder '[ticket_holder]' and Details of '[details]'.") + return TRUE + if(playsound) playsound(src, "keyboard_alt", 15, 1) diff --git a/code/game/machinery/ARES/ARES_records.dm b/code/game/machinery/ARES/ARES_records.dm index 9cb8574e58f7..4e2b479e71a2 100644 --- a/code/game/machinery/ARES/ARES_records.dm +++ b/code/game/machinery/ARES/ARES_records.dm @@ -77,6 +77,8 @@ var/ticket_status = TICKET_PENDING /// Name of who is handling the ticket. Derived from last login. var/ticket_assignee + /// Numerical designation of the ticket. + var/ticket_id = "1111" /// World time in text format. var/ticket_time /// Who submitted the ticket. Derived from last login. @@ -85,15 +87,23 @@ var/ticket_name /// The content of the ticket, usually an explanation of what it is for. var/ticket_details + /// Whether or not the tickey is a priority. + var/ticket_priority = FALSE + +/datum/ares_ticket/New(user, name, details, priority) + var/ref_holder = "\ref[src]" + var/pos = length(ref_holder) + var/new_id = "#[copytext("\ref[src]", pos - 4, pos)]" -/datum/ares_ticket/New(user, name, details) ticket_time = worldtime2text() ticket_submitter = user ticket_details = details ticket_name = name + ticket_priority = priority + ticket_id = new_id /datum/ares_ticket/maintenance ticket_type = ARES_RECORD_MAINTENANCE -/datum/ares_ticket/access_ticket +/datum/ares_ticket/access ticket_type = ARES_RECORD_ACCESS diff --git a/code/game/machinery/ARES/ARES_step_triggers.dm b/code/game/machinery/ARES/ARES_step_triggers.dm index 1562f1badaab..a50aa40abd90 100644 --- a/code/game/machinery/ARES/ARES_step_triggers.dm +++ b/code/game/machinery/ARES/ARES_step_triggers.dm @@ -74,12 +74,7 @@ return FALSE to_chat(passer, SPAN_BOLDWARNING("You hear a soft beeping sound as you cross the threshold.")) - var/datum/language/apollo/apollo = GLOB.all_languages[LANGUAGE_APOLLO] - for(var/mob/living/silicon/decoy/ship_ai/ai in ai_mob_list) - apollo.broadcast(ai, broadcast_message) - for(var/mob/listener as anything in (GLOB.human_mob_list + GLOB.dead_mob_list)) - if(listener.hear_apollo())//Only plays sound to mobs and not observers, to reduce spam. - playsound_client(listener.client, sound('sound/misc/interference.ogg'), listener, vol = 45) + ares_apollo_talk(broadcast_message) COOLDOWN_START(src, sensor_cooldown, cooldown_duration) if(alert_id && link) for(var/obj/effect/step_trigger/ares_alert/sensor in link.linked_alerts) @@ -166,12 +161,7 @@ return FALSE to_chat(passer, SPAN_BOLDWARNING("You hear a harsh buzzing sound as you cross the threshold!")) - var/datum/language/apollo/apollo = GLOB.all_languages[LANGUAGE_APOLLO] - for(var/mob/living/silicon/decoy/ship_ai/ai in ai_mob_list) - apollo.broadcast(ai, broadcast_message) - for(var/mob/listener in (GLOB.human_mob_list + GLOB.dead_mob_list)) - if(listener.hear_apollo())//Only plays sound to mobs and not observers, to reduce spam. - playsound_client(listener.client, sound('sound/misc/interference.ogg'), listener, vol = 45) + ares_apollo_talk(broadcast_message) if(idcard) idcard.access -= ACCESS_MARINE_AI_TEMP COOLDOWN_START(src, sensor_cooldown, COOLDOWN_ARES_ACCESS_CONTROL) diff --git a/code/game/objects/effects/effect_system/smoke.dm b/code/game/objects/effects/effect_system/smoke.dm index 9f7a9c8143c7..2eb36930c542 100644 --- a/code/game/objects/effects/effect_system/smoke.dm +++ b/code/game/objects/effects/effect_system/smoke.dm @@ -180,18 +180,20 @@ /obj/effect/particle_effect/smoke/mustard/Move() . = ..() - for(var/mob/living/carbon/human/R in get_turf(src)) - affect(R) + for(var/mob/living/carbon/human/creature in get_turf(src)) + affect(creature) -/obj/effect/particle_effect/smoke/mustard/affect(mob/living/carbon/human/R) - ..() - R.burn_skin(0.75) - if(R.coughedtime != 1) - R.coughedtime = 1 - if(ishuman(R)) //Humans only to avoid issues - R.emote("gasp") - addtimer(VARSET_CALLBACK(R, coughedtime, 0), 2 SECONDS) - R.updatehealth() +/obj/effect/particle_effect/smoke/mustard/affect(mob/living/carbon/human/creature) + if(!istype(creature) || issynth(creature)) + return FALSE + + creature.burn_skin(0.75) + if(creature.coughedtime != 1) + creature.coughedtime = 1 + if(ishuman(creature)) //Humans only to avoid issues + creature.emote("gasp") + addtimer(VARSET_CALLBACK(creature, coughedtime, 0), 2 SECONDS) + creature.updatehealth() return ///////////////////////////////////////////// @@ -244,6 +246,55 @@ M.updatehealth() +///////////////////////////////////////////// +// CN20 Nerve Gas +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/cn20 + name = "CN20 nerve gas" + smokeranking = SMOKE_RANK_HIGH + color = "#80c7e4" + +/obj/effect/particle_effect/smoke/cn20/Move() + . = ..() + for(var/mob/living/carbon/human/creature in get_turf(src)) + affect(creature) + +/obj/effect/particle_effect/smoke/cn20/affect(mob/living/carbon/human/creature) + if(!istype(creature) || issynth(creature) || creature.stat == DEAD) + return FALSE + if(isyautja(creature) && prob(75)) + return FALSE + + if (creature.wear_mask && (creature.wear_mask.flags_inventory & BLOCKGASEFFECT)) + return FALSE + + var/effect_amt = round(6 + amount*6) + + creature.apply_damage(12, OXY) + creature.SetEarDeafness(max(creature.ear_deaf, round(effect_amt*1.5))) //Paralysis of hearing system, aka deafness + if(!creature.eye_blind) //Eye exposure damage + to_chat(creature, SPAN_DANGER("Your eyes sting. You can't see!")) + creature.SetEyeBlind(round(effect_amt/3)) + if(creature.coughedtime != 1 && !creature.stat) //Coughing/gasping + creature.coughedtime = 1 + if(prob(50)) + creature.emote("cough") + else + creature.emote("gasp") + addtimer(VARSET_CALLBACK(creature, coughedtime, 0), 1.5 SECONDS) + if (prob(20)) + creature.apply_effect(1, WEAKEN) + + //Topical damage (neurotoxin on exposed skin) + to_chat(creature, SPAN_DANGER("Your body is going numb, almost as if paralyzed!")) + if(prob(60 + round(amount*15))) //Highly likely to drop items due to arms/hands seizing up + creature.drop_held_item() + if(ishuman(creature)) + creature.temporary_slowdown = max(creature.temporary_slowdown, 4) //One tick every two second + creature.recalculate_move_delay = TRUE + return TRUE + ////////////////////////////////////// // FLASHBANG SMOKE //////////////////////////////////// @@ -541,6 +592,9 @@ /datum/effect_system/smoke_spread/phosphorus/weak smoke_type = /obj/effect/particle_effect/smoke/phosphorus/weak +/datum/effect_system/smoke_spread/cn20 + smoke_type = /obj/effect/particle_effect/smoke/cn20 + // XENO SMOKES /datum/effect_system/smoke_spread/xeno_acid diff --git a/code/game/objects/structures/pipes/vents/vents.dm b/code/game/objects/structures/pipes/vents/vents.dm index fa3395d9e91d..2b3d5409dc8a 100644 --- a/code/game/objects/structures/pipes/vents/vents.dm +++ b/code/game/objects/structures/pipes/vents/vents.dm @@ -12,6 +12,7 @@ var/uid var/vent_icon = "vent" + var/datum/effect_system/smoke_spread/gas_holder /obj/structure/pipes/vents/Initialize() . = ..() @@ -123,7 +124,35 @@ qdel(src) /obj/structure/pipes/vents/Destroy() + qdel(gas_holder) if(initial_loc) initial_loc.air_vent_info -= id_tag initial_loc.air_vent_names -= id_tag . = ..() + +/obj/structure/pipes/vents/proc/create_gas(gas_type = VENT_GAS_SMOKE, radius = 4, warning_time = 5 SECONDS) + if(welded) + to_chat(usr, SPAN_WARNING("You cannot release gas from a welded vent.")) + return FALSE + var/datum/effect_system/smoke_spread/spreader + switch(gas_type) + if(VENT_GAS_SMOKE) + spreader = new /datum/effect_system/smoke_spread/bad + if(VENT_GAS_CN20) + spreader = new /datum/effect_system/smoke_spread/cn20 + if(!spreader) + return FALSE + gas_holder = spreader + spreader.attach(src) + + new /obj/effect/warning/explosive/gas(loc, warning_time) + visible_message(SPAN_HIGHDANGER("[src] begins to hiss as gas builds up within it."), SPAN_HIGHDANGER("You hear a hissing."), radius) + addtimer(CALLBACK(src, PROC_REF(release_gas), radius), warning_time) + +/obj/structure/pipes/vents/proc/release_gas(radius = 4) + radius = Clamp(radius, 1, 10) + if(!gas_holder || welded) + return FALSE + playsound(loc, 'sound/effects/smoke.ogg', 25, 1, 4) + gas_holder.set_up(radius, 0, get_turf(src), null, 10 SECONDS) + gas_holder.start() diff --git a/code/modules/admin/tabs/event_tab.dm b/code/modules/admin/tabs/event_tab.dm index febc1550fca0..89d709cbdda3 100644 --- a/code/modules/admin/tabs/event_tab.dm +++ b/code/modules/admin/tabs/event_tab.dm @@ -535,10 +535,11 @@ if(!input) return FALSE - var/datum/ares_link/link = GLOB.ares_link - if(link.p_interface.inoperable()) - to_chat(usr, SPAN_WARNING("[MAIN_AI_SYSTEM] is not responding. It may be offline or destroyed.")) - return + if(!ares_can_interface()) + var/prompt = tgui_alert(src, "ARES interface processor is offline or destroyed, send the message anyways?", "Choose.", list("Yes", "No"), 20 SECONDS) + if(prompt == "No") + to_chat(usr, SPAN_WARNING("[MAIN_AI_SYSTEM] is not responding. It's interface processor may be offline or destroyed.")) + return ai_announcement(input) message_admins("[key_name_admin(src)] has created an AI comms report") @@ -558,15 +559,12 @@ var/datum/ares_link/link = GLOB.ares_link if(link.p_apollo.inoperable()) - to_chat(usr, SPAN_WARNING("[MAIN_AI_SYSTEM] is not responding. It may be offline or destroyed.")) - return FALSE + var/prompt = tgui_alert(src, "ARES APOLLO processor is offline or destroyed, send the message anyways?", "Choose.", list("Yes", "No"), 20 SECONDS) + if(prompt == "No") + to_chat(usr, SPAN_WARNING("[MAIN_AI_SYSTEM] is not responding. It's APOLLO processor may be offline or destroyed.")) + return FALSE - var/datum/language/apollo/apollo = GLOB.all_languages[LANGUAGE_APOLLO] - for(var/mob/living/silicon/decoy/ship_ai/AI in ai_mob_list) - apollo.broadcast(AI, input) - for(var/mob/listener as anything in (GLOB.human_mob_list + GLOB.dead_mob_list)) - if(listener.hear_apollo())//Only plays sound to mobs and not observers, to reduce spam. - playsound_client(listener.client, sound('sound/misc/interference.ogg'), listener, vol = 45) + ares_apollo_talk(input) message_admins("[key_name_admin(src)] has created an AI APOLLO report") log_admin("AI APOLLO report: [input]") @@ -580,14 +578,15 @@ var/input = input(usr, "This is an announcement type message from the ship's AI. This will be announced to every conscious human on Almayer z-level. Be aware, this will work even if ARES unpowered/destroyed. Check with online staff before you send this.", "What?", "") as message|null if(!input) return FALSE - for(var/obj/structure/machinery/ares/processor/interface/processor in machines) - if(processor.inoperable()) - to_chat(usr, SPAN_WARNING("[MAIN_AI_SYSTEM] is not responding. It may be offline or destroyed.")) + if(!ares_can_interface()) + var/prompt = tgui_alert(src, "ARES interface processor is offline or destroyed, send the message anyways?", "Choose.", list("Yes", "No"), 20 SECONDS) + if(prompt == "No") + to_chat(usr, SPAN_WARNING("[MAIN_AI_SYSTEM] is not responding. It's interface processor may be offline or destroyed.")) return - shipwide_ai_announcement(input) - message_admins("[key_name_admin(src)] has created an AI shipwide report") - log_admin("[key_name_admin(src)] AI shipwide report: [input]") + shipwide_ai_announcement(input) + message_admins("[key_name_admin(src)] has created an AI shipwide report") + log_admin("[key_name_admin(src)] AI shipwide report: [input]") /client/proc/cmd_admin_create_predator_report() set name = "Report: Yautja AI" diff --git a/code/modules/cm_tech/droppod/lz_effect.dm b/code/modules/cm_tech/droppod/lz_effect.dm index 32e0bed74388..6a73916c7b3f 100644 --- a/code/modules/cm_tech/droppod/lz_effect.dm +++ b/code/modules/cm_tech/droppod/lz_effect.dm @@ -24,8 +24,6 @@ name = "hoverpack warning" color = "#D4AE1E" - color = "#D4AE1E" - /obj/effect/warning/explosive name = "explosive warning" color = "#ff0000" @@ -36,3 +34,6 @@ /obj/effect/warning/explosive/proc/disappear() qdel(src) +/obj/effect/warning/explosive/gas + name = "gas warning" + color = "#42acd6" diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index e5b693e02b80..2844b5841781 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -162,24 +162,31 @@ if (!message) return + ///Font size + var/scale = "message" + if(isARES(speaker)) + scale = "large" + var/message_start = "[name], [speaker.name]" var/message_body = "broadcasts, \"[message]\"" + var/full_message = "[message_start] [message_body]" + + GLOB.STUI.game.Add("\[[time_stamp()]]APOLLO: [key_name(speaker)] : [message]
") GLOB.STUI.processing |= STUI_LOG_GAME_CHAT log_say("[speaker.name != "Unknown" ? speaker.name : "([speaker.real_name])"] \[APOLLO\]: [message] (CKEY: [speaker.key]) (JOB: [speaker.job])") log_ares_apollo(speaker.real_name, message) for (var/mob/dead in GLOB.dead_mob_list) if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping - dead.show_message("[message_start] [message_body]", SHOW_MESSAGE_VISIBLE) + var/dead_message = "[message_start](F) [message_body]" + dead.show_message(dead_message, SHOW_MESSAGE_VISIBLE) for (var/mob/living/listener in GLOB.alive_mob_list) if (!listener.hear_apollo()) continue - else if(isAI(listener)) - message_start = "[name], [speaker.name]" - listener.show_message("[message_start] [message_body]", SHOW_MESSAGE_VISIBLE) + listener.show_message(full_message, SHOW_MESSAGE_VISIBLE) var/list/listening = hearers(1, src) listening -= src diff --git a/code/span_macros.dm b/code/span_macros.dm index d5e9cdcb9c36..1eca82ea563f 100644 --- a/code/span_macros.dm +++ b/code/span_macros.dm @@ -55,6 +55,7 @@ // Misc #define SPAN_BOLD(X) "[X]" #define SPAN_UNDERLINE(X) "[X]" +#define SPAN_LARGE(X) "[X]" #define SPAN_BOLDANNOUNCE(X) "[X]" #define SPAN_BOLDNOTICE(X) "[X]" diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js index 727973378649..97640d062a86 100644 --- a/tgui/packages/tgui/index.js +++ b/tgui/packages/tgui/index.js @@ -10,6 +10,7 @@ import './styles/themes/abductor.scss'; import './styles/themes/cardtable.scss'; import './styles/themes/crt/crt-blue.scss'; import './styles/themes/crt/crt-green.scss'; +import './styles/themes/crt/crt-red.scss'; import './styles/themes/crt/crt-yellow.scss'; import './styles/themes/spookyconsole.scss'; import './styles/themes/hackerman.scss'; diff --git a/tgui/packages/tgui/interfaces/AresInterface.js b/tgui/packages/tgui/interfaces/AresInterface.js index 6bf85e96522f..5cd78011a81c 100644 --- a/tgui/packages/tgui/interfaces/AresInterface.js +++ b/tgui/packages/tgui/interfaces/AresInterface.js @@ -22,11 +22,18 @@ const PAGES = { export const AresInterface = (props, context) => { const { data } = useBackend(context); - const { current_menu } = data; + const { current_menu, sudo } = data; const PageComponent = PAGES[current_menu](); + let themecolor = 'crtblue'; + if (sudo >= 1) { + themecolor = 'crtred'; + } else if (current_menu === 'emergency') { + themecolor = 'crtred'; + } + return ( - + @@ -1364,6 +1371,8 @@ const Emergency = (props, context) => { nuketimelock, nuke_available, } = data; + const canQuarters = alert_level < 2; + let quarters_reason = 'Call for General Quarters.'; const minimumEvacTime = worldtime > distresstimelock; const distressCooldown = worldtime < distresstime; const canDistress = alert_level === 2 && !distressCooldown && minimumEvacTime; @@ -1445,6 +1454,20 @@ const Emergency = (props, context) => {

Emergency Protocols

+ act('general_quarters')} + disabled={!canQuarters} + /> { const PageComponent = PAGES[current_menu](); return ( - + @@ -44,7 +44,7 @@ const Login = (props, context) => { WY-DOS Executive - Version 12.7.1 + Version 12.8.3 Copyright © 2182, Weyland Yutani Corp.