#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, "You have been installed as an AI! ") to_chat(src, "You must obey your silicon laws above all else. Your objectives will consider you to be dead.") if(!mind.has_ever_been_ai) mind.has_ever_been_ai = TRUE to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") 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, "These laws may be changed by other players, or by you being the traitor.") 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, "Wireless control is disabled!") 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, "You are now [is_anchored ? "" : "un"]anchored.") /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, "Wireless control is disabled!") 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, "Unable to locate the holopad.") 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]! ([cam.c_tag])", alarm_type) else var/first_run = FALSE var/dat2 = "" for (var/obj/machinery/camera/camera as anything in cameras) dat2 += "[(!first_run) ? "" : " | "][camera.c_tag]" 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, "Switched to the \"[uppertext(network)]\" camera network.") //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, "You are already in your Main Core.") 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, "Transfer failed.") return disconnect_shell() //If the AI is controlling a borg, force the player back to core! if(!mind) to_chat(user, "No intelligence patterns detected." ) 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, "Transfer successful: [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, "You can't do that right now!") return FALSE if(be_close && !in_range(M, src)) to_chat(src, "You are too far away!") 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 = "" var/jobpart = "Unknown" if (iscarbon(speaker)) var/mob/living/carbon/S = speaker if(S.job) jobpart = "[S.job]" var/rendered = "[start][hrefpart][namepart] ([jobpart]) [treated_message]" 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, "Hack aborted. The designated APC no longer exists on the power network.") playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, TRUE, ignore_walls = FALSE) else if(apc.aidisabled) to_chat(src, "Hack aborted. \The [apc] is no longer responding to our systems.") 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, "Wireless networking module is offline.") 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, "Your remote connection has been reset!") 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