Files
S.P.L.U.R.T-Station-13/code/modules/mob/living/silicon/ai/ai.dm
2023-05-06 18:31:55 -05:00

1095 lines
38 KiB
Plaintext

#define CALL_BOT_COOLDOWN 900
//Not sure why this is necessary...
/proc/AutoUpdateAI(obj/subject)
var/is_in_use = 0
if (subject!=null)
for(var/A in GLOB.ai_list)
var/mob/living/silicon/ai/M = A
if ((M.client && M.machine == subject))
is_in_use = 1
subject.attack_ai(M)
return is_in_use
/mob/living/silicon/ai
name = "AI"
icon = 'icons/mob/ai.dmi'
icon_state = "ai"
move_resist = MOVE_FORCE_OVERPOWERING
density = TRUE
mobility_flags = ALL
status_flags = CANSTUN|CANPUSH
a_intent = INTENT_HARM //so we always get pushed instead of trying to swap
sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
see_in_dark = 8
deathsound = 'sound/voice/scream/android_scream.ogg'
hud_type = /datum/hud/ai
med_hud = DATA_HUD_MEDICAL_BASIC
sec_hud = DATA_HUD_SECURITY_BASIC
d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED
mob_size = MOB_SIZE_LARGE
has_field_of_vision = FALSE //Vision through cameras.
var/list/network = list("ss13")
var/obj/machinery/camera/current
var/list/connected_robots = list()
var/aiRestorePowerRoutine = 0
var/requires_power = POWER_REQ_ALL
var/can_be_carded = TRUE
var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list())
var/viewalerts = 0
var/icon/holo_icon //Female is assigned when AI is created.
var/obj/controlled_equipment //A piece of equipment, to determine whether to relaymove or use the AI eye.
var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not.
radiomod = ";" //AIs will, by default, state their laws on the internal radio.
var/obj/item/pda/ai/aiPDA
var/obj/item/multitool/aiMulti
var/mob/living/simple_animal/bot/Bot
var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed.
var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N
var/obj/machinery/status_display/controlled_display
//MALFUNCTION
var/datum/module_picker/malf_picker
var/list/datum/AI_Module/current_modules = list()
var/can_dominate_mechs = FALSE
var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead
var/control_disabled = FALSE // Set to 1 to stop AI from interacting via Click()
var/malfhacking = FALSE // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite
var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown
var/obj/machinery/power/apc/malfhack
var/explosive = FALSE //does the AI explode when it dies?
var/mob/living/silicon/ai/parent
var/camera_light_on = FALSE
var/list/obj/machinery/camera/lit_cameras = list()
var/datum/trackable/track = new
var/last_paper_seen = null
var/can_shunt = TRUE
var/last_announcement = "" // For AI VOX, if enabled
var/turf/waypoint //Holds the turf of the currently selected waypoint.
var/waypoint_mode = FALSE //Waypoint mode is for selecting a turf via clicking.
var/call_bot_cooldown = 0 //time of next call bot command
var/apc_override = FALSE //hack for letting the AI use its APC even when visionless
var/nuking = FALSE
var/obj/machinery/doomsday_device/doomsday_device
var/mob/camera/aiEye/eyeobj
var/sprint = 10
var/cooldown = 0
var/acceleration = 1
var/obj/structure/AIcore/deactivated/linked_core //For exosuit control
var/mob/living/silicon/robot/deployed_shell = null //For shell control
var/datum/action/innate/deploy_shell/deploy_action = new
var/datum/action/innate/deploy_last_shell/redeploy_action = new
var/datum/action/innate/custom_holoform/custom_holoform = new
var/chnotify = 0
var/multicam_on = FALSE
var/atom/movable/screen/movable/pic_in_pic/ai/master_multicam
var/list/multicam_screens = list()
var/list/all_eyes = list()
var/max_multicams = 6
var/display_icon_override
var/emote_display = "Neutral" //text string of the current emote we set for the status displays, to prevent logins resetting it.
var/datum/robot_control/robot_control
/// Station alert datum for showing alerts UI
var/datum/station_alert/alert_control
///remember AI's last location
var/atom/lastloc
interaction_range = INFINITY
/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai)
. = ..()
if(!target_ai) //If there is no player/brain inside.
new/obj/structure/AIcore/deactivated(loc) //New empty terminal.
return INITIALIZE_HINT_QDEL //Delete AI.
ADD_TRAIT(src, TRAIT_NO_TELEPORT, src)
if(L && istype(L, /datum/ai_laws))
laws = L
laws.associate(src)
else
make_laws()
if(target_ai.mind)
target_ai.mind.transfer_to(src)
if(mind.special_role)
mind.store_memory("As an AI, you must obey your silicon laws above all else. Your objectives will consider you to be dead.")
to_chat(src, "<span class='userdanger'>You have been installed as an AI! </span>")
to_chat(src, "<span class='danger'>You must obey your silicon laws above all else. Your objectives will consider you to be dead.</span>")
to_chat(src, "<B>You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).</B>")
to_chat(src, "<B>To look at other parts of the station, click on yourself to get a camera menu.</B>")
to_chat(src, "<B>While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.</B>")
to_chat(src, "To use something, simply click on it.")
to_chat(src, "Use say :b to speak to your cyborgs through binary.")
to_chat(src, "For department channels, use the following say commands:")
to_chat(src, ":o - AI Private, :c - Command, :s - Security, :e - Engineering, :u - Supply, :v - Service, :m - Medical, :n - Science.")
show_laws()
to_chat(src, "<b>These laws may be changed by other players, or by you being the traitor.</b>")
job = "AI"
create_eye()
apply_pref_name("ai")
set_core_display_icon()
holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"female"))
spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src)
spark_system.attach(src)
add_verb(src, /mob/living/silicon/ai/proc/show_laws_verb)
aiPDA = new/obj/item/pda/ai(src)
aiPDA.owner = name
aiPDA.ownjob = "AI"
aiPDA.name = name + " (" + aiPDA.ownjob + ")"
aiMulti = new(src)
radio = new /obj/item/radio/headset/silicon/ai(src)
aicamera = new/obj/item/camera/siliconcam/ai_camera(src)
deploy_action.Grant(src)
custom_holoform.Grant(src)
if(isturf(loc))
add_verb(src, list(/mob/living/silicon/ai/proc/ai_network_change, \
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
/mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \
/mob/living/silicon/ai/proc/set_automatic_say_channel))
GLOB.ai_list += src
GLOB.shuttle_caller_list += src
builtInCamera = new (src)
builtInCamera.network = list("ss13")
/mob/living/silicon/ai/Destroy()
GLOB.ai_list -= src
GLOB.shuttle_caller_list -= src
SSshuttle.autoEvac()
QDEL_NULL(eyeobj) // No AI, no Eye
QDEL_NULL(spark_system)
QDEL_NULL(malf_picker)
QDEL_NULL(doomsday_device)
// TODO: Why these no work?
// QDEL_NULL(robot_control)
QDEL_NULL(aiMulti)
// QDEL_NULL(alert_control)
malfhack = null
current = null
Bot = null
controlled_equipment = null
linked_core = null
apc_override = null
return ..()
/mob/living/silicon/ai/IgniteMob()
fire_stacks = 0
. = ..()
/mob/living/silicon/ai/proc/set_core_display_icon(input, client/C)
set waitfor = FALSE
if(client && !C)
C = client
if(!input && !C?.prefs?.preferred_ai_core_display)
icon_state = initial(icon_state)
else
var/preferred_icon = input ? input : C.prefs.preferred_ai_core_display
icon_state = resolve_ai_icon(preferred_icon)
/mob/living/silicon/ai/verb/pick_icon()
set category = "AI Commands"
set name = "Set AI Core Display"
if(incapacitated())
return
var/list/iconstates = GLOB.ai_core_display_screens
for(var/option in iconstates)
if(option == "Random")
iconstates[option] = image(icon = src.icon, icon_state = "ai-random")
continue
iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option, radial_preview = TRUE))
view_core()
var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42)
if(!ai_core_icon || incapacitated())
return
display_icon_override = ai_core_icon
set_core_display_icon(ai_core_icon)
/mob/living/silicon/ai/get_status_tab_items()
. = ..()
if(stat != CONSCIOUS)
. += text("Systems nonfunctional")
return
. += text("System integrity: [(health + 100) * 0.5]%")
. += text("Connected cyborgs: [length(connected_robots)]")
for(var/r in connected_robots)
var/mob/living/silicon/robot/connected_robot = r
var/robot_status = "Nominal"
if(connected_robot.shell)
robot_status = "AI SHELL"
else if(connected_robot.stat != CONSCIOUS || !connected_robot.client)
robot_status = "OFFLINE"
else if(!connected_robot.cell || connected_robot.cell.charge <= 0)
robot_status = "DEPOWERED"
//Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies!
. += text("[connected_robot.name] | S.Integrity: [connected_robot.health]% | Cell: [connected_robot.cell ? "[connected_robot.cell.charge]/[connected_robot.cell.maxcharge]" : "Empty"] | \
Module: [connected_robot.designation] | Loc: [get_area_name(connected_robot, TRUE)] | Status: [robot_status]")
. += text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]") //Count of total AI shells
/mob/living/silicon/ai/proc/ai_alerts()
var/dat = "<HEAD><TITLE>Current Station Alerts</TITLE><META HTTP-EQUIV='Refresh' CONTENT='10'></HEAD><BODY>\n"
dat += "<A HREF='?src=[REF(src)];mach_close=aialerts'>Close</A><BR><BR>"
for (var/cat in alarms)
dat += text("<B>[]</B><BR>\n", cat)
var/list/L = alarms[cat]
if (L.len)
for (var/alarm in L)
var/list/alm = L[alarm]
var/area/A = alm[1]
var/C = alm[2]
var/list/sources = alm[3]
dat += "<NOBR>"
if (C && istype(C, /list))
var/dat2 = ""
for (var/obj/machinery/camera/I in C)
dat2 += text("[]<A HREF=?src=[REF(src)];switchcamera=[REF(I)]>[]</A>", (dat2=="") ? "" : " | ", I.c_tag)
dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera")
else if (C && istype(C, /obj/machinery/camera))
var/obj/machinery/camera/Ctmp = C
dat += text("-- [] (<A HREF=?src=[REF(src)];switchcamera=[REF(C)]>[]</A>)", A.name, Ctmp.c_tag)
else
dat += text("-- [] (No Camera)", A.name)
if (sources.len > 1)
dat += text("- [] sources", sources.len)
dat += "</NOBR><BR>\n"
else
dat += "-- All Systems Nominal<BR>\n"
dat += "<BR>\n"
viewalerts = 1
src << browse(dat, "window=aialerts&can_close=0")
/mob/living/silicon/ai/proc/ai_call_shuttle()
if(control_disabled)
to_chat(usr, "<span class='warning'>Wireless control is disabled!</span>")
return
var/reason = input(src, "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call") as null|text
if(incapacitated())
return
if(trim(reason))
SSshuttle.requestEvac(src, reason)
// hack to display shuttle timer
if(!EMERGENCY_IDLE_OR_RECALLED)
var/obj/machinery/computer/communications/C = locate() in GLOB.machines
if(C)
C.post_status("shuttle")
/mob/living/silicon/ai/can_interact_with(atom/A)
. = ..()
var/turf/ai = get_turf(src)
var/turf/target = get_turf(A)
if(!target)
return
if ((ai.z != target.z) && !is_station_level(ai.z))
return FALSE
if (istype(loc, /obj/item/aicard))
var/turf/T0 = get_turf(src)
var/turf/T1 = get_turf(A)
if (!T0 || ! T1)
return FALSE
return ISINRANGE(T1.x, T0.x - interaction_range, T0.x + interaction_range) && ISINRANGE(T1.y, T0.y - interaction_range, T0.y + interaction_range)
else
return GLOB.cameranet.checkTurfVis(get_turf(A))
/mob/living/silicon/ai/cancel_camera()
view_core()
/mob/living/silicon/ai/verb/toggle_anchor()
set category = "AI Commands"
set name = "Toggle Floor Bolts"
if(!isturf(loc)) // if their location isn't a turf
return // stop
if(incapacitated())
return
var/is_anchored = FALSE
if(move_resist == MOVE_FORCE_OVERPOWERING)
move_resist = MOVE_FORCE_NORMAL
REMOVE_TRAIT(src, TRAIT_NO_TELEPORT, src)
else
is_anchored = TRUE
move_resist = MOVE_FORCE_OVERPOWERING
ADD_TRAIT(src, TRAIT_NO_TELEPORT, src)
to_chat(src, "<b>You are now [is_anchored ? "" : "un"]anchored.</b>")
// the message in the [] will change depending whether or not the AI is anchored
// AIs are immobile
/mob/living/silicon/ai/update_mobility()
mobility_flags = ALL
return ALL
/mob/living/silicon/ai/proc/ai_cancel_call()
set category = "Malfunction"
if(control_disabled)
to_chat(src, "<span class='warning'>Wireless control is disabled!</span>")
return
SSshuttle.cancelEvac(src)
/mob/living/silicon/ai/restrained(ignore_grab)
. = 0
/mob/living/silicon/ai/Topic(href, href_list)
if(usr != src || incapacitated())
return
..()
if (href_list["mach_close"])
if (href_list["mach_close"] == "aialerts")
viewalerts = 0
var/t1 = text("window=[]", href_list["mach_close"])
unset_machine()
src << browse(null, t1)
if (href_list["switchcamera"])
switchCamera(locate(href_list["switchcamera"])) in GLOB.cameranet.cameras
if (href_list["showalerts"])
ai_alerts()
#ifdef AI_VOX
if(href_list["say_word"])
play_vox_word(href_list["say_word"], null, src)
return
#endif
if(href_list["show_paper"])
if(last_paper_seen)
src << browse(last_paper_seen, "window=show_paper")
//Carn: holopad requests
if(href_list["jumptoholopad"])
var/obj/machinery/holopad/H = locate(href_list["jumptoholopad"])
if(H)
H.attack_ai(src) //may as well recycle
else
to_chat(src, "<span class='notice'>Unable to locate the holopad.</span>")
if(href_list["track"])
var/string = href_list["track"]
trackable_mobs()
var/list/trackeable = list()
trackeable += track.humans + track.others
var/list/target = list()
for(var/I in trackeable)
var/mob/M = trackeable[I]
if(M.name == string)
target += M
if(name == string)
target += src
if(target.len)
ai_actual_track(pick(target))
else
to_chat(src, "Target is not on or near any active cameras on the station.")
return
if(href_list["callbot"]) //Command a bot to move to a selected location.
if(call_bot_cooldown > world.time)
to_chat(src, "<span class='danger'>Error: Your last call bot command is still processing, please wait for the bot to finish calculating a route.</span>")
return
Bot = locate(href_list["callbot"]) in GLOB.alive_mob_list
if(!Bot || Bot.remote_disabled || src.control_disabled)
return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off.
waypoint_mode = 1
to_chat(src, "<span class='notice'>Set your waypoint by clicking on a valid location free of obstructions.</span>")
return
if(href_list["interface"]) //Remotely connect to a bot!
Bot = locate(href_list["interface"]) in GLOB.alive_mob_list
if(!Bot || Bot.remote_disabled || src.control_disabled)
return
Bot.attack_ai(src)
if(href_list["botrefresh"]) //Refreshes the bot control panel.
botcall()
return
if (href_list["ai_take_control"]) //Mech domination
var/obj/vehicle/sealed/mecha/M = locate(href_list["ai_take_control"])
if(controlled_equipment)
to_chat(src, "<span class='warning'>You are already loaded into an onboard computer!</span>")
return
if(!GLOB.cameranet.checkCameraVis(M))
to_chat(src, "<span class='warning'>Exosuit is no longer near active cameras.</span>")
return
if(!isturf(loc))
to_chat(src, "<span class='warning'>You aren't in your core!</span>")
return
if(M)
M.transfer_ai(AI_MECH_HACK,src, usr) //Called om the mech itself.
/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C)
if(QDELETED(C))
return FALSE
if(!tracking)
cameraFollow = null
if(QDELETED(eyeobj))
view_core()
return
// ok, we're alive, camera is good and in our network...
eyeobj.setLoc(get_turf(C))
return TRUE
/mob/living/silicon/ai/proc/botcall()
set category = "AI Commands"
set name = "Access Robot Control"
set desc = "Wirelessly control various automatic robots."
if(incapacitated())
return
if(control_disabled)
to_chat(src, "<span class='warning'>Wireless control is disabled.</span>")
return
var/turf/ai_current_turf = get_turf(src)
var/ai_Zlevel = ai_current_turf.z
var/d
d += "<A HREF=?src=[REF(src)];botrefresh=1>Query network status</A><br>"
d += "<table width='100%'><tr><td width='40%'><h3>Name</h3></td><td width='30%'><h3>Status</h3></td><td width='30%'><h3>Location</h3></td><td width='10%'><h3>Control</h3></td></tr>"
for (Bot in GLOB.alive_mob_list)
if(Bot.z == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected!
var/bot_mode = Bot.get_mode()
d += "<tr><td width='30%'>[Bot.hacked ? "<span class='bad'>(!)</span>" : ""] [Bot.name]</A> ([Bot.model])</td>"
//If the bot is on, it will display the bot's current mode status. If the bot is not mode, it will just report "Idle". "Inactive if it is not on at all.
d += "<td width='30%'>[bot_mode]</td>"
d += "<td width='30%'>[get_area_name(Bot, TRUE)]</td>"
d += "<td width='10%'><A HREF=?src=[REF(src)];interface=[REF(Bot)]>Interface</A></td>"
d += "<td width='10%'><A HREF=?src=[REF(src)];callbot=[REF(Bot)]>Call</A></td>"
d += "</tr>"
d = format_text(d)
var/datum/browser/popup = new(src, "botcall", "Remote Robot Control", 700, 400)
popup.set_content(d)
popup.open()
/mob/living/silicon/ai/proc/set_waypoint(atom/A)
var/turf/turf_check = get_turf(A)
//The target must be in view of a camera or near the core.
if(turf_check in range(get_turf(src)))
call_bot(turf_check)
else if(GLOB.cameranet && GLOB.cameranet.checkTurfVis(turf_check))
call_bot(turf_check)
else
to_chat(src, "<span class='danger'>Selected location is not visible.</span>")
/mob/living/silicon/ai/proc/call_bot(turf/waypoint)
if(!Bot)
return
if(Bot.calling_ai && Bot.calling_ai != src) //Prevents an override if another AI is controlling this bot.
to_chat(src, "<span class='danger'>Interface error. Unit is already in use.</span>")
return
to_chat(src, "<span class='notice'>Sending command to bot...</span>")
call_bot_cooldown = world.time + CALL_BOT_COOLDOWN
Bot.call_bot(src, waypoint)
call_bot_cooldown = 0
/mob/living/silicon/ai/triggerAlarm(class, area/home, cameras, obj/source)
if(source.z != z)
return
var/list/our_sort = alarms[class]
for(var/areaname in our_sort)
if (areaname == home.name)
var/list/alarm = our_sort[areaname]
var/list/sources = alarm[3]
if (!(source in sources))
sources += source
return TRUE
var/obj/machinery/camera/cam = null
var/list/our_cams = null
if(cameras && islist(cameras))
our_cams = cameras
if (our_cams.len == 1)
cam = our_cams[1]
else if(cameras && istype(cameras, /obj/machinery/camera))
cam = cameras
our_sort[home.name] = list(home, (cam ? cam : cameras), list(source))
if (cameras)
if (cam?.can_use())
queueAlarm("--- [class] alarm detected in [home.name]! (<A HREF=?src=[REF(src)];switchcamera=[REF(cam)]>[cam.c_tag]</A>)", class)
else if (our_cams?.len)
var/foo = 0
var/dat2 = ""
for (var/obj/machinery/camera/I in our_cams)
dat2 += text("[]<A HREF=?src=[REF(src)];switchcamera=[REF(I)]>[]</A>", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit...
foo = 1
queueAlarm(text ("--- [] alarm detected in []! ([])", class, home.name, dat2), class)
else
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class)
else
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, home.name), class)
if (viewalerts)
ai_alerts()
return 1
/mob/living/silicon/ai/freeCamera(area/home, obj/machinery/camera/cam)
for(var/class in alarms)
var/our_area = alarms[class][home.name]
if(!our_area)
continue
var/cams = our_area[2] //Get the cameras
if(!cams)
continue
if(islist(cams))
cams -= cam
if(length(cams) == 1)
our_area[2] = cams[1]
else
our_area[2] = null
/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin)
var/list/L = alarms[class]
var/cleared = 0
for (var/I in L)
if (I == A.name)
var/list/alarm = L[I]
var/list/srcs = alarm[3]
if (origin in srcs)
srcs -= origin
if (srcs.len == 0)
cleared = 1
L -= I
if (cleared)
queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0)
if (viewalerts) ai_alerts()
return !cleared
//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/ai_network_change()
set category = "AI Commands"
set name = "Jump To Network"
unset_machine()
cameraFollow = null
var/cameralist[0]
if(incapacitated())
return
var/mob/living/silicon/ai/U = usr
for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
var/list/tempnetwork = C.network
if(!(is_station_level(C.z) || is_mining_level(C.z) || ("ss13" in tempnetwork)))
continue
if(!C.can_use())
continue
tempnetwork.Remove("rd", "toxins", "prison")
if(tempnetwork.len)
for(var/i in C.network)
cameralist[i] = i
var/old_network = network
network = input(U, "Which network would you like to view?") as null|anything in cameralist
if(!U.eyeobj)
U.view_core()
return
if(isnull(network))
network = old_network // If nothing is selected
else
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
if(!C.can_use())
continue
if(network in C.network)
U.eyeobj.setLoc(get_turf(C))
break
to_chat(src, "<span class='notice'>Switched to the \"[uppertext(network)]\" camera network.</span>")
//End of code by Mord_Sith
/mob/living/silicon/ai/proc/choose_modules()
set category = "Malfunction"
set name = "Choose Module"
malf_picker.use(src)
/mob/living/silicon/ai/proc/ai_statuschange()
set category = "AI Commands"
set name = "AI Status"
if(incapacitated())
return
var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Thinking", "Friend Computer", "Dorfy", "Blue Glow", "Red Glow")
var/n_emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions
if(!n_emote)
return
emote_display = n_emote
for (var/each in GLOB.ai_status_displays) //change status of displays
var/obj/machinery/status_display/ai/M = each
M.emotion = emote_display
M.update()
if (emote_display == "Friend Computer")
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
if(!frequency)
return
var/datum/signal/status_signal = new(list("command" = "friendcomputer"))
frequency.post_signal(src, status_signal)
return
//I am the icon meister. Bow fefore me. //>fefore
/mob/living/silicon/ai/proc/ai_hologram_change()
set name = "Change Hologram"
set desc = "Change the default hologram available to AI to something else."
set category = "AI Commands"
if(incapacitated())
return
var/input
switch(alert("Would you like to select a hologram based on a crew member, an animal, or switch to a unique avatar?",,"Crew Member","Unique","Animal"))
if("Crew Member")
var/list/personnel_list = list()
for(var/datum/data/record/t in GLOB.data_core.locked)//Look in data core locked.
personnel_list["[t.fields["name"]]: [t.fields["rank"]]"] = t.fields["image"]//Pull names, rank, and image.
if(personnel_list.len)
input = input("Select a crew member:") as null|anything in personnel_list
var/icon/character_icon = personnel_list[input]
if(character_icon)
qdel(holo_icon)//Clear old icon so we're not storing it in memory.
holo_icon = getHologramIcon(icon(character_icon))
else
alert("No suitable records found. Aborting.")
if("Animal")
var/list/icon_list = list(
"bear" = 'icons/mob/animal.dmi',
"carp" = 'icons/mob/animal.dmi',
"chicken" = 'icons/mob/animal.dmi',
"corgi" = 'icons/mob/pets.dmi',
"cow" = 'icons/mob/animal.dmi',
"crab" = 'icons/mob/animal.dmi',
"fox" = 'icons/mob/pets.dmi',
"goat" = 'icons/mob/animal.dmi',
"cat" = 'icons/mob/pets.dmi',
"cat2" = 'icons/mob/pets.dmi',
"polly" = 'icons/mob/animal.dmi',
"pug" = 'icons/mob/pets.dmi',
"spider" = 'icons/mob/animal.dmi'
)
input = input("Please select a hologram:") as null|anything in icon_list
if(input)
qdel(holo_icon)
switch(input)
if("polly")
holo_icon = getHologramIcon(icon(icon_list[input],"parrot_fly"))
if("chicken")
holo_icon = getHologramIcon(icon(icon_list[input],"chicken_brown"))
if("spider")
holo_icon = getHologramIcon(icon(icon_list[input],"guard"))
else
holo_icon = getHologramIcon(icon(icon_list[input], input))
else
var/list/icon_list = list(
"female" = 'icons/mob/ai.dmi',
"male" = 'icons/mob/ai.dmi',
"floating face" = 'icons/mob/ai.dmi',
"green face" = 'icons/mob/ai.dmi',
"xeno queen" = 'icons/mob/alien.dmi',
"horror" = 'icons/mob/ai.dmi',
"creature" = 'icons/mob/ai.dmi',
"custom"
)
input = input("Please select a hologram:") as null|anything in icon_list
if(input)
qdel(holo_icon)
switch(input)
if("custom")
if(client?.prefs?.custom_holoform_icon)
holo_icon = client.prefs.get_filtered_holoform(HOLOFORM_FILTER_AI)
else
holo_icon = getHologramIcon(icon('icons/mob/ai.dmi', "female"))
if("xeno queen")
holo_icon = getHologramIcon(icon(icon_list[input],"alienq"))
else
holo_icon = getHologramIcon(icon(icon_list[input], input))
/mob/living/silicon/ai/proc/corereturn()
set category = "Malfunction"
set name = "Return to Main Core"
var/obj/machinery/power/apc/apc = src.loc
if(!istype(apc))
to_chat(src, "<span class='notice'>You are already in your Main Core.</span>")
return
apc.malfvacate()
/mob/living/silicon/ai/proc/toggle_camera_light()
camera_light_on = !camera_light_on
if (!camera_light_on)
to_chat(src, "Camera lights deactivated.")
for (var/obj/machinery/camera/C in lit_cameras)
C.set_light(0)
lit_cameras = list()
return
light_cameras()
to_chat(src, "Camera lights activated.")
//AI_CAMERA_LUMINOSITY
/mob/living/silicon/ai/proc/light_cameras()
var/list/obj/machinery/camera/add = list()
var/list/obj/machinery/camera/remove = list()
var/list/obj/machinery/camera/visible = list()
for (var/datum/camerachunk/CC in eyeobj.visibleCameraChunks)
for (var/obj/machinery/camera/C in CC.cameras)
if (!C.can_use() || get_dist(C, eyeobj) > 7 || !C.internal_light)
continue
visible |= C
add = visible - lit_cameras
remove = lit_cameras - visible
for (var/obj/machinery/camera/C in remove)
lit_cameras -= C //Removed from list before turning off the light so that it doesn't check the AI looking away.
C.Togglelight(0)
for (var/obj/machinery/camera/C in add)
C.Togglelight(1)
lit_cameras |= C
/mob/living/silicon/ai/proc/control_integrated_radio()
set name = "Transceiver Settings"
set desc = "Allows you to change settings of your radio."
set category = "AI Commands"
if(incapacitated())
return
to_chat(src, "Accessing Subspace Transceiver control...")
if (radio)
radio.interact(src)
/mob/living/silicon/ai/proc/set_syndie_radio()
if(radio)
radio.make_syndie()
/mob/living/silicon/ai/proc/set_automatic_say_channel()
set name = "Set Auto Announce Mode"
set desc = "Modify the default radio setting for your automatic announcements."
set category = "AI Commands"
if(incapacitated())
return
set_autosay()
/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
if(!..())
return
if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card.
if(!can_be_carded)
to_chat(user, "<span class='boldwarning'>Transfer failed.</span>")
return
disconnect_shell() //If the AI is controlling a borg, force the player back to core!
if(!mind)
to_chat(user, "<span class='warning'>No intelligence patterns detected.</span>" )
return
ShutOffDoomsdayDevice()
new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location.
ai_restore_power()//So the AI initially has power.
control_disabled = 1//Can't control things remotely if you're stuck in a card!
radio_enabled = 0 //No talking on the built-in radio for you either!
forceMove(card)
card.AI = src
to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.")
to_chat(user, "<span class='boldnotice'>Transfer successful</span>: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.")
/mob/living/silicon/ai/can_buckle()
return 0
/mob/living/silicon/ai/incapacitated(ignore_restraints, ignore_grab)
if(aiRestorePowerRoutine)
return TRUE
return ..()
/mob/living/silicon/ai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE)
if(control_disabled || incapacitated())
to_chat(src, "<span class='warning'>You can't do that right now!</span>")
return FALSE
if(be_close && !in_range(M, src))
to_chat(src, "<span class='warning'>You are too far away!</span>")
return FALSE
return can_see(M) //stop AIs from leaving windows open and using then after they lose vision
/mob/living/silicon/ai/proc/can_see(atom/A)
if(isturf(loc)) //AI in core, check if on cameras
//get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera
//apc_override is needed here because AIs use their own APC when depowered
return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(get_turf_pixel(A))) || apc_override
//AI is carded/shunted
//view(src) returns nothing for carded/shunted AIs and they have X-ray vision so just use get_dist
var/list/viewscale = getviewsize(client.view)
return get_dist(src, A) <= max(viewscale[1]*0.5,viewscale[2]*0.5)
/mob/living/silicon/ai/proc/relay_speech(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
var/treated_message = lang_treat(speaker, message_language, raw_message, spans, message_mode)
var/start = "Relayed Speech: "
var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]"
var/hrefpart = "<a href='?src=[REF(src)];track=[html_encode(namepart)]'>"
var/jobpart = "Unknown"
if (iscarbon(speaker))
var/mob/living/carbon/S = speaker
if(S.job)
jobpart = "[S.job]"
var/rendered = "<i><span class='game say'>[start]<span class='name'>[hrefpart][namepart] ([jobpart])</a> </span><span class='message'>[treated_message]</span></span></i>"
if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker)))
create_chat_message(speaker, message_language, raw_message, spans, message_mode)
show_message(rendered, MSG_AUDIBLE)
/mob/living/silicon/ai/fully_replace_character_name(oldname,newname)
..()
if(oldname != real_name)
if(eyeobj)
eyeobj.name = "[newname] (AI Eye)"
// Notify Cyborgs
for(var/mob/living/silicon/robot/Slave in connected_robots)
Slave.show_laws()
/mob/living/silicon/ai/replace_identification_name(oldname,newname)
if(aiPDA)
aiPDA.owner = newname
aiPDA.name = newname + " (" + aiPDA.ownjob + ")"
/mob/living/silicon/ai/proc/add_malf_picker()
to_chat(src, "In the top right corner of the screen you will find the Malfunctions tab, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices.")
to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.")
view_core() //A BYOND bug requires you to be viewing your core before your verbs update
add_verb(src, /mob/living/silicon/ai/proc/choose_modules)
malf_picker = new /datum/module_picker
/mob/living/silicon/ai/reset_perspective(atom/A)
if(camera_light_on)
light_cameras()
if(istype(A, /obj/machinery/camera))
current = A
if(!client)
return
if(ismovable(A))
if(A != GLOB.ai_camera_room_landmark)
end_multicam()
client.perspective = EYE_PERSPECTIVE
client.eye = A
else
end_multicam()
if(isturf(loc))
if(eyeobj)
client.eye = eyeobj
client.perspective = EYE_PERSPECTIVE
else
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else
client.perspective = EYE_PERSPECTIVE
client.eye = loc
update_sight()
if(client.eye != src)
var/atom/AT = client.eye
AT.get_remote_view_fullscreens(src)
else
clear_fullscreen("remote_view", 0)
SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE, A)
return TRUE
/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0)
. = ..()
if(.) //successfully ressuscitated from death
set_eyeobj_visible(TRUE)
set_core_display_icon(display_icon_override)
/mob/living/silicon/ai/proc/malfhacked(obj/machinery/power/apc/apc)
malfhack = null
malfhacking = 0
clear_alert("hackingapc")
if(!istype(apc) || QDELETED(apc) || apc.stat & BROKEN)
to_chat(src, "<span class='danger'>Hack aborted. The designated APC no longer exists on the power network.</span>")
playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, TRUE, ignore_walls = FALSE)
else if(apc.aidisabled)
to_chat(src, "<span class='danger'>Hack aborted. \The [apc] is no longer responding to our systems.</span>")
playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, TRUE, ignore_walls = FALSE)
else
malf_picker.processing_time += 10
apc.malfai = parent || src
apc.malfhack = TRUE
apc.locked = TRUE
apc.coverlocked = TRUE
playsound(get_turf(src), 'sound/machines/ding.ogg', 50, TRUE, ignore_walls = FALSE)
to_chat(src, "Hack complete. \The [apc] is now under your exclusive control.")
apc.update_icon()
/mob/living/silicon/ai/verb/deploy_to_shell(var/mob/living/silicon/robot/target)
set category = "AI Commands"
set name = "Deploy to Shell"
if(incapacitated())
return
if(control_disabled)
to_chat(src, "<span class='warning'>Wireless networking module is offline.</span>")
return
var/list/possible = list()
for(var/borgie in GLOB.available_ai_shells)
var/mob/living/silicon/robot/R = borgie
if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai ||(R.connected_ai == src)))
possible += R
if(!LAZYLEN(possible))
to_chat(src, "No usable AI shell beacons detected.")
if(!target || !(target in possible)) //If the AI is looking for a new shell, or its pre-selected shell is no longer valid
target = input(src, "Which body to control?") as null|anything in possible
if (!target || target.stat == DEAD || target.deployed || !(!target.connected_ai ||(target.connected_ai == src)))
return
else if(mind)
soullink(/datum/soullink/sharedbody, src, target)
deployed_shell = target
target.deploy_init(src)
mind.transfer_to(target)
diag_hud_set_deployed()
/datum/action/innate/deploy_shell
name = "Deploy to AI Shell"
desc = "Wirelessly control a specialized cyborg shell."
icon_icon = 'icons/mob/actions/actions_AI.dmi'
button_icon_state = "ai_shell"
/datum/action/innate/deploy_shell/Trigger()
var/mob/living/silicon/ai/AI = owner
if(!AI)
return
AI.deploy_to_shell()
/datum/action/innate/deploy_last_shell
name = "Reconnect to shell"
desc = "Reconnect to the most recently used AI shell."
icon_icon = 'icons/mob/actions/actions_AI.dmi'
button_icon_state = "ai_last_shell"
var/mob/living/silicon/robot/last_used_shell
/datum/action/innate/deploy_last_shell/Trigger()
if(!owner)
return
if(last_used_shell)
var/mob/living/silicon/ai/AI = owner
AI.deploy_to_shell(last_used_shell)
else
Remove(owner) //If the last shell is blown, destroy it.
/mob/living/silicon/ai/proc/disconnect_shell()
if(deployed_shell) //Forcibly call back AI in event of things such as damage, EMP or power loss.
to_chat(src, "<span class='danger'>Your remote connection has been reset!</span>")
deployed_shell.undeploy()
diag_hud_set_deployed()
/mob/living/silicon/ai/do_resist()
return
/mob/living/silicon/ai/spawned/Initialize(mapload, datum/ai_laws/L, mob/target_ai)
. = ..()
if(!target_ai)
target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct
/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye)
GLOB.cameranet.visibility(moved_eye, client, all_eyes, USE_STATIC_OPAQUE)
/mob/living/silicon/ai/forceMove(atom/destination)
. = ..()
if(.)
end_multicam()
/mob/living/silicon/ai/verb/ai_cryo()
set name = "AI Cryogenic Stasis"
set desc = "Puts the current AI personality into cryogenic stasis, freeing the space for another."
set category = "AI Commands"
if(incapacitated())
return
//SPLURT CHANGES (No longer tells AI it has to ahelp first)
switch(alert("Would you like to enter cryo? This will ghost you.",,"Yes.","No."))
if("Yes.")
src.ghostize(FALSE, penalize = TRUE)
var/announce_rank = "Artificial Intelligence,"
if(GLOB.announcement_systems.len)
// Sends an announcement the AI has cryoed.
var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems)
announcer.announce("CRYOSTORAGE", src.real_name, announce_rank, list())
new /obj/structure/AIcore/latejoin_inactive(loc)
if(src.mind)
//Handle job slot/tater cleanup.
if(src.mind.assigned_role == "AI")
SSjob.FreeRole("AI")
src.mind.special_role = null
qdel(src)
else
return
/mob/living/silicon/ai/emote(act, m_type=1, message = null, intentional = FALSE)
if(current && eyeobj)
return eyeobj.emote(act, m_type, message, intentional, forced = TRUE)
return ..()
/mob/living/silicon/ai/zMove(dir, feedback = FALSE)
. = eyeobj.zMove(dir, feedback)
/mob/living/silicon/ai/proc/stop_controlling_display()
if(!controlled_display)
return
controlled_display.master = null
controlled_display.cut_overlay(controlled_display.ai_vtuber_overlay)
controlled_display.ai_vtuber_overlay = null
if(current == controlled_display)
current = null
controlled_display.update_appearance()
controlled_display = null