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."