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.