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
@@ -42,4 +42,9 @@ SUBSYSTEM_DEF(misc_late)
var/obj/outfit/new_outfit = new outfit_type()
GLOB.outfit_cache[new_outfit.name] = new_outfit
// Load AI Icons here
for(var/ai_icon in subtypesof(/datum/ai_icon))
var/datum/ai_icon/new_icon = new ai_icon()
GLOB.ai_icons[new_icon.name] = ai_icon
return SS_INIT_SUCCESS
+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)
+59
View File
@@ -0,0 +1,59 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: Ben10083
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- bugfix: "AI core display change verb fixed."
- code_imp: "Improvements to ai.dm, mostly documentation."