mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
1011 lines
34 KiB
Plaintext
1011 lines
34 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"
|
|
real_name = "AI"
|
|
icon = 'icons/mob/ai.dmi'
|
|
icon_state = "ai"
|
|
move_resist = MOVE_FORCE_VERY_STRONG
|
|
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
|
|
hud_type = /datum/hud/ai
|
|
med_hud = DATA_HUD_MEDICAL_ADVANCED
|
|
sec_hud = DATA_HUD_SECURITY_ADVANCED
|
|
d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED
|
|
mob_size = MOB_SIZE_LARGE
|
|
var/battery = 200 //emergency power if the AI's APC is off
|
|
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//Default is assigned when AI is created.
|
|
var/obj/mecha/controlled_mech //For controlled_mech a mech, 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/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
|
|
|
|
//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/chnotify = 0
|
|
|
|
var/multicam_on = FALSE
|
|
var/obj/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/list/cam_hotkeys = new/list(9)
|
|
var/cam_prev
|
|
|
|
var/datum/robot_control/robot_control
|
|
|
|
/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.
|
|
|
|
if(L && istype(L, /datum/ai_laws))
|
|
laws = L
|
|
laws.associate(src)
|
|
else
|
|
make_laws()
|
|
|
|
update_law_history() //yogs
|
|
|
|
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()
|
|
if(client)
|
|
apply_pref_name("ai",client)
|
|
|
|
set_core_display_icon()
|
|
|
|
holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"default"))
|
|
|
|
spark_system = new /datum/effect_system/spark_spread()
|
|
spark_system.set_up(5, 0, src)
|
|
spark_system.attach(src)
|
|
|
|
verbs += /mob/living/silicon/ai/proc/show_laws_verb
|
|
|
|
aiPDA = new/obj/item/pda/ai(src)
|
|
aiPDA.owner = real_name
|
|
aiPDA.ownjob = "AI"
|
|
aiPDA.name = real_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)
|
|
|
|
if(isturf(loc))
|
|
verbs.Add(/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.c_tag = real_name
|
|
builtInCamera.network = list("ss13")
|
|
builtInCamera.built_in = src
|
|
|
|
/mob/living/silicon/ai/key_down(_key, client/user)
|
|
if(findtext(_key, "numpad")) //if it's a numpad number, we can convert it to just the number
|
|
_key = _key[7] //strings, lists, same thing really
|
|
switch(_key)
|
|
if("`", "0")
|
|
if(cam_prev)
|
|
eyeobj.setLoc(cam_prev)
|
|
return
|
|
if("1", "2", "3", "4", "5", "6", "7", "8", "9")
|
|
_key = text2num(_key)
|
|
if(client.keys_held["Ctrl"]) //do we assign a new hotkey?
|
|
cam_hotkeys[_key] = eyeobj.loc
|
|
to_chat(src, "Location saved to Camera Group [_key].")
|
|
return
|
|
if(cam_hotkeys[_key]) //if this is false, no hotkey for this slot exists.
|
|
cam_prev = eyeobj.loc
|
|
eyeobj.setLoc(cam_hotkeys[_key])
|
|
return
|
|
return ..()
|
|
|
|
/mob/living/silicon/ai/Destroy()
|
|
GLOB.ai_list -= src
|
|
GLOB.shuttle_caller_list -= src
|
|
SSshuttle.autoEvac()
|
|
qdel(eyeobj) // No AI, no Eye
|
|
malfhack = null
|
|
|
|
. = ..()
|
|
|
|
/mob/living/silicon/ai/IgniteMob()
|
|
fire_stacks = 0
|
|
. = ..()
|
|
|
|
/mob/living/silicon/ai/proc/set_core_display_icon(input, client/C)
|
|
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 = initial(icon) //yogs
|
|
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 = initial(src.icon), icon_state = "ai-random") //yogs start - AI donor icons
|
|
continue
|
|
iconstates[option] = image(icon = initial(src.icon), icon_state = resolve_ai_icon(option))
|
|
|
|
if(is_donator(client))
|
|
for(var/datum/ai_skin/S in GLOB.DonorBorgHolder.skins)
|
|
if(S.owner == client.ckey || !S.owner) //We own this skin.
|
|
iconstates[S] = image(icon = S.icon, icon_state = S.icon_state)
|
|
|
|
view_core()
|
|
var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42)
|
|
|
|
if(!ai_core_icon || incapacitated())
|
|
return
|
|
|
|
if(ai_core_icon in GLOB.DonorBorgHolder.skins)
|
|
set_core_display_icon_yogs(ai_core_icon)
|
|
return //yogs end - AI donor icons
|
|
|
|
display_icon_override = ai_core_icon
|
|
set_core_display_icon(ai_core_icon)
|
|
|
|
/mob/living/silicon/ai/get_status_tab_items()
|
|
. = ..()
|
|
if(!stat)
|
|
. += text("System integrity: [(health+100)/2]%")
|
|
if(isturf(loc)) //only show if we're "in" a core
|
|
. += text("Backup Power: [battery/2]%")
|
|
. += text("Connected cyborgs: [connected_robots.len]")
|
|
for(var/mob/living/silicon/robot/R in connected_robots)
|
|
var/robot_status = "Nominal"
|
|
if(R.shell)
|
|
robot_status = "AI SHELL"
|
|
else if(R.stat || !R.client)
|
|
robot_status = "OFFLINE"
|
|
else if(!R.cell || R.cell.charge <= 0)
|
|
robot_status = "DEPOWERED"
|
|
//Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies!
|
|
. += text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \
|
|
Module: [R.designation] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")
|
|
. += text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]") //Count of total AI shells
|
|
else
|
|
. += text("Systems nonfunctional")
|
|
|
|
/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
|
|
var/datum/browser/alerts = new(usr, "aitalerts", "Current Station Alerts", 400, 410)
|
|
alerts.set_content(dat)
|
|
alerts.open()
|
|
|
|
/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 (.)
|
|
return
|
|
if ((ai.z != target.z) && !is_station_level(ai))
|
|
return FALSE
|
|
|
|
if (istype(loc, /obj/item/aicard))
|
|
if (!ai || !target)
|
|
return FALSE
|
|
return ISINRANGE(target.x, ai.x - interaction_range, ai.x + interaction_range) && ISINRANGE(target.y, ai.y - interaction_range, ai.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(stat == DEAD)
|
|
return
|
|
if(incapacitated())
|
|
if(battery < 50)
|
|
to_chat(src, "<span class='warning'>Insufficient backup power!</span>")
|
|
return
|
|
battery = battery - 50
|
|
to_chat(src, "<span class='notice'>You route power from your backup battery to move the bolts.</span>")
|
|
var/is_anchored = FALSE
|
|
if(move_resist == MOVE_FORCE_VERY_STRONG)
|
|
move_resist = MOVE_FORCE_NORMAL
|
|
else
|
|
is_anchored = TRUE
|
|
move_resist = MOVE_FORCE_VERY_STRONG
|
|
|
|
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
|
|
|
|
/mob/living/silicon/ai/update_mobility() //If the AI dies, mobs won't go through it anymore
|
|
if(stat != CONSCIOUS)
|
|
mobility_flags = NONE
|
|
else
|
|
mobility_flags = 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"]) in GLOB.machines
|
|
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["ai_take_control"]) //Mech domination
|
|
var/obj/mecha/M = locate(href_list["ai_take_control"]) in GLOB.mechas_list
|
|
if (!M)
|
|
return
|
|
|
|
var/mech_has_controlbeacon = FALSE
|
|
for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in M.trackers)
|
|
mech_has_controlbeacon = TRUE
|
|
break
|
|
if(!can_dominate_mechs && !mech_has_controlbeacon)
|
|
message_admins("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.")
|
|
log_game("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.")
|
|
return
|
|
|
|
if(controlled_mech)
|
|
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(!robot_control)
|
|
robot_control = new(src)
|
|
robot_control.ui_interact(src)
|
|
|
|
/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/A, O, obj/alarmsource)
|
|
if(alarmsource.z != z)
|
|
return
|
|
var/list/L = alarms[class]
|
|
for (var/I in L)
|
|
if (I == A.name)
|
|
var/list/alarm = L[I]
|
|
var/list/sources = alarm[3]
|
|
if (!(alarmsource in sources))
|
|
sources += alarmsource
|
|
return 1
|
|
var/obj/machinery/camera/C = null
|
|
var/list/CL = null
|
|
if (O && istype(O, /list))
|
|
CL = O
|
|
if (CL.len == 1)
|
|
C = CL[1]
|
|
else if (O && istype(O, /obj/machinery/camera))
|
|
C = O
|
|
L[A.name] = list(A, (C) ? C : O, list(alarmsource))
|
|
if (O)
|
|
if (C && C.can_use())
|
|
queueAlarm("--- [class] alarm detected in [A.name]! (<A HREF=?src=[REF(src)];switchcamera=[REF(C)]>[C.c_tag]</A>)", class)
|
|
else if (CL && CL.len)
|
|
var/foo = 0
|
|
var/dat2 = ""
|
|
for (var/obj/machinery/camera/I in CL)
|
|
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, A.name, dat2), class)
|
|
else
|
|
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class)
|
|
else
|
|
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class)
|
|
if (viewalerts) ai_alerts()
|
|
return 1
|
|
|
|
/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.ui_interact(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/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions
|
|
for (var/each in GLOB.ai_status_displays) //change status of displays
|
|
var/obj/machinery/status_display/ai/M = each
|
|
M.emotion = emote
|
|
M.update()
|
|
if (emote == "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',
|
|
"poly" = '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("poly")
|
|
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(
|
|
"default" = 'icons/mob/ai.dmi',
|
|
"floating face" = 'icons/mob/ai.dmi',
|
|
"xeno queen" = 'icons/mob/alien.dmi',
|
|
"horror" = 'icons/mob/ai.dmi',
|
|
"automaton" = 'icons/mob/ai.dmi'
|
|
)
|
|
|
|
input = input("Please select a hologram:") as null|anything in icon_list
|
|
if(input)
|
|
qdel(holo_icon)
|
|
switch(input)
|
|
if("xeno queen")
|
|
holo_icon = getHologramIcon(icon(icon_list[input],"alienq"))
|
|
else
|
|
holo_icon = getHologramIcon(icon(icon_list[input], input))
|
|
return
|
|
|
|
/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()
|
|
var/obj/structure/AIcore/new_core = new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location.
|
|
new_core.circuit.battery = battery
|
|
ai_restore_power()//So the AI initially has power.
|
|
control_disabled = TRUE //Can't control things remotely if you're stuck in a card!
|
|
radio_enabled = FALSE //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 = FALSE, ignore_grab = FALSE, check_immobilized = FALSE, ignore_stasis = FALSE)
|
|
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, list/message_mods = list())
|
|
var/treated_message = lang_treat(speaker, message_language, raw_message, spans, message_mods)
|
|
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(istype(speaker, /obj/effect/overlay/holo_pad_hologram))
|
|
return
|
|
|
|
if (iscarbon(speaker))
|
|
var/mob/living/carbon/S = speaker
|
|
if(S.job)
|
|
jobpart = "[S.job]"
|
|
else
|
|
jobpart = "Unknown"
|
|
|
|
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)
|
|
|
|
show_message(rendered, 2)
|
|
|
|
/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 30 seconds.")
|
|
view_core() //A BYOND bug requires you to be viewing your core before your verbs update
|
|
verbs += /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)
|
|
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)
|
|
|
|
/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0)
|
|
. = ..()
|
|
if(.) //successfully ressuscitated from death
|
|
set_core_display_icon(display_icon_override)
|
|
set_eyeobj_visible(TRUE)
|
|
|
|
/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, 1, 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, 1, ignore_walls = FALSE)
|
|
else
|
|
malf_picker.processing_time += 5
|
|
|
|
apc.malfai = parent || src
|
|
apc.malfhack = TRUE
|
|
apc.locked = TRUE
|
|
apc.coverlocked = TRUE
|
|
|
|
playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1, 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()
|
|
deployed_shell = null
|
|
diag_hud_set_deployed()
|
|
|
|
/mob/living/silicon/ai/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() |