AI Display Fix + AI.dm Improvements (#21132)

Fixes https://github.com/Aurorastation/Aurora.3/issues/20503

Adds documentation to AI.dm and refactors procs that handle AI
Announcements and AI Emergency Messages

---------

Co-authored-by: Ben10083 <Ben10083@users.noreply.github.com>
This commit is contained in:
Ben
2025-08-09 12:23:47 +00:00
committed by GitHub
co-authored by Ben10083
parent dbbc3621bd
commit c505b7adcb
5 changed files with 175 additions and 68 deletions
+109 -64
View File
@@ -1,5 +1,7 @@
#define AI_CHECK_WIRELESS 1
#define AI_CHECK_RADIO 2
#define AI_ANNOUNCEMENT_COOLDOWN 1 MINUTE
#define AI_EMERGENCY_MESSAGE_COOLDOWN 30 SECONDS
GLOBAL_LIST_INIT_TYPED(ai_list, /mob/living/silicon/ai, list())
@@ -55,19 +57,23 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/carded
// Holopad and holograms
var/mob/holo_icon // an abstract mob used to store icon info
var/hologram_follow = TRUE //This is used for the AI eye, to determine if a holopad's hologram should follow it or not
/// An abstract mob used to store icon info
var/mob/holo_icon
/// This is used for the AI eye, to determine if a holopad's hologram should follow it or not
var/hologram_follow = TRUE
// Equipment
/// What camera network the AI can view
var/list/network = list("Station")
var/obj/machinery/camera/camera
var/list/cameraRecords = list() //For storing what is shown to the cameras
/// For storing what is shown to the cameras
var/list/cameraRecords = list()
var/obj/item/device/multitool/ai_multi
var/obj/item/device/radio/headset/heads/ai_integrated/ai_radio
var/datum/announcement/priority/announcement
var/obj/machinery/ai_powersupply/psupply
// Borgs
/// Borgs linked to AI
var/list/connected_robots = list()
// Damage variables
@@ -77,35 +83,59 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
// Misc variables
var/last_announcement = ""
/// Uses `world.time` to track when the last announcement was made. Used in `ai_announcement()`
var/last_announcement_time
/// Uses `world.time` to track when the last emergency message was sent. Used in `ai_emergency_message()`
var/last_emergency_message_time
/// If TRUE, AI is on backup power (restricts most actions)
var/ai_restore_power_routine = FALSE
var/view_alerts = FALSE
var/camera_light_on = FALSE //Defines if the AI toggled the light on the camera it's looking through.
/// Defines if the AI toggled the light on the camera it's looking through.
var/camera_light_on = FALSE
var/datum/trackable/track
var/control_disabled = FALSE
var/multitool_mode = FALSE
// Malf variables
var/malfunctioning = FALSE // Master var that determines if AI is malfunctioning.
var/datum/malf_hardware/hardware // Installed piece of hardware.
var/datum/malf_research/research // Malfunction research datum.
var/obj/machinery/power/apc/hack // APC that is currently being hacked.
var/list/hacked_apcs = list() // List of all hacked APCs
var/APU_power = FALSE // If set to 1 AI runs on APU power
var/hacking = FALSE // Set to 1 if AI is hacking APC, cyborg, other AI, or running system override.
var/system_override = FALSE // Set to 1 if system override is initiated, 2 if succeeded.
var/synthetic_takeover = FALSE // 1 is started, 2 is complete.
var/hack_can_fail = TRUE // If 0, all abilities have zero chance of failing.
var/hack_fails = 0 // This increments with each failed hack, and determines the warning message text.
var/errored = FALSE // Set to 1 if runtime error occurs. Only way of this happening i can think of is admin fucking up with varedit.
var/bombing_core = FALSE // Set to 1 if core auto-destruct is activated
var/bombing_station = FALSE // Set to 1 if station nuke auto-destruct is activated
var/bombing_time = 1200 // How much time is remaining for the nuke
var/override_CPUStorage = 0 // Bonus/Penalty CPU Storage. For use by admins/testers.
var/override_CPURate = 0 // Bonus/Penalty CPU generation rate. For use by admins/testers.
/// Master var that determines if AI is malfunctioning.
var/malfunctioning = FALSE
/// Installed piece of Malfunction hardware.
var/datum/malf_hardware/hardware
/// Malfunction research datum.
var/datum/malf_research/research
/// APC that is currently being hacked.
var/obj/machinery/power/apc/hack
/// List of all hacked APCs
var/list/hacked_apcs = list()
/// If set to TRUE AI runs on APU power
var/APU_power = FALSE
/// Set to TRUE if AI is hacking APC, cyborg, other AI, or running system override.
var/hacking = FALSE
/// Set to 1 if system override is initiated, 2 if succeeded.
var/system_override = 0
/// Set to 1 if synthetic takeover has started, 2 if it is complete.
var/synthetic_takeover = FALSE
/// If FALSE, all abilities have zero chance of failing.
var/hack_can_fail = TRUE
/// This increments with each failed hack, and determines the warning message text.
var/hack_fails = 0
/// Set to TRUE if runtime error occurs. Only way of this happening i can think of is admin fucking up with varedit.
var/errored = FALSE
/// Set to TRUE if core auto-destruct is activated
var/bombing_core = FALSE
/// Set to TRUE if station nuke auto-destruct is activated
var/bombing_station = FALSE
/// How much time is remaining for the nuke
var/bombing_time = 1200
/// Bonus/Penalty CPU Storage. For use by admins/testers.
var/override_CPUStorage = 0
/// Bonus/Penalty CPU generation rate. For use by admins/testers.
var/override_CPURate = 0
// Sprites
var/datum/ai_icon/selected_sprite // The selected icon set
var/custom_sprite = FALSE // Whether the selected icon is custom
/// The selected icon set
var/datum/ai_icon/selected_sprite
/// Whether the selected icon is custom
var/custom_sprite = FALSE
/mob/living/silicon/ai/proc/add_ai_verbs()
add_verb(src, GLOB.ai_verbs_default)
@@ -293,6 +323,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
set src = usr.contents
return FALSE
/// Proc override for AI SetName()
/mob/living/silicon/ai/SetName(pickedName as text)
..()
announcement.announcer = pickedName
@@ -311,9 +342,9 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
setup_icon() //this is because the ai custom name is related to the ai name, so, we just call the setup icon after someone named their ai
SSrecords.reset_manifest()
/*
The AI Power supply is a dummy object used for powering the AI since only machinery should be using power.
The alternative was to rewrite a bunch of AI code instead here we are.
/**
* The AI Power supply is a dummy object used for powering the AI since only machinery should be using power.
* The alternative was to rewrite a bunch of AI code instead here we are.
*/
/obj/machinery/ai_powersupply
name = "power supply"
@@ -393,16 +424,16 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
if (!custom_sprite)
var/new_sprite = tgui_input_list(src, "Select an icon!", "AI", GLOB.ai_icons, selected_sprite)
if(new_sprite) selected_sprite = new_sprite
if(new_sprite) selected_sprite = GLOB.ai_icons[new_sprite]
update_icon()
// this verb lets the ai see the stations manifest
/// This verb lets the ai see the stations manifest
/mob/living/silicon/ai/proc/ai_roster()
set category = "AI Commands"
set name = "Show Crew Manifest"
SSrecords.open_manifest_tgui(usr)
//AI Examine code
/// AI Examine code
/mob/living/silicon/ai/proc/ai_examine(atom/A as mob|obj|turf in view(src.eyeobj))
set category = "AI Commands"
set name = "Examine"
@@ -414,29 +445,33 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
face_atom(A)
A.examine(src)
/mob/living/silicon/ai/var/message_cooldown = 0
/**
* AI verb to make announcements
*
* `last_announcement_time` is compared with `world.time` and `AI_ANNOUNCEMENT_COOLDOWN` to prevent announcement spam
*/
/mob/living/silicon/ai/proc/ai_announcement()
set category = "AI Commands"
set name = "Make Station Announcement"
set name = "Make Ship-wide Announcement"
if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO))
return
return FALSE
if(message_cooldown)
to_chat(src, "Please allow one minute to pass between announcements.")
return
if((world.time - last_announcement_time) < AI_ANNOUNCEMENT_COOLDOWN)
to_chat(src, SPAN_WARNING("You need to wait <b>[DisplayTimeText(AI_ANNOUNCEMENT_COOLDOWN - (world.time - last_announcement_time))]</b> before you can make another announcement."))
return FALSE
var/input = tgui_input_text(usr, "Please write a message to announce to the [station_name(TRUE)] crew.", "A.I. Announcement", multiline = TRUE)
if(!input)
return
return FALSE
if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO))
return
return FALSE
announcement.Announce(input)
message_cooldown = 1
spawn(600)//One minute cooldown
message_cooldown = 0
last_announcement_time = world.time
return TRUE
/// Allows AI to call evacuation
/mob/living/silicon/ai/proc/ai_call_shuttle()
set category = "AI Commands"
set name = "Call Evacuation"
@@ -454,6 +489,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
post_display_status("shuttle")
/// Allows AI to cancel evacuation
/mob/living/silicon/ai/proc/ai_recall_shuttle()
set category = "AI Commands"
set name = "Cancel Evacuation"
@@ -468,28 +504,32 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
if(confirm == "Yes")
cancel_call_proc(src)
/mob/living/silicon/ai/var/emergency_message_cooldown = 0
/**
* AI verb to send emergency message to Centcom (admins)
*
* `last_emergency_message_time` is compared with `world.time` and `AI_EMERGENCY_MESSAGE_COOLDOWN` to reduce message spam
*/
/mob/living/silicon/ai/proc/ai_emergency_message()
set category = "AI Commands"
set name = "Send Emergency Message"
if(check_unable(AI_CHECK_WIRELESS))
return
return FALSE
if(!is_relay_online())
to_chat(usr, SPAN_WARNING("No Emergency Bluespace Relay detected. Unable to transmit message."))
return
if(emergency_message_cooldown)
to_chat(usr, SPAN_WARNING("Arrays recycling. Please stand by."))
return
return FALSE
if((world.time - last_emergency_message_time) < AI_EMERGENCY_MESSAGE_COOLDOWN)
to_chat(src, SPAN_WARNING("Arrays are recycling. You need to wait <b>[DisplayTimeText(AI_EMERGENCY_MESSAGE_COOLDOWN - (world.time - last_emergency_message_time))]</b> before you can send another message."))
return FALSE
var/input = sanitize(input(usr, "Please choose a message to transmit to [SSatlas.current_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", ""))
if(!input)
return
return FALSE
Centcomm_announce(input, usr)
to_chat(usr, SPAN_NOTICE("Message transmitted."))
log_say("[key_name(usr)] has made an AI [SSatlas.current_map.boss_short] announcement: [input]")
emergency_message_cooldown = 1
spawn(300)
emergency_message_cooldown = 0
log_say("[key_name(usr)] has sent a message to [SSatlas.current_map.boss_short]: [input]")
last_emergency_message_time = world.time
return TRUE
/mob/living/silicon/ai/check_eye(var/mob/user as mob)
@@ -498,7 +538,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
return 0
/mob/living/silicon/ai/restrained()
return 0
return FALSE
/mob/living/silicon/ai/emp_act(severity)
. = ..()
@@ -594,9 +634,10 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
//src.cameraFollow = null
src.view_core()
//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm
//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead
//Addition by Mord_Sith to define AI's network change ability
/** Replaces `/mob/living/silicon/ai/verb/change_network()` in ai.dm & camera.dm
* Adds in `/mob/living/silicon/ai/proc/ai_network_change()` instead
* Addition by Mord_Sith to define AI's network change ability
*/
/mob/living/silicon/ai/proc/get_camera_network_list()
if(check_unable())
return
@@ -633,7 +674,6 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
eyeobj.setLoc(get_turf(C))
break
to_chat(src, SPAN_NOTICE("Switched to [network] camera network."))
//End of code by Mord_Sith
/mob/living/silicon/ai/proc/ai_statuschange()
set category = "AI Commands"
@@ -695,7 +735,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
holo_icon.set_invisibility(0)
holo_icon.icon = I
//Toggles the luminosity and applies it by re-entereing the camera.
/// Toggles the luminosity and applies it by re-entereing the camera.
/mob/living/silicon/ai/proc/toggle_camera_light()
set name = "Toggle Camera Light"
set desc = "Toggles the light on the camera the AI is looking through."
@@ -715,9 +755,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
// Handled camera lighting, when toggled.
// It will get the nearest camera from the eyeobj, lighting it.
/// Handles camera lighting, when toggled, it will get the nearest camera from the `eyeobj`, lighting it.
/mob/living/silicon/ai/proc/lightNearbyCamera()
if(camera_light_on && camera_light_on < world.timeofday)
if(src.camera)
@@ -795,6 +833,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
to_chat(src, "Radio keys: [radio_keys]")
src.ai_radio.interact(src)
/// Verb to call `toggle_sensor_mode()` from `silicon.dm`
/mob/living/silicon/ai/proc/sensor_mode()
set name = "Set Sensor Augmentation"
set category = "AI Commands"
@@ -829,6 +868,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
else
SSvirtualreality.mind_transfer(src, choice)
/// AI verb to toggle `hologram_follow`
/mob/living/silicon/ai/proc/toggle_hologram_movement()
set name = "Toggle Hologram Movement"
set category = "AI Commands"
@@ -837,6 +877,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
hologram_follow = !hologram_follow
to_chat(usr, "<span class='info'>Your hologram will now [hologram_follow ? "follow" : "no longer follow"] you.</span>")
/// Checks if AI is able to perform certain actions by checking if dead, depowered, disconnected, etc.
/mob/living/silicon/ai/proc/check_unable(var/flags = 0, var/feedback = 1)
if(stat == DEAD)
if(feedback) to_chat(src, SPAN_WARNING("You are dead!"))
@@ -858,6 +899,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/proc/is_in_chassis()
return istype(loc, /turf)
/// AI verb to toggle `multitool_mode`
/mob/living/silicon/ai/proc/multitool_mode()
set name = "Toggle Multitool Mode"
set category = "AI Commands"
@@ -878,7 +920,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
icon_state = selected_sprite.alive_icon
set_light(1, 1, selected_sprite.alive_light)
// Pass lying down or getting up to our pet human, if we're in a rig.
/// Pass lying down or getting up to our pet human, if we're in a rig.
/mob/living/silicon/ai/lay_down()
set name = "Rest"
set category = "IC"
@@ -904,8 +946,11 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
cameraRecords += list(s)
return cameraRecords.len
/// Checks if AI is on backup power by checking`ai_restore_power_routine`. Returns TRUE if `ai_restore_power_routine` is FALSE
/mob/living/silicon/ai/proc/has_power()
return (ai_restore_power_routine == 0)
return (ai_restore_power_routine == FALSE)
#undef AI_CHECK_WIRELESS
#undef AI_CHECK_RADIO
#undef AI_ANNOUNCEMENT_COOLDOWN
#undef AI_EMERGENCY_MESSAGE_COOLDOWN
+1 -4
View File
@@ -1,5 +1,5 @@
GLOBAL_DATUM_INIT(default_ai_icon, /datum/ai_icon, new/datum/ai_icon/blue())
GLOBAL_LIST_INIT_TYPED(ai_icons, /datum/ai_icon, null)
GLOBAL_LIST_INIT_TYPED(ai_icons, /datum/ai_icon, list()) // Updated in misc_late.dm
/datum/ai_icon
var/name
@@ -19,9 +19,6 @@ GLOBAL_LIST_INIT_TYPED(ai_icons, /datum/ai_icon, null)
src.alive_light = alive_light
src.nopower_light = nopower_light
src.dead_light = dead_light
if(!GLOB.ai_icons)
GLOB.ai_icons = list()
init_subtypes(/datum/ai_icon, GLOB.ai_icons)
..()
/datum/ai_icon/red
@@ -234,6 +234,7 @@
src << browse(HTML_SKELETON(dat), "window=checklanguage")
return
/// Prompts the silicon mob which HUD to choose from, changes `sensor_mode` depending on choice
/mob/living/silicon/proc/toggle_sensor_mode()
var/sensor_type = tgui_input_list(src, "Please select sensor type.", "Sensor Integration", list("Security", "Medical", "Disable"))
switch(sensor_type)