mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 08:08:49 +01:00
# Conflicts: # .github/workflows/compile_all_maps.yml # .github/workflows/run_integration_tests.yml # _maps/map_files/CatwalkStation/CatwalkStation_2023.dmm # code/_onclick/hud/credits.dm # code/controllers/subsystem/networks/id_access.dm # code/datums/diseases/advance/advance.dm # code/datums/diseases/advance/symptoms/heal.dm # code/game/machinery/doors/door.dm # code/game/objects/structures/crates_lockers/closets/secure/medical.dm # code/game/objects/structures/crates_lockers/closets/secure/security.dm # code/modules/antagonists/malf_ai/malf_ai_modules.dm # code/modules/jobs/job_types/_job.dm # code/modules/loadout/categories/accessories.dm # code/modules/loadout/loadout_helpers.dm # code/modules/loadout/loadout_items.dm # code/modules/loadout/loadout_preference.dm # code/modules/mob/living/silicon/robot/robot_defense.dm # code/modules/mod/mod_theme.dm # code/modules/projectiles/ammunition/energy/laser.dm # code/modules/reagents/reagent_containers/cups/drinks.dm # code/modules/shuttle/mobile_port/variants/supply.dm # code/modules/surgery/organs/internal/eyes/_eyes.dm # code/modules/unit_tests/screenshots/screenshot_antag_icons_heretic.png # icons/hud/screen_full.dmi
1254 lines
44 KiB
Plaintext
1254 lines
44 KiB
Plaintext
// Status display
|
|
|
|
#define MAX_STATIC_WIDTH 22
|
|
#define FONT_STYLE "12pt 'TinyUnicode'"
|
|
#define SCROLL_RATE (0.04 SECONDS) // time per pixel
|
|
#define SCROLL_PADDING 2 // how many pixels we chop to make a smooth loop
|
|
#define LINE1_X 1
|
|
#define LINE1_Y -4
|
|
#define LINE2_X 1
|
|
#define LINE2_Y -11
|
|
GLOBAL_DATUM_INIT(status_font, /datum/font, new /datum/font/tiny_unicode/size_12pt())
|
|
|
|
/// Status display which can show images and scrolling text.
|
|
/obj/machinery/status_display
|
|
name = "status display"
|
|
desc = null
|
|
icon = 'icons/obj/machines/status_display.dmi' //// SKYRAT EDIT CHANGE - ICON OVERRIDDEN IN SKYRAT AESTHETICS - SEE MODULE
|
|
icon_state = "frame"
|
|
verb_say = "beeps"
|
|
verb_ask = "beeps"
|
|
verb_exclaim = "beeps"
|
|
density = FALSE
|
|
layer = ABOVE_WINDOW_LAYER
|
|
|
|
// We store overlays as keys, so multiple displays can use the same object safely
|
|
/// String key we use to index the first effect overlay displayed on us
|
|
var/message_key_1
|
|
/// String key we use to index the second effect overlay displayed on us
|
|
var/message_key_2
|
|
var/current_picture = ""
|
|
var/current_mode = SD_BLANK
|
|
var/message1 = ""
|
|
var/message2 = ""
|
|
|
|
/// Normal text color
|
|
var/text_color = COLOR_DISPLAY_BLUE
|
|
/// Color for headers, eg. "- ETA -"
|
|
var/header_text_color = COLOR_DISPLAY_PURPLE
|
|
|
|
/// Used in greenscreen mode: the display currently being displayed (via vis_contents)
|
|
VAR_PRIVATE/list/obj/effect/abstract/greenscreen_display/active_displays
|
|
/// Lazy-inited radio in greenscreen mode to receive speech from the display
|
|
VAR_PRIVATE/obj/item/radio/entertainment/speakers/speakers
|
|
|
|
/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= SHEET_MATERIAL_AMOUNT * 7)
|
|
result_path = /obj/machinery/status_display/evac
|
|
pixel_shift = 32
|
|
|
|
//makes it go on the wall when built
|
|
/obj/machinery/status_display/Initialize(mapload)
|
|
. = ..()
|
|
update_appearance()
|
|
|
|
/obj/machinery/status_display/wrench_act_secondary(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.combat_mode)
|
|
return
|
|
if(atom_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")
|
|
atom_integrity = max_integrity
|
|
set_machine_stat(machine_stat & ~BROKEN)
|
|
update_appearance()
|
|
return TRUE
|
|
|
|
/obj/machinery/status_display/on_deconstruction(disassembled)
|
|
if(!disassembled)
|
|
new /obj/item/stack/sheet/iron(drop_location(), 2)
|
|
new /obj/item/shard(drop_location())
|
|
new /obj/item/shard(drop_location())
|
|
else
|
|
new /obj/item/wallframe/status_display(drop_location())
|
|
|
|
/// Immediately change the display to the given picture.
|
|
/obj/machinery/status_display/proc/set_picture(state)
|
|
if(state != current_picture)
|
|
current_picture = state
|
|
|
|
update_appearance()
|
|
|
|
/// Immediately change the display to the given two lines.
|
|
/obj/machinery/status_display/proc/set_messages(line1, line2)
|
|
line1 = uppertext(line1)
|
|
line2 = uppertext(line2)
|
|
|
|
var/message_changed = FALSE
|
|
if(line1 != message1)
|
|
message1 = line1
|
|
message_changed = TRUE
|
|
|
|
if(line2 != message2)
|
|
message2 = line2
|
|
message_changed = TRUE
|
|
|
|
if(message_changed)
|
|
update_appearance()
|
|
|
|
/**
|
|
* Remove both message objs and null the fields.
|
|
* Don't call this in subclasses.
|
|
*/
|
|
/obj/machinery/status_display/proc/clear_display()
|
|
PRIVATE_PROC(TRUE)
|
|
clear_text()
|
|
clear_green_screen()
|
|
|
|
/// Clears text off the display.
|
|
/obj/machinery/status_display/proc/clear_text()
|
|
PRIVATE_PROC(TRUE)
|
|
var/obj/effect/overlay/status_display_text/overlay_1 = get_status_text(message_key_1)
|
|
message_key_1 = null
|
|
overlay_1?.disown(src)
|
|
var/obj/effect/overlay/status_display_text/overlay_2 = get_status_text(message_key_2)
|
|
message_key_2 = null
|
|
overlay_2?.disown(src)
|
|
|
|
/// Clears the green screen display.
|
|
/obj/machinery/status_display/proc/clear_green_screen()
|
|
PRIVATE_PROC(TRUE)
|
|
speakers?.set_listening(FALSE)
|
|
if(LAZYLEN(active_displays))
|
|
vis_contents -= active_displays
|
|
LAZYNULL(active_displays)
|
|
|
|
// List in the form key -> status display that shows said key
|
|
GLOBAL_LIST_EMPTY(key_to_status_display)
|
|
|
|
/proc/generate_status_text(line_y, message, x_offset, text_color, header_text_color, line_pair)
|
|
var/key = "[line_y]-[message]-[x_offset]-[text_color]-[header_text_color]-[line_pair]"
|
|
var/obj/effect/overlay/status_display_text/new_overlay = GLOB.key_to_status_display[key]
|
|
if(!new_overlay)
|
|
new_overlay = new(null, line_y, message, text_color, header_text_color, x_offset, line_pair, key)
|
|
GLOB.key_to_status_display[key] = new_overlay
|
|
return new_overlay
|
|
|
|
/proc/get_status_text(key)
|
|
return GLOB.key_to_status_display[key]
|
|
|
|
/**
|
|
* 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.
|
|
* * x_offset - Used to offset the text on the X coordinates, not usually needed.
|
|
* * message - the new message text.
|
|
* Returns new /obj/effect/overlay/status_display_text or null if unchanged.
|
|
*/
|
|
/obj/machinery/status_display/proc/update_message(current_key, line_y, message, x_offset, line_pair)
|
|
var/obj/effect/overlay/status_display_text/current_overlay = get_status_text(current_key)
|
|
var/obj/effect/overlay/status_display_text/new_overlay = generate_status_text(line_y, message, x_offset, text_color, header_text_color, line_pair)
|
|
|
|
if(current_overlay == new_overlay)
|
|
return current_key
|
|
|
|
current_overlay?.disown(src)
|
|
new_overlay.own(src)
|
|
return new_overlay.status_key
|
|
|
|
/obj/machinery/status_display/update_appearance(updates=ALL)
|
|
. = ..()
|
|
if( \
|
|
(machine_stat & (NOPOWER|BROKEN)) || \
|
|
(current_mode == SD_BLANK) || \
|
|
(current_mode != SD_PICTURE && message1 == "" && message2 == "") \
|
|
)
|
|
set_light(0)
|
|
return
|
|
set_light(1.5, 0.7, LIGHT_COLOR_FAINT_CYAN) // blue light
|
|
|
|
/obj/machinery/status_display/update_overlays(updates)
|
|
. = ..()
|
|
|
|
if(machine_stat & (NOPOWER|BROKEN))
|
|
clear_display()
|
|
return
|
|
|
|
switch(current_mode)
|
|
if(SD_BLANK)
|
|
clear_display()
|
|
// Turn off backlight.
|
|
return
|
|
if(SD_PICTURE)
|
|
clear_display()
|
|
. += mutable_appearance(icon, current_picture)
|
|
if(current_picture == AI_DISPLAY_DONT_GLOW) // If the thing's off, don't display the emissive yeah?
|
|
return
|
|
if(SD_GREENSCREEN)
|
|
clear_text()
|
|
if(LAZYLEN(active_displays))
|
|
vis_contents |= active_displays
|
|
speakers?.set_listening(TRUE)
|
|
else
|
|
clear_green_screen()
|
|
var/line1_metric
|
|
var/line2_metric
|
|
var/line_pair
|
|
line1_metric = GLOB.status_font.get_metrics(message1)
|
|
line2_metric = GLOB.status_font.get_metrics(message2)
|
|
line_pair = (line1_metric > line2_metric ? line1_metric : line2_metric)
|
|
|
|
message_key_1 = update_message(message_key_1, LINE1_Y, message1, LINE1_X, line_pair)
|
|
message_key_2 = update_message(message_key_2, LINE2_Y, message2, LINE2_X, line_pair)
|
|
|
|
// Turn off backlight if message is blank
|
|
if(message1 == "" && message2 == "")
|
|
return
|
|
|
|
. += emissive_appearance(icon, "outline", src, alpha = src.alpha)
|
|
|
|
// Timed process - performs nothing in the base class
|
|
/obj/machinery/status_display/process()
|
|
if(machine_stat & NOPOWER)
|
|
// No power, no processing.
|
|
update_appearance()
|
|
|
|
return PROCESS_KILL
|
|
|
|
/// Updates the display and starts it processing again if needed
|
|
/obj/machinery/status_display/proc/update()
|
|
if (process(SSMACHINES_DT) != PROCESS_KILL)
|
|
START_PROCESSING(SSmachines, src)
|
|
|
|
/obj/machinery/status_display/power_change()
|
|
. = ..()
|
|
update()
|
|
|
|
/obj/machinery/status_display/emp_act(severity)
|
|
. = ..()
|
|
if((machine_stat & (NOPOWER|BROKEN)) || (. & EMP_PROTECT_SELF))
|
|
return
|
|
current_mode = SD_PICTURE
|
|
set_picture("ai_bsod")
|
|
|
|
/obj/machinery/status_display/examine(mob/user)
|
|
. = ..()
|
|
if(LAZYLEN(active_displays))
|
|
. += span_notice("<hr>It's currently broadcasting. You can see...")
|
|
var/has_any = FALSE
|
|
for(var/obj/effect/abstract/greenscreen_display/display as anything in active_displays)
|
|
for(var/atom/movable/thing as anything in display.displaying)
|
|
. += span_notice("• \A [thing.name]")
|
|
has_any = TRUE
|
|
if(!has_any)
|
|
. += span_notice("• Nothing.")
|
|
|
|
var/obj/effect/overlay/status_display_text/message1_overlay = get_status_text(message_key_1)
|
|
var/obj/effect/overlay/status_display_text/message2_overlay = get_status_text(message_key_2)
|
|
if (message1_overlay || message2_overlay)
|
|
. += "<hr>The display says:"
|
|
if (message1_overlay.message)
|
|
. += "\t<tt>[html_encode(message1_overlay.message)]</tt>"
|
|
if (message2_overlay.message)
|
|
. += "\t<tt>[html_encode(message2_overlay.message)]</tt>"
|
|
|
|
// 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
|
|
set_messages("shutl","not in service")
|
|
return PROCESS_KILL
|
|
else if(shuttle.timer)
|
|
var/line1 = shuttle.getModeStr()
|
|
var/line2 = shuttle.getTimerStr()
|
|
|
|
set_messages(line1, line2)
|
|
else
|
|
// don't kill processing, the timer might turn back on
|
|
set_messages("", "")
|
|
|
|
/obj/machinery/status_display/Destroy()
|
|
clear_display()
|
|
QDEL_NULL(speakers)
|
|
return ..()
|
|
|
|
/**
|
|
* Nice overlay to make text smoothly scroll with no client updates after setup.
|
|
*/
|
|
/obj/effect/overlay/status_display_text
|
|
icon = 'icons/obj/machines/status_display.dmi'
|
|
vis_flags = VIS_INHERIT_LAYER | VIS_INHERIT_PLANE | VIS_INHERIT_ID
|
|
// physically shift down to render correctly
|
|
pixel_y = -32
|
|
pixel_z = 32
|
|
|
|
/// The message this overlay is displaying.
|
|
var/message
|
|
/// Amount of usage this overlay is getting
|
|
var/use_count = 0
|
|
/// The status key we represent
|
|
var/status_key
|
|
|
|
// If the line is short enough to not marquee, and it matches this, it's a header.
|
|
var/static/regex/header_regex = regex("^-.*-$")
|
|
|
|
/obj/effect/overlay/status_display_text/Initialize(mapload, maptext_y, message, text_color, header_text_color, xoffset = 0, line_pair, status_key)
|
|
. = ..()
|
|
|
|
src.maptext_y = maptext_y
|
|
src.message = message
|
|
src.status_key = status_key
|
|
|
|
var/line_width = GLOB.status_font.get_metrics(message)
|
|
|
|
if(line_width > MAX_STATIC_WIDTH)
|
|
// Marquee text
|
|
var/marquee_message = "[message] [message] [message]"
|
|
|
|
// Width of full content. Must of these is never revealed unless the user inputted a single character.
|
|
var/full_marquee_width = GLOB.status_font.get_metrics("[marquee_message] ")
|
|
// We loop after only this much has passed.
|
|
var/looping_marquee_width = (GLOB.status_font.get_metrics("[message] ]") - SCROLL_PADDING)
|
|
|
|
maptext = generate_text(marquee_message, center = FALSE, text_color = text_color)
|
|
maptext_width = full_marquee_width
|
|
maptext_x = 0
|
|
|
|
// Mask off to fit in screen.
|
|
add_filter("mask", 1, alpha_mask_filter(icon = icon(icon, "outline")))
|
|
|
|
// Scroll.
|
|
var/time = line_pair * SCROLL_RATE
|
|
animate(src, maptext_x = (-looping_marquee_width) + MAX_STATIC_WIDTH, time = time, loop = -1)
|
|
animate(maptext_x = MAX_STATIC_WIDTH, time = 0)
|
|
else
|
|
// Centered text
|
|
var/color = header_regex.Find(message) ? header_text_color : text_color
|
|
maptext = generate_text(message, center = TRUE, text_color = color)
|
|
maptext_x = xoffset //Defaults to 0, this would be centered unless overided
|
|
|
|
/obj/effect/overlay/status_display_text/Destroy(force)
|
|
GLOB.key_to_status_display -= status_key
|
|
return ..()
|
|
|
|
/// Status displays are static, shared by everyone who needs them
|
|
/// This marks us as being used by one more guy
|
|
/obj/effect/overlay/status_display_text/proc/own(atom/movable/owned_by)
|
|
owned_by.vis_contents += src
|
|
use_count += 1
|
|
|
|
/// Status displays are static, shared by everyone who needs them
|
|
/// This marks us as no longer being used by a guy
|
|
/obj/effect/overlay/status_display_text/proc/disown(atom/movable/disowned_by)
|
|
disowned_by.vis_contents -= src
|
|
use_count -= 1
|
|
if(use_count <= 0)
|
|
qdel(src)
|
|
|
|
/**
|
|
* Generate the actual maptext.
|
|
* Arguments:
|
|
* * text - the text to display
|
|
* * center - center the text if TRUE, otherwise right-align (the direction the text is coming from)
|
|
* * text_color - the text color
|
|
*/
|
|
/obj/effect/overlay/status_display_text/proc/generate_text(text, center, text_color)
|
|
return {"<div style="color:[text_color];font:[FONT_STYLE][center ? ";text-align:center" : "text-align:right"]" valign="top">[text]</div>"}
|
|
|
|
/// Evac display which shows shuttle timer or message set by Command.
|
|
/obj/machinery/status_display/evac
|
|
current_mode = SD_PICTURE
|
|
current_picture = "default" // Show the logo when we start round
|
|
var/frequency = FREQ_STATUS_DISPLAYS
|
|
var/friendc = FALSE // track if Friend Computer mode
|
|
var/last_picture // For when Friend Computer mode is undone
|
|
/// What mode we should go back to when the alert timer runs out
|
|
var/revert_mode = SD_PICTURE
|
|
/// What picture we should show when reverting back
|
|
var/revert_picture = "default"
|
|
/// Timer for switching back to normal after showing alerts
|
|
var/alert_display_timer
|
|
/// How important our current display is (bigger number = more important)
|
|
var/current_priority = DISPLAY_PRIORITY_LOGO
|
|
/// What priority level we should go back to after alerts
|
|
var/revert_priority = DISPLAY_PRIORITY_LOGO
|
|
/// Keeps track of what someone manually set so we can go back to it later
|
|
var/list/manual_display_state
|
|
/// List of active emergency types - tracks what emergencies are currently happening
|
|
var/list/active_emergencies = list()
|
|
|
|
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
|
|
|
|
/obj/machinery/status_display/evac/Initialize(mapload)
|
|
. = ..()
|
|
// register for radio system
|
|
SSradio.add_object(src, frequency)
|
|
// Circuit USB
|
|
AddComponent(/datum/component/usb_port, typecacheof(list(/obj/item/circuit_component/status_display), only_root_path = TRUE))
|
|
RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(on_sec_level_change))
|
|
if(mapload)
|
|
find_and_mount_on_atom()
|
|
|
|
/obj/machinery/status_display/evac/Destroy()
|
|
SSradio.remove_object(src,frequency)
|
|
UnregisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED)
|
|
// Clean up our timer so we don't have ghost timers floating around
|
|
if(alert_display_timer)
|
|
deltimer(alert_display_timer)
|
|
alert_display_timer = null
|
|
return ..()
|
|
|
|
/obj/machinery/status_display/evac/process()
|
|
if(machine_stat & NOPOWER)
|
|
// No power, no processing.
|
|
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
|
|
|
|
// Figure out what we should be showing right now
|
|
var/list/target_display = get_highest_priority_display()
|
|
var/target_priority = target_display["priority"]
|
|
|
|
// Don't mess with more important stuff that's already showing
|
|
if(target_priority <= current_priority)
|
|
// Keep the shuttle timer updating if that's what we're showing
|
|
if(current_mode == SD_EMERGENCY)
|
|
return display_shuttle_status(SSshuttle.emergency)
|
|
return PROCESS_KILL
|
|
|
|
// Switch to whatever should have priority right now
|
|
switch(target_display["mode"])
|
|
if(SD_EMERGENCY)
|
|
set_display_with_priority(SD_EMERGENCY, target_priority)
|
|
return display_shuttle_status(SSshuttle.emergency)
|
|
if(SD_PICTURE)
|
|
set_display_with_priority(SD_PICTURE, target_priority, picture_state = target_display["picture"])
|
|
return PROCESS_KILL
|
|
else
|
|
return PROCESS_KILL
|
|
|
|
/// Figures out what we should be showing based on what's happening on the station
|
|
/// Returns a list with the mode, priority, and other details
|
|
/obj/machinery/status_display/evac/proc/get_highest_priority_display()
|
|
// Shuttle stuff gets top priority - but only when it's actually doing something
|
|
if(SSshuttle?.emergency)
|
|
switch(SSshuttle.emergency.mode)
|
|
if(SHUTTLE_CALL, SHUTTLE_DOCKED, SHUTTLE_ESCAPE)
|
|
return list("mode" = SD_EMERGENCY, "priority" = DISPLAY_PRIORITY_SHUTTLE)
|
|
// Don't bother showing shuttle status when it's just sitting there
|
|
|
|
// Check if someone manually set something from the bridge
|
|
if(manual_display_state && manual_display_state["priority"] >= DISPLAY_PRIORITY_MESSAGE)
|
|
return manual_display_state.Copy()
|
|
|
|
// Nothing special happening, just show the logo
|
|
return list("mode" = SD_PICTURE, "priority" = DISPLAY_PRIORITY_LOGO, "picture" = "default")
|
|
|
|
/// Tries to change what the display is showing, but respects priority levels
|
|
/// Returns TRUE if we actually changed it, FALSE if something more important was already showing
|
|
/obj/machinery/status_display/evac/proc/set_display_with_priority(new_mode, priority, picture_state = null, message1 = null, message2 = null, force_override = FALSE)
|
|
// Allow manual overrides to always work (except when temporary alerts are showing)
|
|
if(!force_override && priority < current_priority && current_priority != DISPLAY_PRIORITY_ALERT_TEMP)
|
|
return FALSE
|
|
|
|
// Cancel alert timer if we're changing to a different display type
|
|
if(alert_display_timer && priority != DISPLAY_PRIORITY_ALERT_TEMP)
|
|
deltimer(alert_display_timer)
|
|
alert_display_timer = null
|
|
|
|
current_mode = new_mode
|
|
current_priority = priority
|
|
|
|
// Store manually set displays (not temporary alerts, automated shuttle, or logo defaults)
|
|
// Logo selections shouldn't be treated as manual overrides since logo is the default anyway
|
|
var/is_logo_default = (new_mode == SD_PICTURE && (picture_state == "default" || picture_state == null))
|
|
if(priority >= DISPLAY_PRIORITY_MESSAGE && priority != DISPLAY_PRIORITY_ALERT_TEMP && priority != DISPLAY_PRIORITY_SHUTTLE && !is_logo_default)
|
|
manual_display_state = list(
|
|
"mode" = new_mode,
|
|
"priority" = priority,
|
|
"picture" = picture_state,
|
|
"message1" = message1,
|
|
"message2" = message2
|
|
)
|
|
else if(is_logo_default)
|
|
// Clear manual display state when logo is selected
|
|
manual_display_state = null
|
|
|
|
switch(new_mode)
|
|
if(SD_BLANK)
|
|
update_appearance()
|
|
if(SD_EMERGENCY)
|
|
set_messages("", "")
|
|
if(SD_MESSAGE)
|
|
set_messages(message1 || "", message2 || "")
|
|
if(SD_PICTURE)
|
|
last_picture = picture_state
|
|
set_picture(picture_state)
|
|
if(SD_GREENSCREEN)
|
|
update_appearance()
|
|
|
|
// Start processing to watch for higher priority displays (like shuttle calls)
|
|
update()
|
|
|
|
return TRUE
|
|
|
|
/obj/machinery/status_display/evac/receive_signal(datum/signal/signal)
|
|
switch(signal.data["command"])
|
|
if("blank")
|
|
set_display_with_priority(SD_BLANK, DISPLAY_PRIORITY_MESSAGE, force_override = TRUE)
|
|
if("shuttle")
|
|
set_display_with_priority(SD_EMERGENCY, DISPLAY_PRIORITY_SHUTTLE, force_override = TRUE)
|
|
// Start processing to keep shuttle timer updated and handle priority changes
|
|
update()
|
|
if("message")
|
|
set_display_with_priority(SD_MESSAGE, DISPLAY_PRIORITY_MESSAGE, message1 = signal.data["top_text"], message2 = signal.data["bottom_text"], force_override = TRUE)
|
|
if("alert")
|
|
var/picture_state = signal.data["picture_state"]
|
|
var/priority = DISPLAY_PRIORITY_MESSAGE
|
|
var/is_emergency = signal.data["emergency_override"]
|
|
var/emergency_type = signal.data["emergency_type"]
|
|
|
|
// Check if this is just setting the logo back to default
|
|
if(picture_state == "default")
|
|
// Clear any manual display state and reset to logo priority
|
|
manual_display_state = null
|
|
set_display_with_priority(SD_PICTURE, DISPLAY_PRIORITY_LOGO, picture_state = picture_state, force_override = TRUE)
|
|
// Automated emergency alerts use temporary high priority to interrupt even shuttle displays
|
|
else if(is_emergency)
|
|
// Track this emergency type
|
|
if(emergency_type && !(emergency_type in active_emergencies))
|
|
active_emergencies |= emergency_type
|
|
show_temporary_display(SD_PICTURE, DISPLAY_PRIORITY_EMERGENCY_TEMP, picture_state = picture_state)
|
|
else
|
|
// Manual alerts can override existing displays
|
|
set_display_with_priority(SD_PICTURE, priority, picture_state = picture_state, force_override = TRUE)
|
|
if("clear_emergency")
|
|
var/emergency_type = signal.data["emergency_type"]
|
|
|
|
// Clear specific emergency type or all if no type specified
|
|
if(emergency_type)
|
|
active_emergencies -= emergency_type
|
|
else
|
|
active_emergencies.Cut() // Clear all emergencies
|
|
|
|
// Only revert display if no more emergencies are active
|
|
if(!length(active_emergencies))
|
|
// Clear emergency displays - if we're showing a temporary emergency, just cancel the timer
|
|
if(current_priority == DISPLAY_PRIORITY_EMERGENCY_TEMP && alert_display_timer)
|
|
deltimer(alert_display_timer)
|
|
alert_display_timer = null
|
|
revert_from_alert_display()
|
|
// Otherwise clear persistent emergency displays by reverting to highest priority
|
|
else if(current_priority >= DISPLAY_PRIORITY_EMERGENCY)
|
|
var/list/target_display = get_highest_priority_display()
|
|
current_mode = target_display["mode"]
|
|
current_priority = target_display["priority"]
|
|
|
|
switch(current_mode)
|
|
if(SD_EMERGENCY)
|
|
set_messages("", "")
|
|
if(SD_PICTURE)
|
|
set_picture(target_display["picture"])
|
|
if(SD_MESSAGE)
|
|
set_messages(target_display["message1"] || "", target_display["message2"] || "")
|
|
else
|
|
update_appearance()
|
|
if("greenscreen")
|
|
var/datum/weakref/display_ref = signal.data["display"]
|
|
var/obj/effect/abstract/greenscreen_display/new_display = display_ref?.resolve()
|
|
if(istype(new_display))
|
|
if(isnull(speakers))
|
|
speakers = new(src)
|
|
// not seen, primarily for VV
|
|
speakers.name = "[name] speakers"
|
|
// to allow centcom cameras to reach the station
|
|
speakers.special_channels |= RADIO_SPECIAL_CENTCOM
|
|
speakers.set_frequency(FREQ_STATUS_DISPLAYS)
|
|
LAZYOR(active_displays, new_display)
|
|
set_display_with_priority(SD_GREENSCREEN, DISPLAY_PRIORITY_MESSAGE, force_override = TRUE)
|
|
if("friendcomputer")
|
|
// Friend Computer should only affect AI displays, not evac displays
|
|
return
|
|
update()
|
|
|
|
/obj/machinery/status_display/evac/vv_edit_var(vname, vval)
|
|
. = ..()
|
|
if(vname == NAMEOF(src, current_mode))
|
|
update_appearance()
|
|
update()
|
|
|
|
/// Gets called when security changes the alert level - shows it for 30 seconds then goes back to normal
|
|
/obj/machinery/status_display/evac/proc/on_sec_level_change(datum/source, new_level)
|
|
SIGNAL_HANDLER
|
|
|
|
// Get the alert level icon
|
|
var/datum/security_level/alert_level = SSsecurity_level.available_levels[SSsecurity_level.number_level_to_text(new_level)]
|
|
|
|
// Show as temporary alert (can be overridden by emergency temps)
|
|
show_temporary_display(SD_PICTURE, DISPLAY_PRIORITY_ALERT_TEMP, picture_state = alert_level.status_display_icon_state)
|
|
|
|
/// Shows a temporary display for 30 seconds then reverts to previous display
|
|
/// Can interrupt lower priority displays but respects higher priority temporary displays
|
|
/obj/machinery/status_display/evac/proc/show_temporary_display(temp_mode, temp_priority, picture_state = null, message1 = null, message2 = null)
|
|
// Don't interrupt higher priority temporary displays
|
|
if(alert_display_timer && current_priority >= temp_priority)
|
|
return
|
|
|
|
// Remember what we were showing before (unless we're already doing a lower priority alert)
|
|
if(!alert_display_timer || current_priority < temp_priority)
|
|
revert_mode = current_mode
|
|
revert_picture = current_picture
|
|
revert_priority = current_priority
|
|
|
|
// Cancel any existing timer
|
|
if(alert_display_timer)
|
|
deltimer(alert_display_timer)
|
|
alert_display_timer = null
|
|
|
|
// Show the temporary display
|
|
current_mode = temp_mode
|
|
current_priority = temp_priority
|
|
|
|
switch(temp_mode)
|
|
if(SD_PICTURE)
|
|
set_picture(picture_state)
|
|
if(SD_MESSAGE)
|
|
set_messages(message1 || "", message2 || "")
|
|
else
|
|
update_appearance()
|
|
|
|
// Go back to what we were showing after 30 seconds
|
|
alert_display_timer = addtimer(CALLBACK(src, PROC_REF(revert_from_alert_display)), 30 SECONDS, TIMER_STOPPABLE)
|
|
|
|
/// Goes back to showing whatever we were displaying before the alert level interrupt
|
|
/obj/machinery/status_display/evac/proc/revert_from_alert_display()
|
|
alert_display_timer = null
|
|
|
|
// Figure out what we should be showing now
|
|
var/list/target_display = get_highest_priority_display()
|
|
current_mode = target_display["mode"]
|
|
current_priority = target_display["priority"]
|
|
|
|
switch(current_mode)
|
|
if(SD_EMERGENCY)
|
|
set_messages("", "")
|
|
if(SD_PICTURE)
|
|
set_picture(target_display["picture"])
|
|
if(SD_MESSAGE)
|
|
set_messages(target_display["message1"] || "", target_display["message2"] || "")
|
|
else
|
|
update_appearance()
|
|
|
|
/// Supply display which shows the status of the supply shuttle.
|
|
/obj/machinery/status_display/supply
|
|
name = "supply display"
|
|
current_mode = SD_MESSAGE
|
|
text_color = COLOR_DISPLAY_ORANGE
|
|
header_text_color = COLOR_DISPLAY_YELLOW
|
|
|
|
/obj/machinery/status_display/supply/process()
|
|
if(machine_stat & NOPOWER)
|
|
// No power, no processing.
|
|
update_appearance()
|
|
return PROCESS_KILL
|
|
|
|
var/line1
|
|
var/line2
|
|
if(!SSshuttle.supply)
|
|
// Might be missing in our first update on initialize before shuttles
|
|
// have loaded. Cross our fingers that it will soon return.
|
|
line1 = "shutl"
|
|
line2 = "not in service"
|
|
else if(SSshuttle.supply.mode == SHUTTLE_IDLE)
|
|
if(is_station_level(SSshuttle.supply.z))
|
|
line1 = "CARGO"
|
|
line2 = "Docked"
|
|
else
|
|
line1 = ""
|
|
line2 = ""
|
|
else
|
|
line1 = SSshuttle.supply.getModeStr()
|
|
line2 = SSshuttle.supply.getTimerStr()
|
|
set_messages(line1, line2)
|
|
|
|
|
|
/// General-purpose shuttle status display.
|
|
/obj/machinery/status_display/shuttle
|
|
name = "shuttle display"
|
|
current_mode = SD_MESSAGE
|
|
var/shuttle_id
|
|
|
|
text_color = COLOR_DISPLAY_GREEN
|
|
header_text_color = COLOR_DISPLAY_CYAN
|
|
|
|
/obj/machinery/status_display/shuttle/process()
|
|
if(!shuttle_id || (machine_stat & NOPOWER))
|
|
// No power, no processing.
|
|
update_appearance()
|
|
return PROCESS_KILL
|
|
|
|
return display_shuttle_status(SSshuttle.getShuttle(shuttle_id))
|
|
|
|
/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
switch(var_name)
|
|
if(NAMEOF(src, shuttle_id))
|
|
update()
|
|
|
|
/obj/machinery/status_display/shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
|
|
if(port)
|
|
shuttle_id = port.shuttle_id
|
|
update()
|
|
|
|
|
|
/// Pictograph display which the AI can use to emote.
|
|
/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/emotion = AI_DISPLAY_DONT_GLOW
|
|
var/frequency = FREQ_STATUS_DISPLAYS
|
|
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/ai, 32)
|
|
|
|
/obj/machinery/status_display/ai/Initialize(mapload)
|
|
. = ..()
|
|
// register for radio system to receive AI emote signals
|
|
SSradio.add_object(src, frequency)
|
|
if(mapload)
|
|
find_and_mount_on_atom()
|
|
|
|
/obj/machinery/status_display/ai/Destroy()
|
|
SSradio.remove_object(src, frequency)
|
|
return ..()
|
|
|
|
/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user)
|
|
if(!isAI(user))
|
|
return
|
|
|
|
// Open the TGUI interface for selecting status display options
|
|
if(!user.status_display_picker)
|
|
user.status_display_picker = new(user, src)
|
|
user.status_display_picker.ui_interact(user)
|
|
|
|
/obj/machinery/status_display/ai/process()
|
|
if(machine_stat & NOPOWER)
|
|
update_appearance()
|
|
return PROCESS_KILL
|
|
|
|
if(!length(GLOB.ai_status_display_all_options))
|
|
init_ai_status_display_options()
|
|
|
|
var/icon_state
|
|
// First try the combined list
|
|
if(emotion in GLOB.ai_status_display_all_options)
|
|
icon_state = GLOB.ai_status_display_all_options[emotion]
|
|
// Then try the original emotes list
|
|
else if(emotion in GLOB.ai_status_display_emotes)
|
|
icon_state = GLOB.ai_status_display_emotes[emotion]
|
|
// Default fallback
|
|
else
|
|
icon_state = "ai_download"
|
|
|
|
set_picture(icon_state)
|
|
return PROCESS_KILL
|
|
|
|
/obj/machinery/status_display/ai/receive_signal(datum/signal/signal)
|
|
switch(signal.data["command"])
|
|
if("friendcomputer")
|
|
friendc = !friendc
|
|
if(friendc)
|
|
last_picture = emotion // Save current display
|
|
emotion = AI_EMOTION_FRIEND_COMPUTER
|
|
else
|
|
if(last_picture)
|
|
emotion = last_picture // Restore previous display
|
|
else
|
|
emotion = AI_DISPLAY_DONT_GLOW
|
|
update_appearance()
|
|
|
|
/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",
|
|
"Delta Alert" = "deltaalert",
|
|
"Red Alert" = "redalert",
|
|
"Blue Alert" = "bluealert",
|
|
"Green Alert" = "greenalert",
|
|
"Violet Alert" = "violetalert", // SKYRAT EDIT ADD - Alert Levels
|
|
"Orange Alert" = "orangealert", // SKYRAT EDIT ADD - Alert Levels
|
|
"Amber Alert" = "amberalert", // SKYRAT EDIT ADD - Alert Levels
|
|
"Gamma Alert" = "gammaalert", // SKYRAT EDIT ADD - Alert Levels
|
|
"Biohazard" = "biohazard",
|
|
"Lockdown" = "lockdown",
|
|
"Radiation" = "radiation",
|
|
"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["top_text"] = message1.value
|
|
status_signal.data["bottom_text"] = message2.value
|
|
if("alert")
|
|
status_signal.data["picture_state"] = picture_map[picture.value]
|
|
|
|
connected_display.receive_signal(status_signal)
|
|
|
|
/obj/machinery/status_display/random_message
|
|
current_mode = SD_MESSAGE
|
|
/// list to pick the first line from
|
|
var/list/firstline_to_secondline = list()
|
|
|
|
/obj/machinery/status_display/random_message/Initialize(mapload)
|
|
if(firstline_to_secondline?.len)
|
|
message1 = pick(firstline_to_secondline)
|
|
message2 = firstline_to_secondline[message1]
|
|
return ..() // status displays call update appearance on init so i suppose we should set the messages before calling parent as to not call it twice
|
|
|
|
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/random_message, 32)
|
|
|
|
#undef MAX_STATIC_WIDTH
|
|
#undef FONT_STYLE
|
|
#undef SCROLL_RATE
|
|
#undef LINE1_X
|
|
#undef LINE1_Y
|
|
#undef LINE2_X
|
|
#undef LINE2_Y
|
|
#undef SCROLL_PADDING
|
|
|
|
/// Tracks all active greenscreen displays
|
|
GLOBAL_LIST_EMPTY_TYPED(greenscreen_displays, /obj/effect/abstract/greenscreen_display)
|
|
|
|
/// Used to indicate where the greenscreen is recording
|
|
/obj/effect/abstract/greenscreen_location_indicator
|
|
icon = 'icons/mob/telegraphing/telegraph.dmi'
|
|
icon_state = "blank_semi_transparent"
|
|
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
layer = BELOW_OPEN_DOOR_LAYER
|
|
|
|
/obj/effect/abstract/greenscreen_location_indicator/Initialize(mapload)
|
|
. = ..()
|
|
add_filter("greenscreen_indicator", 1, outline_filter(1.5, COLOR_YELLOW))
|
|
animate(get_filter("greenscreen_indicator"), alpha = 0, time = 2.5 SECONDS, loop = -1)
|
|
animate(alpha = 200, time = 1 SECONDS)
|
|
|
|
/// Basically exists to hold an appearance that we can slot into vis_contents
|
|
/obj/effect/abstract/greenscreen_appearance_holder
|
|
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
|
|
/obj/effect/abstract/greenscreen_appearance_holder/Initialize(mapload, atom/movable/to_copy)
|
|
. = ..()
|
|
if(istype(to_copy))
|
|
copy_appearance(to_copy)
|
|
|
|
/obj/effect/abstract/greenscreen_appearance_holder/proc/copy_appearance(atom/movable/from)
|
|
var/mutable_appearance/copy = copy_appearance_filter_overlays(from.appearance)
|
|
// if they have keep apart overlays we NEED to manually propogate the mask
|
|
for(var/mutable_appearance/subcopy as anything in list() + copy.underlays + copy.overlays)
|
|
if(subcopy.appearance_flags & KEEP_APART)
|
|
subcopy.filters |= loc.filters // where loc = the display
|
|
appearance = copy
|
|
var/display_dir = ISDIAGONALDIR(loc.dir) ? EWCOMPONENT(loc.dir) : loc.dir
|
|
var/from_dir = ISDIAGONALDIR(from.dir) ? EWCOMPONENT(from.dir) : from.dir
|
|
switch(display_dir)
|
|
if(NORTH)
|
|
pass() // it just works
|
|
if(EAST)
|
|
dir = turn(from_dir, 90)
|
|
if(SOUTH)
|
|
dir = turn(from_dir, 180)
|
|
if(WEST)
|
|
dir = turn(from_dir, 270)
|
|
// appearance copies these so we need to manually set them
|
|
vis_flags |= VIS_INHERIT_PLANE|VIS_INHERIT_LAYER|VIS_INHERIT_ID // is vis_inherit recursive?
|
|
|
|
/// Basically exists to compile a list of appearances to display to all status displays
|
|
/obj/effect/abstract/greenscreen_display
|
|
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
pixel_y = -8
|
|
appearance_flags = parent_type::appearance_flags | KEEP_TOGETHER
|
|
vis_flags = VIS_INHERIT_PLANE|VIS_INHERIT_LAYER
|
|
/// Tracks who we're displaying
|
|
VAR_FINAL/list/atom/movable/displaying = list()
|
|
|
|
/obj/effect/abstract/greenscreen_display/Initialize(mapload)
|
|
. = ..()
|
|
GLOB.greenscreen_displays += src
|
|
// crops out the bits that don't fit the screen
|
|
add_filter("display_mask", 1, alpha_mask_filter(x = -1 * pixel_x, y = -1 * pixel_y, icon = icon('icons/obj/machines/status_display.dmi', "outline")))
|
|
// adds some pizzazz (copied from records)
|
|
underlays += mutable_appearance('icons/effects/effects.dmi', "static_base", alpha = 20)
|
|
add_overlay(mutable_appearance(generate_icon_alpha_mask('icons/effects/effects.dmi', "scanline"), alpha = 20))
|
|
|
|
/obj/effect/abstract/greenscreen_display/Destroy()
|
|
for(var/thing in displaying)
|
|
remove_from_display(thing)
|
|
GLOB.greenscreen_displays -= src
|
|
return ..()
|
|
|
|
/obj/effect/abstract/greenscreen_display/proc/add_to_display(atom/movable/thing)
|
|
if(displaying[thing])
|
|
return
|
|
|
|
displaying[thing] = new /obj/effect/abstract/greenscreen_appearance_holder(src, thing)
|
|
RegisterSignals(thing, list(
|
|
COMSIG_ATOM_POST_DIR_CHANGE,
|
|
COMSIG_ATOM_UPDATED_ICON,
|
|
COMSIG_CARBON_APPLY_OVERLAY,
|
|
COMSIG_CARBON_REMOVE_OVERLAY,
|
|
COMSIG_LIVING_POST_UPDATE_TRANSFORM,
|
|
), PROC_REF(thing_changed))
|
|
RegisterSignal(thing, COMSIG_QDELETING, PROC_REF(remove_from_display))
|
|
vis_contents += displaying[thing]
|
|
|
|
/obj/effect/abstract/greenscreen_display/proc/remove_from_display(atom/movable/thing)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!displaying[thing])
|
|
return
|
|
|
|
UnregisterSignal(thing, list(
|
|
COMSIG_ATOM_POST_DIR_CHANGE,
|
|
COMSIG_ATOM_UPDATED_ICON,
|
|
COMSIG_CARBON_APPLY_OVERLAY,
|
|
COMSIG_CARBON_REMOVE_OVERLAY,
|
|
COMSIG_LIVING_POST_UPDATE_TRANSFORM,
|
|
COMSIG_QDELETING,
|
|
))
|
|
vis_contents -= displaying[thing]
|
|
qdel(displaying[thing])
|
|
displaying -= thing
|
|
|
|
/obj/effect/abstract/greenscreen_display/proc/thing_changed(atom/movable/source)
|
|
SIGNAL_HANDLER
|
|
|
|
var/obj/effect/abstract/greenscreen_appearance_holder/holder = displaying[source]
|
|
holder.copy_appearance(source)
|
|
|
|
/// A stationary object which "records" anyone who is in front of it and broadcasts them to all status displays
|
|
/obj/machinery/greenscreen_camera
|
|
name = "captain-caster"
|
|
desc = "A camera that can be used to display whomever is in front of it across all status displays. \
|
|
Pair with a greenscreen for best results."
|
|
density = FALSE
|
|
icon = 'icons/obj/machines/stationary_camera.dmi'
|
|
icon_state = "camera"
|
|
interaction_flags_atom = INTERACT_ATOM_REQUIRES_ANCHORED|INTERACT_ATOM_ATTACK_HAND|INTERACT_ATOM_ATTACK_PAW
|
|
interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON
|
|
processing_flags = NONE
|
|
use_power = IDLE_POWER_USE
|
|
idle_power_usage = 0
|
|
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.1
|
|
mouse_over_pointer = MOUSE_HAND_POINTER
|
|
|
|
/// Tracks the turf we are recording
|
|
VAR_PRIVATE/turf/greenscreen_turf
|
|
/// The actual atom we hold the appearance on, held in nullspace
|
|
VAR_PRIVATE/obj/effect/abstract/greenscreen_display/display
|
|
/// Lazy-inited microphone to relay speech over the displays
|
|
VAR_PRIVATE/obj/item/radio/entertainment/microphone/mic
|
|
|
|
/obj/machinery/greenscreen_camera/Initialize(mapload)
|
|
. = ..()
|
|
AddComponent(/datum/component/simple_rotation, ROTATION_IGNORE_ANCHORED)
|
|
update_appearance(UPDATE_OVERLAYS)
|
|
|
|
/obj/machinery/greenscreen_camera/Destroy()
|
|
deactivate_feed()
|
|
return ..()
|
|
|
|
/obj/machinery/greenscreen_camera/examine(mob/user)
|
|
. = ..()
|
|
. += span_notice("It's currently [isnull(display) ? "not " : ""]broadcasting. <i>Click it to change that.</i>")
|
|
|
|
/obj/machinery/greenscreen_camera/interact(mob/user)
|
|
. = ..()
|
|
if(.)
|
|
return TRUE
|
|
|
|
toggle_feed()
|
|
return TRUE
|
|
|
|
/obj/machinery/greenscreen_camera/proc/toggle_feed()
|
|
if(isnull(display))
|
|
if(!is_operational)
|
|
return
|
|
activate_feed()
|
|
if(isnull(display))
|
|
playsound(src, 'sound/machines/terminal/terminal_on.ogg', 33, TRUE, frequency = 0.5)
|
|
balloon_alert_to_viewers("no backdrop, can't broadcast!")
|
|
return
|
|
playsound(src, 'sound/machines/terminal/terminal_on.ogg', 33, FALSE)
|
|
use_power = ACTIVE_POWER_USE
|
|
else
|
|
deactivate_feed()
|
|
playsound(src, 'sound/machines/terminal/terminal_off.ogg', 33, FALSE)
|
|
use_power = IDLE_POWER_USE
|
|
balloon_alert_to_viewers("feed [isnull(display) ? "de" : ""]activated")
|
|
|
|
/obj/machinery/greenscreen_camera/proc/activate_feed()
|
|
greenscreen_turf = find_displayed_turf()
|
|
if(isnull(greenscreen_turf))
|
|
return
|
|
|
|
new /obj/effect/abstract/greenscreen_location_indicator(greenscreen_turf)
|
|
mic = new(src)
|
|
// not seen, primarily for VV
|
|
mic.name = "[name] microphone"
|
|
// allows centcom cameras to reach the station + keeps transmission active even if comms are out
|
|
mic.special_channels |= RADIO_SPECIAL_CENTCOM
|
|
mic.set_frequency(FREQ_STATUS_DISPLAYS)
|
|
display = new()
|
|
display.dir = dir
|
|
RegisterSignal(greenscreen_turf, COMSIG_ATOM_ENTERED, PROC_REF(turf_entered))
|
|
RegisterSignal(greenscreen_turf, COMSIG_ATOM_EXITED, PROC_REF(turf_exited))
|
|
|
|
for(var/mob/living/existing in greenscreen_turf)
|
|
turf_entered(greenscreen_turf, existing)
|
|
|
|
become_hearing_sensitive("active")
|
|
|
|
INVOKE_ASYNC(src, PROC_REF(update_status_displays), list("command" = "greenscreen", "display" = WEAKREF(display)))
|
|
update_appearance(UPDATE_OVERLAYS)
|
|
|
|
/// Sends a signal to all relevant status displays to update their greenscreen
|
|
/obj/machinery/greenscreen_camera/proc/update_status_displays(list/signal_args)
|
|
// update the display on all status displays
|
|
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
|
|
frequency?.post_signal(src, new /datum/signal(signal_args))
|
|
|
|
/obj/machinery/greenscreen_camera/proc/deactivate_feed()
|
|
if(!isnull(greenscreen_turf))
|
|
for(var/obj/effect/abstract/greenscreen_location_indicator/indicator in greenscreen_turf)
|
|
qdel(indicator)
|
|
UnregisterSignal(greenscreen_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED))
|
|
greenscreen_turf = null
|
|
QDEL_NULL(mic)
|
|
QDEL_NULL(display)
|
|
|
|
lose_hearing_sensitivity("active")
|
|
|
|
// there's another green screen to broadcast from, switch to it - otherwise, blank
|
|
INVOKE_ASYNC(src, PROC_REF(update_status_displays), list("command" = (length(GLOB.greenscreen_displays) ? "greenscreen" : "shuttle")))
|
|
update_appearance(UPDATE_OVERLAYS)
|
|
|
|
/// Check if the passed atom can be shown on the display
|
|
/obj/machinery/greenscreen_camera/proc/can_broadcast(atom/movable/thing)
|
|
// blacklist underfloor, just in case
|
|
// i would also blacklist invisible, but i figure it might be funny to see a ghost
|
|
if(HAS_TRAIT(thing, TRAIT_UNDERFLOOR))
|
|
return FALSE
|
|
// always show any mobs
|
|
if(isliving(thing))
|
|
return TRUE
|
|
// show things like wheelchairs, but not beds which are low
|
|
if(thing.can_buckle && (thing.buckle_lying == NO_BUCKLE_LYING || thing.buckle_lying == 0))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/obj/machinery/greenscreen_camera/proc/turf_entered(datum/source, atom/movable/entered)
|
|
SIGNAL_HANDLER
|
|
if(can_broadcast(entered))
|
|
display.add_to_display(entered)
|
|
|
|
/obj/machinery/greenscreen_camera/proc/turf_exited(datum/source, atom/movable/exited)
|
|
SIGNAL_HANDLER
|
|
display.remove_from_display(exited)
|
|
|
|
/obj/machinery/greenscreen_camera/proc/find_displayed_turf()
|
|
var/list/turf/line = get_line(src, get_ranged_target_turf(src, dir, 5))
|
|
for(var/i in 1 to length(line))
|
|
var/turf/current = line[i]
|
|
// found a greenscreen turf
|
|
if(istype(current, /turf/open/floor/greenscreen))
|
|
return current
|
|
// found any wall, or something we can't see through
|
|
if(i != 1)
|
|
if(current.opacity)
|
|
return line[i - 1]
|
|
for(var/atom/movable/thing in current)
|
|
if(thing.opacity)
|
|
return line[i - 1]
|
|
// found a greenscreen poster
|
|
var/obj/structure/sign/poster/greenscreen = locate() in current
|
|
if(isnull(greenscreen))
|
|
continue
|
|
// the poster is wallmounted, so return this turf
|
|
if(isProbablyWallMounted(greenscreen))
|
|
return current
|
|
// the poster is clipped into a wall, so return the last turf
|
|
if(i != 1)
|
|
return line[i - 1]
|
|
|
|
return null
|
|
|
|
/obj/machinery/greenscreen_camera/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
|
. = ..()
|
|
if(isnull(display))
|
|
return
|
|
// stop the feed if we're moved
|
|
deactivate_feed()
|
|
|
|
/obj/machinery/greenscreen_camera/setDir(newdir)
|
|
var/old_dir
|
|
. = ..()
|
|
if(dir == old_dir || isnull(display))
|
|
return
|
|
// stop the feed if we're rotated
|
|
deactivate_feed()
|
|
|
|
/obj/machinery/greenscreen_camera/set_anchored(anchorvalue)
|
|
. = ..()
|
|
if(anchored || isnull(display))
|
|
return
|
|
// stop the feed if we're unanchored
|
|
deactivate_feed()
|
|
|
|
/obj/machinery/greenscreen_camera/on_set_is_operational(old_value)
|
|
. = ..()
|
|
if(is_operational || isnull(display))
|
|
return
|
|
// no more power, no more feed
|
|
// (uses toggle for feedback)
|
|
toggle_feed()
|
|
|
|
/obj/machinery/greenscreen_camera/update_overlays()
|
|
. = ..()
|
|
if(isnull(display))
|
|
. += "camera_off"
|
|
else
|
|
. += "camera_on"
|
|
. += emissive_appearance(icon, "camera_emissive", src, alpha = src.alpha)
|
|
|
|
/// Send an emergency alert signal to all status displays
|
|
/// alert_type: The picture state to display ("biohazard", "lockdown", "radiation", etc.)
|
|
/proc/send_status_display_alert(alert_type)
|
|
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
|
|
if(frequency)
|
|
var/datum/signal/alert_signal = new
|
|
alert_signal.data["command"] = "alert"
|
|
alert_signal.data["picture_state"] = alert_type
|
|
alert_signal.data["emergency_override"] = TRUE
|
|
alert_signal.data["emergency_type"] = alert_type // Track specific emergency type
|
|
var/atom/movable/virtualspeaker/virtual_speaker = new(null)
|
|
frequency.post_signal(virtual_speaker, alert_signal)
|
|
|
|
/// Clear a specific emergency type from status displays
|
|
/// emergency_type: The specific type to clear ("biohazard", "lockdown", "radiation", etc.)
|
|
/// If emergency_type is null, clears ALL emergencies
|
|
/proc/clear_status_display_emergency(emergency_type = null)
|
|
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
|
|
if(frequency)
|
|
var/datum/signal/clear_signal = new
|
|
clear_signal.data["command"] = "clear_emergency"
|
|
clear_signal.data["emergency_override"] = FALSE
|
|
if(emergency_type)
|
|
clear_signal.data["emergency_type"] = emergency_type
|
|
var/atom/movable/virtualspeaker/virtual_speaker = new(null)
|
|
frequency.post_signal(virtual_speaker, clear_signal)
|
|
|
|
/// Send a biohazard alert signal to all status displays
|
|
/proc/send_status_display_biohazard_alert()
|
|
send_status_display_alert("biohazard")
|
|
|
|
/// Send a lockdown alert signal to all status displays
|
|
/proc/send_status_display_lockdown_alert()
|
|
send_status_display_alert("lockdown")
|
|
|
|
/// Send a radiation alert signal to all status displays
|
|
/proc/send_status_display_radiation_alert()
|
|
send_status_display_alert("radiation")
|
|
|
|
/// Clear specific emergency types
|
|
/proc/clear_status_display_biohazard()
|
|
clear_status_display_emergency("biohazard")
|
|
|
|
/proc/clear_status_display_lockdown()
|
|
clear_status_display_emergency("lockdown")
|
|
|
|
/proc/clear_status_display_radiation()
|
|
clear_status_display_emergency("radiation")
|