From 70ea101c96a216328d8b9db69dc2877e857ab53a Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 29 May 2021 01:37:44 -0400 Subject: [PATCH 1/8] Message piece combiner returns a list --- .../objects/effects/map_effects/portal.dm | 3 +- .../items/devices/communicator/phone.dm | 3 +- code/game/objects/items/uav.dm | 3 +- code/modules/mob/hear_say.dm | 23 +- code/modules/mob/living/silicon/ai/ai.dm | 2005 +++++++++-------- code/modules/mob/living/silicon/say.dm | 253 +-- code/modules/nifsoft/software/14_commlink.dm | 3 +- 7 files changed, 1154 insertions(+), 1139 deletions(-) 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]" From e0183093ca9d5a00006eb1bb5ea21e7f8d5c68df Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 29 May 2021 01:38:24 -0400 Subject: [PATCH 2/8] Emote formatter returns a list --- code/modules/emotes/emote_mob.dm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index cbc53019d86..61544715803 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -149,7 +149,7 @@ subtext = html_encode(subtext) // Store the player's name in a nice bold, naturalement nametext = "[emoter]" - return pretext + nametext + subtext + return list("pretext" = pretext, "nametext" = nametext, "subtext" = subtext) /mob/proc/custom_emote(var/m_type = VISIBLE_MESSAGE, var/message, var/range = world.view) @@ -163,8 +163,10 @@ else input = message + var/list/formatted if(input) - message = format_emote(src, message) + formatted = format_emote(src, message) + message = formatted["pretext"] + formatted["nametext"] + formatted["subtext"] else return From ea42ee23034ef841302aa49742ebebfb0198242a Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 29 May 2021 01:38:36 -0400 Subject: [PATCH 3/8] Port Runechat --- code/__defines/_planes+layers.dm | 2 + code/_helpers/time.dm | 2 + code/_helpers/unsorted.dm | 16 + code/datums/chat_message.dm | 293 ++++++++++++++++++ code/game/atoms.dm | 17 +- code/game/world.dm | 1 + code/modules/admin/admin_verbs.dm | 3 +- code/modules/client/client defines.dm | 3 + .../preference_setup/global/setting_datums.dm | 26 ++ code/modules/emotes/emote_define.dm | 17 +- code/modules/emotes/emote_mob.dm | 8 +- code/modules/mob/hear_say.dm | 1 + code/modules/mob/living/say.dm | 29 +- code/modules/mob/mob.dm | 13 +- .../ventcrawl/ventcrawl_atmospherics.dm | 11 +- interface/skin.dmf | 2 +- vorestation.dme | 1 + 17 files changed, 414 insertions(+), 31 deletions(-) create mode 100644 code/datums/chat_message.dm diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index 96d65c6f495..111349d887c 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -93,6 +93,8 @@ What is the naming convention for planes or layers? #define BELOW_MOB_LAYER 3.9 // Should be converted to plane swaps #define ABOVE_MOB_LAYER 4.1 // Should be converted to plane swaps +#define ABOVE_MOB_PLANE -24 + // Invisible things plane #define CLOAKED_PLANE -15 diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index 6e63dabb45a..b75aeb77e38 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -20,6 +20,8 @@ #define TICKS2DS(T) ((T) TICKS) // Convert ticks to deciseconds #define DS2NEARESTTICK(DS) TICKS2DS(-round(-(DS2TICKS(DS)))) +var/world_startup_time + /proc/get_game_time() var/global/time_offset = 0 var/global/last_time = 0 diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index 5701521ec52..fe2b51195ab 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -1691,3 +1691,19 @@ GLOBAL_REAL_VAR(list/stack_trace_storage) return "CLIENT: [D]" else return "Unknown data type: [D]" + +/* + is_holder_of(): Returns 1 if A is a holder of B, meaning, A is B.loc or B.loc.loc or B.loc.loc.loc etc. + This is essentially the same as calling (locate(B) in A), but a little clearer as to what you're doing, and locate() has been known to bug out or be extremely slow in the past. +*/ +/proc/is_holder_of(const/atom/movable/A, const/atom/movable/B) + if(istype(A, /turf) || istype(B, /turf)) //Clicking on turfs is a common thing and turfs are also not /atom/movable, so it was causing the assertion to fail. + return 0 + ASSERT(istype(A) && istype(B)) + var/atom/O = B + while(O && !isturf(O)) + if(O == A) + return 1 + O = O.loc + return 0 + \ No newline at end of file diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm new file mode 100644 index 00000000000..37fdca02f72 --- /dev/null +++ b/code/datums/chat_message.dm @@ -0,0 +1,293 @@ +#define CHAT_MESSAGE_SPAWN_TIME 0.2 SECONDS +#define CHAT_MESSAGE_LIFESPAN 5 SECONDS +#define CHAT_MESSAGE_EOL_FADE 0.7 SECONDS +#define CHAT_MESSAGE_EXP_DECAY 0.8 // Messages decay at pow(factor, idx in stack) +#define CHAT_MESSAGE_HEIGHT_DECAY 0.7 // Increase message decay based on the height of the message +#define CHAT_MESSAGE_APPROX_LHEIGHT 11 // Approximate height in pixels of an 'average' line, used for height decay +#define CHAT_MESSAGE_WIDTH 96 // pixels +#define CHAT_MESSAGE_NORM_LENGTH 68 // characters +#define CHAT_MESSAGE_EXT_LENGTH 150 // characters +#define CHAT_MESSAGE_MOB 1 +#define CHAT_MESSAGE_OBJ 2 +#define WXH_TO_HEIGHT(x) text2num(copytext((x), findtextEx((x), "x") + 1)) // thanks lummox + +/** + * # Chat Message Overlay + * + * Datum for generating a message overlay on the map + * Ported from TGStation; https://github.com/tgstation/tgstation/pull/50608/, author: bobbahbrown + */ + +// Cached runechat icon +var/runechat_icon = null + +/datum/chatmessage + /// The visual element of the chat messsage + var/image/message + /// The location in which the message is appearing + var/atom/message_loc + /// The client who heard this message + var/client/owned_by + /// Contains the scheduled destruction time + var/scheduled_destruction + /// Contains the approximate amount of lines for height decay + var/approx_lines + +/** + * Constructs a chat message overlay + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ +/datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = null, lifespan = CHAT_MESSAGE_LIFESPAN) + . = ..() + if (!istype(target)) + CRASH("Invalid target given for chatmessage") + if(!istype(owner) || QDELETED(owner) || !owner.client) + stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner") + qdel(src) + return + generate_image(text, target, owner, extra_classes, lifespan) + +/datum/chatmessage/Destroy() + if (owned_by) + owned_by.seen_messages.Remove(src) + owned_by.images.Remove(message) + UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) + owned_by = null + message_loc = null + message = null + return ..() + +/** + * Generates a chat message image representation + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ +/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, list/extra_classes, lifespan) + set waitfor = FALSE + // Register client who owns this message + owned_by = owner.client + RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/qdel_self) + + // Clip message + var/maxlen = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_NORM_LENGTH + if (length_char(text) > maxlen) + text = copytext_char(text, 1, maxlen + 1) + "..." // BYOND index moment + + // Calculate target color if not already present + if (!target.chat_color || target.chat_color_name != target.name) + target.chat_color = colorize_string(target.name) + target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85) + target.chat_color_name = target.name + + // Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag + var/static/regex/url_scheme = new(@"[A-Za-z][A-Za-z0-9+-\.]*:\/\/", "g") + text = replacetext(text, url_scheme, "") + + // Reject whitespace + var/static/regex/whitespace = new(@"^\s*$") + if (whitespace.Find(text)) + qdel(src) + return + + // Non mobs speakers can be small + if (!ismob(target)) + extra_classes |= "small" + + // If we heard our name, it's important + var/list/names = splittext(owner.name, " ") + for (var/word in names) + text = replacetext(text, word, "[word]") + + // Append radio icon if comes from a radio + if (extra_classes.Find("spoken_into_radio")) + if (!runechat_icon) + var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio") + runechat_icon = "\icon[r_icon] " + text = runechat_icon + text + + // We dim italicized text to make it more distinguishable from regular text + var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color + // Approximate text height + // Note we have to replace HTML encoded metacharacters otherwise MeasureText will return a zero height + // BYOND Bug #2563917 + // Construct text + var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g") + var/complete_text = "[text]" + var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(replacetext(complete_text, html_metachars, "m"), null, CHAT_MESSAGE_WIDTH)) + approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) + + // Translate any existing messages upwards, apply exponential decay factors to timers + message_loc = target + if (owned_by.seen_messages) + var/idx = 1 + var/combined_height = approx_lines + for(var/msg in owned_by.seen_messages) + var/datum/chatmessage/m = msg + animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME) + combined_height += m.approx_lines + var/sched_remaining = m.scheduled_destruction - world.time + if (sched_remaining > CHAT_MESSAGE_SPAWN_TIME) + var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) + m.scheduled_destruction = world.time + remaining_time + spawn(remaining_time) + m.end_of_life() + + // Build message image + message = image(loc = message_loc, layer = ABOVE_MOB_LAYER) + message.plane = PLANE_LIGHTING_ABOVE + message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART + message.alpha = 0 + message.pixel_y = owner.bound_height * 0.95 + message.maptext_width = CHAT_MESSAGE_WIDTH + message.maptext_height = mheight + message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5 + message.maptext = complete_text + + if (is_holder_of(owner, target)) // Special case, holding an atom speaking (pAI, recorder...) + message.plane = PLANE_PLAYER_HUD_ABOVE + + // View the message + owned_by.seen_messages.Add(src) + owned_by.images += message + animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME) + + // Prepare for destruction + scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE) + spawn(lifespan - CHAT_MESSAGE_EOL_FADE) + end_of_life() + +/** + * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion + */ +/datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE) + if (gc_destroyed) + return + animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) + spawn(fadetime) + qdel(src) + +/** + * Creates a message overlay at a defined location for a given speaker + * + * Arguments: + * * speaker - The atom who is saying this message + * * message - The text content of the message + * * italics - Decides if this should be small or not, as generally italics text are for whisper/radio overhear + * * existing_extra_classes - Additional classes to add to the message + */ +/mob/proc/create_chat_message(atom/movable/speaker, message, italics, list/existing_extra_classes, audible = TRUE) + if(!client) + return + + // Doesn't want to hear + if(ismob(speaker) && !client.is_preference_enabled(/datum/client_preference/runechat_mob)) + return + else if(isobj(speaker) && !client.is_preference_enabled(/datum/client_preference/runechat_obj)) + return + + // Incapable of receiving + if((audible && is_deaf()) || (!audible && is_blind())) + return + + // Check for virtual speakers (aka hearing a message through a radio) + if (existing_extra_classes.Find("radio")) + return + + /* Not currently necessary + message = strip_html_properly(message) + if(!message) + return + */ + + var/list/extra_classes = list() + extra_classes += existing_extra_classes + + if (italics) + extra_classes |= "italics" + + if (client.is_preference_enabled(/datum/client_preference/runechat_border)) + extra_classes |= "black_outline" + + var/dist = get_dist(src, speaker) + switch (dist) + if (4 to 5) + extra_classes |= "small" + if (5 to 16) + extra_classes |= "very_small" + + // Display visual above source + new /datum/chatmessage(message, speaker, src, extra_classes) + +// Tweak these defines to change the available color ranges +#define CM_COLOR_SAT_MIN 0.6 +#define CM_COLOR_SAT_MAX 0.95 +#define CM_COLOR_LUM_MIN 0.70 +#define CM_COLOR_LUM_MAX 0.90 + +/** + * Gets a color for a name, will return the same color for a given string consistently within a round.atom + * + * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map. + * + * Arguments: + * * name - The name to generate a color for + * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation + * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence + */ +/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1) + // seed to help randomness + var/static/rseed = rand(1,26) + + // get hsl using the selected 6 characters of the md5 hash + var/hash = copytext(md5(name + "[world_startup_time]"), rseed, rseed + 6) + var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255) + var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN + var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN + + // adjust for shifts + s *= clamp(sat_shift, 0, 1) + l *= clamp(lum_shift, 0, 1) + + // convert to rgba + var/h_int = round(h/60) // mapping each section of H to 60 degree sections + var/c = (1 - abs(2 * l - 1)) * s + var/x = c * (1 - abs((h / 60) % 2 - 1)) + var/m = l - c * 0.5 + x = (x + m) * 255 + c = (c + m) * 255 + m *= 255 + switch(h_int) + if(0) + return rgb(c,x,m) + if(1) + return rgb(x,c,m) + if(2) + return rgb(m,c,x) + if(3) + return rgb(m,x,c) + if(4) + return rgb(x,m,c) + if(5) + return rgb(c,m,x) + +/atom/proc/runechat_message(message, range = world.view, italics, list/classes = list(), audible = TRUE) + var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src), range, remote_ghosts = FALSE) + + var/list/hearing_mobs = hear["mobs"] + + for(var/mob in hearing_mobs) + var/mob/M = mob + if(!M.client) + continue + M.create_chat_message(src, message, italics, classes, audible) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 46f001c8e1b..f4117d37e84 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -34,6 +34,15 @@ // Track if we are already had initialize() called to prevent double-initialization. var/initialized = FALSE + /// Last name used to calculate a color for the chatmessage overlays + var/chat_color_name + /// Last color calculated for the the chatmessage overlays + var/chat_color + /// A luminescence-shifted value of the last color calculated for chatmessage overlays + var/chat_color_darkened + /// The chat color var, without alpha. + var/chat_color_hover + /atom/New(loc, ...) // Don't call ..() unless /datum/New() ever exists @@ -490,7 +499,7 @@ // Use for objects performing visible actions // message is output to anyone who can see, e.g. "The [src] does something!" // blind_message (optional) is what blind people will hear e.g. "You hear something!" -/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view) +/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view, var/runemessage = "👁") //VOREStation Edit var/list/see @@ -513,6 +522,8 @@ var/mob/M = mob if(M.see_invisible >= invisibility && MOB_CAN_SEE_PLANE(M, plane)) M.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote"), audible = FALSE) else if(blind_message) M.show_message(blind_message, AUDIBLE_MESSAGE) @@ -521,7 +532,7 @@ // message is the message output to anyone who can hear. // deaf_message (optional) is what deaf people will see. // hearing_distance (optional) is the range, how many tiles away the message can be heard. -/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance, var/radio_message) +/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance, var/radio_message, var/runemessage) var/range = hearing_distance || world.view var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) @@ -542,6 +553,8 @@ var/mob/M = mob var/msg = message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote")) /atom/movable/proc/dropInto(var/atom/destination) while(istype(destination)) diff --git a/code/game/world.dm b/code/game/world.dm index 9f84772b9fc..2895a191dba 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,5 +1,6 @@ #define RECOMMENDED_VERSION 501 /world/New() + world_startup_time = world.timeofday to_world_log("Map Loading Complete") //logs //VOREStation Edit Start diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index d8deb0dce60..b0f553a4871 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -324,8 +324,7 @@ var/message = sanitize(input("What do you want the message to be?", "Make Sound") as text|null) if(!message) return - for (var/mob/V in hearers(O)) - V.show_message(message, 2) + O.audible_message(message) log_admin("[key_name(usr)] made [O] at [O.x], [O.y], [O.z]. make a sound") message_admins("[key_name_admin(usr)] made [O] at [O.x], [O.y], [O.z]. make a sound.", 1) feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 9fb7295266c..abd55097a8c 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -76,3 +76,6 @@ var/connection_realtime ///world.timeofday they connected var/connection_timeofday + + // Runechat messages + var/list/seen_messages = list() diff --git a/code/modules/client/preference_setup/global/setting_datums.dm b/code/modules/client/preference_setup/global/setting_datums.dm index 24cca9ca18b..25eae07ff1b 100644 --- a/code/modules/client/preference_setup/global/setting_datums.dm +++ b/code/modules/client/preference_setup/global/setting_datums.dm @@ -290,6 +290,32 @@ var/list/_client_preferences_by_type enabled_description = "Show" disabled_description = "Hide" +/datum/client_preference/runechat_mob + description = "Runechat (Mobs)" + key = "RUNECHAT_MOB" + enabled_description = "Show" + disabled_description = "Hide" + +/datum/client_preference/runechat_obj + description = "Runechat (Objs)" + key = "RUNECHAT_OBJ" + enabled_description = "Show" + disabled_description = "Hide" + +/datum/client_preference/runechat_border + description = "Runechat Message Border" + key = "RUNECHAT_BORDER" + enabled_description = "Show" + disabled_description = "Hide" + enabled_by_default = FALSE + +/datum/client_preference/runechat_long_messages + description = "Runechat Message Length" + key = "RUNECHAT_LONG" + enabled_description = "ERP KING" + disabled_description = "Normie" + enabled_by_default = FALSE + /datum/client_preference/status_indicators/toggled(mob/preference_mob, enabled) . = ..() if(preference_mob && preference_mob.plane_holder) diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm index 6b50b09dde1..d8184b9bae2 100644 --- a/code/modules/emotes/emote_define.dm +++ b/code/modules/emotes/emote_define.dm @@ -103,11 +103,14 @@ var/global/list/emotes_by_key if(target) use_1p = replace_target_tokens(use_1p, target) use_1p = "[capitalize(replace_user_tokens(use_1p, user))]" - var/use_3p = get_emote_message_3p(user, target, extra_params) - if(use_3p) + var/prefinal_3p + var/use_3p + var/raw_3p = get_emote_message_3p(user, target, extra_params) + if(raw_3p) if(target) - use_3p = replace_target_tokens(use_3p, target) - use_3p = "\The [user] [replace_user_tokens(use_3p, user)]" + raw_3p = replace_target_tokens(raw_3p, target) + prefinal_3p = replace_user_tokens(raw_3p, user) + use_3p = "\The [user] [prefinal_3p]" var/use_radio = get_radio_message(user) if(use_radio) if(target) @@ -124,12 +127,12 @@ var/global/list/emotes_by_key if(isliving(user)) var/mob/living/L = user if(L.silent) - M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired) + M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired, runemessage = "opens their mouth silently!") return else - M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio) + M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio, runemessage = prefinal_3p) else - M.visible_message(message = use_3p, self_message = use_1p, blind_message = emote_message_impaired, range = use_range) + M.visible_message(message = use_3p, self_message = use_1p, blind_message = emote_message_impaired, range = use_range, runemessage = prefinal_3p) do_extra(user, target) do_sound(user) diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index 61544715803..ef4129ae464 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -86,7 +86,8 @@ return if(use_emote.message_type == AUDIBLE_MESSAGE && is_muzzled()) - audible_message("\The [src] [use_emote.emote_message_muffled || "makes a muffled sound."]") + var/muffle_message = use_emote.emote_message_muffled || "makes a muffled sound." + audible_message("\The [src] [muffle_message]", runemessage = "* [muffle_message] *") return next_emote = world.time + use_emote.emote_delay @@ -164,9 +165,13 @@ input = message var/list/formatted + var/runemessage if(input) formatted = format_emote(src, message) message = formatted["pretext"] + formatted["nametext"] + formatted["subtext"] + runemessage = formatted["subtext"] + // This is just personal preference (but I'm objectively right) that custom emotes shouldn't have periods at the end in runechat + runemessage = replacetext(runemessage,".","",length(runemessage),length(runemessage)+1) else return @@ -194,6 +199,7 @@ if(isobserver(M)) message = "[src] ([ghost_follow_link(src, M)]) [input]" M.show_message(message, m_type) + M.create_chat_message(src, "* [runemessage] *", FALSE, list("emote"), (m_type == AUDIBLE_MESSAGE)) for(var/obj in o_viewers) var/obj/O = obj diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index df959f07a24..8e4fd3970ea 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -116,6 +116,7 @@ message_to_send = "[message_to_send]" on_hear_say(message_to_send) + create_chat_message(speaker, combined["raw"], italics, list()) if(speech_sound && (get_dist(speaker, src) <= world.view && z == speaker.z)) var/turf/source = speaker ? get_turf(speaker) : get_turf(src) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index ae1b09ae714..07ae8377608 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -40,18 +40,18 @@ var/list/department_radio_keys = list( //kinda localization -- rastaf0 //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. - ":" = "right ear", "." = "right ear", - ":" = "left ear", "." = "left ear", - ":" = "intercom", "." = "intercom", - ":" = "department", "." = "department", - ":" = "Command", "." = "Command", - ":" = "Science", "." = "Science", - ":" = "Medical", "." = "Medical", - ":" = "Engineering", "." = "Engineering", - ":" = "Security", "." = "Security", - ":" = "whisper", "." = "whisper", - ":" = "Mercenary", "." = "Mercenary", - ":" = "Supply", "." = "Supply", + ":�" = "right ear", ".�" = "right ear", + ":�" = "left ear", ".�" = "left ear", + ":�" = "intercom", ".�" = "intercom", + ":�" = "department", ".�" = "department", + ":�" = "Command", ".�" = "Command", + ":�" = "Science", ".�" = "Science", + ":�" = "Medical", ".�" = "Medical", + ":�" = "Engineering", ".�" = "Engineering", + ":�" = "Security", ".�" = "Security", + ":�" = "whisper", ".�" = "whisper", + ":�" = "Mercenary", ".�" = "Mercenary", + ":�" = "Supply", ".�" = "Supply", ) @@ -362,16 +362,17 @@ proc/get_radio_key_from_channel(var/channel) //VOREStation Add End var/dst = get_dist(get_turf(M),get_turf(src)) + var/runechat_enabled = M.client?.is_preference_enabled(/datum/client_preference/runechat_mob) if(dst <= message_range || (M.stat == DEAD && !forbid_seeing_deadchat)) //Inside normal message range, or dead with ears (handled in the view proc) - if(M.client) + if(M.client && !runechat_enabled) var/image/I1 = listening[M] || speech_bubble images_to_clients[I1] |= M.client M << I1 M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol) if(whispering && !isobserver(M)) //Don't even bother with these unless whispering if(dst > message_range && dst <= w_scramble_range) //Inside whisper scramble range - if(M.client) + if(M.client && !runechat_enabled) var/image/I2 = listening[M] || speech_bubble images_to_clients[I2] |= M.client M << I2 diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 00d705f8e1a..6626b689726 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -77,7 +77,7 @@ // message is the message output to anyone who can see e.g. "[src] does something!" // self_message (optional) is what the src mob sees e.g. "You do something!" // blind_message (optional) is what blind people will hear e.g. "You hear something!" -/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view) +/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view, var/runemessage) if(self_message) if(LAZYLEN(exclude_mobs)) exclude_mobs |= src @@ -87,7 +87,9 @@ // Transfer messages about what we are doing to upstairs if(shadow) shadow.visible_message(message, self_message, blind_message, exclude_mobs, range) - . = ..(message, blind_message, exclude_mobs, range) // Really not ideal that atom/visible_message has different arg numbering :( + if(isnull(runemessage)) + runemessage = -1 + . = ..(message, blind_message, exclude_mobs, range, runemessage) // Really not ideal that atom/visible_message has different arg numbering :( // Returns an amount of power drawn from the object (-1 if it's not viable). // If drain_check is set it will not actually drain power, just return a value. @@ -102,7 +104,7 @@ // self_message (optional) is what the src mob hears. // deaf_message (optional) is what deaf people will see. // hearing_distance (optional) is the range, how many tiles away the message can be heard. -/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message) +/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message, var/runemessage) var/range = hearing_distance || world.view var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) @@ -110,6 +112,9 @@ var/list/hearing_mobs = hear["mobs"] var/list/hearing_objs = hear["objs"] + if(isnull(runemessage)) + runemessage = -1 // Symmetry with mob/audible_message, despite the fact this one doesn't call parent. Maybe it should! + if(radio_message) for(var/obj in hearing_objs) var/obj/O = obj @@ -125,6 +130,8 @@ if(self_message && M==src) msg = self_message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote"), audible = FALSE) /mob/proc/findname(msg) for(var/mob/M in mob_list) diff --git a/code/modules/ventcrawl/ventcrawl_atmospherics.dm b/code/modules/ventcrawl/ventcrawl_atmospherics.dm index 1fd9dd077ca..80348c76994 100644 --- a/code/modules/ventcrawl/ventcrawl_atmospherics.dm +++ b/code/modules/ventcrawl/ventcrawl_atmospherics.dm @@ -42,7 +42,16 @@ user.client.eye = target_move //if we don't do this, Byond only updates the eye every tick - required for smooth movement if(world.time > user.next_play_vent) user.next_play_vent = world.time+30 - playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3) + var/turf/T = get_turf(src) + playsound(T, 'sound/machines/ventcrawl.ogg', 50, 1, -3) + var/message = pick( + prob(90);"* clunk *", + prob(90);"* thud *", + prob(90);"* clatter *", + prob(1);"* *" + ) + T.runechat_message(message) + else if((direction & initialize_directions) || is_type_in_list(src, ventcrawl_machinery) && src.can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent user.remove_ventcrawl() diff --git a/interface/skin.dmf b/interface/skin.dmf index fc14eb8a69d..b1d9a6267e2 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -1282,7 +1282,7 @@ window "mapwindow" saved-params = "icon-size" on-show = ".winset\"mainwindow.mainvsplit.left=mapwindow\"" on-hide = ".winset\"mainwindow.mainvsplit.left=\"" - style=".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .clown { color: #FF69Bf;} .tajaran {color: #803B56;} .skrell {color: #00CED1;} .solcom {color: #22228B;} .com_srus {color: #7c4848;} .zombie {color: #ff0000;} .soghun {color: #228B22;} .vox {color: #AA00AA;} .diona {color: #804000; font-weight: bold;} .trinary {color: #727272;} .kidan {color: #664205;} .slime {color: #0077AA;} .drask {color: #a3d4eb;} .vulpkanin {color: #B97A57;} .abductor {color: #800080; font-style: italic;} .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; }" + style=".center { text-align: center; } .runechatdiv {background-color: #20202070} .black_outline { -dm-text-outline: 1px black } .boldtext { font-weight: bold; } .maptext { font-family: 'Small Fonts'; font-size: 7px; color: white; line-height: 1.1; } .command_headset { font-weight: bold; font-size: 8px; } .small { font-size: 6px; } .very_small { font-size: 5px;} .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 7px; font-style: italic; }" window "outputwindow" elem "outputwindow" diff --git a/vorestation.dme b/vorestation.dme index f3e9b290287..f5938321638 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -308,6 +308,7 @@ #include "code\datums\browser.dm" #include "code\datums\callback.dm" #include "code\datums\category.dm" +#include "code\datums\chat_message.dm" #include "code\datums\computerfiles.dm" #include "code\datums\datacore.dm" #include "code\datums\datum.dm" From eaf2298825f5397495bb0a88583ae605cbe043bf Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 29 May 2021 15:14:04 -0400 Subject: [PATCH 4/8] VS: Account for mob size --- code/datums/chat_message.dm | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm index 37fdca02f72..0668380059d 100644 --- a/code/datums/chat_message.dm +++ b/code/datums/chat_message.dm @@ -4,9 +4,12 @@ #define CHAT_MESSAGE_EXP_DECAY 0.8 // Messages decay at pow(factor, idx in stack) #define CHAT_MESSAGE_HEIGHT_DECAY 0.7 // Increase message decay based on the height of the message #define CHAT_MESSAGE_APPROX_LHEIGHT 11 // Approximate height in pixels of an 'average' line, used for height decay + #define CHAT_MESSAGE_WIDTH 96 // pixels -#define CHAT_MESSAGE_NORM_LENGTH 68 // characters -#define CHAT_MESSAGE_EXT_LENGTH 150 // characters +#define CHAT_MESSAGE_EXT_WIDTH 128 +#define CHAT_MESSAGE_LENGTH 68 // characters +#define CHAT_MESSAGE_EXT_LENGTH 150 + #define CHAT_MESSAGE_MOB 1 #define CHAT_MESSAGE_OBJ 2 #define WXH_TO_HEIGHT(x) text2num(copytext((x), findtextEx((x), "x") + 1)) // thanks lummox @@ -79,8 +82,11 @@ var/runechat_icon = null owned_by = owner.client RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/qdel_self) + var/erp_king = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) + var/maxlen = erp_king ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH + var/msgwidth = erp_king ? CHAT_MESSAGE_EXT_WIDTH : CHAT_MESSAGE_WIDTH + // Clip message - var/maxlen = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_NORM_LENGTH if (length_char(text) > maxlen) text = copytext_char(text, 1, maxlen + 1) + "..." // BYOND index moment @@ -124,7 +130,7 @@ var/runechat_icon = null // Construct text var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g") var/complete_text = "[text]" - var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(replacetext(complete_text, html_metachars, "m"), null, CHAT_MESSAGE_WIDTH)) + var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(replacetext(complete_text, html_metachars, "m"), null, msgwidth)) approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) // Translate any existing messages upwards, apply exponential decay factors to timers @@ -149,9 +155,9 @@ var/runechat_icon = null message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART message.alpha = 0 message.pixel_y = owner.bound_height * 0.95 - message.maptext_width = CHAT_MESSAGE_WIDTH + message.maptext_width = msgwidth message.maptext_height = mheight - message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5 + message.maptext_x = (msgwidth - owner.bound_width) * -0.5 message.maptext = complete_text if (is_holder_of(owner, target)) // Special case, holding an atom speaking (pAI, recorder...) From b12b146343c45f6cb1ad53f3aada55448de9537c Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 29 May 2021 19:15:09 -0400 Subject: [PATCH 5/8] VOREStation Specific Stuffs --- code/modules/admin/verbs/smite_vr.dm | 2 +- code/modules/clothing/under/accessories/accessory_vr.dm | 2 +- code/modules/mob/mob_defines_vr.dm | 2 ++ code/modules/vore/fluffstuff/custom_items_vr.dm | 2 +- code/modules/vore/resizing/resize_vr.dm | 1 - 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/code/modules/admin/verbs/smite_vr.dm b/code/modules/admin/verbs/smite_vr.dm index cbf78c6f5ae..f065293a26b 100644 --- a/code/modules/admin/verbs/smite_vr.dm +++ b/code/modules/admin/verbs/smite_vr.dm @@ -104,7 +104,7 @@ sleep(1 SECOND) shadekin.dir = SOUTH sleep(1 SECOND) - shadekin.audible_message("[shadekin] belches loudly!") + shadekin.audible_message("[shadekin] belches loudly!", runemessage = "* URRRRRP *") sleep(2 SECONDS) shadekin.phase_shift() target.transforming = FALSE //Undo cheap hack diff --git a/code/modules/clothing/under/accessories/accessory_vr.dm b/code/modules/clothing/under/accessories/accessory_vr.dm index 5422e294b4e..e9d1ae21ac3 100644 --- a/code/modules/clothing/under/accessories/accessory_vr.dm +++ b/code/modules/clothing/under/accessories/accessory_vr.dm @@ -124,7 +124,7 @@ if(usr.stat) return if(!jingled) - usr.audible_message("[usr] jingles the [src]'s bell.") + usr.audible_message("[usr] jingles the [src]'s bell.", runemessage = "* jingle *") playsound(src, 'sound/items/pickup/ring.ogg', 50, 1) jingled = 1 addtimer(CALLBACK(src, .proc/jingledreset), 50) diff --git a/code/modules/mob/mob_defines_vr.dm b/code/modules/mob/mob_defines_vr.dm index ff5f1ae5826..3854620e7c1 100644 --- a/code/modules/mob/mob_defines_vr.dm +++ b/code/modules/mob/mob_defines_vr.dm @@ -7,6 +7,8 @@ var/obj/screen/shadekin/shadekin_display = null var/obj/screen/xenochimera/danger_level/xenochimera_danger_display = null + var/size_multiplier = 1 //multiplier for the mob's icon size + /mob/drop_location() if(temporary_form) return temporary_form.drop_location() diff --git a/code/modules/vore/fluffstuff/custom_items_vr.dm b/code/modules/vore/fluffstuff/custom_items_vr.dm index ba633a1cf24..8ba4a14b3a9 100644 --- a/code/modules/vore/fluffstuff/custom_items_vr.dm +++ b/code/modules/vore/fluffstuff/custom_items_vr.dm @@ -551,7 +551,7 @@ //He's dead, jim if((state == 1) && owner && (owner.stat == DEAD)) update_state(2) - audible_message("The [name] begins flashing red.") + visible_message("The [name] begins flashing red.") sleep(30) visible_message("The [name] shatters into dust!") if(owner_c) diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm index 04c3e47dba2..8dc575ce208 100644 --- a/code/modules/vore/resizing/resize_vr.dm +++ b/code/modules/vore/resizing/resize_vr.dm @@ -1,7 +1,6 @@ // Adding needed defines to /mob/living // Note: Polaris had this on /mob/living/carbon/human We need it higher up for animals and stuff. /mob/living - var/size_multiplier = 1 //multiplier for the mob's icon size var/holder_default var/step_mechanics_pref = TRUE // Allow participation in macro-micro step mechanics var/pickup_pref = TRUE // Allow participation in macro-micro pickup mechanics From 177fd15c307aa633d68088a8a9aaef5f95832906 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 29 May 2021 19:14:29 -0400 Subject: [PATCH 6/8] Various onamonapoea --- code/game/machinery/air_alarm.dm | 4 ++-- code/game/machinery/cloning.dm | 2 +- code/game/machinery/computer/arcade.dm | 4 ++-- code/game/objects/items/devices/defib.dm | 4 ++-- code/game/objects/items/devices/megaphone.dm | 10 ++++++---- code/game/objects/items/devices/taperecorder.dm | 8 ++++---- code/game/objects/items/devices/text_to_speech.dm | 5 +++-- code/game/objects/items/devices/whistle.dm | 4 ++-- code/modules/integrated_electronics/subtypes/output.dm | 2 +- code/modules/multiz/ladders.dm | 2 +- code/modules/multiz/movement.dm | 8 ++++---- code/modules/overmap/ships/engines/gas_thruster.dm | 2 +- code/modules/paperwork/photocopier.dm | 10 +++++----- code/modules/projectiles/guns/magnetic/bore.dm | 6 +++--- code/modules/projectiles/guns/magnetic/magnetic.dm | 2 +- code/modules/research/message_server.dm | 4 ++-- code/modules/resleeving/machines.dm | 2 +- code/modules/shuttles/shuttles_web.dm | 2 +- code/modules/turbolift/turbolift.dm | 4 ++-- code/modules/turbolift/turbolift_console.dm | 4 ++-- code/modules/turbolift/turbolift_door.dm | 2 +- 21 files changed, 47 insertions(+), 44 deletions(-) diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm index c8222d00b4b..f330d839f6a 100644 --- a/code/game/machinery/air_alarm.dm +++ b/code/game/machinery/air_alarm.dm @@ -191,7 +191,7 @@ update_use_power(USE_POWER_ACTIVE) regulating_temperature = 1 audible_message("\The [src] clicks as it starts [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ - "You hear a click and a faint electronic hum.") + "You hear a click and a faint electronic hum.", runemessage = "* click *") playsound(src, 'sound/machines/click.ogg', 50, 1) else //check for when we should stop adjusting temperature @@ -199,7 +199,7 @@ update_use_power(USE_POWER_IDLE) regulating_temperature = 0 audible_message("\The [src] clicks quietly as it stops [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ - "You hear a click as a faint electronic humming stops.") + "You hear a click as a faint electronic humming stops.", runemessage = "* click *") playsound(src, 'sound/machines/click.ogg', 50, 1) if(regulating_temperature) diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 2c34326fa7d..197848d9adb 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -206,7 +206,7 @@ else if((occupant.health >= heal_level || occupant.health == occupant.getMaxHealth()) && (!eject_wait)) playsound(src, 'sound/machines/medbayscanner1.ogg', 50, 1) - audible_message("\The [src] signals that the cloning process is complete.") + audible_message("\The [src] signals that the cloning process is complete.", runemessage = "* ding *") connected_message("Cloning Process Complete.") locked = 0 go_out() diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index 77e7e037a9d..df72c0d7678 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -519,9 +519,9 @@ if(electronics) sleep(10) if(oldfuel > fuel && oldfood > food) - src.audible_message("\The [src] lets out a somehow reassuring chime.") + src.audible_message("\The [src] lets out a somehow reassuring chime.", runemessage = "* reassuring chime *") else if(oldfuel < fuel || oldfood < food) - src.audible_message("\The [src] lets out a somehow ominous chime.") + src.audible_message("\The [src] lets out a somehow ominous chime.", runemessage = "* ominous chime *") food = oldfood fuel = oldfuel diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm index f938f0eab18..4bd2ac9a307 100644 --- a/code/game/objects/items/devices/defib.dm +++ b/code/game/objects/items/devices/defib.dm @@ -460,7 +460,7 @@ return playsound(src, 'sound/machines/defib_charge.ogg', 50, 0) - audible_message("\The [src] lets out a steadily rising hum...") + audible_message("\The [src] lets out a steadily rising hum...", runemessage = "* whines *") if(!do_after(user, chargetime, H)) return @@ -527,7 +527,7 @@ H.setBrainLoss(brain_damage) /obj/item/weapon/shockpaddles/proc/make_announcement(var/message, var/msg_class) - audible_message("\The [src] [message]", "\The [src] vibrates slightly.") + audible_message("\The [src] [message]", "\The [src] vibrates slightly.", runemessage = "* buzz *") /obj/item/weapon/shockpaddles/emag_act(mob/user) if(safety) diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm index 32c9d75959a..86602094c1c 100644 --- a/code/game/objects/items/devices/megaphone.dm +++ b/code/game/objects/items/devices/megaphone.dm @@ -31,12 +31,13 @@ /obj/item/device/megaphone/proc/do_broadcast(var/mob/living/user, var/message) if(emagged) if(insults) - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[pick(insultmsg)]\"") + var/insult = pick(insultmsg) + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[insult]\"", runemessage = insult) insults-- else to_chat(user, "*BZZZZzzzzzt*") else - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"") + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"", runemessage = message) /obj/item/device/megaphone/attack_self(var/mob/living/user) var/message = sanitize(input(user, "Shout a message?", "Megaphone", null) as text) @@ -131,7 +132,8 @@ /obj/item/device/megaphone/super/do_broadcast(var/mob/living/user, var/message) if(emagged) if(insults) - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[pick(insultmsg)]\"") + var/insult = pick(insultmsg) + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[insult]\"", runemessage = insult) if(broadcast_size >= 11) var/turf/T = get_turf(user) playsound(src, 'sound/items/AirHorn.ogg', 100, 1) @@ -160,4 +162,4 @@ qdel(src) return else - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"") + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"", runemessage = message) diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index bdb35fb6431..3e16975fbe3 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -258,13 +258,13 @@ var/playedmessage = mytape.storedinfo[i] if (findtextEx(playedmessage,"*",1,2)) //remove marker for action sounds playedmessage = copytext(playedmessage,2) - T.audible_message("Tape Recorder: [playedmessage]") + T.audible_message("Tape Recorder: [playedmessage]", runemessage = playedmessage) if(mytape.storedinfo.len < i+1) playsleepseconds = 1 sleep(10) T = get_turf(src) - T.audible_message("Tape Recorder: End of recording.") + T.audible_message("Tape Recorder: End of recording.", runemessage = "* click *") break else playsleepseconds = mytape.timestamp[i+1] - mytape.timestamp[i] @@ -272,7 +272,7 @@ if(playsleepseconds > 14) sleep(10) T = get_turf(src) - T.audible_message("Tape Recorder: Skipping [playsleepseconds] seconds of silence") + T.audible_message("Tape Recorder: Skipping [playsleepseconds] seconds of silence", runemessage = "* tape winding *") playsleepseconds = 1 sleep(10 * playsleepseconds) @@ -282,7 +282,7 @@ if(emagged) var/turf/T = get_turf(src) - T.audible_message("Tape Recorder: This tape recorder will self-destruct in... Five.") + T.audible_message("Tape Recorder: This tape recorder will self-destruct in... Five.", runemessage = "* beep beep *") sleep(10) T = get_turf(src) T.audible_message("Tape Recorder: Four.") diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm index f3feacd86ea..edbb49b6a8a 100644 --- a/code/game/objects/items/devices/text_to_speech.dm +++ b/code/game/objects/items/devices/text_to_speech.dm @@ -24,5 +24,6 @@ var/message = sanitize(input(user,"Choose a message to relay to those around you.") as text|null) if(message) - var/obj/item/device/text_to_speech/O = src - audible_message("[bicon(O)] \The [O.name] states, \"[message]\"") + audible_message("[bicon(src)] \The [src.name] states, \"[message]\"", runemessage = "* synthesized speech *") + if(ismob(loc)) + loc.audible_message("", runemessage = "\[TTS Voice\] [message]") diff --git a/code/game/objects/items/devices/whistle.dm b/code/game/objects/items/devices/whistle.dm index cf8cebeec15..54ed5404757 100644 --- a/code/game/objects/items/devices/whistle.dm +++ b/code/game/objects/items/devices/whistle.dm @@ -33,12 +33,12 @@ if(isnull(insults)) playsound(src, 'sound/voice/halt.ogg', 100, 1, vary = 0) - user.audible_message("[user]'s [name] rasps, \"[use_message]\"", "\The [user] holds up \the [name].") + user.audible_message("[user]'s [name] rasps, \"[use_message]\"", "\The [user] holds up \the [name].", runemessage = "\[TTS Voice\] [use_message]") else if(insults > 0) playsound(src, 'sound/voice/binsult.ogg', 100, 1, vary = 0) // Yes, it used to show the transcription of the sound clip. That was a) inaccurate b) immature as shit. - user.audible_message("[user]'s [name] gurgles something indecipherable and deeply offensive.", "\The [user] holds up \the [name].") + user.audible_message("[user]'s [name] gurgles something indecipherable and deeply offensive.", "\The [user] holds up \the [name].", runemessage = "\[TTS Voice\] #&@&^%(*") insults-- else to_chat(user, "*BZZZZZZZZT*") diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm index 39dae337848..56a6e543070 100644 --- a/code/modules/integrated_electronics/subtypes/output.dm +++ b/code/modules/integrated_electronics/subtypes/output.dm @@ -134,7 +134,7 @@ text = get_pin_data(IC_INPUT, 1) if(!isnull(text)) var/obj/O = assembly ? loc : assembly - audible_message("[bicon(O)] \The [O.name] states, \"[text]\"") + audible_message("[bicon(O)] \The [O.name] states, \"[text]\"", runemessage = text) /obj/item/integrated_circuit/output/text_to_speech/advanced name = "advanced text-to-speech circuit" diff --git a/code/modules/multiz/ladders.dm b/code/modules/multiz/ladders.dm index cca3019543c..c9797203027 100644 --- a/code/modules/multiz/ladders.dm +++ b/code/modules/multiz/ladders.dm @@ -98,7 +98,7 @@ "You begin climbing [direction] \the [src]!", "You hear the grunting and clanging of a metal ladder being used.") - target_ladder.audible_message("You hear something coming [direction] \the [src]") + target_ladder.audible_message("You hear something coming [direction] \the [src]", runemessage = "* clank clank *") if(do_after(M, climb_time, src)) var/turf/T = get_turf(target_ladder) diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 40447a5a4b9..1c197530e5e 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -59,7 +59,7 @@ if(lattice) var/pull_up_time = max(5 SECONDS + (src.movement_delay() * 10), 1) to_chat(src, "You grab \the [lattice] and start pulling yourself upward...") - destination.audible_message("You hear something climbing up \the [lattice].") + destination.audible_message("You hear something climbing up \the [lattice].", runemessage = "* clank clang *") if(do_after(src, pull_up_time)) to_chat(src, "You pull yourself up.") else @@ -74,7 +74,7 @@ if(!destination?.Enter(src, old_dest)) to_chat(src, "There's something in the way up above in that direction, try another.") return 0 - destination.audible_message("You hear something climbing up \the [catwalk].") + destination.audible_message("You hear something climbing up \the [catwalk].", runemessage = "* clank clang *") if(do_after(src, pull_up_time)) to_chat(src, "You pull yourself up.") else @@ -90,8 +90,8 @@ return 0 var/fly_time = max(7 SECONDS + (H.movement_delay() * 10), 1) //So it's not too useful for combat. Could make this variable somehow, but that's down the road. to_chat(src, "You begin to fly upwards...") - destination.audible_message("You hear the flapping of wings.") - H.audible_message("[H] begins to flap \his wings, preparing to move upwards!") + destination.audible_message("You hear the flapping of wings.", runemessage = "* flap flap *") + H.audible_message("[H] begins to flap \his wings, preparing to move upwards!", runemessage = "* flap flap *") if(do_after(H, fly_time) && H.flying) to_chat(src, "You fly upwards.") else diff --git a/code/modules/overmap/ships/engines/gas_thruster.dm b/code/modules/overmap/ships/engines/gas_thruster.dm index 667cbae441b..182fd6c3f32 100644 --- a/code/modules/overmap/ships/engines/gas_thruster.dm +++ b/code/modules/overmap/ships/engines/gas_thruster.dm @@ -150,7 +150,7 @@ if(!is_on()) return 0 if(!check_fuel() || (use_power_oneoff(charge_per_burn) < charge_per_burn) || check_blockage()) - audible_message(src,"[src] coughs once and goes silent!") + audible_message(src,"[src] coughs once and goes silent!", runemessage = "* sputtercough *") update_use_power(USE_POWER_OFF) return 0 diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index 07009a99c09..2306b3eca1f 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -111,28 +111,28 @@ playsound(src, "sound/machines/copier.ogg", 100, 1) sleep(11) copy(copyitem) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else if (istype(copyitem, /obj/item/weapon/photo)) playsound(src, "sound/machines/copier.ogg", 100, 1) sleep(11) photocopy(copyitem) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else if (istype(copyitem, /obj/item/weapon/paper_bundle)) sleep(11) playsound(src, "sound/machines/copier.ogg", 100, 1) var/obj/item/weapon/paper_bundle/B = bundlecopy(copyitem) sleep(11*B.pages.len) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else if (has_buckled_mobs()) // VOREStation EDIT: For ass-copying. playsound(src, "sound/machines/copier.ogg", 100, 1) - audible_message("You can hear [src] whirring as it attempts to scan.") + audible_message("You can hear [src] whirring as it attempts to scan.", runemessage = "* whirr *") sleep(rand(20,45)) // Sit with your bare ass on the copier for a random time, feel like a fool, get stared at. copyass(user) sleep(15) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else to_chat(user, "\The [copyitem] can't be copied by [src].") diff --git a/code/modules/projectiles/guns/magnetic/bore.dm b/code/modules/projectiles/guns/magnetic/bore.dm index 897a43d8880..a39e383afff 100644 --- a/code/modules/projectiles/guns/magnetic/bore.dm +++ b/code/modules/projectiles/guns/magnetic/bore.dm @@ -208,7 +208,7 @@ /obj/item/weapon/gun/magnetic/matfed/phoronbore/process() if(generator_state && !mat_storage) - audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out.")) + audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out."), runemessage = "* goes quiet *") soundloop.stop() generator_state = GEN_OFF @@ -258,12 +258,12 @@ soundloop.start() time_started = world.time cell?.use(100) - audible_message(SPAN_NOTICE("\The [src] starts chugging."),SPAN_NOTICE("A motor noise starts up.")) + audible_message(SPAN_NOTICE("\The [src] starts chugging."),SPAN_NOTICE("A motor noise starts up."), runemessage = "* whirr *") generator_state = GEN_IDLE else if(generator_state > GEN_OFF && time_started + 3 SECONDS < world.time) soundloop.stop() - audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out.")) + audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out."), runemessage = "* goes quiet *") generator_state = GEN_OFF /obj/item/weapon/gun/magnetic/matfed/phoronbore/loaded diff --git a/code/modules/projectiles/guns/magnetic/magnetic.dm b/code/modules/projectiles/guns/magnetic/magnetic.dm index 1b13bd8f8c4..babeb7643a1 100644 --- a/code/modules/projectiles/guns/magnetic/magnetic.dm +++ b/code/modules/projectiles/guns/magnetic/magnetic.dm @@ -284,7 +284,7 @@ visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") removable_components = FALSE spawn(15) - audible_message("\The [src]'s power supply begins to overload as the device crumples!") //Why are you still holding this? + audible_message("\The [src]'s power supply begins to overload as the device crumples!", runemessage = "* VWRRRRRRRR *") //Why are you still holding this? playsound(src, 'sound/effects/grillehit.ogg', 10, 1) var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() var/turf/T = get_turf(src) diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm index 81f888155d1..ae745dcc1fd 100644 --- a/code/modules/research/message_server.dm +++ b/code/modules/research/message_server.dm @@ -130,12 +130,12 @@ var/global/list/obj/machinery/message_server/message_servers = list() if(2) if(!Console.silent) playsound(Console, 'sound/machines/twobeep.ogg', 50, 1) - Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5) + Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5, runemessage = "* beep! beep! *") Console.message_log += list(list("High Priority message from [sender]", "[authmsg]")) else if(!Console.silent) playsound(Console, 'sound/machines/twobeep.ogg', 50, 1) - Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4) + Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4, runemessage = "* beep beep *") Console.message_log += list(list("Message from [sender]", "[authmsg]")) Console.set_light(2) diff --git a/code/modules/resleeving/machines.dm b/code/modules/resleeving/machines.dm index 1ddab8b9b42..c8b697220de 100644 --- a/code/modules/resleeving/machines.dm +++ b/code/modules/resleeving/machines.dm @@ -148,7 +148,7 @@ else if(((occupant.health == occupant.maxHealth)) && (!eject_wait)) playsound(src, 'sound/machines/ding.ogg', 50, 1) - audible_message("\The [src] signals that the growing process is complete.") + audible_message("\The [src] signals that the growing process is complete.", runemessage = "* ding *") connected_message("Growing Process Complete.") locked = 0 go_out() diff --git a/code/modules/shuttles/shuttles_web.dm b/code/modules/shuttles/shuttles_web.dm index ea11f21d482..7f4cc22abaa 100644 --- a/code/modules/shuttles/shuttles_web.dm +++ b/code/modules/shuttles/shuttles_web.dm @@ -119,7 +119,7 @@ continue if(!H.shuttle_comp || !(get_area(H) in shuttle_area)) H.shuttle_comp = null - H.audible_message("\The [H] pings as it loses it's connection with the ship.") + H.audible_message("\The [H] pings as it loses it's connection with the ship.", runemessage = "* ping *") H.update_hud("discon") helmets -= H else diff --git a/code/modules/turbolift/turbolift.dm b/code/modules/turbolift/turbolift.dm index 25a5f70601f..d8f8373f096 100644 --- a/code/modules/turbolift/turbolift.dm +++ b/code/modules/turbolift/turbolift.dm @@ -28,7 +28,7 @@ priority_mode = TRUE cancel_pending_floors() update_ext_panel_icons() - control_panel_interior.audible_message("This turbolift is responding to a priority call. Please exit the lift when it stops and make way.") + control_panel_interior.audible_message("This turbolift is responding to a priority call. Please exit the lift when it stops and make way.", runemessage = "* BUZZ *") spawn(time) priority_mode = FALSE update_ext_panel_icons() @@ -158,7 +158,7 @@ doors_closing = 0 if(!fire_mode) open_doors() - control_panel_interior.audible_message("\The [current_floor.ext_panel] buzzes loudly.") + control_panel_interior.audible_message("\The [current_floor.ext_panel] buzzes loudly.", runemessage = "* BUZZ *") playsound(control_panel_interior, "sound/machines/buzz-two.ogg", 50, 1) return 0 diff --git a/code/modules/turbolift/turbolift_console.dm b/code/modules/turbolift/turbolift_console.dm index 9868188d189..e3719bc97ec 100644 --- a/code/modules/turbolift/turbolift_console.dm +++ b/code/modules/turbolift/turbolift_console.dm @@ -130,10 +130,10 @@ return lift.update_fire_mode(!lift.fire_mode) if(lift.fire_mode) - audible_message("Firefighter Mode Activated. Door safeties disabled. Manual control engaged.") + audible_message("Firefighter Mode Activated. Door safeties disabled. Manual control engaged.", runemessage = "* SCREECH *") playsound(src, 'sound/machines/airalarm.ogg', 25, 0, 4, volume_channel = VOLUME_CHANNEL_ALARMS) else - audible_message("Firefighter Mode Deactivated. Door safeties enabled. Automatic control engaged.") + audible_message("Firefighter Mode Deactivated. Door safeties enabled. Automatic control engaged.", runemessage = "* ding *") return . = ..() diff --git a/code/modules/turbolift/turbolift_door.dm b/code/modules/turbolift/turbolift_door.dm index 4b83bc60cf9..d21e344097f 100644 --- a/code/modules/turbolift/turbolift_door.dm +++ b/code/modules/turbolift/turbolift_door.dm @@ -40,7 +40,7 @@ if(!moved) // nowhere to go.... LM.gib() else // the mob is too big to just move, so we need to give up what we're doing - audible_message("\The [src]'s motors grind as they quickly reverse direction, unable to safely close.") + audible_message("\The [src]'s motors grind as they quickly reverse direction, unable to safely close.", runemessage = "* WRRRRR *") cur_command = null // the door will just keep trying otherwise return 0 return ..() \ No newline at end of file From 44399bf7f5bcd6e7a0de9a8bc7038efdb51f1629 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sun, 30 May 2021 00:33:10 -0400 Subject: [PATCH 7/8] Suggested changes, small opts --- code/__defines/_planes+layers.dm | 1 + code/_helpers/unsorted.dm | 16 ---- code/datums/chat_message.dm | 109 +++++++++++++++++--------- code/game/atoms.dm | 4 +- code/modules/client/client defines.dm | 2 +- code/modules/emotes/emote_mob.dm | 2 +- code/modules/mob/living/say.dm | 107 +++++++++++++------------ code/modules/mob/mob.dm | 2 +- 8 files changed, 132 insertions(+), 111 deletions(-) diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index 111349d887c..551e94183c1 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -124,6 +124,7 @@ What is the naming convention for planes or layers? #define PLANE_PLANETLIGHTING 4 //Lighting on planets #define PLANE_LIGHTING 5 //Where the lighting (and darkness) lives #define PLANE_LIGHTING_ABOVE 6 //For glowy eyes etc. that shouldn't be affected by darkness +#define PLANE_RUNECHAT 7 #define PLANE_GHOSTS 10 //Spooooooooky ghooooooosts #define PLANE_AI_EYE 11 //The AI eye lives here diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index fe2b51195ab..5701521ec52 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -1691,19 +1691,3 @@ GLOBAL_REAL_VAR(list/stack_trace_storage) return "CLIENT: [D]" else return "Unknown data type: [D]" - -/* - is_holder_of(): Returns 1 if A is a holder of B, meaning, A is B.loc or B.loc.loc or B.loc.loc.loc etc. - This is essentially the same as calling (locate(B) in A), but a little clearer as to what you're doing, and locate() has been known to bug out or be extremely slow in the past. -*/ -/proc/is_holder_of(const/atom/movable/A, const/atom/movable/B) - if(istype(A, /turf) || istype(B, /turf)) //Clicking on turfs is a common thing and turfs are also not /atom/movable, so it was causing the assertion to fail. - return 0 - ASSERT(istype(A) && istype(B)) - var/atom/O = B - while(O && !isturf(O)) - if(O == A) - return 1 - O = O.loc - return 0 - \ No newline at end of file diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm index 0668380059d..5cd8a9babe7 100644 --- a/code/datums/chat_message.dm +++ b/code/datums/chat_message.dm @@ -14,6 +14,9 @@ #define CHAT_MESSAGE_OBJ 2 #define WXH_TO_HEIGHT(x) text2num(copytext((x), findtextEx((x), "x") + 1)) // thanks lummox +#define CHAT_RUNE_EMOTE 0x1 +#define CHAT_RUNE_RADIO 0x2 + /** * # Chat Message Overlay * @@ -22,7 +25,17 @@ */ // Cached runechat icon -var/runechat_icon = null +var/list/runechat_image_cache = list() + + +/hook/startup/proc/runechat_images() + var/image/radio_image = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio") + runechat_image_cache["radio"] = radio_image + + var/image/emote_image = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "emote") + runechat_image_cache["emote"] = emote_image + + return TRUE /datum/chatmessage /// The visual element of the chat messsage @@ -35,6 +48,8 @@ var/runechat_icon = null var/scheduled_destruction /// Contains the approximate amount of lines for height decay var/approx_lines + /// If we are currently processing animation and cleanup at EOL + var/ending_life /** * Constructs a chat message overlay @@ -48,7 +63,7 @@ var/runechat_icon = null */ /datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = null, lifespan = CHAT_MESSAGE_LIFESPAN) . = ..() - if (!istype(target)) + if(!istype(target)) CRASH("Invalid target given for chatmessage") if(!istype(owner) || QDELETED(owner) || !owner.client) stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner") @@ -57,10 +72,12 @@ var/runechat_icon = null generate_image(text, target, owner, extra_classes, lifespan) /datum/chatmessage/Destroy() - if (owned_by) - owned_by.seen_messages.Remove(src) + if(owned_by) + LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) owned_by.images.Remove(message) UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) + if(message_loc) + UnregisterSignal(message_loc, COMSIG_PARENT_QDELETING) owned_by = null message_loc = null message = null @@ -78,20 +95,21 @@ var/runechat_icon = null */ /datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, list/extra_classes, lifespan) set waitfor = FALSE + // Register client who owns this message owned_by = owner.client RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/qdel_self) - var/erp_king = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) - var/maxlen = erp_king ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH - var/msgwidth = erp_king ? CHAT_MESSAGE_EXT_WIDTH : CHAT_MESSAGE_WIDTH + var/extra_length = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) + var/maxlen = extra_length ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH + var/msgwidth = extra_length ? CHAT_MESSAGE_EXT_WIDTH : CHAT_MESSAGE_WIDTH // Clip message - if (length_char(text) > maxlen) + if(length_char(text) > maxlen) text = copytext_char(text, 1, maxlen + 1) + "..." // BYOND index moment // Calculate target color if not already present - if (!target.chat_color || target.chat_color_name != target.name) + if(!target.chat_color || target.chat_color_name != target.name) target.chat_color = colorize_string(target.name) target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85) target.chat_color_name = target.name @@ -102,56 +120,68 @@ var/runechat_icon = null // Reject whitespace var/static/regex/whitespace = new(@"^\s*$") - if (whitespace.Find(text)) + if(whitespace.Find(text)) qdel(src) return // Non mobs speakers can be small - if (!ismob(target)) + if(!ismob(target)) extra_classes |= "small" // If we heard our name, it's important + // Differnt from our own system of name emphasis, maybe unify var/list/names = splittext(owner.name, " ") for (var/word in names) text = replacetext(text, word, "[word]") - // Append radio icon if comes from a radio - if (extra_classes.Find("spoken_into_radio")) - if (!runechat_icon) - var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio") - runechat_icon = "\icon[r_icon] " - text = runechat_icon + text + var/list/prefixes + + // Append prefixes + if(extra_classes.Find("virtual-speaker")) + LAZYADD(prefixes, "\icon[runechat_image_cache["radio"]]") + if(extra_classes.Find("emote")) + // Icon on both ends? + //var/image/I = runechat_image_cache["emote"] + //text = "\icon[I][text]\icon[I]" + + // Icon on one end? + //LAZYADD(prefixes, "\icon[runechat_image_cache["emote"]]") + + // Asterisks instead? + text = "* [text] *" + + text = "[prefixes?.Join(" ")][text]" // We dim italicized text to make it more distinguishable from regular text var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color + // Approximate text height - // Note we have to replace HTML encoded metacharacters otherwise MeasureText will return a zero height - // BYOND Bug #2563917 - // Construct text - var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g") var/complete_text = "[text]" - var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(replacetext(complete_text, html_metachars, "m"), null, msgwidth)) + var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, msgwidth)) approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) // Translate any existing messages upwards, apply exponential decay factors to timers message_loc = target - if (owned_by.seen_messages) + RegisterSignal(message_loc, COMSIG_PARENT_QDELETING, .proc/qdel_self) + if(owned_by.seen_messages) var/idx = 1 var/combined_height = approx_lines - for(var/msg in owned_by.seen_messages) + for(var/msg in owned_by.seen_messages[message_loc]) var/datum/chatmessage/m = msg animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME) combined_height += m.approx_lines - var/sched_remaining = m.scheduled_destruction - world.time - if (sched_remaining > CHAT_MESSAGE_SPAWN_TIME) - var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) - m.scheduled_destruction = world.time + remaining_time - spawn(remaining_time) - m.end_of_life() + + if(!m.ending_life) // Don't bother! + var/sched_remaining = m.scheduled_destruction - world.time + if(sched_remaining > CHAT_MESSAGE_SPAWN_TIME) + var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) + m.scheduled_destruction = world.time + remaining_time + spawn(remaining_time) + m.end_of_life() // Build message image message = image(loc = message_loc, layer = ABOVE_MOB_LAYER) - message.plane = PLANE_LIGHTING_ABOVE + message.plane = PLANE_RUNECHAT message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART message.alpha = 0 message.pixel_y = owner.bound_height * 0.95 @@ -160,11 +190,11 @@ var/runechat_icon = null message.maptext_x = (msgwidth - owner.bound_width) * -0.5 message.maptext = complete_text - if (is_holder_of(owner, target)) // Special case, holding an atom speaking (pAI, recorder...) + if(owner.contains(target)) // Special case, holding an atom speaking (pAI, recorder...) message.plane = PLANE_PLAYER_HUD_ABOVE // View the message - owned_by.seen_messages.Add(src) + LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src) owned_by.images += message animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME) @@ -177,8 +207,9 @@ var/runechat_icon = null * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion */ /datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE) - if (gc_destroyed) + if(gc_destroyed || ending_life) return + ending_life = TRUE animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) spawn(fadetime) qdel(src) @@ -207,7 +238,7 @@ var/runechat_icon = null return // Check for virtual speakers (aka hearing a message through a radio) - if (existing_extra_classes.Find("radio")) + if(existing_extra_classes.Find("radio")) return /* Not currently necessary @@ -219,17 +250,17 @@ var/runechat_icon = null var/list/extra_classes = list() extra_classes += existing_extra_classes - if (italics) + if(italics) extra_classes |= "italics" - if (client.is_preference_enabled(/datum/client_preference/runechat_border)) + if(client.is_preference_enabled(/datum/client_preference/runechat_border)) extra_classes |= "black_outline" var/dist = get_dist(src, speaker) switch (dist) - if (4 to 5) + if(4 to 5) extra_classes |= "small" - if (5 to 16) + if(5 to 16) extra_classes |= "very_small" // Display visual above source diff --git a/code/game/atoms.dm b/code/game/atoms.dm index f4117d37e84..fc27bb9f4e0 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -523,7 +523,7 @@ if(M.see_invisible >= invisibility && MOB_CAN_SEE_PLANE(M, plane)) M.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE) if(runemessage != -1) - M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote"), audible = FALSE) + M.create_chat_message(src, "[runemessage]", FALSE, list("emote"), audible = FALSE) else if(blind_message) M.show_message(blind_message, AUDIBLE_MESSAGE) @@ -554,7 +554,7 @@ var/msg = message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) if(runemessage != -1) - M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote")) + M.create_chat_message(src, "[runemessage || message]", FALSE, list("emote")) /atom/movable/proc/dropInto(var/atom/destination) while(istype(destination)) diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index abd55097a8c..81ec230f9b6 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -78,4 +78,4 @@ var/connection_timeofday // Runechat messages - var/list/seen_messages = list() + var/list/seen_messages diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index ef4129ae464..3baccce8e32 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -199,7 +199,7 @@ if(isobserver(M)) message = "[src] ([ghost_follow_link(src, M)]) [input]" M.show_message(message, m_type) - M.create_chat_message(src, "* [runemessage] *", FALSE, list("emote"), (m_type == AUDIBLE_MESSAGE)) + M.create_chat_message(src, "[runemessage]", FALSE, list("emote"), (m_type == AUDIBLE_MESSAGE)) for(var/obj in o_viewers) var/obj/O = obj diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 07ae8377608..cd5919394f8 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -1,57 +1,62 @@ var/list/department_radio_keys = list( - ":r" = "right ear", ".r" = "right ear", - ":l" = "left ear", ".l" = "left ear", - ":i" = "intercom", ".i" = "intercom", - ":h" = "department", ".h" = "department", - ":+" = "special", ".+" = "special", //activate radio-specific special functions - ":c" = "Command", ".c" = "Command", - ":n" = "Science", ".n" = "Science", - ":m" = "Medical", ".m" = "Medical", - ":e" = "Engineering", ".e" = "Engineering", - ":k" = "Response Team", ".k" = "Response Team", - ":s" = "Security", ".s" = "Security", - ":w" = "whisper", ".w" = "whisper", - ":t" = "Mercenary", ".t" = "Mercenary", - ":x" = "Raider", ".x" = "Raider", - ":u" = "Supply", ".u" = "Supply", - ":v" = "Service", ".v" = "Service", - ":p" = "AI Private", ".p" = "AI Private", - ":y" = "Explorer", ".y" = "Explorer", - ":a" = "Talon", ".a" = "Talon", //VOREStation Add, + ":r" = "right ear", ".r" = "right ear", + ":l" = "left ear", ".l" = "left ear", + ":i" = "intercom", ".i" = "intercom", + ":h" = "department", ".h" = "department", + ":+" = "special", ".+" = "special", //activate radio-specific special functions + ":c" = "Command", ".c" = "Command", + ":n" = "Science", ".n" = "Science", + ":m" = "Medical", ".m" = "Medical", + ":e" = "Engineering", ".e" = "Engineering", + ":k" = "Response Team", ".k" = "Response Team", + ":s" = "Security", ".s" = "Security", + ":w" = "whisper", ".w" = "whisper", + ":t" = "Mercenary", ".t" = "Mercenary", + ":x" = "Raider", ".x" = "Raider", + ":u" = "Supply", ".u" = "Supply", + ":v" = "Service", ".v" = "Service", + ":p" = "AI Private", ".p" = "AI Private", + ":y" = "Explorer", ".y" = "Explorer", + ":a" = "Talon", ".a" = "Talon", //VOREStation Add, - ":R" = "right ear", ".R" = "right ear", - ":L" = "left ear", ".L" = "left ear", - ":I" = "intercom", ".I" = "intercom", - ":H" = "department", ".H" = "department", - ":C" = "Command", ".C" = "Command", - ":N" = "Science", ".N" = "Science", - ":M" = "Medical", ".M" = "Medical", - ":E" = "Engineering", ".E" = "Engineering", - ":k" = "Response Team", ".k" = "Response Team", - ":S" = "Security", ".S" = "Security", - ":W" = "whisper", ".W" = "whisper", - ":T" = "Mercenary", ".T" = "Mercenary", - ":X" = "Raider", ".X" = "Raider", - ":U" = "Supply", ".U" = "Supply", - ":V" = "Service", ".V" = "Service", - ":P" = "AI Private", ".P" = "AI Private", - ":Y" = "Explorer", ".Y" = "Explorer", - ":A" = "Talon", ".A" = "Talon", //VOREStation Add, + ":R" = "right ear", ".R" = "right ear", + ":L" = "left ear", ".L" = "left ear", + ":I" = "intercom", ".I" = "intercom", + ":H" = "department", ".H" = "department", + ":C" = "Command", ".C" = "Command", + ":N" = "Science", ".N" = "Science", + ":M" = "Medical", ".M" = "Medical", + ":E" = "Engineering", ".E" = "Engineering", + ":k" = "Response Team", ".k" = "Response Team", + ":S" = "Security", ".S" = "Security", + ":W" = "whisper", ".W" = "whisper", + ":T" = "Mercenary", ".T" = "Mercenary", + ":X" = "Raider", ".X" = "Raider", + ":U" = "Supply", ".U" = "Supply", + ":V" = "Service", ".V" = "Service", + ":P" = "AI Private", ".P" = "AI Private", + ":Y" = "Explorer", ".Y" = "Explorer", + ":A" = "Talon", ".A" = "Talon", //VOREStation Add, - //kinda localization -- rastaf0 - //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. - ":�" = "right ear", ".�" = "right ear", - ":�" = "left ear", ".�" = "left ear", - ":�" = "intercom", ".�" = "intercom", - ":�" = "department", ".�" = "department", - ":�" = "Command", ".�" = "Command", - ":�" = "Science", ".�" = "Science", - ":�" = "Medical", ".�" = "Medical", - ":�" = "Engineering", ".�" = "Engineering", - ":�" = "Security", ".�" = "Security", - ":�" = "whisper", ".�" = "whisper", - ":�" = "Mercenary", ".�" = "Mercenary", - ":�" = "Supply", ".�" = "Supply", + // Cyrillic characters on the same keys on the Russian QWERTY (phonetic) layout + ":к" = "right ear", ".к" = "right ear", + ":д" = "left ear", ".д" = "left ear", + ":ш" = "intercom", ".ш" = "intercom", + ":р" = "department", ".р" = "department", + ":+" = "special", ".+" = "special", //activate radio-specific special functions + ":с" = "Command", ".с" = "Command", + ":т" = "Science", ".т" = "Science", + ":ь" = "Medical", ".ь" = "Medical", + ":у" = "Engineering", ".у" = "Engineering", + ":л" = "Response Team", ".л" = "Response Team", + ":ы" = "Security", ".ы" = "Security", + ":ц" = "whisper", ".ц" = "whisper", + ":е" = "Mercenary", ".е" = "Mercenary", + ":ч" = "Raider", ".ч" = "Raider", + ":г" = "Supply", ".г" = "Supply", + ":м" = "Service", ".м" = "Service", + ":з" = "AI Private", ".з" = "AI Private", + ":н" = "Explorer", ".н" = "Explorer" ) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 6626b689726..547bb19304e 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -131,7 +131,7 @@ msg = self_message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) if(runemessage != -1) - M.create_chat_message(src, "* [runemessage || message] *", FALSE, list("emote"), audible = FALSE) + M.create_chat_message(src, "[runemessage || message]", FALSE, list("emote"), audible = FALSE) /mob/proc/findname(msg) for(var/mob/M in mob_list) From 114cb47ca838d3995395b77844e88a74ba316ba7 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sun, 30 May 2021 00:34:26 -0400 Subject: [PATCH 8/8] VS: Talon RU --- code/modules/mob/living/say.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index cd5919394f8..5686993d7af 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -56,7 +56,8 @@ var/list/department_radio_keys = list( ":г" = "Supply", ".г" = "Supply", ":м" = "Service", ".м" = "Service", ":з" = "AI Private", ".з" = "AI Private", - ":н" = "Explorer", ".н" = "Explorer" + ":н" = "Explorer", ".н" = "Explorer", + ":ф" = "Talon", ".ф" = "Talon" //VOREStation Add )