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.