diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 3ce91f4332..07ffbc568d 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -344,6 +344,28 @@
///Define for spawning megafauna instead of a mob for cave gen
#define SPAWN_MEGAFAUNA "bluh bluh huge boss"
+/*
+ * Defines for "AI emotions", allowing the AI to expression emotions
+ * with status displays via emotes.
+ */
+
+#define AI_EMOTION_VERY_HAPPY "Very Happy"
+#define AI_EMOTION_HAPPY "Happy"
+#define AI_EMOTION_NEUTRAL "Neutral"
+#define AI_EMOTION_UNSURE "Unsure"
+#define AI_EMOTION_CONFUSED "Confused"
+#define AI_EMOTION_SAD "Sad"
+#define AI_EMOTION_BSOD "BSOD"
+#define AI_EMOTION_BLANK "Blank"
+#define AI_EMOTION_PROBLEMS "Problems?"
+#define AI_EMOTION_AWESOME "Awesome"
+#define AI_EMOTION_FACEPALM "Facepalm"
+#define AI_EMOTION_THINKING "Thinking"
+#define AI_EMOTION_FRIEND_COMPUTER "Friend Computer"
+#define AI_EMOTION_DORFY "Dorfy"
+#define AI_EMOTION_BLUE_GLOW "Blue Glow"
+#define AI_EMOTION_RED_GLOW "Red Glow"
+
// / Breathing types. Lungs can access either by these or by a string, which will be considered a gas ID.
#define BREATH_OXY /datum/breathing_class/oxygen
#define BREATH_PLASMA /datum/breathing_class/plasma
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 2f394fcb4d..7c22846ece 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -89,6 +89,7 @@
/obj/machinery/computer/communications/ui_act(action, list/params)
var/static/list/approved_states = list(STATE_BUYING_SHUTTLE, STATE_CHANGING_STATUS, STATE_MAIN, STATE_MESSAGES)
var/static/list/approved_status_pictures = list("biohazard", "blank", "default", "lockdown", "redalert", "shuttle")
+ var/static/list/state_status_pictures = list("blank", "shuttle")
. = ..()
if (.)
@@ -307,7 +308,10 @@
var/picture = params["picture"]
if (!(picture in approved_status_pictures))
return
- post_status("alert", picture)
+ if(picture in state_status_pictures)
+ post_status(picture)
+ else
+ post_status("alert", picture)
playsound(src, "terminal_type", 50, FALSE)
if ("toggleAuthentication")
// Log out if we're logged in
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index 8627a79772..2438a6b66b 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -4,102 +4,182 @@
#define CHARS_PER_LINE 5
#define FONT_SIZE "5pt"
#define FONT_COLOR "#09f"
-#define FONT_STYLE "Arial Black"
-#define SCROLL_SPEED 2
+#define FONT_STYLE "Small Fonts"
+#define SCROLL_RATE (0.04 SECONDS) // time per pixel
+#define LINE1_Y -8
+#define LINE2_Y -15
#define SD_BLANK 0 // 0 = Blank
#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer
#define SD_MESSAGE 2 // 2 = Arbitrary message(s)
#define SD_PICTURE 3 // 3 = alert picture
-#define SD_AI_EMOTE 1 // 1 = AI emoticon
-#define SD_AI_BSOD 2 // 2 = Blue screen of death
-
-/// Status display which can show images and scrolling text. !!!USE /obj/machinery/status_display/evac NOT THIS!!!
+/// Status display which can show images and scrolling text.
/obj/machinery/status_display
name = "status display"
desc = null
icon = 'icons/obj/status_display.dmi'
icon_state = "frame"
- plane = ABOVE_WALL_PLANE
+ base_icon_state = "unanchoredstatusdisplay"
+ verb_say = "beeps"
+ verb_ask = "beeps"
+ verb_exclaim = "beeps"
density = FALSE
- use_power = IDLE_POWER_USE
- idle_power_usage = 10
+ layer = ABOVE_WINDOW_LAYER
- maptext_height = 26
- maptext_width = 32
+ var/obj/effect/overlay/status_display_text/message1_overlay
+ var/obj/effect/overlay/status_display_text/message2_overlay
+ var/current_picture = ""
+ var/current_mode = SD_BLANK
+ var/message1 = ""
+ var/message2 = ""
- var/message1 = "" // message line 1
- var/message2 = "" // message line 2
- var/index1 // display index for scrolling messages or 0 if non-scrolling
- var/index2
+/obj/item/wallframe/status_display
+ name = "status display frame"
+ desc = "Used to build status displays, just secure to the wall."
+ icon_state = "unanchoredstatusdisplay"
+ custom_materials = list(/datum/material/iron=14000, /datum/material/glass=8000)
+ result_path = /obj/machinery/status_display/evac
+ pixel_shift = -32
-/// Immediately blank the display.
-/obj/machinery/status_display/proc/remove_display()
- cut_overlays()
- if(maptext)
- maptext = ""
+/obj/machinery/status_display/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ balloon_alert(user, "[anchored ? "un" : ""]securing...")
+ tool.play_tool_sound(src)
+ if(tool.use_tool(src, user, 6 SECONDS))
+ playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
+ balloon_alert(user, "[anchored ? "un" : ""]secured")
+ deconstruct()
+ return TRUE
+
+/obj/machinery/status_display/welder_act(mob/living/user, obj/item/tool)
+ if(user.a_intent == INTENT_HARM)
+ return
+ if(obj_integrity >= max_integrity)
+ balloon_alert(user, "it doesn't need repairs!")
+ return TRUE
+ user.balloon_alert_to_viewers("repairing display...", "repairing...")
+ if(!tool.use_tool(src, user, 4 SECONDS, amount = 0, volume=50))
+ return TRUE
+ balloon_alert(user, "repaired")
+ obj_integrity = max_integrity
+ set_machine_stat(stat & ~BROKEN)
+ update_appearance()
+ return TRUE
+
+/obj/machinery/status_display/deconstruct(disassembled = TRUE)
+ if(flags_1 & NODECONSTRUCT_1)
+ return
+ if(!disassembled)
+ new /obj/item/stack/sheet/metal(drop_location(), 2)
+ new /obj/item/shard(drop_location())
+ new /obj/item/shard(drop_location())
+ else
+ new /obj/item/wallframe/status_display(drop_location())
+ qdel(src)
/// Immediately change the display to the given picture.
/obj/machinery/status_display/proc/set_picture(state)
- remove_display()
- add_overlay(state)
+ if(state != current_picture)
+ current_picture = state
+
+ update_appearance()
/// Immediately change the display to the given two lines.
-/obj/machinery/status_display/proc/update_display(line1, line2)
- var/new_text = {"
[line1]
[line2]
"}
- if(maptext != new_text)
- maptext = new_text
+/obj/machinery/status_display/proc/set_messages(line1, line2)
+ line1 = uppertext(line1)
+ line2 = uppertext(line2)
-/// Prepare the display to marquee the given two lines.
-///
-/// Call with no arguments to disable.
-/obj/machinery/status_display/proc/set_message(m1, m2)
- if(m1)
- index1 = (length_char(m1) > CHARS_PER_LINE)
- message1 = m1
- else
- message1 = ""
- index1 = 0
+ if(line1 != message1)
+ message1 = line1
- if(m2)
- index2 = (length_char(m2) > CHARS_PER_LINE)
- message2 = m2
- else
- message2 = ""
- index2 = 0
+ if(line2 != message2)
+ message2 = line2
-// Timed process - performs default marquee action if so needed.
+ update_appearance()
+
+/**
+ * Remove both message objs and null the fields.
+ * Don't call this in subclasses.
+ */
+/obj/machinery/status_display/proc/remove_messages()
+ if(message1_overlay)
+ QDEL_NULL(message1_overlay)
+ if(message2_overlay)
+ QDEL_NULL(message2_overlay)
+
+/**
+ * Create/update message overlay.
+ * They must be handled as real objects for the animation to run.
+ * Don't call this in subclasses.
+ * Arguments:
+ * * overlay - the current /obj/effect/overlay/status_display_text instance
+ * * line_y - The Y offset to render the text.
+ * * message - the new message text.
+ * Returns new /obj/effect/overlay/status_display_text or null if unchanged.
+ */
+/obj/machinery/status_display/proc/update_message(obj/effect/overlay/status_display_text/overlay, line_y, message)
+ if(overlay && message == overlay.message)
+ return null
+
+ if(overlay)
+ qdel(overlay)
+
+ var/obj/effect/overlay/status_display_text/new_status_display_text = new(src, line_y, message)
+ vis_contents += new_status_display_text
+ return new_status_display_text
+
+/obj/machinery/status_display/update_appearance(updates=ALL)
+ . = ..()
+ if( \
+ (stat & (NOPOWER|BROKEN)) || \
+ (current_mode == SD_BLANK) || \
+ (current_mode != SD_PICTURE && message1 == "" && message2 == "") \
+ )
+ set_light(0)
+ return
+ set_light(1.4, 0.7, LIGHT_COLOR_BLUE) // blue light
+
+/obj/machinery/status_display/update_overlays()
+ . = ..()
+
+ if(stat & (NOPOWER|BROKEN))
+ remove_messages()
+ return
+
+ switch(current_mode)
+ if(SD_BLANK)
+ remove_messages()
+ // Turn off backlight.
+ return
+ if(SD_PICTURE)
+ remove_messages()
+ . += mutable_appearance(icon, current_picture)
+ else
+ var/overlay = update_message(message1_overlay, LINE1_Y, message1)
+ if(overlay)
+ message1_overlay = overlay
+ overlay = update_message(message2_overlay, LINE2_Y, message2)
+ if(overlay)
+ message2_overlay = overlay
+
+ // Turn off backlight if message is blank
+ if(message1 == "" && message2 == "")
+ return
+
+ . += emissive_appearance(icon, "outline", alpha = src.alpha)
+
+// Timed process - performs nothing in the base class
/obj/machinery/status_display/process()
if(stat & NOPOWER)
// No power, no processing.
- remove_display()
- return PROCESS_KILL
+ update_appearance()
- var/line1 = message1
- if(index1)
- line1 = copytext_char("[message1]|[message1]", index1, index1+CHARS_PER_LINE)
- var/message1_len = length_char(message1)
- index1 += SCROLL_SPEED
- if(index1 > message1_len + 1)
- index1 -= (message1_len + 1)
-
- var/line2 = message2
- if(index2)
- line2 = copytext_char("[message2]|[message2]", index2, index2+CHARS_PER_LINE)
- var/message2_len = length(message2)
- index2 += SCROLL_SPEED
- if(index2 > message2_len + 1)
- index2 -= (message2_len + 1)
-
- update_display(line1, line2)
- if (!index1 && !index2)
- // No marquee, no processing.
- return PROCESS_KILL
+ return PROCESS_KILL
/// Update the display and, if necessary, re-enable processing.
/obj/machinery/status_display/proc/update()
- if (process() != PROCESS_KILL)
+ if (process(SSMACHINES_DT) != PROCESS_KILL)
START_PROCESSING(SSmachines, src)
/obj/machinery/status_display/power_change()
@@ -110,22 +190,23 @@
. = ..()
if(stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF)
return
+ current_mode = SD_PICTURE
set_picture("ai_bsod")
/obj/machinery/status_display/examine(mob/user)
. = ..()
- if (message1 || message2)
+ if (current_mode == SD_MESSAGE && (message1_overlay?.message || message2_overlay?.message))
. += "The display says:"
- if (message1)
- . += "\t[html_encode(message1)]"
- if (message2)
- . += "\t[html_encode(message2)]"
+ if (message1_overlay.message)
+ . += "\t[html_encode(message1_overlay.message)]"
+ if (message2_overlay.message)
+ . += "\t[html_encode(message2_overlay.message)]"
// Helper procs for child display types.
/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle)
if(!shuttle)
// the shuttle is missing - no processing
- update_display("shutl?","")
+ set_messages("shutl?","")
return PROCESS_KILL
else if(shuttle.timer)
var/line1 = "-[shuttle.getModeStr()]-"
@@ -133,10 +214,10 @@
if(length_char(line2) > CHARS_PER_LINE)
line2 = "error"
- update_display(line1, line2)
+ set_messages(line1, line2)
else
// don't kill processing, the timer might turn back on
- remove_display()
+ set_messages("", "")
/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle)
if (shuttle)
@@ -150,18 +231,75 @@
else
return "The display says:
\tShuttle missing!"
+/obj/machinery/status_display/Destroy()
+ remove_messages()
+ return ..()
+
+/**
+ * Nice overlay to make text smoothly scroll with no client updates after setup.
+ */
+/obj/effect/overlay/status_display_text
+ icon = 'icons/obj/status_display.dmi'
+ vis_flags = VIS_INHERIT_LAYER | VIS_INHERIT_PLANE | VIS_INHERIT_ID
+
+ var/message
+
+/obj/effect/overlay/status_display_text/Initialize(mapload, yoffset, line)
+ . = ..()
+
+ maptext_y = yoffset
+ message = line
+
+ var/line_length = length_char(line)
+
+ if(line_length > CHARS_PER_LINE)
+ // Marquee text
+ var/marquee_message = "[line] • [line] • [line]"
+ var/marquee_length = line_length * 3 + 6
+ maptext = generate_text(marquee_message, center = FALSE)
+ maptext_width = 6 * marquee_length
+ maptext_x = 32
+
+ // Mask off to fit in screen.
+ add_filter("mask", 1, alpha_mask_filter(icon = icon(icon, "outline")))
+
+ // Scroll.
+ var/width = 4 * marquee_length
+ var/time = (width + 32) * SCROLL_RATE
+ animate(src, maptext_x = -width, time = time, loop = -1)
+ animate(maptext_x = 32, time = 0)
+ else
+ // Centered text
+ maptext = generate_text(line, center = TRUE)
+ maptext_x = 0
+
+/obj/effect/overlay/status_display_text/proc/generate_text(text, center)
+ return {"[text]
"}
/// Evac display which shows shuttle timer or message set by Command.
/obj/machinery/status_display/evac
+ current_mode = SD_EMERGENCY
var/frequency = FREQ_STATUS_DISPLAYS
- var/mode = SD_EMERGENCY
var/friendc = FALSE // track if Friend Computer mode
var/last_picture // For when Friend Computer mode is undone
+// MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
+
+//makes it go on the wall when built
+/obj/machinery/status_display/Initialize(mapload, ndir, building)
+ . = ..()
+ update_appearance()
+
/obj/machinery/status_display/evac/Initialize(mapload)
. = ..()
// register for radio system
SSradio.add_object(src, frequency)
+ // Circuit USB
+ /*
+ AddComponent(/datum/component/usb_port, list(
+ /obj/item/circuit_component/status_display,
+ ))
+ */
/obj/machinery/status_display/evac/Destroy()
SSradio.remove_object(src,frequency)
@@ -170,23 +308,23 @@
/obj/machinery/status_display/evac/process()
if(stat & NOPOWER)
// No power, no processing.
- remove_display()
+ update_appearance()
return PROCESS_KILL
if(friendc) //Makes all status displays except supply shuttle timer display the eye -- Urist
+ current_mode = SD_PICTURE
set_picture("ai_friend")
return PROCESS_KILL
- switch(mode)
+ switch(current_mode)
if(SD_BLANK)
- remove_display()
return PROCESS_KILL
if(SD_EMERGENCY)
return display_shuttle_status(SSshuttle.emergency)
if(SD_MESSAGE)
- return ..()
+ return PROCESS_KILL
if(SD_PICTURE)
set_picture(last_picture)
@@ -194,7 +332,7 @@
/obj/machinery/status_display/evac/examine(mob/user)
. = ..()
- if(mode == SD_EMERGENCY)
+ if(current_mode == SD_EMERGENCY)
. += examine_shuttle(user, SSshuttle.emergency)
else if(!message1 && !message2)
. += "The display is blank."
@@ -202,16 +340,16 @@
/obj/machinery/status_display/evac/receive_signal(datum/signal/signal)
switch(signal.data["command"])
if("blank")
- mode = SD_BLANK
- set_message(null, null)
+ current_mode = SD_BLANK
+ update_appearance()
if("shuttle")
- mode = SD_EMERGENCY
- set_message(null, null)
+ current_mode = SD_EMERGENCY
+ set_messages("", "")
if("message")
- mode = SD_MESSAGE
- set_message(signal.data["msg1"], signal.data["msg2"])
+ current_mode = SD_MESSAGE
+ set_messages(signal.data["msg1"] || "", signal.data["msg2"] || "")
if("alert")
- mode = SD_PICTURE
+ current_mode = SD_PICTURE
last_picture = signal.data["picture_state"]
set_picture(last_picture)
if("friendcomputer")
@@ -222,11 +360,12 @@
/// Supply display which shows the status of the supply shuttle.
/obj/machinery/status_display/supply
name = "supply display"
+ current_mode = SD_MESSAGE
/obj/machinery/status_display/supply/process()
if(stat & NOPOWER)
// No power, no processing.
- remove_display()
+ update_appearance()
return PROCESS_KILL
var/line1
@@ -240,12 +379,15 @@
if(is_station_level(SSshuttle.supply.z))
line1 = "CARGO"
line2 = "Docked"
+ else
+ line1 = ""
+ line2 = ""
else
line1 = "CARGO"
line2 = SSshuttle.supply.getTimerStr()
if(length_char(line2) > CHARS_PER_LINE)
line2 = "Error"
- update_display(line1, line2)
+ set_messages(line1, line2)
/obj/machinery/status_display/supply/examine(mob/user)
. = ..()
@@ -265,12 +407,13 @@
/// General-purpose shuttle status display.
/obj/machinery/status_display/shuttle
name = "shuttle display"
+ current_mode = SD_MESSAGE
var/shuttle_id
/obj/machinery/status_display/shuttle/process()
if(!shuttle_id || (stat & NOPOWER))
// No power, no processing.
- remove_display()
+ update_appearance()
return PROCESS_KILL
return display_shuttle_status(SSshuttle.getShuttle(shuttle_id))
@@ -291,7 +434,7 @@
update()
/obj/machinery/status_display/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override)
- if (port && (shuttle_id == initial(shuttle_id) || override))
+ if(port && (shuttle_id == initial(shuttle_id) || override))
shuttle_id = port.id
update()
@@ -300,9 +443,32 @@
/obj/machinery/status_display/ai
name = "\improper AI display"
desc = "A small screen which the AI can use to present itself."
+ current_mode = SD_PICTURE
- var/mode = SD_BLANK
- var/emotion = "Neutral"
+ var/emotion = AI_EMOTION_BLANK
+
+ /// A mapping between AI_EMOTION_* string constants, which also double as user readable descriptions, and the name of the iconfile.
+ var/static/list/emotion_map = list(
+ AI_EMOTION_BLANK = "ai_off",
+ AI_EMOTION_VERY_HAPPY = "ai_veryhappy",
+ AI_EMOTION_HAPPY = "ai_happy",
+ AI_EMOTION_NEUTRAL = "ai_neutral",
+ AI_EMOTION_UNSURE = "ai_unsure",
+ AI_EMOTION_CONFUSED = "ai_confused",
+ AI_EMOTION_SAD = "ai_sad",
+ AI_EMOTION_BSOD = "ai_bsod",
+ AI_EMOTION_PROBLEMS = "ai_trollface",
+ AI_EMOTION_AWESOME = "ai_awesome",
+ AI_EMOTION_DORFY = "ai_urist",
+ AI_EMOTION_THINKING = "ai_thinking",
+ AI_EMOTION_FACEPALM = "ai_facepalm",
+ AI_EMOTION_FRIEND_COMPUTER = "ai_friend",
+ AI_EMOTION_BLUE_GLOW = "ai_sal",
+ AI_EMOTION_RED_GLOW = "ai_hal",
+ )
+
+
+// MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/ai, 32)
/obj/machinery/status_display/ai/Initialize(mapload)
. = ..()
@@ -313,57 +479,109 @@
. = ..()
/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user)
- if(isAI(user))
- user.ai_statuschange()
+ if(!isAI(user))
+ return
+ var/list/choices = list()
+ for(var/emotion_const in emotion_map)
+ var/icon_state = emotion_map[emotion_const]
+ choices[emotion_const] = image(icon = 'icons/obj/status_display.dmi', icon_state = icon_state)
+
+ var/emotion_result = show_radial_menu(user, src, choices, tooltips = TRUE)
+ for(var/_emote in typesof(/datum/emote/ai/emotion_display))
+ var/datum/emote/ai/emotion_display/emote = _emote
+ if(initial(emote.emotion) == emotion_result)
+ user.emote(initial(emote.key))
+ break
/obj/machinery/status_display/ai/process()
- if(mode == SD_BLANK || (stat & NOPOWER))
- remove_display()
+ if(stat & NOPOWER)
+ update_appearance()
return PROCESS_KILL
- if(mode == SD_AI_EMOTE)
- switch(emotion)
- if("Very Happy")
- set_picture("ai_veryhappy")
- if("Happy")
- set_picture("ai_happy")
- if("Neutral")
- set_picture("ai_neutral")
- if("Unsure")
- set_picture("ai_unsure")
- if("Confused")
- set_picture("ai_confused")
- if("Sad")
- set_picture("ai_sad")
- if("BSOD")
- set_picture("ai_bsod")
- if("Blank")
- set_picture("ai_off")
- if("Problems?")
- set_picture("ai_trollface")
- if("Awesome")
- set_picture("ai_awesome")
- if("Dorfy")
- set_picture("ai_urist")
- if("Thinking")
- set_picture("ai_thinking")
- if("Facepalm")
- set_picture("ai_facepalm")
- if("Friend Computer")
- set_picture("ai_friend")
- if("Blue Glow")
- set_picture("ai_sal")
- if("Red Glow")
- set_picture("ai_hal")
- return PROCESS_KILL
+ set_picture(emotion_map[emotion])
+ return PROCESS_KILL
- if(mode == SD_AI_BSOD)
- set_picture("ai_bsod")
- return PROCESS_KILL
+/*
+/obj/item/circuit_component/status_display
+ display_name = "Status Display"
+ desc = "Output text and pictures to a status display."
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+ var/datum/port/input/option/command
+ var/datum/port/input/option/picture
+ var/datum/port/input/message1
+ var/datum/port/input/message2
+
+ var/obj/machinery/status_display/connected_display
+
+ var/list/command_map
+ var/list/picture_map
+
+/obj/item/circuit_component/status_display/populate_ports()
+ message1 = add_input_port("Message 1", PORT_TYPE_STRING)
+ message2 = add_input_port("Message 2", PORT_TYPE_STRING)
+
+/obj/item/circuit_component/status_display/populate_options()
+ var/static/list/command_options = list(
+ "Blank" = "blank",
+ "Shuttle" = "shuttle",
+ "Message" = "message",
+ "Alert" = "alert"
+ )
+
+ var/static/list/picture_options = list(
+ "Default" = "default",
+ "Red Alert" = "redalert",
+ "Biohazard" = "biohazard",
+ "Lockdown" = "lockdown",
+ "Happy" = "ai_happy",
+ "Neutral" = "ai_neutral",
+ "Very Happy" = "ai_veryhappy",
+ "Sad" = "ai_sad",
+ "Unsure" = "ai_unsure",
+ "Confused" = "ai_confused",
+ "Surprised" = "ai_surprised",
+ "BSOD" = "ai_bsod"
+ )
+
+ command = add_option_port("Command", command_options)
+ command_map = command_options
+
+ picture = add_option_port("Picture", picture_options)
+ picture_map = picture_options
+
+/obj/item/circuit_component/status_display/register_usb_parent(atom/movable/shell)
+ . = ..()
+ if(istype(shell, /obj/machinery/status_display))
+ connected_display = shell
+
+/obj/item/circuit_component/status_display/unregister_usb_parent(atom/movable/parent)
+ connected_display = null
+ return ..()
+
+/obj/item/circuit_component/status_display/input_received(datum/port/input/port)
+ // Just use command handling built into status display.
+ // The option inputs thankfully sanitize command and picture for us.
+
+ if(!connected_display)
+ return
+
+ var/command_value = command_map[command.value]
+ var/datum/signal/status_signal = new(list("command" = command_value))
+ switch(command_value)
+ if("message")
+ status_signal.data["msg1"] = message1.value
+ status_signal.data["msg2"] = message2.value
+ if("alert")
+ status_signal.data["picture_state"] = picture_map[picture.value]
+
+ connected_display.receive_signal(status_signal)
+*/
#undef CHARS_PER_LINE
#undef FONT_SIZE
#undef FONT_COLOR
#undef FONT_STYLE
-#undef SCROLL_SPEED
+#undef SCROLL_RATE
+#undef LINE1_Y
+#undef LINE2_Y
diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm
index afabafd929..14f9490481 100644
--- a/code/modules/mob/living/silicon/ai/death.dm
+++ b/code/modules/mob/living/silicon/ai/death.dm
@@ -2,6 +2,10 @@
if(stat == DEAD)
return
+ if(!gibbed)
+ // Will update all AI status displays with a blue screen of death
+ INVOKE_ASYNC(src, .proc/emote, "bsod")
+
. = ..()
var/old_icon = icon_state
@@ -29,12 +33,6 @@
spawn(10)
explosion(src.loc, 3, 6, 12, 15)
- if(src.key)
- for(var/each in GLOB.ai_status_displays) //change status
- var/obj/machinery/status_display/ai/O = each
- O.mode = 2
- O.update()
-
if(istype(loc, /obj/item/aicard/aitater))
loc.icon_state = "aitater-404"
else if(istype(loc, /obj/item/aicard/aispook))
diff --git a/code/modules/mob/living/silicon/ai/emote.dm b/code/modules/mob/living/silicon/ai/emote.dm
new file mode 100644
index 0000000000..c48fefbe1f
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/emote.dm
@@ -0,0 +1,101 @@
+/datum/emote/ai
+ mob_type_allowed_typecache = /mob/living/silicon/ai
+ mob_type_blacklist_typecache = list()
+
+
+/datum/emote/ai/emotion_display
+ key = "blank"
+ var/emotion = AI_EMOTION_BLANK
+
+/datum/emote/ai/emotion_display/run_emote(mob/user, params, type_override, intentional)
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/silicon/ai/ai = user
+ var/turf/ai_turf = get_turf(ai)
+
+ for(var/_display in GLOB.ai_status_displays)
+ var/obj/machinery/status_display/ai/ai_display = _display
+ var/turf/display_turf = get_turf(ai_display)
+
+ // Derelict AIs can't affect station displays.
+ // TODO does this need to be made multiZ aware?
+ if(ai_turf.z != display_turf.z)
+ continue
+
+ ai_display.emotion = emotion
+ ai_display.update()
+
+/datum/emote/ai/emotion_display/very_happy
+ key = "veryhappy"
+ emotion = AI_EMOTION_VERY_HAPPY
+
+/datum/emote/ai/emotion_display/happy
+ key = "happy"
+ emotion = AI_EMOTION_HAPPY
+
+/datum/emote/ai/emotion_display/neutral
+ key = "neutral"
+ emotion = AI_EMOTION_NEUTRAL
+
+/datum/emote/ai/emotion_display/unsure
+ key = "unsure"
+ emotion = AI_EMOTION_UNSURE
+
+/datum/emote/ai/emotion_display/confused
+ key = "confused"
+ emotion = AI_EMOTION_CONFUSED
+
+/datum/emote/ai/emotion_display/sad
+ key = "sad"
+ emotion = AI_EMOTION_SAD
+
+/datum/emote/ai/emotion_display/bsod
+ key = "bsod"
+ emotion = AI_EMOTION_BSOD
+
+/datum/emote/ai/emotion_display/trollface
+ key = "trollface"
+ emotion = AI_EMOTION_PROBLEMS
+
+/datum/emote/ai/emotion_display/awesome
+ key = "awesome"
+ emotion = AI_EMOTION_AWESOME
+
+/datum/emote/ai/emotion_display/dorfy
+ key = "dorfy"
+ emotion = AI_EMOTION_DORFY
+
+/datum/emote/ai/emotion_display/thinking
+ key = "thinking"
+ emotion = AI_EMOTION_THINKING
+
+/datum/emote/ai/emotion_display/facepalm
+ key = "facepalm"
+ key_third_person = "facepalms"
+ emotion = AI_EMOTION_FACEPALM
+
+/datum/emote/ai/emotion_display/friend_computer
+ key = "friendcomputer"
+ emotion = AI_EMOTION_FRIEND_COMPUTER
+
+/datum/emote/ai/emotion_display/friend_computer/run_emote(mob/user, params, type_override, intentional)
+ . = ..()
+ if(!.)
+ return
+
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
+
+ if(!frequency)
+ return
+
+ var/datum/signal/status_signal = new(list("command" = "friendcomputer"))
+ frequency.post_signal(src, status_signal)
+
+/datum/emote/ai/emotion_display/blue_glow
+ key = "blueglow"
+ emotion = AI_EMOTION_BLUE_GLOW
+
+/datum/emote/ai/emotion_display/red_glow
+ key = "redglow"
+ emotion = AI_EMOTION_RED_GLOW
diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm
index 36e87a8e16..a1826c38af 100644
--- a/code/modules/mob/living/silicon/ai/login.dm
+++ b/code/modules/mob/living/silicon/ai/login.dm
@@ -1,11 +1,5 @@
/mob/living/silicon/ai/Login()
..()
- if(stat != DEAD)
- for(var/each in GLOB.ai_status_displays) //change status
- var/obj/machinery/status_display/ai/O = each
- O.mode = 1
- O.emotion = emote_display
- O.update()
set_eyeobj_visible(TRUE)
if(multicam_on)
end_multicam()
diff --git a/code/modules/mob/living/silicon/ai/logout.dm b/code/modules/mob/living/silicon/ai/logout.dm
index 73161f12d5..a027871716 100644
--- a/code/modules/mob/living/silicon/ai/logout.dm
+++ b/code/modules/mob/living/silicon/ai/logout.dm
@@ -1,8 +1,4 @@
/mob/living/silicon/ai/Logout()
..()
- for(var/each in GLOB.ai_status_displays) //change status
- var/obj/machinery/status_display/ai/O = each
- O.mode = 0
- O.update()
set_eyeobj_visible(FALSE)
view_core()
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm
index f8cf5c1d84..0fdef4819e 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm
@@ -75,6 +75,14 @@
build_path = /obj/item/wallframe/newscaster
category = list("initial", "Construction")
+/datum/design/status_display_frame
+ name = "Status Display Frame"
+ id = "status_display_frame"
+ build_type = AUTOLATHE
+ materials = list(/datum/material/iron = 14000, /datum/material/glass = 8000)
+ build_path = /obj/item/wallframe/status_display
+ category = list("initial", "Construction")
+
/datum/design/turret_control_frame
name = "Turret Control Frame"
id = "turret_control"
diff --git a/icons/obj/wallframe.dmi b/icons/obj/wallframe.dmi
index 70149901e3..c447263db5 100644
Binary files a/icons/obj/wallframe.dmi and b/icons/obj/wallframe.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index f3d34ee6bd..f17fa4a25d 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2726,6 +2726,7 @@
#include "code\modules\mob\living\silicon\ai\ai_defense.dm"
#include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm"
#include "code\modules\mob\living\silicon\ai\death.dm"
+#include "code\modules\mob\living\silicon\ai\emote.dm"
#include "code\modules\mob\living\silicon\ai\examine.dm"
#include "code\modules\mob\living\silicon\ai\laws.dm"
#include "code\modules\mob\living\silicon\ai\life.dm"