diff --git a/code/game/objects/effects/map_effects/portal.dm b/code/game/objects/effects/map_effects/portal.dm index 6eda8b6c726..094de59e2af 100644 --- a/code/game/objects/effects/map_effects/portal.dm +++ b/code/game/objects/effects/map_effects/portal.dm @@ -277,7 +277,8 @@ when portals are shortly lived, or when portals are made to be obvious with spec for(var/thing in mobs_to_relay) var/mob/mob = thing - var/message = mob.combine_message(message_pieces, verb, M) + var/list/combined = mob.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/name_used = M.GetVoice() var/rendered = null rendered = "[name_used] [message]" diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm index bb25dce953f..16543ee2c05 100644 --- a/code/game/objects/items/devices/communicator/phone.dm +++ b/code/game/objects/items/devices/communicator/phone.dm @@ -246,7 +246,8 @@ //VOREStation Edit End for(var/mob/mob in mobs_to_relay) - var/message = mob.combine_message(message_pieces, verb, M) + var/list/combined = mob.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/name_used = M.GetVoice() var/rendered = null rendered = "[bicon(src)] [name_used] [message]" diff --git a/code/game/objects/items/uav.dm b/code/game/objects/items/uav.dm index 37dd6ec1469..168eec0a3ff 100644 --- a/code/game/objects/items/uav.dm +++ b/code/game/objects/items/uav.dm @@ -305,7 +305,8 @@ for(var/wr_master in masters) var/weakref/wr = wr_master var/mob/master = wr.resolve() - var/message = master.combine_message(message_pieces, verb, M) + var/list/combined = master.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/rendered = "UAV received: [name_used] [message]" master.show_message(rendered, 2) diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index c5685aa0dcc..df959f07a24 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -2,7 +2,9 @@ /mob/proc/combine_message(var/list/message_pieces, var/verb, var/mob/speaker, always_stars = FALSE, var/radio = FALSE) var/iteration_count = 0 var/msg = "" // This is to make sure that the pieces have actually added something - . = "[verb], \"" + var/raw_msg = "" + . = list("formatted" = "[verb], \"", "raw" = "") + for(var/datum/multilingual_say_piece/SP in message_pieces) iteration_count++ var/piece = SP.message @@ -27,6 +29,9 @@ if(istype(S.say_list) && length(S.say_list.speak)) piece = pick(S.say_list.speak) + raw_msg += (piece + " ") + + //HTML formatting if(!SP.speaking) // Catch the most generic case first piece = "[piece]" else if(radio) // SP.speaking == TRUE enforced by previous !SP.speaking @@ -38,10 +43,11 @@ if(msg == "") // There is literally no content left in this message, we need to shut this shit down - . = "" // hear_say will suppress it + .["formatted"] = "" // hear_say will suppress it else - . = trim(. + trim(msg)) - . += "\"" + .["formatted"] = trim(.["formatted"] + trim(msg)) + .["formatted"] += "\"" + .["raw"] = trim(raw_msg) /mob/proc/saypiece_scramble(datum/multilingual_say_piece/SP) if(SP.speaking) @@ -76,7 +82,8 @@ var/mob/living/carbon/human/H = speaker speaker_name = H.GetVoice() - var/message = combine_message(message_pieces, verb, speaker) + var/list/combined = combine_message(message_pieces, verb, speaker) + var/message = combined["formatted"] if(message == "") return @@ -164,7 +171,8 @@ if(!client) return - var/message = combine_message(message_pieces, verb, speaker, always_stars = hard_to_hear, radio = TRUE) + var/list/combined = combine_message(message_pieces, verb, speaker, always_stars = hard_to_hear, radio = TRUE) + var/message = combined["formatted"] if(sleeping || stat == UNCONSCIOUS) //If unconscious or sleeping hear_sleep(multilingual_to_message(message_pieces)) return @@ -272,7 +280,8 @@ return /mob/proc/hear_holopad_talk(list/message_pieces, var/verb = "says", var/mob/speaker = null) - var/message = combine_message(message_pieces, verb, speaker) + var/list/combined = combine_message(message_pieces, verb, speaker) + var/message = combined["formatted"] var/name = speaker.name if(!say_understands(speaker)) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index f3cdc716e32..a4789aa7250 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1,1002 +1,1003 @@ -#define AI_CHECK_WIRELESS 1 -#define AI_CHECK_RADIO 2 - -var/list/ai_verbs_default = list( - // /mob/living/silicon/ai/proc/ai_recall_shuttle, - /mob/living/silicon/ai/proc/ai_emergency_message, - /mob/living/silicon/ai/proc/ai_goto_location, - /mob/living/silicon/ai/proc/ai_remove_location, - /mob/living/silicon/ai/proc/ai_hologram_change, - /mob/living/silicon/ai/proc/ai_network_change, - /mob/living/silicon/ai/proc/ai_statuschange, - /mob/living/silicon/ai/proc/ai_store_location, - /mob/living/silicon/ai/proc/control_integrated_radio, - /mob/living/silicon/ai/proc/pick_icon, - /mob/living/silicon/ai/proc/sensor_mode, - /mob/living/silicon/ai/proc/show_laws_verb, - /mob/living/silicon/ai/proc/toggle_acceleration, - /mob/living/silicon/ai/proc/toggle_hologram_movement, - /mob/living/silicon/ai/proc/ai_announcement, - /mob/living/silicon/ai/proc/ai_call_shuttle, - /mob/living/silicon/ai/proc/ai_camera_track, - /mob/living/silicon/ai/proc/ai_camera_list, - /mob/living/silicon/ai/proc/ai_checklaws, - /mob/living/silicon/ai/proc/toggle_camera_light, - /mob/living/silicon/ai/proc/take_image, - /mob/living/silicon/ai/proc/view_images, - /mob/living/silicon/ai/proc/toggle_multicam_verb, - /mob/living/silicon/ai/proc/add_multicam_verb -) - -//Not sure why this is necessary... -/proc/AutoUpdateAI(obj/subject) - var/is_in_use = 0 - if (subject!=null) - for(var/A in 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" - anchored = 1 // -- TLE - density = 1 - status_flags = CANSTUN|CANPARALYSE|CANPUSH - shouldnt_see = list(/mob/observer/eye, /obj/effect/rune) - var/list/network = list(NETWORK_DEFAULT) - var/obj/machinery/camera/camera = null - var/aiRestorePowerRoutine = 0 - var/viewalerts = 0 - var/icon/holo_icon//Default is assigned when AI is created. - var/list/connected_robots = list() - var/obj/item/device/pda/ai/aiPDA = null - var/obj/item/device/communicator/aiCommunicator = null - var/obj/item/device/multitool/aiMulti = null - var/obj/item/device/radio/headset/heads/ai_integrated/aiRadio = null - var/camera_light_on = 0 //Defines if the AI toggled the light on the camera it's looking through. - var/datum/trackable/track = null - var/last_announcement = "" - var/control_disabled = 0 - var/datum/announcement/priority/announcement - var/obj/machinery/ai_powersupply/psupply = null // Backwards reference to AI's powersupply object. - var/hologram_follow = 1 //This is used for the AI eye, to determine if a holopad's hologram should follow it or not. - var/is_dummy = 0 //Used to prevent dummy AIs from spawning with communicators. - //NEWMALF VARIABLES - var/malfunctioning = 0 // Master var that determines if AI is malfunctioning. - var/datum/malf_hardware/hardware = null // Installed piece of hardware. - var/datum/malf_research/research = null // Malfunction research datum. - var/obj/machinery/power/apc/hack = null // APC that is currently being hacked. - var/list/hacked_apcs = null // List of all hacked APCs - var/APU_power = 0 // If set to 1 AI runs on APU power - var/hacking = 0 // Set to 1 if AI is hacking APC, cyborg, other AI, or running system override. - var/system_override = 0 // Set to 1 if system override is initiated, 2 if succeeded. - var/hack_can_fail = 1 // If 0, all abilities have zero chance of failing. - var/hack_fails = 0 // This increments with each failed hack, and determines the warning message text. - var/errored = 0 // Set to 1 if runtime error occurs. Only way of this happening i can think of is admin fucking up with varedit. - var/bombing_core = 0 // Set to 1 if core auto-destruct is activated - var/bombing_station = 0 // Set to 1 if station nuke auto-destruct is activated - var/override_CPUStorage = 0 // Bonus/Penalty CPU Storage. For use by admins/testers. - var/override_CPURate = 0 // Bonus/Penalty CPU generation rate. For use by admins/testers. - - var/datum/ai_icon/selected_sprite // The selected icon set - var/custom_sprite = 0 // Whether the selected icon is custom - var/carded - - // Multicam Vars - var/multicam_allowed = TRUE - 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 - - can_be_antagged = TRUE - -/mob/living/silicon/ai/proc/add_ai_verbs() - src.verbs |= ai_verbs_default - src.verbs |= silicon_subsystems - -/mob/living/silicon/ai/proc/remove_ai_verbs() - src.verbs -= ai_verbs_default - src.verbs -= silicon_subsystems - -/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/device/mmi/B, var/safety = 0) - announcement = new() - announcement.title = "A.I. Announcement" - announcement.announcement_type = "A.I. Announcement" - announcement.newscast = 1 - - var/list/possibleNames = ai_names - - var/pickedName = null - while(!pickedName) - pickedName = pick(ai_names) - for (var/mob/living/silicon/ai/A in mob_list) - if (A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop - possibleNames -= pickedName - pickedName = null - - if(!is_dummy) - aiPDA = new/obj/item/device/pda/ai(src) - SetName(pickedName) - anchored = 1 - canmove = 0 - density = 1 - loc = loc - - if(!is_dummy) - aiCommunicator = new /obj/item/device/communicator/integrated(src) - - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) - - proc_holder_list = new() - - if(L) - if (istype(L, /datum/ai_laws)) - laws = L - else - laws = new using_map.default_law_type - - aiMulti = new(src) - aiRadio = new(src) - common_radio = aiRadio - aiRadio.myAi = src - additional_law_channels["Binary"] = "#b" - additional_law_channels["Holopad"] = ":h" - - aiCamera = new/obj/item/device/camera/siliconcam/ai_camera(src) - - if (istype(loc, /turf)) - add_ai_verbs(src) - - //Languages - add_language("Robot Talk", 1) - add_language(LANGUAGE_GALCOM, 1) - add_language(LANGUAGE_SOL_COMMON, 1) - add_language(LANGUAGE_UNATHI, 1) - add_language(LANGUAGE_SIIK, 1) - add_language(LANGUAGE_AKHANI, 1) - add_language(LANGUAGE_SKRELLIAN, 1) - add_language(LANGUAGE_SKRELLIANFAR, 0) - add_language(LANGUAGE_TRADEBAND, 1) - add_language(LANGUAGE_GUTTER, 1) - add_language(LANGUAGE_EAL, 1) - add_language(LANGUAGE_SCHECHI, 1) - add_language(LANGUAGE_SIGN, 1) - add_language(LANGUAGE_ROOTLOCAL, 1) - add_language(LANGUAGE_TERMINUS, 1) - add_language(LANGUAGE_ZADDAT, 1) - - if(!safety)//Only used by AIize() to successfully spawn an AI. - if (!B)//If there is no player/brain inside. - empty_playable_ai_cores += new/obj/structure/AIcore/deactivated(loc)//New empty terminal. - qdel(src)//Delete AI. - return - else - if (B.brainmob.mind) - B.brainmob.mind.transfer_to(src) - - on_mob_init() - - spawn(5) - new /obj/machinery/ai_powersupply(src) - - ai_list += src - ..() - return - -/mob/living/silicon/ai/proc/on_mob_init() - 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. Use say :h to speak from an active holopad.") - to_chat(src, "For department channels, use the following say commands:") - - var/radio_text = "" - for(var/i = 1 to common_radio.channels.len) - var/channel = common_radio.channels[i] - var/key = get_radio_key_from_channel(channel) - radio_text += "[key] - [channel]" - if(i != common_radio.channels.len) - radio_text += ", " - - to_chat(src,radio_text) - - // Vorestation Edit: Meta Info for AI's. Mostly used for Holograms - if (client) - var/meta_info = client.prefs.metadata - if (meta_info) - ooc_notes = meta_info - - if (malf && !(mind in malf.current_antagonists)) - show_laws() - to_chat(src, "These laws may be changed by other players, or by you being the traitor.") - - job = "AI" - setup_icon() - -/mob/living/silicon/ai/Destroy() - ai_list -= src - - QDEL_NULL(announcement) - QDEL_NULL(eyeobj) - QDEL_NULL(psupply) - QDEL_NULL(aiPDA) - QDEL_NULL(aiCommunicator) - QDEL_NULL(aiMulti) - QDEL_NULL(aiRadio) - QDEL_NULL(aiCamera) - hack = null - - return ..() - -/mob/living/silicon/ai/Stat() - ..() - if(statpanel("Status")) - if(!stat) // Make sure we're not unconscious/dead. - stat(null, text("System integrity: [(health+100)/2]%")) - stat(null, text("Connected synthetics: [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! - stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ - Module: [R.modtype] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) - stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells - else - stat(null, text("Systems nonfunctional")) - - -/mob/living/silicon/ai/proc/setup_icon() - var/file = file2text("config/custom_sprites.txt") - var/lines = splittext(file, "\n") - - for(var/line in lines) - // split & clean up - var/list/Entry = splittext(line, ":") - for(var/i = 1 to Entry.len) - Entry[i] = trim(Entry[i]) - - if(Entry.len < 2) - continue; - - if(Entry[1] == src.ckey && Entry[2] == src.real_name) - icon = CUSTOM_ITEM_SYNTH - custom_sprite = 1 - selected_sprite = new/datum/ai_icon("Custom", "[src.ckey]-ai", "4", "[ckey]-ai-crash", "#FFFFFF", "#FFFFFF", "#FFFFFF") - else - selected_sprite = default_ai_icon - updateicon() - -/mob/living/silicon/ai/pointed(atom/A as mob|obj|turf in view()) - set popup_menu = 0 - set src = usr.contents - return 0 - -/mob/living/silicon/ai/SetName(pickedName as text) - ..() - announcement.announcer = pickedName - if(eyeobj) - eyeobj.name = "[pickedName] (AI Eye)" - - // Set ai pda name - if(aiPDA) - aiPDA.ownjob = "AI" - aiPDA.owner = pickedName - aiPDA.name = pickedName + " (" + aiPDA.ownjob + ")" - - if(aiCommunicator) - aiCommunicator.register_device(src.name) - -/* - The AI Power supply is a dummy object used for powering the AI since only machinery should be using power. - The alternative was to rewrite a bunch of AI code instead here we are. -*/ -/obj/machinery/ai_powersupply - name="Power Supply" - active_power_usage=50000 // Station AIs use significant amounts of power. This, when combined with charged SMES should mean AI lasts for 1hr without external power. - use_power = USE_POWER_ACTIVE - power_channel = EQUIP - var/mob/living/silicon/ai/powered_ai = null - invisibility = 100 - -/obj/machinery/ai_powersupply/New(var/mob/living/silicon/ai/ai=null) - powered_ai = ai - powered_ai.psupply = src - if(istype(powered_ai,/mob/living/silicon/ai/announcer)) //Don't try to get a loc for a nullspace announcer mob, just put it into it - forceMove(powered_ai) - else - forceMove(powered_ai.loc) - - ..() - use_power(1) // Just incase we need to wake up the power system. - -/obj/machinery/ai_powersupply/Destroy() - . = ..() - powered_ai = null - -/obj/machinery/ai_powersupply/process() - if(!powered_ai || powered_ai.stat == DEAD) - qdel(src) - return - if(powered_ai.psupply != src) // For some reason, the AI has different powersupply object. Delete this one, it's no longer needed. - qdel(src) - return - if(powered_ai.APU_power) - update_use_power(USE_POWER_OFF) - return - if(!powered_ai.anchored) - loc = powered_ai.loc - update_use_power(USE_POWER_OFF) - use_power(50000) // Less optimalised but only called if AI is unwrenched. This prevents usage of wrenching as method to keep AI operational without power. Intellicard is for that. - if(powered_ai.anchored) - update_use_power(USE_POWER_ACTIVE) - -/mob/living/silicon/ai/proc/pick_icon() - set category = "AI Settings" - set name = "Set AI Core Display" - if(stat || aiRestorePowerRoutine) - return - - if (!custom_sprite) - var/new_sprite = input("Select an icon!", "AI", selected_sprite) as null|anything in ai_icons - if(new_sprite) selected_sprite = new_sprite - updateicon() - -/mob/living/silicon/ai/var/message_cooldown = 0 -/mob/living/silicon/ai/proc/ai_announcement() - set category = "AI Commands" - set name = "Make Station Announcement" - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - if(message_cooldown) - to_chat(src, "Please allow one minute to pass between announcements.") - return - var/input = input(usr, "Please write a message to announce to the station crew.", "A.I. Announcement") - if(!input) - return - - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - announcement.Announce(input) - message_cooldown = 1 - spawn(600)//One minute cooldown - message_cooldown = 0 - -/mob/living/silicon/ai/proc/ai_call_shuttle() - set category = "AI Commands" - set name = "Call Emergency Shuttle" - if(check_unable(AI_CHECK_WIRELESS)) - return - - var/confirm = alert("Are you sure you want to call the shuttle?", "Confirm Shuttle Call", "Yes", "No") - - if(check_unable(AI_CHECK_WIRELESS)) - return - - if(confirm == "Yes") - call_shuttle_proc(src) - - // hack to display shuttle timer - if(emergency_shuttle.online()) - post_status(src, "shuttle", user = src) - -/mob/living/silicon/ai/proc/ai_recall_shuttle() - set category = "AI Commands" - set name = "Recall Emergency Shuttle" - - if(check_unable(AI_CHECK_WIRELESS)) - return - - var/confirm = alert("Are you sure you want to recall the shuttle?", "Confirm Shuttle Recall", "Yes", "No") - if(check_unable(AI_CHECK_WIRELESS)) - return - - if(confirm == "Yes") - cancel_call_proc(src) - -/mob/living/silicon/ai/var/emergency_message_cooldown = 0 - -/mob/living/silicon/ai/proc/ai_emergency_message() - set category = "AI Commands" - set name = "Send Emergency Message" - - if(check_unable(AI_CHECK_WIRELESS)) - return - if(emergency_message_cooldown) - to_chat(usr, "Arrays recycling. Please stand by.") - return - var/input = sanitize(input(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", "")) - if(!input) - return - CentCom_announce(input, usr) - to_chat(usr, "Message transmitted.") - log_game("[key_name(usr)] has made an IA [using_map.boss_short] announcement: [input]") - emergency_message_cooldown = 1 - spawn(300) - emergency_message_cooldown = 0 -/mob/living/silicon/ai/check_eye(var/mob/user as mob) - if (!camera) - return -1 - return 0 - -/mob/living/silicon/ai/restrained() - return 0 - -/mob/living/silicon/ai/emp_act(severity) - disconnect_shell("Disconnected from remote shell due to ionic interfe%*@$^___") - if (prob(30)) - view_core() - ..() - -/mob/living/silicon/ai/Topic(href, href_list) - if(..()) //VOREstation edit: So the AI can actually can actually get its OOC prefs read - return - if(usr != src) - return - /*if(..()) // <------ MOVED FROM HERE - 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 cameranet.cameras - if (href_list["showalerts"]) - subsystem_alarm_monitor() - //Carn: holopad requests - if (href_list["jumptoholopad"]) - var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"]) - if(stat == CONSCIOUS) - if(H) - H.attack_ai(src) //may as well recycle - else - to_chat(src, "Unable to locate the holopad.") - - if (href_list["track"]) - var/mob/target = locate(href_list["track"]) in mob_list - - if(target && (!istype(target, /mob/living/carbon/human) || html_decode(href_list["trackname"]) == target:get_face_name())) - ai_actual_track(target) - else - to_chat(src, "System error. Cannot locate [html_decode(href_list["trackname"])].") - return - - if(href_list["trackbot"]) - var/mob/living/bot/target = locate(href_list["trackbot"]) in mob_list - if(target) - ai_actual_track(target) - else - to_chat(src, "Target is not on or near any active cameras on the station.") - return - - if(href_list["open"]) - var/mob/target = locate(href_list["open"]) in mob_list - if(target) - open_nearest_door(target) - - return - -/mob/living/silicon/ai/proc/camera_visibility(mob/observer/eye/aiEye/moved_eye) - cameranet.visibility(moved_eye, client, all_eyes) - -/mob/living/silicon/ai/forceMove(atom/destination) - . = ..() - if(.) - end_multicam() - -/mob/living/silicon/ai/reset_view(atom/A) - if(camera) - camera.set_light(0) - if(istype(A,/obj/machinery/camera)) - camera = A - if(A != GLOB.ai_camera_room_landmark) - end_multicam() - . = ..() - if(.) - if(!A && isturf(loc) && eyeobj) - end_multicam() - client.eye = eyeobj - client.perspective = MOB_PERSPECTIVE - if(istype(A,/obj/machinery/camera)) - if(camera_light_on) A.set_light(AI_CAMERA_LUMINOSITY) - else A.set_light(0) - - -/mob/living/silicon/ai/proc/switchCamera(var/obj/machinery/camera/C) - if (!C || stat == DEAD) //C.can_use()) - return 0 - - if(!src.eyeobj) - view_core() - return - // ok, we're alive, camera is good and in our network... - eyeobj.setLoc(get_turf(C)) - //machine = src - - return 1 - -/mob/living/silicon/ai/cancel_camera() - set category = "AI Commands" - set name = "Cancel Camera View" - view_core() - -//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm -//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead -//Addition by Mord_Sith to define AI's network change ability -/mob/living/silicon/ai/proc/get_camera_network_list() - if(check_unable()) - return - - var/list/cameralist = new() - for (var/obj/machinery/camera/C in cameranet.cameras) - if(!C.can_use()) - continue - var/list/tempnetwork = difflist(C.network,restricted_camera_networks,1) - for(var/i in tempnetwork) - cameralist[i] = i - - cameralist = sortAssoc(cameralist) - return cameralist - -/mob/living/silicon/ai/proc/ai_network_change(var/network in get_camera_network_list()) - set category = "AI Commands" - set name = "Jump To Network" - unset_machine() - - if(!network) - return - - if(!eyeobj) - view_core() - return - - src.network = network - - for(var/obj/machinery/camera/C in cameranet.cameras) - if(!C.can_use()) - continue - if(network in C.network) - eyeobj.setLoc(get_turf(C)) - break - to_chat(src, "Switched to [network] camera network.") -//End of code by Mord_Sith - -/mob/living/silicon/ai/proc/ai_statuschange() - set category = "AI Settings" - set name = "AI Status" - - if(check_unable(AI_CHECK_WIRELESS)) - return - - set_ai_status_displays(src) - 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 Settings" - - if(check_unable()) - return - - var/input - var/choice = alert("Would you like to select a hologram based on a (visible) crew member, switch to unique avatar, or load your character from your character slot?",,"Crew Member","Unique","My Character") - - switch(choice) - if("Crew Member") //A seeable crew member (or a dog) - var/list/targets = trackable_mobs() - if(targets.len) - input = input("Select a crew member:") as null|anything in targets //The definition of "crew member" is a little loose... - //This is torture, I know. If someone knows a better way... - if(!input) return - var/new_holo = getHologramIcon(getCompoundIcon(targets[input])) - qdel(holo_icon) - holo_icon = new_holo - - else - alert("No suitable records found. Aborting.") - - if("My Character") //Loaded character slot - if(!client || !client.prefs) return - var/mob/living/carbon/human/dummy/dummy = new () - //This doesn't include custom_items because that's ... hard. - client.prefs.dress_preview_mob(dummy) - sleep(1 SECOND) //Strange bug in preview code? Without this, certain things won't show up. Yay race conditions? - dummy.regenerate_icons() - - var/new_holo = getHologramIcon(getCompoundIcon(dummy)) - qdel(holo_icon) - qdel(dummy) - holo_icon = new_holo - - else //A premade from the dmi - var/icon_list[] = list( - "default", - "floating face", - "singularity", - "drone", - "carp", - "spider", - "bear", - "slime", - "ian", - "runtime", - "poly", - "pun pun", - "male human", - "female human", - "male unathi", - "female unathi", - "male tajaran", - "female tajaran", - "male tesharii", - "female tesharii", - "male skrell", - "female skrell" - ) - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("default") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) - if("floating face") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo2")) - if("singularity") - holo_icon = getHologramIcon(icon('icons/obj/singularity.dmi',"singularity_s1")) - if("drone") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"drone0")) - if("carp") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo4")) - if("spider") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"nurse")) - if("bear") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"brownbear")) - if("slime") - holo_icon = getHologramIcon(icon('icons/mob/slimes.dmi',"cerulean adult slime")) - if("ian") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"corgi")) - if("runtime") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"cat")) - if("poly") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"parrot_fly")) - if("pun pun") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"punpun")) - if("male human") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumm")) - if("female human") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumf")) - if("male unathi") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounam")) - if("female unathi") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounaf")) - if("male tajaran") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajm")) - if("female tajaran") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajf")) - if("male tesharii") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesm")) - if("female tesharii") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesf")) - if("male skrell") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrm")) - if("female skrell") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrf")) - -//Toggles the luminosity and applies it by re-entereing the camera. -/mob/living/silicon/ai/proc/toggle_camera_light() - set name = "Toggle Camera Light" - set desc = "Toggles the light on the camera the AI is looking through." - set category = "AI Commands" - if(check_unable()) - return - - camera_light_on = !camera_light_on - to_chat(src, "Camera lights [camera_light_on ? "activated" : "deactivated"].") - if(!camera_light_on) - if(camera) - camera.set_light(0) - camera = null - else - lightNearbyCamera() - - - -// Handled camera lighting, when toggled. -// It will get the nearest camera from the eyeobj, lighting it. - -/mob/living/silicon/ai/proc/lightNearbyCamera() - if(camera_light_on && camera_light_on < world.timeofday) - if(src.camera) - var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) - if(camera && src.camera != camera) - src.camera.set_light(0) - if(!camera.light_disabled) - src.camera = camera - src.camera.set_light(AI_CAMERA_LUMINOSITY) - else - src.camera = null - else if(isnull(camera)) - src.camera.set_light(0) - src.camera = null - else - var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) - if(camera && !camera.light_disabled) - src.camera = camera - src.camera.set_light(AI_CAMERA_LUMINOSITY) - camera_light_on = world.timeofday + 1 * 20 // Update the light every 2 seconds. - - -/mob/living/silicon/ai/attackby(obj/item/weapon/W as obj, mob/user as mob) - if(istype(W, /obj/item/device/aicard)) - - var/obj/item/device/aicard/card = W - card.grab_ai(src, user) - - else if(W.is_wrench()) - if(user == deployed_shell) - to_chat(user, "The shell's subsystems resist your efforts to tamper with your bolts.") - return - if(anchored) - playsound(src, W.usesound, 50, 1) - user.visible_message("\The [user] starts to unbolt \the [src] from the plating...") - if(!do_after(user,40 * W.toolspeed)) - user.visible_message("\The [user] decides not to unbolt \the [src].") - return - user.visible_message("\The [user] finishes unfastening \the [src]!") - anchored = 0 - return - else - playsound(src, W.usesound, 50, 1) - user.visible_message("\The [user] starts to bolt \the [src] to the plating...") - if(!do_after(user,40 * W.toolspeed)) - user.visible_message("\The [user] decides not to bolt \the [src].") - return - user.visible_message("\The [user] finishes fastening down \the [src]!") - anchored = 1 - return - else - return ..() - -/mob/living/silicon/ai/proc/control_integrated_radio() - set name = "Radio Settings" - set desc = "Allows you to change settings of your radio." - set category = "AI Settings" - - if(check_unable(AI_CHECK_RADIO)) - return - - to_chat(src, "Accessing Subspace Transceiver control...") - if (src.aiRadio) - src.aiRadio.interact(src) - -/mob/living/silicon/ai/proc/sensor_mode() - set name = "Set Sensor Augmentation" - set category = "AI Settings" - set desc = "Augment visual feed with internal sensor overlays" - toggle_sensor_mode() - -/mob/living/silicon/ai/proc/toggle_hologram_movement() - set name = "Toggle Hologram Movement" - set category = "AI Settings" - set desc = "Toggles hologram movement based on moving with your virtual eye." - - hologram_follow = !hologram_follow - //VOREStation Add - Required to stop movement because we use walk_to(wards) in hologram.dm - if(holo) - var/obj/effect/overlay/aiholo/hologram = holo.masters[src] - walk(hologram, 0) - //VOREStation Add End - to_chat(usr, "Your hologram will [hologram_follow ? "follow" : "no longer follow"] you now.") - - -/mob/living/silicon/ai/proc/check_unable(var/flags = 0, var/feedback = 1) - if(stat == DEAD) - if(feedback) - to_chat(src, "You are dead!") - return 1 - - if(aiRestorePowerRoutine) - if(feedback) - to_chat(src, "You lack power!") - return 1 - - if((flags & AI_CHECK_WIRELESS) && src.control_disabled) - if(feedback) - to_chat(src, "Wireless control is disabled!") - return 1 - if((flags & AI_CHECK_RADIO) && src.aiRadio.disabledAi) - if(feedback) - to_chat(src, "System Error - Transceiver Disabled!") - return 1 - return 0 - -/mob/living/silicon/ai/proc/is_in_chassis() - return istype(loc, /turf) - -/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target) // Rykka ports AI opening doors - if(!istype(target)) - return - - if(target && ai_actual_track(target)) - var/obj/machinery/door/airlock/A = null - - var/dist = -1 - for(var/obj/machinery/door/airlock/D in range(3, target)) - if(!D.density) - continue - - var/curr_dist = get_dist(D, target) - - if(dist < 0) - dist = curr_dist - A = D - else if(dist > curr_dist) - dist = curr_dist - A = D - - if(istype(A)) - switch(alert(src, "Do you want to open \the [A] for [target]?", "Doorknob_v2a.exe", "Yes", "No")) - if("Yes") - A.AIShiftClick() - to_chat(src, "You open \the [A] for [target].") - else - to_chat(src, "You deny the request.") - else - to_chat(src, "Unable to locate an airlock near [target].") - - else - to_chat(src, "Target is not on or near any active cameras on the station.") - -/mob/living/silicon/ai/ex_act(var/severity) - if(severity == 1.0) - qdel(src) - return - ..() - -/mob/living/silicon/ai/updateicon() - if(!selected_sprite) selected_sprite = default_ai_icon - - if(stat == DEAD) - icon_state = selected_sprite.dead_icon - set_light(3, 1, selected_sprite.dead_light) - else if(aiRestorePowerRoutine) - icon_state = selected_sprite.nopower_icon - set_light(1, 1, selected_sprite.nopower_light) - else - icon_state = selected_sprite.alive_icon - set_light(1, 1, selected_sprite.alive_light) - -// Pass lying down or getting up to our pet human, if we're in a rig. -/mob/living/silicon/ai/lay_down() - set name = "Rest" - set category = "IC" - - resting = 0 - var/obj/item/weapon/rig/rig = src.get_rig() - if(rig) - rig.force_rest(src) - -/mob/living/silicon/ai/is_sentient() - // AI cores don't store what brain was used to build them so we're just gonna assume they can think to some degree. - // If that is ever fixed please update this proc. - return TRUE - - -/mob/living/silicon/ai/handle_track(message, verb = "says", mob/speaker = null, speaker_name, hard_to_hear) - if(hard_to_hear) - return - - var/jobname // the mob's "job" - var/mob/living/carbon/human/impersonating //The crew member being impersonated, if any. - var/changed_voice - - if(ishuman(speaker)) - var/mob/living/carbon/human/H = speaker - - if(H.wear_mask && istype(H.wear_mask,/obj/item/clothing/mask/gas/voice)) - changed_voice = 1 - var/list/impersonated = new() - var/mob/living/carbon/human/I = impersonated[speaker_name] - - if(!I) - for(var/mob/living/carbon/human/M in mob_list) - if(M.real_name == speaker_name) - I = M - impersonated[speaker_name] = I - break - - // If I's display name is currently different from the voice name and using an agent ID then don't impersonate - // as this would allow the AI to track I and realize the mismatch. - if(I && !(I.name != speaker_name && I.wear_id && istype(I.wear_id,/obj/item/weapon/card/id/syndicate))) - impersonating = I - jobname = impersonating.get_assignment() - else - jobname = "Unknown" - else - jobname = H.get_assignment() - - else if(iscarbon(speaker)) // Nonhuman carbon mob - jobname = "No id" - else if(isAI(speaker)) - jobname = "AI" - else if(isrobot(speaker)) - jobname = "Cyborg" - else if(istype(speaker, /mob/living/silicon/pai)) - jobname = "Personal AI" - else - jobname = "Unknown" - - var/track = "" - if(changed_voice) // They have a fake name - if(impersonating) // And we found a mob with that name above, track them instead - track = "[speaker_name] ([jobname])" - track += "\[OPEN\]" // Rykka ports AI opening doors - else // We couldn't find a mob with their fake name, don't track at all - track = "[speaker_name] ([jobname])" - else // Not faking their name - if(istype(speaker, /mob/living/bot)) // It's a bot, and no fake name! (That'd be kinda weird.) :p - track = "[speaker_name] ([jobname])" - else // It's not a bot, and no fake name! - track = "[speaker_name] ([jobname])" - track += "\[OPEN\]" // Rykka ports AI opening doors - - return track // Feed variable back to AI - -/mob/living/silicon/ai/proc/relay_speech(mob/living/M, list/message_pieces, verb) - var/message = combine_message(message_pieces, verb, M) - var/name_used = M.GetVoice() - //This communication is imperfect because the holopad "filters" voices and is only designed to connect to the master only. - var/rendered = "Relayed Speech: [name_used] [message]" - show_message(rendered, 2) - -/mob/living/silicon/ai/proc/toggle_multicam_verb() - set name = "Toggle Multicam" - set category = "AI Commands" - toggle_multicam() - -/mob/living/silicon/ai/proc/add_multicam_verb() - set name = "Add Multicam Viewport" - set category = "AI Commands" - drop_new_multicam() - -//Special subtype kept around for global announcements -/mob/living/silicon/ai/announcer - is_dummy = 1 - -/mob/living/silicon/ai/announcer/Initialize() - . = ..() - mob_list -= src - living_mob_list -= src - dead_mob_list -= src - ai_list -= src - silicon_mob_list -= src - QDEL_NULL(eyeobj) - -/mob/living/silicon/ai/announcer/Life() - mob_list -= src - living_mob_list -= src - dead_mob_list -= src - ai_list -= src - silicon_mob_list -= src - QDEL_NULL(eyeobj) - -#undef AI_CHECK_WIRELESS -#undef AI_CHECK_RADIO +#define AI_CHECK_WIRELESS 1 +#define AI_CHECK_RADIO 2 + +var/list/ai_verbs_default = list( + // /mob/living/silicon/ai/proc/ai_recall_shuttle, + /mob/living/silicon/ai/proc/ai_emergency_message, + /mob/living/silicon/ai/proc/ai_goto_location, + /mob/living/silicon/ai/proc/ai_remove_location, + /mob/living/silicon/ai/proc/ai_hologram_change, + /mob/living/silicon/ai/proc/ai_network_change, + /mob/living/silicon/ai/proc/ai_statuschange, + /mob/living/silicon/ai/proc/ai_store_location, + /mob/living/silicon/ai/proc/control_integrated_radio, + /mob/living/silicon/ai/proc/pick_icon, + /mob/living/silicon/ai/proc/sensor_mode, + /mob/living/silicon/ai/proc/show_laws_verb, + /mob/living/silicon/ai/proc/toggle_acceleration, + /mob/living/silicon/ai/proc/toggle_hologram_movement, + /mob/living/silicon/ai/proc/ai_announcement, + /mob/living/silicon/ai/proc/ai_call_shuttle, + /mob/living/silicon/ai/proc/ai_camera_track, + /mob/living/silicon/ai/proc/ai_camera_list, + /mob/living/silicon/ai/proc/ai_checklaws, + /mob/living/silicon/ai/proc/toggle_camera_light, + /mob/living/silicon/ai/proc/take_image, + /mob/living/silicon/ai/proc/view_images, + /mob/living/silicon/ai/proc/toggle_multicam_verb, + /mob/living/silicon/ai/proc/add_multicam_verb +) + +//Not sure why this is necessary... +/proc/AutoUpdateAI(obj/subject) + var/is_in_use = 0 + if (subject!=null) + for(var/A in 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" + anchored = 1 // -- TLE + density = 1 + status_flags = CANSTUN|CANPARALYSE|CANPUSH + shouldnt_see = list(/mob/observer/eye, /obj/effect/rune) + var/list/network = list(NETWORK_DEFAULT) + var/obj/machinery/camera/camera = null + var/aiRestorePowerRoutine = 0 + var/viewalerts = 0 + var/icon/holo_icon//Default is assigned when AI is created. + var/list/connected_robots = list() + var/obj/item/device/pda/ai/aiPDA = null + var/obj/item/device/communicator/aiCommunicator = null + var/obj/item/device/multitool/aiMulti = null + var/obj/item/device/radio/headset/heads/ai_integrated/aiRadio = null + var/camera_light_on = 0 //Defines if the AI toggled the light on the camera it's looking through. + var/datum/trackable/track = null + var/last_announcement = "" + var/control_disabled = 0 + var/datum/announcement/priority/announcement + var/obj/machinery/ai_powersupply/psupply = null // Backwards reference to AI's powersupply object. + var/hologram_follow = 1 //This is used for the AI eye, to determine if a holopad's hologram should follow it or not. + var/is_dummy = 0 //Used to prevent dummy AIs from spawning with communicators. + //NEWMALF VARIABLES + var/malfunctioning = 0 // Master var that determines if AI is malfunctioning. + var/datum/malf_hardware/hardware = null // Installed piece of hardware. + var/datum/malf_research/research = null // Malfunction research datum. + var/obj/machinery/power/apc/hack = null // APC that is currently being hacked. + var/list/hacked_apcs = null // List of all hacked APCs + var/APU_power = 0 // If set to 1 AI runs on APU power + var/hacking = 0 // Set to 1 if AI is hacking APC, cyborg, other AI, or running system override. + var/system_override = 0 // Set to 1 if system override is initiated, 2 if succeeded. + var/hack_can_fail = 1 // If 0, all abilities have zero chance of failing. + var/hack_fails = 0 // This increments with each failed hack, and determines the warning message text. + var/errored = 0 // Set to 1 if runtime error occurs. Only way of this happening i can think of is admin fucking up with varedit. + var/bombing_core = 0 // Set to 1 if core auto-destruct is activated + var/bombing_station = 0 // Set to 1 if station nuke auto-destruct is activated + var/override_CPUStorage = 0 // Bonus/Penalty CPU Storage. For use by admins/testers. + var/override_CPURate = 0 // Bonus/Penalty CPU generation rate. For use by admins/testers. + + var/datum/ai_icon/selected_sprite // The selected icon set + var/custom_sprite = 0 // Whether the selected icon is custom + var/carded + + // Multicam Vars + var/multicam_allowed = TRUE + 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 + + can_be_antagged = TRUE + +/mob/living/silicon/ai/proc/add_ai_verbs() + src.verbs |= ai_verbs_default + src.verbs |= silicon_subsystems + +/mob/living/silicon/ai/proc/remove_ai_verbs() + src.verbs -= ai_verbs_default + src.verbs -= silicon_subsystems + +/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/device/mmi/B, var/safety = 0) + announcement = new() + announcement.title = "A.I. Announcement" + announcement.announcement_type = "A.I. Announcement" + announcement.newscast = 1 + + var/list/possibleNames = ai_names + + var/pickedName = null + while(!pickedName) + pickedName = pick(ai_names) + for (var/mob/living/silicon/ai/A in mob_list) + if (A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop + possibleNames -= pickedName + pickedName = null + + if(!is_dummy) + aiPDA = new/obj/item/device/pda/ai(src) + SetName(pickedName) + anchored = 1 + canmove = 0 + density = 1 + loc = loc + + if(!is_dummy) + aiCommunicator = new /obj/item/device/communicator/integrated(src) + + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) + + proc_holder_list = new() + + if(L) + if (istype(L, /datum/ai_laws)) + laws = L + else + laws = new using_map.default_law_type + + aiMulti = new(src) + aiRadio = new(src) + common_radio = aiRadio + aiRadio.myAi = src + additional_law_channels["Binary"] = "#b" + additional_law_channels["Holopad"] = ":h" + + aiCamera = new/obj/item/device/camera/siliconcam/ai_camera(src) + + if (istype(loc, /turf)) + add_ai_verbs(src) + + //Languages + add_language("Robot Talk", 1) + add_language(LANGUAGE_GALCOM, 1) + add_language(LANGUAGE_SOL_COMMON, 1) + add_language(LANGUAGE_UNATHI, 1) + add_language(LANGUAGE_SIIK, 1) + add_language(LANGUAGE_AKHANI, 1) + add_language(LANGUAGE_SKRELLIAN, 1) + add_language(LANGUAGE_SKRELLIANFAR, 0) + add_language(LANGUAGE_TRADEBAND, 1) + add_language(LANGUAGE_GUTTER, 1) + add_language(LANGUAGE_EAL, 1) + add_language(LANGUAGE_SCHECHI, 1) + add_language(LANGUAGE_SIGN, 1) + add_language(LANGUAGE_ROOTLOCAL, 1) + add_language(LANGUAGE_TERMINUS, 1) + add_language(LANGUAGE_ZADDAT, 1) + + if(!safety)//Only used by AIize() to successfully spawn an AI. + if (!B)//If there is no player/brain inside. + empty_playable_ai_cores += new/obj/structure/AIcore/deactivated(loc)//New empty terminal. + qdel(src)//Delete AI. + return + else + if (B.brainmob.mind) + B.brainmob.mind.transfer_to(src) + + on_mob_init() + + spawn(5) + new /obj/machinery/ai_powersupply(src) + + ai_list += src + ..() + return + +/mob/living/silicon/ai/proc/on_mob_init() + 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. Use say :h to speak from an active holopad.") + to_chat(src, "For department channels, use the following say commands:") + + var/radio_text = "" + for(var/i = 1 to common_radio.channels.len) + var/channel = common_radio.channels[i] + var/key = get_radio_key_from_channel(channel) + radio_text += "[key] - [channel]" + if(i != common_radio.channels.len) + radio_text += ", " + + to_chat(src,radio_text) + + // Vorestation Edit: Meta Info for AI's. Mostly used for Holograms + if (client) + var/meta_info = client.prefs.metadata + if (meta_info) + ooc_notes = meta_info + + if (malf && !(mind in malf.current_antagonists)) + show_laws() + to_chat(src, "These laws may be changed by other players, or by you being the traitor.") + + job = "AI" + setup_icon() + +/mob/living/silicon/ai/Destroy() + ai_list -= src + + QDEL_NULL(announcement) + QDEL_NULL(eyeobj) + QDEL_NULL(psupply) + QDEL_NULL(aiPDA) + QDEL_NULL(aiCommunicator) + QDEL_NULL(aiMulti) + QDEL_NULL(aiRadio) + QDEL_NULL(aiCamera) + hack = null + + return ..() + +/mob/living/silicon/ai/Stat() + ..() + if(statpanel("Status")) + if(!stat) // Make sure we're not unconscious/dead. + stat(null, text("System integrity: [(health+100)/2]%")) + stat(null, text("Connected synthetics: [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! + stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ + Module: [R.modtype] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) + stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells + else + stat(null, text("Systems nonfunctional")) + + +/mob/living/silicon/ai/proc/setup_icon() + var/file = file2text("config/custom_sprites.txt") + var/lines = splittext(file, "\n") + + for(var/line in lines) + // split & clean up + var/list/Entry = splittext(line, ":") + for(var/i = 1 to Entry.len) + Entry[i] = trim(Entry[i]) + + if(Entry.len < 2) + continue; + + if(Entry[1] == src.ckey && Entry[2] == src.real_name) + icon = CUSTOM_ITEM_SYNTH + custom_sprite = 1 + selected_sprite = new/datum/ai_icon("Custom", "[src.ckey]-ai", "4", "[ckey]-ai-crash", "#FFFFFF", "#FFFFFF", "#FFFFFF") + else + selected_sprite = default_ai_icon + updateicon() + +/mob/living/silicon/ai/pointed(atom/A as mob|obj|turf in view()) + set popup_menu = 0 + set src = usr.contents + return 0 + +/mob/living/silicon/ai/SetName(pickedName as text) + ..() + announcement.announcer = pickedName + if(eyeobj) + eyeobj.name = "[pickedName] (AI Eye)" + + // Set ai pda name + if(aiPDA) + aiPDA.ownjob = "AI" + aiPDA.owner = pickedName + aiPDA.name = pickedName + " (" + aiPDA.ownjob + ")" + + if(aiCommunicator) + aiCommunicator.register_device(src.name) + +/* + The AI Power supply is a dummy object used for powering the AI since only machinery should be using power. + The alternative was to rewrite a bunch of AI code instead here we are. +*/ +/obj/machinery/ai_powersupply + name="Power Supply" + active_power_usage=50000 // Station AIs use significant amounts of power. This, when combined with charged SMES should mean AI lasts for 1hr without external power. + use_power = USE_POWER_ACTIVE + power_channel = EQUIP + var/mob/living/silicon/ai/powered_ai = null + invisibility = 100 + +/obj/machinery/ai_powersupply/New(var/mob/living/silicon/ai/ai=null) + powered_ai = ai + powered_ai.psupply = src + if(istype(powered_ai,/mob/living/silicon/ai/announcer)) //Don't try to get a loc for a nullspace announcer mob, just put it into it + forceMove(powered_ai) + else + forceMove(powered_ai.loc) + + ..() + use_power(1) // Just incase we need to wake up the power system. + +/obj/machinery/ai_powersupply/Destroy() + . = ..() + powered_ai = null + +/obj/machinery/ai_powersupply/process() + if(!powered_ai || powered_ai.stat == DEAD) + qdel(src) + return + if(powered_ai.psupply != src) // For some reason, the AI has different powersupply object. Delete this one, it's no longer needed. + qdel(src) + return + if(powered_ai.APU_power) + update_use_power(USE_POWER_OFF) + return + if(!powered_ai.anchored) + loc = powered_ai.loc + update_use_power(USE_POWER_OFF) + use_power(50000) // Less optimalised but only called if AI is unwrenched. This prevents usage of wrenching as method to keep AI operational without power. Intellicard is for that. + if(powered_ai.anchored) + update_use_power(USE_POWER_ACTIVE) + +/mob/living/silicon/ai/proc/pick_icon() + set category = "AI Settings" + set name = "Set AI Core Display" + if(stat || aiRestorePowerRoutine) + return + + if (!custom_sprite) + var/new_sprite = input("Select an icon!", "AI", selected_sprite) as null|anything in ai_icons + if(new_sprite) selected_sprite = new_sprite + updateicon() + +/mob/living/silicon/ai/var/message_cooldown = 0 +/mob/living/silicon/ai/proc/ai_announcement() + set category = "AI Commands" + set name = "Make Station Announcement" + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + if(message_cooldown) + to_chat(src, "Please allow one minute to pass between announcements.") + return + var/input = input(usr, "Please write a message to announce to the station crew.", "A.I. Announcement") + if(!input) + return + + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + announcement.Announce(input) + message_cooldown = 1 + spawn(600)//One minute cooldown + message_cooldown = 0 + +/mob/living/silicon/ai/proc/ai_call_shuttle() + set category = "AI Commands" + set name = "Call Emergency Shuttle" + if(check_unable(AI_CHECK_WIRELESS)) + return + + var/confirm = alert("Are you sure you want to call the shuttle?", "Confirm Shuttle Call", "Yes", "No") + + if(check_unable(AI_CHECK_WIRELESS)) + return + + if(confirm == "Yes") + call_shuttle_proc(src) + + // hack to display shuttle timer + if(emergency_shuttle.online()) + post_status(src, "shuttle", user = src) + +/mob/living/silicon/ai/proc/ai_recall_shuttle() + set category = "AI Commands" + set name = "Recall Emergency Shuttle" + + if(check_unable(AI_CHECK_WIRELESS)) + return + + var/confirm = alert("Are you sure you want to recall the shuttle?", "Confirm Shuttle Recall", "Yes", "No") + if(check_unable(AI_CHECK_WIRELESS)) + return + + if(confirm == "Yes") + cancel_call_proc(src) + +/mob/living/silicon/ai/var/emergency_message_cooldown = 0 + +/mob/living/silicon/ai/proc/ai_emergency_message() + set category = "AI Commands" + set name = "Send Emergency Message" + + if(check_unable(AI_CHECK_WIRELESS)) + return + if(emergency_message_cooldown) + to_chat(usr, "Arrays recycling. Please stand by.") + return + var/input = sanitize(input(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", "")) + if(!input) + return + CentCom_announce(input, usr) + to_chat(usr, "Message transmitted.") + log_game("[key_name(usr)] has made an IA [using_map.boss_short] announcement: [input]") + emergency_message_cooldown = 1 + spawn(300) + emergency_message_cooldown = 0 +/mob/living/silicon/ai/check_eye(var/mob/user as mob) + if (!camera) + return -1 + return 0 + +/mob/living/silicon/ai/restrained() + return 0 + +/mob/living/silicon/ai/emp_act(severity) + disconnect_shell("Disconnected from remote shell due to ionic interfe%*@$^___") + if (prob(30)) + view_core() + ..() + +/mob/living/silicon/ai/Topic(href, href_list) + if(..()) //VOREstation edit: So the AI can actually can actually get its OOC prefs read + return + if(usr != src) + return + /*if(..()) // <------ MOVED FROM HERE + 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 cameranet.cameras + if (href_list["showalerts"]) + subsystem_alarm_monitor() + //Carn: holopad requests + if (href_list["jumptoholopad"]) + var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"]) + if(stat == CONSCIOUS) + if(H) + H.attack_ai(src) //may as well recycle + else + to_chat(src, "Unable to locate the holopad.") + + if (href_list["track"]) + var/mob/target = locate(href_list["track"]) in mob_list + + if(target && (!istype(target, /mob/living/carbon/human) || html_decode(href_list["trackname"]) == target:get_face_name())) + ai_actual_track(target) + else + to_chat(src, "System error. Cannot locate [html_decode(href_list["trackname"])].") + return + + if(href_list["trackbot"]) + var/mob/living/bot/target = locate(href_list["trackbot"]) in mob_list + if(target) + ai_actual_track(target) + else + to_chat(src, "Target is not on or near any active cameras on the station.") + return + + if(href_list["open"]) + var/mob/target = locate(href_list["open"]) in mob_list + if(target) + open_nearest_door(target) + + return + +/mob/living/silicon/ai/proc/camera_visibility(mob/observer/eye/aiEye/moved_eye) + cameranet.visibility(moved_eye, client, all_eyes) + +/mob/living/silicon/ai/forceMove(atom/destination) + . = ..() + if(.) + end_multicam() + +/mob/living/silicon/ai/reset_view(atom/A) + if(camera) + camera.set_light(0) + if(istype(A,/obj/machinery/camera)) + camera = A + if(A != GLOB.ai_camera_room_landmark) + end_multicam() + . = ..() + if(.) + if(!A && isturf(loc) && eyeobj) + end_multicam() + client.eye = eyeobj + client.perspective = MOB_PERSPECTIVE + if(istype(A,/obj/machinery/camera)) + if(camera_light_on) A.set_light(AI_CAMERA_LUMINOSITY) + else A.set_light(0) + + +/mob/living/silicon/ai/proc/switchCamera(var/obj/machinery/camera/C) + if (!C || stat == DEAD) //C.can_use()) + return 0 + + if(!src.eyeobj) + view_core() + return + // ok, we're alive, camera is good and in our network... + eyeobj.setLoc(get_turf(C)) + //machine = src + + return 1 + +/mob/living/silicon/ai/cancel_camera() + set category = "AI Commands" + set name = "Cancel Camera View" + view_core() + +//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm +//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead +//Addition by Mord_Sith to define AI's network change ability +/mob/living/silicon/ai/proc/get_camera_network_list() + if(check_unable()) + return + + var/list/cameralist = new() + for (var/obj/machinery/camera/C in cameranet.cameras) + if(!C.can_use()) + continue + var/list/tempnetwork = difflist(C.network,restricted_camera_networks,1) + for(var/i in tempnetwork) + cameralist[i] = i + + cameralist = sortAssoc(cameralist) + return cameralist + +/mob/living/silicon/ai/proc/ai_network_change(var/network in get_camera_network_list()) + set category = "AI Commands" + set name = "Jump To Network" + unset_machine() + + if(!network) + return + + if(!eyeobj) + view_core() + return + + src.network = network + + for(var/obj/machinery/camera/C in cameranet.cameras) + if(!C.can_use()) + continue + if(network in C.network) + eyeobj.setLoc(get_turf(C)) + break + to_chat(src, "Switched to [network] camera network.") +//End of code by Mord_Sith + +/mob/living/silicon/ai/proc/ai_statuschange() + set category = "AI Settings" + set name = "AI Status" + + if(check_unable(AI_CHECK_WIRELESS)) + return + + set_ai_status_displays(src) + 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 Settings" + + if(check_unable()) + return + + var/input + var/choice = alert("Would you like to select a hologram based on a (visible) crew member, switch to unique avatar, or load your character from your character slot?",,"Crew Member","Unique","My Character") + + switch(choice) + if("Crew Member") //A seeable crew member (or a dog) + var/list/targets = trackable_mobs() + if(targets.len) + input = input("Select a crew member:") as null|anything in targets //The definition of "crew member" is a little loose... + //This is torture, I know. If someone knows a better way... + if(!input) return + var/new_holo = getHologramIcon(getCompoundIcon(targets[input])) + qdel(holo_icon) + holo_icon = new_holo + + else + alert("No suitable records found. Aborting.") + + if("My Character") //Loaded character slot + if(!client || !client.prefs) return + var/mob/living/carbon/human/dummy/dummy = new () + //This doesn't include custom_items because that's ... hard. + client.prefs.dress_preview_mob(dummy) + sleep(1 SECOND) //Strange bug in preview code? Without this, certain things won't show up. Yay race conditions? + dummy.regenerate_icons() + + var/new_holo = getHologramIcon(getCompoundIcon(dummy)) + qdel(holo_icon) + qdel(dummy) + holo_icon = new_holo + + else //A premade from the dmi + var/icon_list[] = list( + "default", + "floating face", + "singularity", + "drone", + "carp", + "spider", + "bear", + "slime", + "ian", + "runtime", + "poly", + "pun pun", + "male human", + "female human", + "male unathi", + "female unathi", + "male tajaran", + "female tajaran", + "male tesharii", + "female tesharii", + "male skrell", + "female skrell" + ) + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("default") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) + if("floating face") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo2")) + if("singularity") + holo_icon = getHologramIcon(icon('icons/obj/singularity.dmi',"singularity_s1")) + if("drone") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"drone0")) + if("carp") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo4")) + if("spider") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"nurse")) + if("bear") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"brownbear")) + if("slime") + holo_icon = getHologramIcon(icon('icons/mob/slimes.dmi',"cerulean adult slime")) + if("ian") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"corgi")) + if("runtime") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"cat")) + if("poly") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"parrot_fly")) + if("pun pun") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"punpun")) + if("male human") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumm")) + if("female human") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumf")) + if("male unathi") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounam")) + if("female unathi") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounaf")) + if("male tajaran") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajm")) + if("female tajaran") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajf")) + if("male tesharii") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesm")) + if("female tesharii") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesf")) + if("male skrell") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrm")) + if("female skrell") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrf")) + +//Toggles the luminosity and applies it by re-entereing the camera. +/mob/living/silicon/ai/proc/toggle_camera_light() + set name = "Toggle Camera Light" + set desc = "Toggles the light on the camera the AI is looking through." + set category = "AI Commands" + if(check_unable()) + return + + camera_light_on = !camera_light_on + to_chat(src, "Camera lights [camera_light_on ? "activated" : "deactivated"].") + if(!camera_light_on) + if(camera) + camera.set_light(0) + camera = null + else + lightNearbyCamera() + + + +// Handled camera lighting, when toggled. +// It will get the nearest camera from the eyeobj, lighting it. + +/mob/living/silicon/ai/proc/lightNearbyCamera() + if(camera_light_on && camera_light_on < world.timeofday) + if(src.camera) + var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) + if(camera && src.camera != camera) + src.camera.set_light(0) + if(!camera.light_disabled) + src.camera = camera + src.camera.set_light(AI_CAMERA_LUMINOSITY) + else + src.camera = null + else if(isnull(camera)) + src.camera.set_light(0) + src.camera = null + else + var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) + if(camera && !camera.light_disabled) + src.camera = camera + src.camera.set_light(AI_CAMERA_LUMINOSITY) + camera_light_on = world.timeofday + 1 * 20 // Update the light every 2 seconds. + + +/mob/living/silicon/ai/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W, /obj/item/device/aicard)) + + var/obj/item/device/aicard/card = W + card.grab_ai(src, user) + + else if(W.is_wrench()) + if(user == deployed_shell) + to_chat(user, "The shell's subsystems resist your efforts to tamper with your bolts.") + return + if(anchored) + playsound(src, W.usesound, 50, 1) + user.visible_message("\The [user] starts to unbolt \the [src] from the plating...") + if(!do_after(user,40 * W.toolspeed)) + user.visible_message("\The [user] decides not to unbolt \the [src].") + return + user.visible_message("\The [user] finishes unfastening \the [src]!") + anchored = 0 + return + else + playsound(src, W.usesound, 50, 1) + user.visible_message("\The [user] starts to bolt \the [src] to the plating...") + if(!do_after(user,40 * W.toolspeed)) + user.visible_message("\The [user] decides not to bolt \the [src].") + return + user.visible_message("\The [user] finishes fastening down \the [src]!") + anchored = 1 + return + else + return ..() + +/mob/living/silicon/ai/proc/control_integrated_radio() + set name = "Radio Settings" + set desc = "Allows you to change settings of your radio." + set category = "AI Settings" + + if(check_unable(AI_CHECK_RADIO)) + return + + to_chat(src, "Accessing Subspace Transceiver control...") + if (src.aiRadio) + src.aiRadio.interact(src) + +/mob/living/silicon/ai/proc/sensor_mode() + set name = "Set Sensor Augmentation" + set category = "AI Settings" + set desc = "Augment visual feed with internal sensor overlays" + toggle_sensor_mode() + +/mob/living/silicon/ai/proc/toggle_hologram_movement() + set name = "Toggle Hologram Movement" + set category = "AI Settings" + set desc = "Toggles hologram movement based on moving with your virtual eye." + + hologram_follow = !hologram_follow + //VOREStation Add - Required to stop movement because we use walk_to(wards) in hologram.dm + if(holo) + var/obj/effect/overlay/aiholo/hologram = holo.masters[src] + walk(hologram, 0) + //VOREStation Add End + to_chat(usr, "Your hologram will [hologram_follow ? "follow" : "no longer follow"] you now.") + + +/mob/living/silicon/ai/proc/check_unable(var/flags = 0, var/feedback = 1) + if(stat == DEAD) + if(feedback) + to_chat(src, "You are dead!") + return 1 + + if(aiRestorePowerRoutine) + if(feedback) + to_chat(src, "You lack power!") + return 1 + + if((flags & AI_CHECK_WIRELESS) && src.control_disabled) + if(feedback) + to_chat(src, "Wireless control is disabled!") + return 1 + if((flags & AI_CHECK_RADIO) && src.aiRadio.disabledAi) + if(feedback) + to_chat(src, "System Error - Transceiver Disabled!") + return 1 + return 0 + +/mob/living/silicon/ai/proc/is_in_chassis() + return istype(loc, /turf) + +/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target) // Rykka ports AI opening doors + if(!istype(target)) + return + + if(target && ai_actual_track(target)) + var/obj/machinery/door/airlock/A = null + + var/dist = -1 + for(var/obj/machinery/door/airlock/D in range(3, target)) + if(!D.density) + continue + + var/curr_dist = get_dist(D, target) + + if(dist < 0) + dist = curr_dist + A = D + else if(dist > curr_dist) + dist = curr_dist + A = D + + if(istype(A)) + switch(alert(src, "Do you want to open \the [A] for [target]?", "Doorknob_v2a.exe", "Yes", "No")) + if("Yes") + A.AIShiftClick() + to_chat(src, "You open \the [A] for [target].") + else + to_chat(src, "You deny the request.") + else + to_chat(src, "Unable to locate an airlock near [target].") + + else + to_chat(src, "Target is not on or near any active cameras on the station.") + +/mob/living/silicon/ai/ex_act(var/severity) + if(severity == 1.0) + qdel(src) + return + ..() + +/mob/living/silicon/ai/updateicon() + if(!selected_sprite) selected_sprite = default_ai_icon + + if(stat == DEAD) + icon_state = selected_sprite.dead_icon + set_light(3, 1, selected_sprite.dead_light) + else if(aiRestorePowerRoutine) + icon_state = selected_sprite.nopower_icon + set_light(1, 1, selected_sprite.nopower_light) + else + icon_state = selected_sprite.alive_icon + set_light(1, 1, selected_sprite.alive_light) + +// Pass lying down or getting up to our pet human, if we're in a rig. +/mob/living/silicon/ai/lay_down() + set name = "Rest" + set category = "IC" + + resting = 0 + var/obj/item/weapon/rig/rig = src.get_rig() + if(rig) + rig.force_rest(src) + +/mob/living/silicon/ai/is_sentient() + // AI cores don't store what brain was used to build them so we're just gonna assume they can think to some degree. + // If that is ever fixed please update this proc. + return TRUE + + +/mob/living/silicon/ai/handle_track(message, verb = "says", mob/speaker = null, speaker_name, hard_to_hear) + if(hard_to_hear) + return + + var/jobname // the mob's "job" + var/mob/living/carbon/human/impersonating //The crew member being impersonated, if any. + var/changed_voice + + if(ishuman(speaker)) + var/mob/living/carbon/human/H = speaker + + if(H.wear_mask && istype(H.wear_mask,/obj/item/clothing/mask/gas/voice)) + changed_voice = 1 + var/list/impersonated = new() + var/mob/living/carbon/human/I = impersonated[speaker_name] + + if(!I) + for(var/mob/living/carbon/human/M in mob_list) + if(M.real_name == speaker_name) + I = M + impersonated[speaker_name] = I + break + + // If I's display name is currently different from the voice name and using an agent ID then don't impersonate + // as this would allow the AI to track I and realize the mismatch. + if(I && !(I.name != speaker_name && I.wear_id && istype(I.wear_id,/obj/item/weapon/card/id/syndicate))) + impersonating = I + jobname = impersonating.get_assignment() + else + jobname = "Unknown" + else + jobname = H.get_assignment() + + else if(iscarbon(speaker)) // Nonhuman carbon mob + jobname = "No id" + else if(isAI(speaker)) + jobname = "AI" + else if(isrobot(speaker)) + jobname = "Cyborg" + else if(istype(speaker, /mob/living/silicon/pai)) + jobname = "Personal AI" + else + jobname = "Unknown" + + var/track = "" + if(changed_voice) // They have a fake name + if(impersonating) // And we found a mob with that name above, track them instead + track = "[speaker_name] ([jobname])" + track += "\[OPEN\]" // Rykka ports AI opening doors + else // We couldn't find a mob with their fake name, don't track at all + track = "[speaker_name] ([jobname])" + else // Not faking their name + if(istype(speaker, /mob/living/bot)) // It's a bot, and no fake name! (That'd be kinda weird.) :p + track = "[speaker_name] ([jobname])" + else // It's not a bot, and no fake name! + track = "[speaker_name] ([jobname])" + track += "\[OPEN\]" // Rykka ports AI opening doors + + return track // Feed variable back to AI + +/mob/living/silicon/ai/proc/relay_speech(mob/living/M, list/message_pieces, verb) + var/list/combined = combine_message(message_pieces, verb, M) + var/message = combined["formatted"] + var/name_used = M.GetVoice() + //This communication is imperfect because the holopad "filters" voices and is only designed to connect to the master only. + var/rendered = "Relayed Speech: [name_used] [message]" + show_message(rendered, 2) + +/mob/living/silicon/ai/proc/toggle_multicam_verb() + set name = "Toggle Multicam" + set category = "AI Commands" + toggle_multicam() + +/mob/living/silicon/ai/proc/add_multicam_verb() + set name = "Add Multicam Viewport" + set category = "AI Commands" + drop_new_multicam() + +//Special subtype kept around for global announcements +/mob/living/silicon/ai/announcer + is_dummy = 1 + +/mob/living/silicon/ai/announcer/Initialize() + . = ..() + mob_list -= src + living_mob_list -= src + dead_mob_list -= src + ai_list -= src + silicon_mob_list -= src + QDEL_NULL(eyeobj) + +/mob/living/silicon/ai/announcer/Life() + mob_list -= src + living_mob_list -= src + dead_mob_list -= src + ai_list -= src + silicon_mob_list -= src + QDEL_NULL(eyeobj) + +#undef AI_CHECK_WIRELESS +#undef AI_CHECK_RADIO diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 34f6096ede0..daa144bf898 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -1,126 +1,127 @@ -/mob/living/silicon/robot/handle_message_mode(message_mode, message, verb, speaking, used_radios) - ..() - if(message_mode) - if(!is_component_functioning("radio")) - to_chat(src, "Your radio isn't functional at this time.") - return 0 - if(message_mode == "general") - message_mode = null - return radio.talk_into(src,message,message_mode,verb,speaking) - -/mob/living/silicon/speech_bubble_appearance() - return "synthetic" - -/mob/living/silicon/ai/handle_message_mode(message_mode, message, verb, speaking, used_radios) - ..() - if(message_mode == "department") - return holopad_talk(message, verb, speaking) - else if(message_mode) - if (aiRadio.disabledAi || aiRestorePowerRoutine || stat) - to_chat(src, "System Error - Transceiver Disabled.") - return 0 - if(message_mode == "general") - message_mode = null - return aiRadio.talk_into(src,message,message_mode,verb,speaking) - -/mob/living/silicon/pai/handle_message_mode(message_mode, message, verb, speaking, used_radios) - ..() - if(message_mode) - if(message_mode == "general") - message_mode = null - return radio.talk_into(src,message,message_mode,verb,speaking) - -/mob/living/silicon/say_quote(var/text) - var/ending = copytext(text, length(text)) - - if (ending == "?") - return speak_query - else if (ending == "!") - return speak_exclamation - - return speak_statement - -#define IS_AI 1 -#define IS_ROBOT 2 -#define IS_PAI 3 - -/mob/living/silicon/say_understands(var/other, var/datum/language/speaking = null) - //These only pertain to common. Languages are handled by mob/say_understands() - if(!speaking) - if(iscarbon(other)) - return TRUE - if(issilicon(other)) - return TRUE - if(isbrain(other)) - return TRUE - return ..() - -//For holopads only. Usable by AI. -/mob/living/silicon/ai/proc/holopad_talk(list/message_pieces, verb) - log_say("(HPAD) [multilingual_to_message(message_pieces)]",src) - - var/obj/machinery/hologram/holopad/T = src.holo - if(T && T.masters[src])//If there is a hologram and its master is the user. - var/list/listeners = get_mobs_and_objs_in_view_fast(get_turf(T), world.view) - var/list/listening = listeners["mobs"] - var/list/listening_obj = listeners["objs"] - for(var/mob/M in listening) - M.hear_holopad_talk(message_pieces, verb, src) - for(var/obj/O in listening_obj) - if(O == T) //Don't recieve your own speech - continue - O.hear_talk(src, message_pieces, verb) - /*Radios "filter out" this conversation channel so we don't need to account for them. - This is another way of saying that we won't bother dealing with them.*/ - to_chat(src, "Holopad transmitted, [real_name] [combine_message(message_pieces, verb, src)]") - else - to_chat(src, "No holopad connected.") - return 0 - return 1 - -/mob/living/silicon/ai/proc/holopad_emote(var/message) //This is called when the AI uses the 'me' verb while using a holopad. - message = trim(message) - - if(!message) - return - - var/obj/machinery/hologram/holopad/T = src.holo - if(T && T.masters[src]) - var/rendered = "[name] [message]" - to_chat(src, "Holopad action relayed, [real_name] [message]") - var/obj/effect/overlay/aiholo/hologram = T.masters[src] //VOREStation Add for people in the hologram to hear the messages - - //var/obj/effect/overlay/hologram = T.masters[src] //VOREStation edit. Done above. - var/list/in_range = get_mobs_and_objs_in_view_fast(get_turf(hologram), world.view, 2) //Emotes are displayed from the hologram, not the pad - var/list/m_viewers = in_range["mobs"] - var/list/o_viewers = in_range["objs"] - - for(var/mob/M in m_viewers) - spawn(0) - if(M) - M.show_message(rendered, 2) - - for(var/obj/O in o_viewers) - if(O == T) - continue - spawn(0) - if(O) - O.see_emote(src, message) - - log_emote("(HPAD) [message]", src) - - else //This shouldn't occur, but better safe then sorry. - to_chat(src, "No holopad connected.") - return 0 - return 1 - -/mob/living/silicon/ai/emote(var/act, var/m_type, var/message) - var/obj/machinery/hologram/holopad/T = holo - if(T && T.masters[src]) //Is the AI using a holopad? - . = holopad_emote(message) - else //Emote normally, then. - . = ..() - -#undef IS_AI -#undef IS_ROBOT -#undef IS_PAI +/mob/living/silicon/robot/handle_message_mode(message_mode, message, verb, speaking, used_radios) + ..() + if(message_mode) + if(!is_component_functioning("radio")) + to_chat(src, "Your radio isn't functional at this time.") + return 0 + if(message_mode == "general") + message_mode = null + return radio.talk_into(src,message,message_mode,verb,speaking) + +/mob/living/silicon/speech_bubble_appearance() + return "synthetic" + +/mob/living/silicon/ai/handle_message_mode(message_mode, message, verb, speaking, used_radios) + ..() + if(message_mode == "department") + return holopad_talk(message, verb, speaking) + else if(message_mode) + if (aiRadio.disabledAi || aiRestorePowerRoutine || stat) + to_chat(src, "System Error - Transceiver Disabled.") + return 0 + if(message_mode == "general") + message_mode = null + return aiRadio.talk_into(src,message,message_mode,verb,speaking) + +/mob/living/silicon/pai/handle_message_mode(message_mode, message, verb, speaking, used_radios) + ..() + if(message_mode) + if(message_mode == "general") + message_mode = null + return radio.talk_into(src,message,message_mode,verb,speaking) + +/mob/living/silicon/say_quote(var/text) + var/ending = copytext(text, length(text)) + + if (ending == "?") + return speak_query + else if (ending == "!") + return speak_exclamation + + return speak_statement + +#define IS_AI 1 +#define IS_ROBOT 2 +#define IS_PAI 3 + +/mob/living/silicon/say_understands(var/other, var/datum/language/speaking = null) + //These only pertain to common. Languages are handled by mob/say_understands() + if(!speaking) + if(iscarbon(other)) + return TRUE + if(issilicon(other)) + return TRUE + if(isbrain(other)) + return TRUE + return ..() + +//For holopads only. Usable by AI. +/mob/living/silicon/ai/proc/holopad_talk(list/message_pieces, verb) + log_say("(HPAD) [multilingual_to_message(message_pieces)]",src) + + var/obj/machinery/hologram/holopad/T = src.holo + if(T && T.masters[src])//If there is a hologram and its master is the user. + var/list/listeners = get_mobs_and_objs_in_view_fast(get_turf(T), world.view) + var/list/listening = listeners["mobs"] + var/list/listening_obj = listeners["objs"] + for(var/mob/M in listening) + M.hear_holopad_talk(message_pieces, verb, src) + for(var/obj/O in listening_obj) + if(O == T) //Don't recieve your own speech + continue + O.hear_talk(src, message_pieces, verb) + /*Radios "filter out" this conversation channel so we don't need to account for them. + This is another way of saying that we won't bother dealing with them.*/ + var/list/combined = combine_message(message_pieces, verb, src) + to_chat(src, "Holopad transmitted, [real_name] [combined["formatted"]]") + else + to_chat(src, "No holopad connected.") + return 0 + return 1 + +/mob/living/silicon/ai/proc/holopad_emote(var/message) //This is called when the AI uses the 'me' verb while using a holopad. + message = trim(message) + + if(!message) + return + + var/obj/machinery/hologram/holopad/T = src.holo + if(T && T.masters[src]) + var/rendered = "[name] [message]" + to_chat(src, "Holopad action relayed, [real_name] [message]") + var/obj/effect/overlay/aiholo/hologram = T.masters[src] //VOREStation Add for people in the hologram to hear the messages + + //var/obj/effect/overlay/hologram = T.masters[src] //VOREStation edit. Done above. + var/list/in_range = get_mobs_and_objs_in_view_fast(get_turf(hologram), world.view, 2) //Emotes are displayed from the hologram, not the pad + var/list/m_viewers = in_range["mobs"] + var/list/o_viewers = in_range["objs"] + + for(var/mob/M in m_viewers) + spawn(0) + if(M) + M.show_message(rendered, 2) + + for(var/obj/O in o_viewers) + if(O == T) + continue + spawn(0) + if(O) + O.see_emote(src, message) + + log_emote("(HPAD) [message]", src) + + else //This shouldn't occur, but better safe then sorry. + to_chat(src, "No holopad connected.") + return 0 + return 1 + +/mob/living/silicon/ai/emote(var/act, var/m_type, var/message) + var/obj/machinery/hologram/holopad/T = holo + if(T && T.masters[src]) //Is the AI using a holopad? + . = holopad_emote(message) + else //Emote normally, then. + . = ..() + +#undef IS_AI +#undef IS_ROBOT +#undef IS_PAI diff --git a/code/modules/nifsoft/software/14_commlink.dm b/code/modules/nifsoft/software/14_commlink.dm index 34e36f65276..93e1af799c3 100644 --- a/code/modules/nifsoft/software/14_commlink.dm +++ b/code/modules/nifsoft/software/14_commlink.dm @@ -77,7 +77,8 @@ mobs_to_relay = in_range["mobs"] for(var/mob/mob in mobs_to_relay) - var/message = mob.combine_message(message_pieces, verb, M) + var/list/combined = mob.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/name_used = M.GetVoice() var/rendered = null rendered = "[bicon(icon_object)] [name_used] [message]"