diff --git a/code/controllers/subsystems/initialization/misc_late.dm b/code/controllers/subsystems/initialization/misc_late.dm
index 2b4d8347728..deaf4f98b66 100644
--- a/code/controllers/subsystems/initialization/misc_late.dm
+++ b/code/controllers/subsystems/initialization/misc_late.dm
@@ -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
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index f9fcd68da43..ff54cf301ee 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -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 [DisplayTimeText(AI_ANNOUNCEMENT_COOLDOWN - (world.time - last_announcement_time))] 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 [DisplayTimeText(AI_EMERGENCY_MESSAGE_COOLDOWN - (world.time - last_emergency_message_time))] 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, "Your hologram will now [hologram_follow ? "follow" : "no longer follow"] you.")
+/// 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
diff --git a/code/modules/mob/living/silicon/ai/icons.dm b/code/modules/mob/living/silicon/ai/icons.dm
index b0443ef463f..02bd2fc19d4 100644
--- a/code/modules/mob/living/silicon/ai/icons.dm
+++ b/code/modules/mob/living/silicon/ai/icons.dm
@@ -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
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index adf78e52b59..16cade30444 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -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)
diff --git a/html/changelogs/Ben10083 - AI Fixes.yml b/html/changelogs/Ben10083 - AI Fixes.yml
new file mode 100644
index 00000000000..ea3556bef87
--- /dev/null
+++ b/html/changelogs/Ben10083 - AI Fixes.yml
@@ -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."