5ec3f3eab8
Fixed alternate rank not showing on PDA Fixed Manifest because record not being filled correctly
1063 lines
36 KiB
Plaintext
1063 lines
36 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/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/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/datum/weakref/bot_ref
|
|
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/ai_core/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
|
|
/// Announcement UI
|
|
var/datum/ai_announcement/ai_announcement
|
|
/// Lists possible spoken words for announcements
|
|
var/datum/announcement_help/announcement_help
|
|
///remember AI's last location
|
|
var/atom/lastloc
|
|
interaction_range = INFINITY
|
|
///whether its mmi is a posibrain or regular mmi when going ai mob to ai core structure
|
|
var/posibrain_inside = TRUE
|
|
///whether its cover is opened, so you can wirecut it for deconstruction
|
|
var/opened = FALSE
|
|
///whether AI is anchored or not, used for checks
|
|
var/is_anchored = TRUE
|
|
|
|
/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/ai_core/deactivated(loc) //New empty terminal.
|
|
return INITIALIZE_HINT_QDEL //Delete AI.
|
|
|
|
ADD_TRAIT(src, TRAIT_NO_TELEPORT, INNATE_TRAIT)
|
|
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>")
|
|
if(!mind.has_ever_been_ai)
|
|
mind.has_ever_been_ai = TRUE
|
|
|
|
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")
|
|
|
|
alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z), camera_view = TRUE)
|
|
RegisterSignal(alert_control.listener, COMSIG_ALARM_TRIGGERED, PROC_REF(alarm_triggered))
|
|
RegisterSignal(alert_control.listener, COMSIG_ALARM_CLEARED, PROC_REF(alarm_cleared))
|
|
|
|
/mob/living/silicon/ai/Destroy()
|
|
GLOB.ai_list -= src
|
|
GLOB.shuttle_caller_list -= src
|
|
SSshuttle.autoEvac()
|
|
stop_controlling_display()
|
|
QDEL_NULL(eyeobj) // No AI, no Eye
|
|
QDEL_NULL(spark_system)
|
|
QDEL_NULL(deploy_action)
|
|
QDEL_NULL(redeploy_action)
|
|
QDEL_NULL(custom_holoform)
|
|
QDEL_NULL(master_multicam)
|
|
deployed_shell = null
|
|
parent = null
|
|
QDEL_NULL(spark_system)
|
|
QDEL_NULL(malf_picker)
|
|
QDEL_NULL(doomsday_device)
|
|
QDEL_NULL(robot_control)
|
|
QDEL_NULL(alert_control)
|
|
QDEL_NULL(ai_announcement)
|
|
QDEL_NULL(announcement_help)
|
|
QDEL_NULL(aiMulti)
|
|
QDEL_NULL(aiPDA)
|
|
malfhack = null
|
|
current = null
|
|
bot_ref = null
|
|
controlled_equipment = null
|
|
linked_core = null
|
|
apc_override = null
|
|
for(var/mob/living/silicon/robot/linked_robot as anything in connected_robots)
|
|
if(QDELETED(linked_robot))
|
|
continue
|
|
linked_robot.set_connected_ai(null)
|
|
connected_robots.Cut()
|
|
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)
|
|
. += "Systems nonfunctional"
|
|
return
|
|
. += "System integrity: [(health + 100) * 0.5]%"
|
|
if(isturf(loc)) //only show if we're "in" a core
|
|
. += "Backup Power: [battery * 0.5]%"
|
|
. += "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!
|
|
. += "[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]"
|
|
. += "AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]" //Count of total AI shells
|
|
|
|
/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())
|
|
if(battery < 50)
|
|
to_chat(src, span_warning("Insufficient backup power!"))
|
|
return
|
|
battery = battery - 50
|
|
to_chat(src, span_notice("You route power from your backup battery to move the bolts."))
|
|
flip_anchored()
|
|
to_chat(src, "<b>You are now [is_anchored ? "" : "un"]anchored.</b>")
|
|
|
|
/mob/living/silicon/ai/proc/flip_anchored()
|
|
if(is_anchored)
|
|
is_anchored = !is_anchored
|
|
move_resist = MOVE_FORCE_NORMAL
|
|
status_flags |= CANPUSH
|
|
REMOVE_TRAIT(src, TRAIT_NO_TELEPORT, INNATE_TRAIT)
|
|
else
|
|
is_anchored = !is_anchored
|
|
move_resist = MOVE_FORCE_OVERPOWERING
|
|
status_flags &= ~CANPUSH
|
|
ADD_TRAIT(src, TRAIT_NO_TELEPORT, INNATE_TRAIT)
|
|
|
|
/mob/living/silicon/ai/proc/ai_mob_to_structure()
|
|
disconnect_shell()
|
|
ShutOffDoomsdayDevice()
|
|
var/obj/structure/ai_core/deactivated/ai_core = new(get_turf(src), /* skip_mmi_creation = */ TRUE)
|
|
if(make_mmi_drop_and_transfer(ai_core.core_mmi, the_core = ai_core))
|
|
qdel(src)
|
|
return ai_core
|
|
|
|
/mob/living/silicon/ai/proc/make_mmi_drop_and_transfer(obj/item/mmi/the_mmi, the_core)
|
|
var/mmi_type
|
|
if(posibrain_inside)
|
|
mmi_type = new/obj/item/mmi/posibrain(src, /* autoping = */ FALSE)
|
|
else
|
|
mmi_type = new/obj/item/mmi(src)
|
|
if(hack_software)
|
|
new/obj/item/malf_upgrade(get_turf(src))
|
|
the_mmi = mmi_type
|
|
the_mmi.brain = new /obj/item/organ/brain(the_mmi)
|
|
the_mmi.brain.organ_flags |= ORGAN_FROZEN
|
|
the_mmi.brain.name = "[real_name]'s brain"
|
|
the_mmi.name = "[initial(the_mmi.name)]: [real_name]"
|
|
the_mmi.brainmob = new /mob/living/brain(the_mmi)
|
|
the_mmi.brainmob.name = src.real_name
|
|
the_mmi.brainmob.real_name = src.real_name
|
|
the_mmi.brainmob.container = the_mmi
|
|
the_mmi.brainmob.suiciding = suiciding
|
|
if(the_core)
|
|
var/obj/structure/ai_core/core = the_core
|
|
core.core_mmi = the_mmi
|
|
the_mmi.forceMove(the_core)
|
|
else
|
|
the_mmi.forceMove(get_turf(src))
|
|
if(the_mmi.brainmob.stat == DEAD && !suiciding)
|
|
the_mmi.brainmob.set_stat(CONSCIOUS)
|
|
if(mind)
|
|
mind.transfer_to(the_mmi.brainmob)
|
|
the_mmi.update_appearance()
|
|
return TRUE
|
|
|
|
// 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"])
|
|
var/t1 = "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"])
|
|
alert_control.ui_interact(src)
|
|
#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["ai_take_control"]) //Mech domination
|
|
var/obj/vehicle/sealed/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.")
|
|
usr.log_message("possibly attempting href exploit - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.", LOG_ADMIN)
|
|
return
|
|
|
|
if(controlled_equipment)
|
|
to_chat(src, span_warning("You are already loaded into an onboard computer!"))
|
|
return
|
|
if(!GLOB.cameranet.checkCameraVis(M))
|
|
to_chat(src, span_warning("Exosuit is no longer near active cameras."))
|
|
return
|
|
if(!isturf(loc))
|
|
to_chat(src, span_warning("You aren't in your core!"))
|
|
return
|
|
if(M)
|
|
M.transfer_ai(AI_MECH_HACK, src, usr) //Called on 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_danger("Selected location is not visible."))
|
|
|
|
/mob/living/silicon/ai/proc/call_bot(turf/waypoint)
|
|
var/mob/living/simple_animal/bot/bot = bot_ref.resolve()
|
|
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_danger("Interface error. Unit is already in use."))
|
|
return
|
|
to_chat(src, span_notice("Sending command to bot..."))
|
|
call_bot_cooldown = world.time + CALL_BOT_COOLDOWN
|
|
bot.call_bot(src, waypoint)
|
|
call_bot_cooldown = 0
|
|
|
|
/mob/living/silicon/ai/proc/alarm_triggered(datum/source, alarm_type, area/source_area)
|
|
SIGNAL_HANDLER
|
|
var/list/cameras = source_area.cameras
|
|
var/home_name = source_area.name
|
|
|
|
if (length(cameras))
|
|
var/obj/machinery/camera/cam = cameras[1]
|
|
if (cam.can_use())
|
|
queueAlarm("--- [alarm_type] alarm detected in [home_name]! (<A HREF=?src=[REF(src)];switchcamera=[REF(cam)]>[cam.c_tag]</A>)", alarm_type)
|
|
else
|
|
var/first_run = FALSE
|
|
var/dat2 = ""
|
|
for (var/obj/machinery/camera/camera as anything in cameras)
|
|
dat2 += "[(!first_run) ? "" : " | "]<A HREF=?src=[REF(src)];switchcamera=[REF(camera)]>[camera.c_tag]</A>"
|
|
first_run = TRUE
|
|
queueAlarm("--- [alarm_type] alarm detected in [home_name]! ([dat2])", alarm_type)
|
|
else
|
|
queueAlarm("--- [alarm_type] alarm detected in [home_name]! (No Camera)", alarm_type)
|
|
return TRUE
|
|
|
|
/mob/living/silicon/ai/proc/alarm_cleared(datum/source, alarm_type, area/source_area)
|
|
SIGNAL_HANDLER
|
|
queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, 0)
|
|
|
|
//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()
|
|
var/obj/structure/ai_core/new_core = new /obj/structure/ai_core/deactivated(loc, posibrain_inside)//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!
|
|
interaction_range = 0
|
|
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 FALSE
|
|
|
|
/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.machine_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.
|
|
|
|
/datum/action/innate/deploy_last_shell/Destroy()
|
|
last_used_shell = null
|
|
return ..()
|
|
|
|
/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)
|
|
. = ..(mapload, L, 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
|
|
switch(alert("Would you like to enter cryo? This will ghost you. Remember to AHELP before cryoing out of important roles, even with no admins online.",,"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, channels = list())
|
|
new /obj/structure/ai_core/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
|