[MIRROR] Integrated circuits for modular computers (#26196)

Integrated circuits for modular computers (#80530)

This PR integrates circuits for modular computers and a good bits of
their programs.
The peculiarity here is that modular computers have no fixed amount of
unremovable components (except the base one with just a couple ports for
now), instead, they're added and removed along with programs. With a few
exceptions (such as the messenger and signaler), for these program
circuits to work, their associated program has to be either open or in
the background.

For a reason or another, not all programs have a circuit associated to
them, still, however the programs with a circuit are still a handful.
They are:
- Nanotrasen Pay System
- Notepad
- SiliConnect
- WireCarp
- MODsuit Control
- Spectre Meter
- Direct Messenger*
- LifeConnect
- Custodial Locator
- Fission360
- Camera
- Status Display
- SignalCommander

*By the by, sending messages has a cooldown, so it shouldn't be as
spammy. If it turns out to not be enough, I can make it so messages from
circuit will be ignored by other messenger circuits.

The PR is no longer WIP.

I believe modular computers could make for some interesting setups with
circuits, since they're fairly flexible and stocked with features unlike
many other appliances, therefore also a speck more abusable, though
limits, cooldowns, logging and sanitization have been implemented to
keep it in check.

🆑
add: Modular Computers now support integrated circuits. What can be done
with them depends on the programs installed and whether they're running
(open or background).
add: Modular Consoles (the machinery) now have a small backup cell they
draw power from if the power goes out.
/🆑

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
This commit is contained in:
SkyratBot
2024-01-21 15:59:14 +01:00
committed by GitHub
parent ab5a4d0f99
commit fdcfabefd8
60 changed files with 1286 additions and 240 deletions
@@ -81,3 +81,8 @@
/// Called when an equipment action component is removed from a shell (/obj/item/circuit_component/equipment_action/action_comp)
#define COMSIG_CIRCUIT_ACTION_COMPONENT_UNREGISTERED "circuit_action_component_unregistered"
///Sent to the shell component when a circuit is attached.
#define COMSIG_SHELL_CIRCUIT_ATTACHED "shell_circuit_attached"
///Sent to the shell component when a circuit is removed.
#define COMSIG_SHELL_CIRCUIT_REMOVED "shell_circuit_removed"
@@ -9,7 +9,33 @@
/// From /obj/item/modular_computer/proc/store_file: ()
#define COMSIG_COMPUTER_FILE_DELETE "comsig_computer_file_delete"
/// From /datum/computer_file/program/on_start: (user)
#define COMSIG_COMPUTER_PROGRAM_START "computer_program_start"
/// From /datum/computer_file/program/kill_program: (user)
#define COMSIG_COMPUTER_PROGRAM_KILL "computer_program_kill"
/// From /datum/computer_file/program/nt_pay/make_payment: (payment_result)
#define COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT "comsig_modular_computer_nt_pay_result"
/// From /datum/computer_file/program/nt_pay/make_payment: (spookiness, manual)
#define COMSIG_MODULAR_COMPUTER_SPECTRE_SCAN "comsig_modular_computer_spectre_scan"
/// From /datum/computer_file/program/radar/trackable: (atom/signal, turf/signal_turf, turf/computer_turf)
#define COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE "comsig_modular_computer_radar_trackable"
#define COMPONENT_RADAR_TRACK_ANYWAY (1<<0)
#define COMPONENT_RADAR_DONT_TRACK (1<<1)
/// From /datum/computer_file/program/radar/find_atom: (list/atom_container)
#define COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM "comsig_modular_computer_radar_find_atom"
/// From /datum/computer_file/program/radar/ui_act, when action is "selecttarget": (selected_ref)
#define COMSIG_MODULAR_COMPUTER_RADAR_SELECTED "comsig_modular_computer_radar_selected"
/// from /obj/item/modular_computer/imprint_id(): (name, job)
#define COMSIG_MODULAR_PDA_IMPRINT_UPDATED "comsig_modular_pda_imprint_updated"
/// from /obj/item/modular_computer/reset_id(): ()
#define COMSIG_MODULAR_PDA_IMPRINT_RESET "comsig_modular_pda_imprint_reset"
/// From /datum/computer_file/program/messenger/receive_message, sent to the computer: (signal/subspace/messaging/tablet_message/signal, sender_job, sender_name)
#define COMSIG_MODULAR_PDA_MESSAGE_RECEIVED "comsig_modular_pda_message_received"
/// From /datum/computer_file/program/messenger/send_message_signal, sent to the computer: (atom/origin, datum/signal/subspace/messaging/tablet_message/signal)
#define COMSIG_MODULAR_PDA_MESSAGE_SENT "comsig_modular_pda_message_sent"
+2
View File
@@ -28,6 +28,8 @@
#define PROGRAM_HEADER (1<<4)
///The program will run despite the ModPC not having any power in it.
#define PROGRAM_RUNS_WITHOUT_POWER (1<<5)
///The circuit ports of this program can be triggered even if the program is not open
#define PROGRAM_CIRCUITS_RUN_WHEN_CLOSED (1<<6)
//Program categories
#define PROGRAM_CATEGORY_DEVICE "Device Tools"
+2
View File
@@ -796,6 +796,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_PDA_CAN_EXPLODE "pda_can_explode"
///The download speeds of programs from the dowloader is halved.
#define TRAIT_MODPC_HALVED_DOWNLOAD_SPEED "modpc_halved_download_speed"
///Dictates whether a user (source) is interacting with the frame of a stationary modular computer or the pc inside it. Needed for circuits I guess.
#define TRAIT_MODPC_INTERACTING_WITH_FRAME "modpc_interacting_with_frame"
/// If present on a [/mob/living/carbon], will make them appear to have a medium level disease on health HUDs.
#define TRAIT_DISEASELIKE_SEVERITY_MEDIUM "diseaselike_severity_medium"
+3
View File
@@ -563,6 +563,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
/obj/item/reagent_containers = list(
"TRAIT_MAY_CONTAIN_BLENDED_DUST" = TRAIT_MAY_CONTAIN_BLENDED_DUST,
),
/obj/machinery/modular_computer = list(
"TRAIT_MODPC_INTERACTING_WITH_FRAME" = TRAIT_MODPC_INTERACTING_WITH_FRAME,
),
/obj/projectile = list(
"TRAIT_ALWAYS_HIT_ZONE" = TRAIT_ALWAYS_HIT_ZONE,
),
+12 -7
View File
@@ -1,6 +1,6 @@
/// Makes an atom a shell that is able to take in an attached circuit.
/datum/component/shell
dupe_mode = COMPONENT_DUPE_UNIQUE
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// The circuitboard attached to this shell
var/obj/item/integrated_circuit/attached_circuit
@@ -60,12 +60,15 @@
unremovable_circuit_components = list()
for(var/obj/item/circuit_component/circuit_component as anything in components)
if(ispath(circuit_component))
circuit_component = new circuit_component()
circuit_component.removable = FALSE
circuit_component.set_circuit_size(0)
RegisterSignal(circuit_component, COMSIG_CIRCUIT_COMPONENT_SAVE, PROC_REF(save_component))
unremovable_circuit_components += circuit_component
add_unremovable_circuit_component(circuit_component)
/datum/component/shell/proc/add_unremovable_circuit_component(obj/item/circuit_component/component)
if(ispath(component))
component = new component()
component.removable = FALSE
component.set_circuit_size(0)
RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_SAVE, PROC_REF(save_component))
unremovable_circuit_components += component
/datum/component/shell/proc/save_component(datum/source, list/objects)
SIGNAL_HANDLER
@@ -305,6 +308,7 @@
return
locked = FALSE
attached_circuit = circuitboard
SEND_SIGNAL(src, COMSIG_SHELL_CIRCUIT_ATTACHED)
if(!(shell_flags & SHELL_FLAG_CIRCUIT_UNREMOVABLE) && !circuitboard.admin_only)
RegisterSignal(circuitboard, COMSIG_MOVABLE_MOVED, PROC_REF(on_circuit_moved))
if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR)
@@ -348,6 +352,7 @@
attached_circuit.remove_component(to_remove)
to_remove.moveToNullspace()
attached_circuit.set_locked(FALSE)
SEND_SIGNAL(src, COMSIG_SHELL_CIRCUIT_REMOVED)
attached_circuit = null
/datum/component/shell/proc/on_atom_usb_cable_try_attach(atom/source, obj/item/usb_cable/usb_cable, mob/user)
+16 -7
View File
@@ -384,17 +384,18 @@
// PDA signal responses
/datum/component/uplink/proc/new_ringtone(datum/source, mob/living/user, new_ring_text)
/datum/component/uplink/proc/new_ringtone(datum/source, atom/source, new_ring_text)
SIGNAL_HANDLER
if(trim(lowertext(new_ring_text)) != trim(lowertext(unlock_code)))
if(trim(lowertext(new_ring_text)) == trim(lowertext(failsafe_code)))
failsafe(user)
failsafe(source)
return COMPONENT_STOP_RINGTONE_CHANGE
return
locked = FALSE
interact(null, user)
to_chat(user, span_hear("The computer softly beeps."))
if(ismob(source))
interact(null, source)
to_chat(source, span_hear("The computer softly beeps."))
return COMPONENT_STOP_RINGTONE_CHANGE
/datum/component/uplink/proc/check_detonate()
@@ -500,14 +501,22 @@
locked = FALSE
replacement_uplink.balloon_alert_to_viewers("beep", vision_distance = COMBAT_MESSAGE_RANGE)
/datum/component/uplink/proc/failsafe(mob/living/carbon/user)
/datum/component/uplink/proc/failsafe(atom/source)
if(!parent)
return
var/turf/T = get_turf(parent)
if(!T)
return
message_admins("[ADMIN_LOOKUPFLW(user)] has triggered an uplink failsafe explosion at [AREACOORD(T)] The owner of the uplink was [ADMIN_LOOKUPFLW(owner)].")
user.log_message("triggered an uplink failsafe explosion. Uplink owner: [key_name(owner)].", LOG_ATTACK)
var/user_deets = "an uplink failsafe explosion has been triggered"
if(ismob(source))
user_deets = "[ADMIN_LOOKUPFLW(source)] has triggered an uplink failsafe explosion"
source.log_message("triggered an uplink failsafe explosion. Uplink owner: [key_name(owner)].", LOG_ATTACK)
else if(istype(source, /obj/item/circuit_component))
var/obj/item/circuit_component/circuit = source
user_deets = "[circuit.parent.get_creator_admin()] has triggered an uplink failsafe explosion"
else
source?.log_message("somehow triggered an uplink failsafe explosion. Uplink owner: [key_name(owner)].", LOG_ATTACK)
message_admins("[user_deets] at [AREACOORD(T)] The owner of the uplink was [ADMIN_LOOKUPFLW(owner)].")
explosion(parent, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 3)
qdel(parent) //Alternatively could brick the uplink.
@@ -110,6 +110,15 @@
///The max amount of paper that can be held at once.
var/max_paper = 30
/// The capacity of the circuit shell component of this item
var/shell_capacity = SHELL_CAPACITY_MEDIUM
/**
* Reference to the circuit shell component, because we're special and do special things with it,
* such as creating and deleting unremovable circuit comps based on the programs installed.
*/
var/datum/component/shell/shell
/datum/armor/item_modular_computer
bullet = 20
laser = 20
@@ -120,6 +129,7 @@
START_PROCESSING(SSobj, src)
if(!physical)
physical = src
add_shell_component(shell_capacity)
set_light_color(comp_light_color)
set_light_range(comp_light_luminosity)
if(looping_sound)
@@ -137,6 +147,26 @@
register_context()
update_appearance()
///Initialize the shell for this item, or the physical machinery it belongs to.
/obj/item/modular_computer/proc/add_shell_component(capacity = SHELL_CAPACITY_MEDIUM, shell_flags = NONE)
shell = physical.AddComponent(/datum/component/shell, list(new /obj/item/circuit_component/modpc), capacity, shell_flags)
RegisterSignal(shell, COMSIG_SHELL_CIRCUIT_ATTACHED, PROC_REF(on_circuit_attached))
RegisterSignal(shell, COMSIG_SHELL_CIRCUIT_REMOVED, PROC_REF(on_circuit_removed))
/obj/item/modular_computer/proc/on_circuit_attached(datum/source)
SIGNAL_HANDLER
RegisterSignal(shell.attached_circuit, COMSIG_CIRCUIT_PRE_POWER_USAGE, PROC_REF(use_power_for_circuits))
///Try to draw power from our internal cell first, before switching to that of the circuit.
/obj/item/modular_computer/proc/use_power_for_circuits(datum/source, power_usage_per_input)
SIGNAL_HANDLER
if(use_power(power_usage_per_input, check_programs = FALSE))
return COMPONENT_OVERRIDE_POWER_USAGE
/obj/item/modular_computer/proc/on_circuit_removed(datum/source)
SIGNAL_HANDLER
UnregisterSignal(shell.attached_circuit, COMSIG_CIRCUIT_PRE_POWER_USAGE)
/obj/item/modular_computer/proc/install_default_programs()
SHOULD_CALL_PARENT(FALSE)
for(var/programs in default_programs + starting_programs)
@@ -158,6 +188,7 @@
if(computer_id_slot)
QDEL_NULL(computer_id_slot)
shell = null
physical = null
return ..()
@@ -438,29 +469,32 @@
/obj/item/modular_computer/proc/turn_on(mob/user, open_ui = TRUE)
var/issynth = issilicon(user) // Robots and AIs get different activation messages.
if(atom_integrity <= integrity_failure * max_integrity)
if(issynth)
to_chat(user, span_warning("You send an activation signal to \the [src], but it responds with an error code. It must be damaged."))
else
to_chat(user, span_warning("You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again."))
if(user)
if(issynth)
to_chat(user, span_warning("You send an activation signal to \the [src], but it responds with an error code. It must be damaged."))
else
to_chat(user, span_warning("You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again."))
return FALSE
if(use_power()) // checks if the PC is powered
if(issynth)
to_chat(user, span_notice("You send an activation signal to \the [src], turning it on."))
else
to_chat(user, span_notice("You press the power button and start up \the [src]."))
if(looping_sound)
soundloop.start()
enabled = TRUE
update_appearance()
if(open_ui)
update_tablet_open_uis(user)
if(user)
if(issynth)
to_chat(user, span_notice("You send an activation signal to \the [src], turning it on."))
else
to_chat(user, span_notice("You press the power button and start up \the [src]."))
if(open_ui)
update_tablet_open_uis(user)
return TRUE
else // Unpowered
if(issynth)
to_chat(user, span_warning("You send an activation signal to \the [src] but it does not respond."))
else
to_chat(user, span_warning("You press the power button but \the [src] does not respond."))
if(user)
if(issynth)
to_chat(user, span_warning("You send an activation signal to \the [src] but it does not respond."))
else
to_chat(user, span_warning("You press the power button but \the [src] does not respond."))
return FALSE
// Process currently calls handle_power(), may be expanded in future if more things are added.
@@ -572,18 +606,22 @@
if(program.computer != src)
CRASH("tried to open program that does not belong to this computer")
if(!program || !istype(program)) // Program not found or it's not executable program.
if(isnull(program) || !istype(program)) // Program not found or it's not executable program.
if(user)
to_chat(user, span_danger("\The [src]'s screen shows \"I/O ERROR - Unable to run program\" warning."))
return FALSE
if(active_program == program)
return FALSE
// The program is already running. Resume it.
if(program in idle_threads)
active_program?.background_program()
active_program = program
program.alert_pending = FALSE
idle_threads.Remove(program)
if(open_ui)
update_tablet_open_uis(user)
INVOKE_ASYNC(src, PROC_REF(update_tablet_open_uis), user)
update_appearance(UPDATE_ICON)
return TRUE
@@ -603,10 +641,12 @@
if(!program.on_start(user))
return FALSE
active_program?.background_program()
active_program = program
program.alert_pending = FALSE
if(open_ui)
update_tablet_open_uis(user)
INVOKE_ASYNC(src, PROC_REF(update_tablet_open_uis), user)
update_appearance(UPDATE_ICON)
return TRUE
@@ -682,10 +722,11 @@
* It is separated from ui_act() to be overwritten as needed.
*/
/obj/item/modular_computer/proc/toggle_flashlight(mob/user)
if(!has_light || !internal_cell || !internal_cell.charge)
if(!has_light || !internal_cell?.charge)
return FALSE
if(!COOLDOWN_FINISHED(src, disabled_time))
balloon_alert(user, "disrupted!")
if(user)
balloon_alert(user, "disrupted!")
return FALSE
set_light_on(!light_on)
update_appearance()
@@ -0,0 +1,76 @@
///A simple circuit component compatible with stationary consoles, laptops and PDAs, independent from programs.
/obj/item/circuit_component/modpc
display_name = "Modular Computer"
desc = "Circuit for basic functions of a modular computer."
var/obj/item/modular_computer/computer
///Turns the PC on/off
var/datum/port/input/on_off
///When set, will print a piece of paper with the value as text.
var/datum/port/input/print
///Toggles lights on and off. Also RGB.
var/datum/port/input/lights
var/datum/port/input/red
var/datum/port/input/green
var/datum/port/input/blue
/obj/item/circuit_component/modpc/register_shell(atom/movable/shell)
. = ..()
if(istype(shell, /obj/item/modular_computer))
computer = shell
else if(istype(shell, /obj/machinery/modular_computer))
var/obj/machinery/modular_computer/console = shell
computer = console.cpu
/**
* Some mod pc have lights while some don't, but populate_ports()
* is called before we get to know which object this has attahed to,
* I hope you're cool with me doing it here.
*/
if(computer?.has_light)
lights = add_input_port("Toggle Lights", PORT_TYPE_SIGNAL)
red = add_input_port("Red", PORT_TYPE_NUMBER)
green = add_input_port("Green", PORT_TYPE_NUMBER)
blue = add_input_port("Blue", PORT_TYPE_NUMBER)
/obj/item/circuit_component/modpc/unregister_shell(atom/movable/shell)
computer = null
return ..()
/obj/item/circuit_component/modpc/populate_ports()
on_off = add_input_port("Turn On/Off", PORT_TYPE_SIGNAL)
print = add_input_port("Print Text", PORT_TYPE_STRING)
/obj/item/circuit_component/modpc/pre_input_received(datum/port/input/port)
if(isnull(computer))
return
if(COMPONENT_TRIGGERED_BY(print, port))
print.set_value(html_encode(trim(print.value, MAX_PAPER_LENGTH)))
else if(COMPONENT_TRIGGERED_BY(red, port))
red.set_value(clamp(red.value, 0, 255))
else if(COMPONENT_TRIGGERED_BY(blue, port))
blue.set_value(clamp(blue.value, 0, 255))
else if(COMPONENT_TRIGGERED_BY(green, port))
green.set_value(clamp(green.value, 0, 255))
/obj/item/circuit_component/modpc/input_received(datum/port/input/port)
if(isnull(computer))
return
if(COMPONENT_TRIGGERED_BY(on_off, port))
if(computer.enabled)
INVOKE_ASYNC(computer, TYPE_PROC_REF(/obj/item/modular_computer, shutdown_computer))
else
INVOKE_ASYNC(computer, TYPE_PROC_REF(/obj/item/modular_computer, turn_on))
return
if(!computer.enabled)
return
if(COMPONENT_TRIGGERED_BY(print, port))
computer.print_text(print.value)
if(lights)
if(COMPONENT_TRIGGERED_BY(lights, port))
computer.toggle_flashlight()
if(COMPONENT_TRIGGERED_BY(red, port) || COMPONENT_TRIGGERED_BY(green, port) || COMPONENT_TRIGGERED_BY(blue, port))
computer.set_flashlight_color(rgb(red.value || 0, green.value || 0, blue.value || 0))
@@ -3,23 +3,24 @@
///Draws power from its rightful source (area if its a computer, the cell otherwise)
///Takes into account special cases, like silicon PDAs through override, and nopower apps.
/obj/item/modular_computer/proc/use_power(amount = 0)
if(check_power_override())
/obj/item/modular_computer/proc/use_power(amount = 0, check_programs = TRUE)
if(check_power_override(amount))
return TRUE
if(!internal_cell)
return FALSE
if(!internal_cell.charge && (isnull(active_program) || !(active_program.program_flags & PROGRAM_RUNS_WITHOUT_POWER)))
close_all_programs()
for(var/datum/computer_file/program/programs as anything in stored_files)
if((programs.program_flags & PROGRAM_RUNS_WITHOUT_POWER) && open_program(program = programs))
return TRUE
if(internal_cell.use(amount JOULES))
return TRUE
if(!check_programs)
return FALSE
if(!internal_cell.use(amount JOULES))
internal_cell.use(min(amount JOULES, internal_cell.charge)) //drain it anyways.
return FALSE
return TRUE
internal_cell.use(min(amount JOULES, internal_cell.charge)) //drain it anyways.
if(active_program?.program_flags & PROGRAM_RUNS_WITHOUT_POWER)
return TRUE
INVOKE_ASYNC(src, PROC_REF(close_all_programs))
for(var/datum/computer_file/program/programs as anything in stored_files)
if((programs.program_flags & PROGRAM_RUNS_WITHOUT_POWER) && open_program(program = programs))
return TRUE
return FALSE
/obj/item/modular_computer/proc/give_power(amount)
if(internal_cell)
@@ -60,8 +61,8 @@
///Returns TRUE if the PC should not be using any power, FALSE otherwise.
///Checks to see if the current app allows to be ran without power, if so we'll run with it.
/obj/item/modular_computer/proc/check_power_override()
return (!internal_cell?.charge && (active_program?.program_flags & PROGRAM_RUNS_WITHOUT_POWER))
/obj/item/modular_computer/proc/check_power_override(amount)
return !amount && !internal_cell?.charge && (active_program?.program_flags & PROGRAM_RUNS_WITHOUT_POWER)
//Integrated (Silicon) tablets don't drain power, because the tablet is required to state laws, so it being disabled WILL cause problems.
/obj/item/modular_computer/pda/silicon/check_power_override()
@@ -146,7 +146,7 @@
if("PC_minimize")
if(!active_program || (!isnull(internal_cell) && !internal_cell.charge))
return
active_program.background_program()
active_program.background_program(usr)
return TRUE
if("PC_killprogram")
@@ -26,6 +26,8 @@
comp_light_luminosity = 2.3 //this is what old PDAs were set to
looping_sound = FALSE
shell_capacity = SHELL_CAPACITY_SMALL
///The item currently inserted into the PDA, starts with a pen.
var/obj/item/inserted_item = /obj/item/pen
@@ -337,6 +339,10 @@
silicon_owner = null
return ..()
///Silicons don't have the tools (or hands) to make circuits setups with their own PDAs.
/obj/item/modular_computer/pda/silicon/add_shell_component(capacity)
return
/obj/item/modular_computer/pda/silicon/turn_on(mob/user, open_ui = FALSE)
if(silicon_owner?.stat != DEAD)
return ..()
@@ -7,7 +7,8 @@
icon_state = null
icon_state_unpowered = null
icon_state_menu = null
hardware_flag = 0
hardware_flag = NONE
internal_cell = /obj/item/stock_parts/cell/crap
///The modular computer MACHINE that hosts us.
var/obj/machinery/modular_computer/machinery_computer
@@ -25,7 +26,6 @@
physical = loc
machinery_computer = loc
machinery_computer.cpu = src
internal_cell = machinery_computer.internal_cell
hardware_flag = machinery_computer.hardware_flag
steel_sheet_cost = machinery_computer.steel_sheet_cost
max_idle_programs = machinery_computer.max_idle_programs
@@ -44,12 +44,12 @@
machinery_computer = null
return ..()
/obj/item/modular_computer/processor/use_power(amount = 0)
/obj/item/modular_computer/processor/use_power(amount = 0, check_programs = TRUE)
var/obj/machinery/machine_holder = physical
if(machine_holder.powered())
machine_holder.use_power(amount)
return TRUE
return FALSE
return ..()
/obj/item/modular_computer/processor/relay_qdel()
qdel(machinery_computer)
@@ -1,3 +1,5 @@
#define CPU_INTERACTABLE(user) (cpu && !HAS_TRAIT_FROM(src, TRAIT_MODPC_INTERACTING_WITH_FRAME, REF(user)))
// Modular Computer - A machinery that is mostly just a host to the Modular Computer item.
/obj/machinery/modular_computer
name = "modular computer"
@@ -9,8 +11,6 @@
max_integrity = 300
integrity_failure = 0.5
///The power cell, null by default as we use the APC we're in
var/internal_cell = null
///A flag that describes this device type
var/hardware_flag = PROGRAM_CONSOLE
/// Amount of programs that can be ran at once
@@ -41,23 +41,42 @@
. = ..()
cpu = new(src)
cpu.screen_on = TRUE
cpu.add_shell_component(SHELL_CAPACITY_LARGE, SHELL_FLAG_USB_PORT)
update_appearance()
register_context()
/obj/machinery/modular_computer/Destroy()
QDEL_NULL(cpu)
return ..()
/obj/machinery/modular_computer/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if(isnull(held_item))
context[SCREENTIP_CONTEXT_RMB] = "Toggle processor interaction"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/modular_computer/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return
if(HAS_TRAIT_FROM(src, TRAIT_MODPC_INTERACTING_WITH_FRAME, REF(user)))
REMOVE_TRAIT(src, TRAIT_MODPC_INTERACTING_WITH_FRAME, REF(user))
balloon_alert(user, "now interacting with computer")
else
ADD_TRAIT(src, TRAIT_MODPC_INTERACTING_WITH_FRAME, REF(user))
balloon_alert(user, "now interacting with frame")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/machinery/modular_computer/examine(mob/user)
if(cpu)
return cpu.examine(user)
return ..()
. = cpu?.examine(user) || ..()
. += span_info("You can toggle interaction between computer and its machinery frame with [EXAMINE_HINT("Right-Click")] while empty-handed.")
. += span_info("Currently interacting with [EXAMINE_HINT(HAS_TRAIT_FROM(src, TRAIT_MODPC_INTERACTING_WITH_FRAME, REF(user)) ? "frame" : "computer")].")
/obj/machinery/modular_computer/attack_ghost(mob/dead/observer/user)
. = ..()
if(.)
return
if(cpu)
cpu.attack_ghost(user)
cpu?.attack_ghost(user)
/obj/machinery/modular_computer/emag_act(mob/user, obj/item/card/emag/emag_card)
if(!cpu)
@@ -98,17 +117,14 @@
/obj/machinery/modular_computer/AltClick(mob/user)
. = ..()
if(!can_interact(user))
if(CPU_INTERACTABLE(user) || !can_interact(user))
return
if(cpu)
cpu.AltClick(user)
cpu.AltClick(user)
//ATTACK HAND IGNORING PARENT RETURN VALUE
// On-click handling. Turns on the computer if it's off and opens the GUI.
/obj/machinery/modular_computer/interact(mob/user)
if(cpu)
return cpu.interact(user)
return ..()
return CPU_INTERACTABLE(user) ? cpu.interact(user) : ..()
// Modular computers can have battery in them, we handle power in previous proc, so prevent this from messing it up for us.
/obj/machinery/modular_computer/power_change()
@@ -118,30 +134,33 @@
return
return ..()
///Try to recharge our internal cell if it isn't fully charged.
/obj/machinery/modular_computer/process(seconds_per_tick)
var/obj/item/stock_parts/cell/cell = get_cell()
if(isnull(cell) || cell.percent() >= 100)
return
var/power_to_draw = idle_power_usage * seconds_per_tick * 0.5
if(!use_power_from_net(power_to_draw))
return
cell.give(power_to_draw)
/obj/machinery/modular_computer/get_cell()
return cpu?.internal_cell
/obj/machinery/modular_computer/screwdriver_act(mob/user, obj/item/tool)
if(cpu)
return cpu.screwdriver_act(user, tool)
return ..()
return CPU_INTERACTABLE(user) ? cpu.screwdriver_act(user, tool) : ..()
/obj/machinery/modular_computer/wrench_act_secondary(mob/user, obj/item/tool)
if(cpu)
return cpu.wrench_act_secondary(user, tool)
return ..()
return CPU_INTERACTABLE(user) ? cpu.wrench_act_secondary(user, tool) : ..()
/obj/machinery/modular_computer/welder_act(mob/user, obj/item/tool)
if(cpu)
return cpu.welder_act(user, tool)
return ..()
return CPU_INTERACTABLE(user) ? cpu.welder_act(user, tool) : ..()
/obj/machinery/modular_computer/attackby(obj/item/W as obj, mob/living/user)
if (cpu && !user.combat_mode && !(obj_flags & NO_DECONSTRUCTION))
return cpu.attackby(W, user)
return ..()
/obj/machinery/modular_computer/attackby(obj/item/weapon, mob/living/user)
return (CPU_INTERACTABLE(user) && !user.combat_mode) ? cpu.attackby(weapon, user) : ..()
/obj/machinery/modular_computer/attacked_by(obj/item/attacking_item, mob/living/user)
if (cpu)
return cpu.attacked_by(attacking_item, user)
return ..()
return CPU_INTERACTABLE(user) ? cpu.attacked_by(attacking_item, user) : ..()
// Stronger explosions cause serious damage to internal components
// Minor explosions are mostly mitigitated by casing.
@@ -170,6 +189,6 @@
// "Burn" damage is equally strong against internal components and exterior casing
// "Brute" damage mostly damages the casing.
/obj/machinery/modular_computer/bullet_act(obj/projectile/Proj)
if(cpu)
return cpu.bullet_act(Proj)
return ..()
return cpu?.bullet_act(Proj) || ..()
#undef CPU_INTERACTABLE
@@ -52,6 +52,7 @@
///Called post-installation of an application in a computer, after 'computer' var is set.
/datum/computer_file/proc/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing)
SIGNAL_HANDLER
SHOULD_CALL_PARENT(TRUE)
computer_installing.stored_files.Add(src)
/**
@@ -44,6 +44,40 @@
var/alert_pending = FALSE
/// How well this program will help combat detomatix viruses.
var/detomatix_resistance = NONE
/// Unremovable circuit componentn added to the physical computer while the program is installed
var/obj/item/circuit_component/mod_program/circuit_comp_type
/datum/computer_file/program/New()
..()
///We need to ensure that different programs (subtypes mostly) won't try to load in the same circuit comps into the shell or usb port of the modpc.
if(circuit_comp_type && initial(circuit_comp_type.associated_program) != type)
stack_trace("circuit comp type mismatch: [type] has circuit comp type \[[circuit_comp_type]\], while \[[circuit_comp_type]\] has associated program \[[initial(circuit_comp_type.associated_program)]\].")
/**
* Here we deal with peculiarity of adding unremovable components to the computer shell.
* It probably doesn't look badass, but it's a decent way of doing it without taining the component with
* oddities like this.
*/
/datum/computer_file/program/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing)
. = ..()
if(isnull(circuit_comp_type) || isnull(computer.shell))
return
if(!(locate(circuit_comp_type) in computer.shell.unremovable_circuit_components))
var/obj/item/circuit_component/mod_program/comp = new circuit_comp_type()
computer.shell.add_unremovable_circuit_component(comp)
if(computer.shell.attached_circuit)
comp.forceMove(computer.shell.attached_circuit)
computer.shell.attached_circuit.add_component(comp)
///Here we deal with killing the associated components instead.
/datum/computer_file/program/Destroy()
if(isnull(circuit_comp_type) || isnull(computer?.shell))
return ..()
for(var/obj/item/circuit_component/mod_program/comp in computer.shell.unremovable_circuit_components)
if(comp.associated_program == src)
computer.shell.unremovable_circuit_components -= comp
qdel(comp)
return ..()
/datum/computer_file/program/clone()
var/datum/computer_file/program/temp = ..()
@@ -67,10 +101,6 @@
/datum/computer_file/program/ui_interact(mob/user, datum/tgui/ui)
SHOULD_CALL_PARENT(FALSE)
///We are not calling parent as it's handled by the computer itself, this is only called after.
/datum/computer_file/program/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
SHOULD_CALL_PARENT(FALSE)
// Relays icon update to the computer.
/datum/computer_file/program/proc/update_computer_icon()
if(computer)
@@ -167,12 +197,13 @@
**/
/datum/computer_file/program/proc/on_start(mob/living/user)
SHOULD_CALL_PARENT(TRUE)
if(can_run(user, loud = TRUE))
if(program_flags & PROGRAM_REQUIRES_NTNET)
var/obj/item/card/id/ID = computer.computer_id_slot?.GetID()
generate_network_log("Connection opened -- Program ID:[filename] User:[ID?"[ID.registered_name]":"None"]")
return TRUE
return FALSE
if(!can_run(user, loud = TRUE))
return FALSE
if(program_flags & PROGRAM_REQUIRES_NTNET)
var/obj/item/card/id/ID = computer.computer_id_slot?.GetID()
generate_network_log("Connection opened -- Program ID:[filename] User:[ID?"[ID.registered_name]":"None"]")
SEND_SIGNAL(src, COMSIG_COMPUTER_PROGRAM_START, user)
return TRUE
/**
* Kills the running program
@@ -189,25 +220,29 @@
computer.active_program = null
if(!QDELETED(computer) && computer.enabled)
INVOKE_ASYNC(computer, TYPE_PROC_REF(/obj/item/modular_computer, update_tablet_open_uis), user)
if(src in computer.idle_threads)
else if(src in computer.idle_threads)
computer.idle_threads.Remove(src)
else //The program wasn't running to begin with.
return FALSE
if(program_flags & PROGRAM_REQUIRES_NTNET)
var/obj/item/card/id/ID = computer.computer_id_slot?.GetID()
generate_network_log("Connection closed -- Program ID: [filename] User:[ID ? "[ID.registered_name]" : "None"]")
computer.update_appearance(UPDATE_ICON)
SEND_SIGNAL(src, COMSIG_COMPUTER_PROGRAM_KILL, user)
return TRUE
///Sends the running program to the background/idle threads. Header programs can't be minimized and will kill instead.
/datum/computer_file/program/proc/background_program()
/datum/computer_file/program/proc/background_program(mob/user)
SHOULD_CALL_PARENT(TRUE)
if(program_flags & PROGRAM_HEADER)
if(program_flags & PROGRAM_HEADER || length(computer.idle_threads) > computer.max_idle_programs)
return kill_program()
computer.idle_threads.Add(src)
computer.active_program = null
computer.update_tablet_open_uis(usr)
if(user)
INVOKE_ASYNC(computer, TYPE_PROC_REF(/obj/item/modular_computer, update_tablet_open_uis), user)
computer.update_appearance(UPDATE_ICON)
return TRUE
@@ -0,0 +1,93 @@
/**
* Circuit components of modular programs are special.
* They're added to the unremovable components of the shell when the prog is installed and deleted if uninstalled.
* This means they don't work like normal unremovable comps that live and die along with their shell.
*/
/obj/item/circuit_component/mod_program
display_name = "Abstract Modular Program"
desc = "I've spent lot of time thinking how to get this to work. If you see this, I either failed or someone else did, so report it."
/**
* The program that installed us into the shell/usb_port comp. Needed to avoid having too many signals for every program.
* This is also the program we need to install on the modular computer if the circuit is admin-loaded.
* Just make sure each of these components is associated to one and only type of program, no subtypes of anything.
*/
var/datum/computer_file/program/associated_program
///Starts the program if possible, placing it in the background if another's active.
var/datum/port/input/start
///kills the program.
var/datum/port/input/kill
///binary for whether the program is running or not
var/datum/port/output/running
/obj/item/circuit_component/mod_program/Initialize(mapload)
if(associated_program)
display_name = initial(associated_program.filedesc)
desc = initial(associated_program.extended_desc)
return ..() // Set the name correctly
/obj/item/circuit_component/mod_program/register_shell(atom/movable/shell)
. = ..()
var/obj/item/modular_computer/computer
if(istype(shell, /obj/item/modular_computer))
computer = shell
else if(istype(shell, /obj/machinery/modular_computer))
var/obj/machinery/modular_computer/console = shell
computer = console.cpu
///Find the associated program in the computer's stored_files (install it otherwise) and store a reference to it.
var/datum/computer_file/program/found_program = locate(associated_program) in computer.stored_files
///The integrated circuit was loaded/duplicated
if(isnull(found_program))
associated_program = new associated_program()
computer.store_file(associated_program)
else
associated_program = found_program
RegisterSignal(associated_program, COMSIG_COMPUTER_PROGRAM_START, PROC_REF(on_start))
RegisterSignal(associated_program, COMSIG_COMPUTER_PROGRAM_KILL, PROC_REF(on_kill))
/obj/item/circuit_component/mod_program/unregister_shell()
UnregisterSignal(associated_program, list(COMSIG_COMPUTER_PROGRAM_START, COMSIG_COMPUTER_PROGRAM_KILL))
associated_program = initial(associated_program)
return ..()
/obj/item/circuit_component/mod_program/populate_ports()
. = ..()
SHOULD_CALL_PARENT(TRUE)
start = add_input_port("Start", PORT_TYPE_SIGNAL, trigger = PROC_REF(start_prog))
kill = add_input_port("Kill", PORT_TYPE_SIGNAL, trigger = PROC_REF(kill_prog))
running = add_output_port("Running", PORT_TYPE_NUMBER)
///For most programs, triggers only work if they're open (either active or idle).
/obj/item/circuit_component/mod_program/should_receive_input(datum/port/input/port)
. = ..()
if(!.)
return FALSE
if(isnull(associated_program))
return FALSE
if(associated_program.program_flags & PROGRAM_CIRCUITS_RUN_WHEN_CLOSED || COMPONENT_TRIGGERED_BY(start, port))
return TRUE
var/obj/item/modular_computer/computer = associated_program.computer
if(computer.active_program == associated_program || (associated_program in computer.idle_threads))
return TRUE
return FALSE
/obj/item/circuit_component/mod_program/proc/start_prog(datum/port/input/port)
associated_program.computer.open_program(program = associated_program)
/obj/item/circuit_component/mod_program/proc/on_start(mob/living/user)
SIGNAL_HANDLER
running.set_value(TRUE)
/obj/item/circuit_component/mod_program/proc/kill_prog(datum/port/input/port)
associated_program.kill_program()
/obj/item/circuit_component/mod_program/proc/on_kill(mob/living/user)
SIGNAL_HANDLER
running.set_value(FALSE)
/obj/item/circuit_component/mod_program/get_ui_notices()
. = ..()
if(!(associated_program.program_flags & PROGRAM_CIRCUITS_RUN_WHEN_CLOSED))
. += create_ui_notice("Requires program to be running", "purple")
@@ -97,6 +97,7 @@
/datum/computer_file/program/ai_restorer/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_beginReconstruction")
if(!stored_card || !stored_card.AI)
@@ -29,7 +29,7 @@
traitor_data = null
return ..()
/datum/computer_file/program/contract_uplink/ui_act(action, params)
/datum/computer_file/program/contract_uplink/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
@@ -38,6 +38,7 @@
return ..()
/datum/computer_file/program/ntnet_dos/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_target_relay")
for(var/obj/machinery/ntnet_relay/relays as anything in SSmachines.get_machines_by_type(/obj/machinery/ntnet_relay))
@@ -38,6 +38,7 @@
spark_system.start()
/datum/computer_file/program/revelation/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_arm")
armed = !armed
@@ -107,7 +107,8 @@
data["BossID"] = "boss[boss_id].gif"
return data
/datum/computer_file/program/arcade/ui_act(action, list/params)
/datum/computer_file/program/arcade/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
usr.played_game()
var/gamerSkillLevel = 0
var/gamerSkill = 0
@@ -67,7 +67,8 @@
data["gasmixes"] = last_gasmix_data
return data
/datum/computer_file/program/atmosscan/ui_act(action, list/params)
/datum/computer_file/program/atmosscan/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("scantoggle")
if(atmozphere_mode == ATMOZPHERE_SCAN_CLICK)
@@ -10,6 +10,7 @@
size = 5
tgui_id = "NtosCyborgRemoteMonitor"
program_icon = "project-diagram"
circuit_comp_type = /obj/item/circuit_component/mod_program/borg_monitor
var/list/loglist = list() ///A list to copy a borg's IC log list into
var/mob/living/silicon/robot/DL_source ///reference of a borg if we're downloading a log, or null if not.
var/DL_progress = -1 ///Progress of current download, 0 to 100, -1 for no current download
@@ -105,29 +106,46 @@
return data
/datum/computer_file/program/borg_monitor/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("messagebot")
var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs
if(!istype(R))
return TRUE
var/ID = checkID()
if(!ID)
return TRUE
if(R.stat == DEAD) //Dead borgs will listen to you no longer
to_chat(usr, span_warning("Error -- Could not open a connection to unit:[R]"))
var/message = tgui_input_text(usr, "Message to be sent to remote cyborg", "Send Message")
if(!message)
return TRUE
to_chat(R, "<br><br>[span_notice("Message from [ID] -- \"[message]\"")]<br>")
to_chat(usr, "Message sent to [R]: [message]")
R.logevent("Message from [ID] -- \"[message]\"")
SEND_SOUND(R, 'sound/machines/twobeep_high.ogg')
if(R.connected_ai)
to_chat(R.connected_ai, "<br><br>[span_notice("Message from [ID] to [R] -- \"[message]\"")]<br>")
SEND_SOUND(R.connected_ai, 'sound/machines/twobeep_high.ogg')
usr.log_talk(message, LOG_PDA, tag="Cyborg Monitor Program: ID name \"[ID]\" to [R]")
var/mob/living/silicon/robot/robot = locate(params["ref"]) in GLOB.silicon_mobs
message_robot(robot, usr)
return TRUE
/datum/computer_file/program/borg_monitor/proc/message_robot(mob/living/silicon/robot/robot, mob/user)
if(!istype(robot))
return TRUE
var/ID = checkID()
if(!ID)
return FALSE
if(robot.stat == DEAD) //Dead borgs will listen to you no longer
to_chat(user, span_warning("Error -- Could not open a connection to unit:[robot]"))
return FALSE
var/message = tgui_input_text(user, "Message to be sent to remote cyborg", "Send Message")
if(!message)
return FALSE
send_message(message, robot, user)
/datum/computer_file/program/borg_monitor/proc/send_message(message, mob/living/silicon/robot/robot, mob/user)
var/ID = checkID()
if(!ID)
return FALSE
if(robot.stat == DEAD) //Dead borgs will listen to you no longer
if(user)
to_chat(user, span_warning("Error -- Could not open a connection to unit:[robot]"))
return FALSE
to_chat(robot, "<br><br>[span_notice("Message from [ID] -- \"[message]\"")]<br>")
if(user)
to_chat(user, "Message sent to [robot]: [message]")
robot.logevent("Message from [ID] -- \"[message]\"")
SEND_SOUND(robot, 'sound/machines/twobeep_high.ogg')
if(robot.connected_ai)
to_chat(robot.connected_ai, "<br><br>[span_notice("Message from [ID] to [robot] -- \"[message]\"")]<br>")
SEND_SOUND(robot.connected_ai, 'sound/machines/twobeep_high.ogg')
user?.log_talk(message, LOG_PDA, tag = "Cyborg Monitor Program: ID name \"[ID]\" to [robot]")
return TRUE
///This proc is used to determin if a borg should be shown in the list (based on the borg's scrambledcodes var). Syndicate version overrides this to show only syndicate borgs.
/datum/computer_file/program/borg_monitor/proc/evaluate_borg(mob/living/silicon/robot/R)
if(!is_valid_z_level(get_turf(computer), get_turf(R)))
@@ -154,6 +172,7 @@
extended_desc = "This program allows for remote monitoring of mission-assigned cyborgs."
program_flags = PROGRAM_ON_SYNDINET_STORE
download_access = list()
circuit_comp_type = /obj/item/circuit_component/mod_program/borg_monitor/syndie
/datum/computer_file/program/borg_monitor/syndicate/evaluate_borg(mob/living/silicon/robot/R)
if(!is_valid_z_level(get_turf(computer), get_turf(R)))
@@ -164,3 +183,31 @@
/datum/computer_file/program/borg_monitor/syndicate/checkID()
return "\[CLASSIFIED\]" //no ID is needed for the syndicate version's message function, and the borg will see "[CLASSIFIED]" as the message sender.
/obj/item/circuit_component/mod_program/borg_monitor
associated_program = /datum/computer_file/program/borg_monitor
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
///Circuit input for the robot we want to message
var/datum/port/input/target_robot
///The message we want to send
var/datum/port/input/set_message
/obj/item/circuit_component/mod_program/borg_monitor/populate_ports()
. = ..()
target_robot = add_input_port("Receiver", PORT_TYPE_ATOM)
set_message = add_input_port("Set Message", PORT_TYPE_STRING, trigger = PROC_REF(sanitize_borg_message))
/obj/item/circuit_component/mod_program/borg_monitor/proc/sanitize_borg_message(datum/port/port)
set_message.set_value(trim(html_encode(set_message.value), MAX_MESSAGE_LEN))
/obj/item/circuit_component/mod_program/borg_monitor/input_received(datum/port/port)
if(!length(set_message.value) || !iscyborg(target_robot.value))
return
var/mob/living/silicon/robot/robot = target_robot.value
var/datum/computer_file/program/borg_monitor/monitor = associated_program
if(monitor.send_message(set_message.value, robot))
monitor.computer.log_talk("Cyborg Monitor message (ID name \"[monitor.checkID()]\") sent to [key_name(robot)] by [parent.get_creator()]: [set_message.value]")
/obj/item/circuit_component/mod_program/borg_monitor/syndie
associated_program = /datum/computer_file/program/borg_monitor/syndicate
@@ -57,7 +57,8 @@
data["bountyText"] = bounty_text
return data
/datum/computer_file/program/bounty_board/ui_act(action, list/params)
/datum/computer_file/program/bounty_board/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/current_ref_num = params["request"]
var/current_app_num = params["applicant"]
var/datum/bank_account/request_target
@@ -166,7 +166,8 @@
data["max_order"] = CARGO_MAX_ORDER
return data
/datum/computer_file/program/budgetorders/ui_act(action, params, datum/tgui/ui)
/datum/computer_file/program/budgetorders/ui_act(action, params, datum/tgui/ui, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("send")
if(!SSshuttle.supply.canMove())
@@ -87,6 +87,7 @@
return ..()
/datum/computer_file/program/card_mod/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/mob/user = usr
var/obj/item/card/id/inserted_auth_card = computer.computer_id_slot
@@ -26,7 +26,8 @@
data["barcode_split"] = cut_multiplier * 100
return data
/datum/computer_file/program/shipping/ui_act(action, list/params)
/datum/computer_file/program/shipping/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(!computer.computer_id_slot) //We need an ID to successfully run
return FALSE
@@ -49,6 +49,7 @@
return new_converstaion
/datum/computer_file/program/chatclient/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/datum/ntnet_conversation/channel = SSmodular_computers.get_chat_channel_by_id(active_channel)
var/authed = FALSE
if(channel && ((channel.channel_operator == src) || netadmin_mode))
@@ -44,7 +44,8 @@
data["valid_id"] = TRUE
return data
/datum/computer_file/program/coupon/ui_act(action, params, datum/tgui/ui)
/datum/computer_file/program/coupon/ui_act(action, params, datum/tgui/ui, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/obj/item/card/id/user_id = computer.computer_id_slot
if(!(user_id?.registered_account.add_to_accounts))
return TRUE
@@ -16,7 +16,8 @@
data["manifest"] = GLOB.manifest.get_manifest()
return data
/datum/computer_file/program/crew_manifest/ui_act(action, params, datum/tgui/ui)
/datum/computer_file/program/crew_manifest/ui_act(action, params, datum/tgui/ui, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_print")
if(computer) //This option should never be called if there is no printer
@@ -13,6 +13,7 @@
var/error
/datum/computer_file/program/filemanager/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_deletefile")
var/datum/computer_file/file = computer.find_file_by_name(params["name"])
@@ -162,6 +162,7 @@
return data
/datum/computer_file/program/scipaper_program/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("et_alia")
paper_to_be.et_alia = !paper_to_be.et_alia
@@ -53,7 +53,8 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
return FALSE
/datum/computer_file/program/job_management/ui_act(action, params, datum/tgui/ui)
/datum/computer_file/program/job_management/ui_act(action, params, datum/tgui/ui, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/obj/item/card/id/user_id = computer.computer_id_slot
if(!user_id || !(ACCESS_CHANGE_IDS in user_id.access))
return TRUE
@@ -49,7 +49,8 @@
data += game.ui_assets(user)
return data
/datum/computer_file/program/mafia/ui_act(mob/user, params, datum/tgui/ui, datum/ui_state/state)
/datum/computer_file/program/mafia/ui_act(mob/user, params, datum/tgui/ui, datum/ui_state/state, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/datum/mafia_controller/game = GLOB.mafia_game
if(!game)
game = create_mafia_game()
@@ -8,6 +8,7 @@
can_run_on_flags = PROGRAM_PDA
tgui_id = "NtosCamera"
program_icon = "camera"
circuit_comp_type = /obj/item/circuit_component/mod_program/camera
/// Camera built-into the tablet.
var/obj/item/camera/internal_camera
@@ -16,6 +17,48 @@
/// How many pictures were taken already, used for the camera's TGUI photo display
var/picture_number = 1
/obj/item/circuit_component/mod_program/camera
associated_program = /datum/computer_file/program/maintenance/camera
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
///A target to take a picture of.
var/datum/port/input/picture_target
///The photographed target
var/datum/port/output/photographed
/obj/item/circuit_component/mod_program/camera/populate_ports()
. = ..()
picture_target = add_input_port("Picture Target", PORT_TYPE_ATOM)
photographed = add_output_port("Photographed Entity", PORT_TYPE_ATOM)
/obj/item/circuit_component/mod_program/camera/register_shell(atom/movable/shell)
. = ..()
var/datum/computer_file/program/maintenance/camera/cam = associated_program
RegisterSignal(cam.internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_image_captured))
/obj/item/circuit_component/mod_program/camera/unregister_shell()
var/datum/computer_file/program/maintenance/camera/cam = associated_program
UnregisterSignal(cam.internal_camera, COMSIG_CAMERA_IMAGE_CAPTURED)
return ..()
/obj/item/circuit_component/mod_program/camera/input_received(datum/port/input/port)
var/atom/target = picture_target.value
if(!target)
var/turf/our_turf = get_location()
target = locate(our_turf.x, our_turf.y, our_turf.z)
if(!target)
return
var/datum/computer_file/program/maintenance/camera/cam = associated_program
if(!cam.internal_camera.can_target(target))
return
var/pic_size_x = cam.internal_camera.picture_size_x - 1
var/pic_size_y = cam.internal_camera.picture_size_y - 1
INVOKE_ASYNC(cam.internal_camera, TYPE_PROC_REF(/obj/item/camera, captureimage), target, null, pic_size_x, pic_size_y)
/obj/item/circuit_component/mod_program/camera/proc/on_image_captured(obj/item/camera/source, atom/target, mob/user)
SIGNAL_HANDLER
photographed.set_output(target)
/datum/computer_file/program/maintenance/camera/on_install()
. = ..()
internal_camera = new(computer)
@@ -50,13 +93,13 @@
return data
/datum/computer_file/program/maintenance/camera/ui_act(action, params, datum/tgui/ui)
var/mob/living/user = usr
/datum/computer_file/program/maintenance/camera/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("print_photo")
if(computer.stored_paper <= 0)
to_chat(usr, span_notice("Hardware error: Printer out of paper."))
return
internal_camera.printpicture(user, internal_picture)
internal_camera.printpicture(usr, internal_picture)
computer.stored_paper--
computer.visible_message(span_notice("\The [computer] prints out a paper."))
@@ -7,6 +7,7 @@
size = 2
tgui_id = "NtosMODsuit"
program_icon = "user-astronaut"
circuit_comp_type = /obj/item/circuit_component/mod_program/modsuit_control
///The suit we have control over.
var/obj/item/mod/control/controlled_suit
@@ -20,12 +21,15 @@
. = ..()
if(!istype(attacking_item, /obj/item/mod/control))
return FALSE
sync_modsuit(attacking_item, user)
return TRUE
/datum/computer_file/program/maintenance/modsuit_control/proc/sync_modsuit(obj/item/mod/control/new_modsuit, mob/living/user)
if(controlled_suit)
unsync_modsuit()
controlled_suit = attacking_item
controlled_suit = new_modsuit
RegisterSignal(controlled_suit, COMSIG_QDELETING, PROC_REF(unsync_modsuit))
user.balloon_alert(user, "suit updated")
return TRUE
user?.balloon_alert(user, "suit updated")
/datum/computer_file/program/maintenance/modsuit_control/proc/unsync_modsuit(atom/source)
SIGNAL_HANDLER
@@ -43,4 +47,26 @@
return controlled_suit?.ui_static_data()
/datum/computer_file/program/maintenance/modsuit_control/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
return controlled_suit?.ui_act(action, params, ui, state)
/obj/item/circuit_component/mod_program/modsuit_control
associated_program = /datum/computer_file/program/maintenance/modsuit_control
///Circuit port for loading a new suit to control
var/datum/port/input/suit_port
/obj/item/circuit_component/mod_program/modsuit_control/populate_ports()
. = ..()
suit_port = add_input_port("MODsuit Controlled", PORT_TYPE_ATOM)
/obj/item/circuit_component/mod_program/modsuit_control/input_received(datum/port/port)
var/datum/computer_file/program/maintenance/modsuit_control/control = associated_program
var/obj/item/mod/control/mod = suit_port.value
if(isnull(mod) && control.controlled_suit)
control.unsync_modsuit()
return
if(!istype(mod))
return
control.sync_modsuit(mod)
@@ -1,9 +1,9 @@
#define SPOOK_VALUE_SAME_TURF_MULT 1.5
#define SPOOK_VALUE_LIVING_MULT 6
#define SPOOK_VALUE_DEF_MOB 10
#define SPOOK_VALUE_ICON_STATE_MAX 120
#define SPOOK_VALUE_SEGMENT 15
#define SPOOK_COOLDOWN 2 SECONDS
/datum/computer_file/program/maintenance/spectre_meter
filename = "spectre_meter"
@@ -16,6 +16,7 @@
tgui_id = "NtosSpectreMeter"
program_icon = "ghost"
program_open_overlay = "spectre_meter_0"
circuit_comp_type = /obj/item/circuit_component/mod_program/spectre_meter
/// The cooldown for manual scans
COOLDOWN_DECLARE(manual_scan_cd)
/// Whether the automatic scan mode is active or not
@@ -46,13 +47,11 @@
return data
/datum/computer_file/program/maintenance/spectre_meter/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("manual_scan")
if(COOLDOWN_FINISHED(src, manual_scan_cd))
INVOKE_ASYNC(src, PROC_REF(scan_surroundings))
COOLDOWN_START(src, manual_scan_cd, 2 SECONDS)
playsound(computer, 'sound/effects/ping_hit.ogg', vol = 40, vary = TRUE)
return TRUE
INVOKE_ASYNC(src, PROC_REF(scan_surroundings))
return TRUE
if("toggle_mode")
auto_mode = !auto_mode
if(auto_mode)
@@ -67,10 +66,13 @@
/datum/computer_file/program/maintenance/spectre_meter/process(seconds_per_tick)
if(auto_mode)
INVOKE_ASYNC(src, PROC_REF(scan_surroundings))
INVOKE_ASYNC(src, PROC_REF(scan_surroundings), FALSE)
///Return the "spook level" of the area the computer is in.
/datum/computer_file/program/maintenance/spectre_meter/proc/scan_surroundings()
/datum/computer_file/program/maintenance/spectre_meter/proc/scan_surroundings(manual = TRUE)
if(manual && !COOLDOWN_FINISHED(src, manual_scan_cd))
return
var/spook_value = 0
var/turf/turf = get_turf(computer)
@@ -103,6 +105,12 @@
if(program_open_overlay != old_open_overlay)
computer.update_appearance(UPDATE_OVERLAYS)
if(manual)
COOLDOWN_START(src, manual_scan_cd, SPOOK_COOLDOWN)
playsound(computer, 'sound/effects/ping_hit.ogg', vol = 40, vary = TRUE)
SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_SPECTRE_SCAN, last_spook_value)
/datum/looping_sound/spectre_meter
mid_sounds = /datum/looping_sound/geiger::mid_sounds
mid_length = 2
@@ -128,8 +136,41 @@
last_spook_value = 0
return ..()
/obj/item/circuit_component/mod_program/spectre_meter
associated_program = /datum/computer_file/program/maintenance/spectre_meter
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
/// Returns the spookiness of each scan.
var/datum/port/output/scan_results
/obj/item/circuit_component/mod_program/spectre_meter/populate_ports()
. = ..()
scan_results = add_output_port("Scan Results", PORT_TYPE_NUMBER)
/obj/item/circuit_component/mod_program/spectre_meter/register_shell(atom/movable/shell)
. = ..()
RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_SPECTRE_SCAN, PROC_REF(on_scan))
/obj/item/circuit_component/mod_program/spectre_meter/unregister_shell()
UnregisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_SPECTRE_SCAN)
return ..()
/obj/item/circuit_component/mod_program/spectre_meter/get_ui_notices()
. = ..()
. += create_ui_notice("Scan Coooldown: [SPOOK_COOLDOWN]", "orange", "stopwatch")
/obj/item/circuit_component/mod_program/spectre_meter/input_received(datum/port/port)
var/datum/computer_file/program/maintenance/spectre_meter/meter = associated_program
INVOKE_ASYNC(meter, TYPE_PROC_REF(/datum/computer_file/program/maintenance/spectre_meter, scan_surroundings))
/obj/item/circuit_component/mod_program/spectre_meter/proc/on_scan(datum/source, spook_value)
SIGNAL_HANDLER
scan_results.set_value(spook_value)
#undef SPOOK_VALUE_SAME_TURF_MULT
#undef SPOOK_VALUE_LIVING_MULT
#undef SPOOK_VALUE_DEF_MOB
#undef SPOOK_VALUE_ICON_STATE_MAX
#undef SPOOK_VALUE_SEGMENT
#undef SPOOK_COOLDOWN
@@ -26,6 +26,7 @@
///Called post-installation of an application in a computer, after 'computer' var is set.
/datum/computer_file/program/maintenance/theme/on_install()
SHOULD_CALL_PARENT(FALSE)
//add the theme to the computer and increase its size to match
var/datum/computer_file/program/themeify/theme_app = locate() in computer.stored_files
if(theme_app)
@@ -0,0 +1,111 @@
#define MESSENGER_CIRCUIT_MIN_COOLDOWN 5 SECONDS
#define MESSENGER_CIRCUIT_MAX_COOLDOWN 45 SECONDS
#define MESSENGER_CIRCUIT_CD_PER_RECIPIENT 1.5 SECONDS
/obj/item/circuit_component/mod_program/messenger
associated_program = /datum/computer_file/program/messenger
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
///Contents of the last received message
var/datum/port/output/received_message
///Name of the sender of the above
var/datum/port/output/sender_name
///Job title of the sender of the above
var/datum/port/output/sender_job
///Reference to the device that sent the message. Usually a PDA.
var/datum/port/output/sender_device
///A message to be sent when triggered
var/datum/port/input/message
///A list of PDA targets for the message to be sent
var/datum/port/input/targets
///Plays the ringtone when the input is received
var/datum/port/input/ring
///Set the ringtone to the input
var/datum/port/input/set_ring
/obj/item/circuit_component/mod_program/messenger/populate_ports()
. = ..()
received_message = add_output_port("Received Message", PORT_TYPE_STRING)
sender_name = add_output_port("Sender Name", PORT_TYPE_STRING)
sender_job = add_output_port("Sender Job", PORT_TYPE_STRING)
sender_device = add_output_port("Sender Device", PORT_TYPE_ATOM)
message = add_input_port("Message", PORT_TYPE_STRING)
targets = add_input_port("Targets", PORT_TYPE_LIST(PORT_TYPE_ATOM))
ring = add_input_port("Play Ringtone", PORT_TYPE_SIGNAL, trigger = PROC_REF(play_ringtone))
set_ring = add_input_port("Set Ringtone", PORT_TYPE_STRING, trigger = PROC_REF(set_ringtone))
///Sanitize the targets list so it doesn't contain duplicate targets and our own computer.
/obj/item/circuit_component/mod_program/messenger/pre_input_received(datum/port/port)
if(COMPONENT_TRIGGERED_BY(targets, port))
targets.value = unique_list(targets.value) - associated_program.computer
/obj/item/circuit_component/mod_program/messenger/input_received(datum/port/port)
var/list/messenger_targets = list()
for(var/obj/item/modular_computer/modpc in targets.value)
var/datum/computer_file/program/messenger/messenger = locate() in modpc.stored_files
if(messenger)
messenger_targets |= messenger
var/messenger_length = length(messenger_targets)
if(!messenger_length)
return
var/datum/computer_file/program/messenger/messenger = associated_program
if(is_ic_filtered_for_pdas(message.value) || is_soft_ic_filtered_for_pdas(message.value))
return
///We need to async send_message() because some tcomms devices might sleep. Also because of (non-existent) user tgui alerts.
INVOKE_ASYNC(messenger, TYPE_PROC_REF(/datum/computer_file/program/messenger, send_message), src, message.value, messenger_targets)
/obj/item/circuit_component/mod_program/messenger/register_shell(atom/movable/shell)
. = ..()
RegisterSignal(associated_program.computer, COMSIG_MODULAR_PDA_MESSAGE_RECEIVED, PROC_REF(message_received))
RegisterSignal(associated_program.computer, COMSIG_MODULAR_PDA_MESSAGE_SENT, PROC_REF(message_sent))
/obj/item/circuit_component/mod_program/messenger/unregister_shell()
UnregisterSignal(associated_program.computer, list(COMSIG_MODULAR_PDA_MESSAGE_RECEIVED, COMSIG_MODULAR_PDA_MESSAGE_SENT))
return ..()
/obj/item/circuit_component/mod_program/messenger/get_ui_notices()
. = ..()
. += create_ui_notice("Cooldown per recipient: [DisplayTimeText(MESSENGER_CIRCUIT_CD_PER_RECIPIENT)]", "orange", "stopwatch")
. += create_ui_notice("Minimum cooldown: [DisplayTimeText(MESSENGER_CIRCUIT_MIN_COOLDOWN)]", "orange", "stopwatch")
. += create_ui_notice("Maximum cooldown: [DisplayTimeText(MESSENGER_CIRCUIT_MAX_COOLDOWN)]", "orange", "stopwatch")
/obj/item/circuit_component/mod_program/messenger/proc/message_received(datum/source, datum/signal/subspace/messaging/tablet_message/signal, message_job, message_name)
SIGNAL_HANDLER
received_message.set_value(signal.data["message"])
sender_name.set_value(message_name)
sender_job.set_value(message_job)
var/atom/source_device
if(istype(signal.source, /datum/computer_file/program/messenger))
var/datum/computer_file/program/messenger/sender_messenger = source
source_device = sender_messenger.computer
else if(isatom(signal.source))
source_device = signal.source
sender_device.set_value(source_device)
///Set the cooldown after the message was sent (by us)
/obj/item/circuit_component/mod_program/messenger/proc/message_sent(datum/source, atom/origin, datum/signal/subspace/messaging/tablet_message/signal)
SIGNAL_HANDLER
if(origin != src)
return
var/targets_length = length(signal.data["targets"])
var/datum/computer_file/program/messenger/messenger = associated_program
var/cool = clamp(targets_length * MESSENGER_CIRCUIT_CD_PER_RECIPIENT, MESSENGER_CIRCUIT_MIN_COOLDOWN, MESSENGER_CIRCUIT_MAX_COOLDOWN)
COOLDOWN_START(messenger, last_text, cool)
/obj/item/circuit_component/mod_program/messenger/proc/set_ringtone(datum/port/port)
var/datum/computer_file/program/messenger/messenger = associated_program
messenger.set_ringtone(set_ring.value)
/obj/item/circuit_component/mod_program/messenger/proc/play_ringtone(datum/port/port)
var/datum/computer_file/program/messenger/messenger = associated_program
messenger.computer.ring(messenger.ringtone)
#undef MESSENGER_CIRCUIT_MIN_COOLDOWN
#undef MESSENGER_CIRCUIT_MAX_COOLDOWN
#undef MESSENGER_CIRCUIT_CD_PER_RECIPIENT
@@ -14,12 +14,13 @@
size = 0
undeletable = TRUE // It comes by default in tablets, can't be downloaded, takes no space and should obviously not be able to be deleted.
power_cell_use = NONE
program_flags = PROGRAM_HEADER | PROGRAM_RUNS_WITHOUT_POWER
program_flags = PROGRAM_HEADER | PROGRAM_RUNS_WITHOUT_POWER | PROGRAM_CIRCUITS_RUN_WHEN_CLOSED
can_run_on_flags = PROGRAM_PDA
ui_header = "ntnrc_idle.gif"
tgui_id = "NtosMessenger"
program_icon = "comment-alt"
alert_able = TRUE
circuit_comp_type = /obj/item/circuit_component/mod_program/messenger
/// Whether the user is invisible to the message list.
var/invisible = FALSE
@@ -138,6 +139,18 @@
for(var/datum/tgui/window as anything in computer.open_uis)
SSassets.transport.send_assets(window.user, data)
/// Set the ringtone if possible. Also handles encoding.
/datum/computer_file/program/messenger/proc/set_ringtone(new_ringtone, mob/user)
new_ringtone = trim(html_encode(new_ringtone), MESSENGER_RINGTONE_MAX_LENGTH)
if(!new_ringtone)
return FALSE
if(SEND_SIGNAL(computer, COMSIG_TABLET_CHANGE_ID, user, new_ringtone) & COMPONENT_STOP_RINGTONE_CHANGE)
return FALSE
ringtone = ringtone
return TRUE
/datum/computer_file/program/messenger/ui_interact(mob/user, datum/tgui/ui)
var/list/data = get_picture_assets()
SSassets.transport.send_assets(user, data)
@@ -147,19 +160,15 @@
return GLOB.reverse_contained_state
return GLOB.default_state
/datum/computer_file/program/messenger/ui_act(action, list/params, datum/tgui/ui)
/datum/computer_file/program/messenger/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PDA_ringSet")
var/new_ringtone = tgui_input_text(usr, "Enter a new ringtone", "Ringtone", ringtone, MESSENGER_RINGTONE_MAX_LENGTH)
var/mob/living/usr_mob = usr
if(!new_ringtone || !in_range(computer, usr_mob) || computer.loc != usr_mob)
var/mob/living/user = usr
var/new_ringtone = tgui_input_text(user, "Enter a new ringtone", "Ringtone", ringtone, encode = FALSE)
if(!in_range(computer, user) || computer.loc != user)
return FALSE
if(SEND_SIGNAL(computer, COMSIG_TABLET_CHANGE_ID, usr_mob, new_ringtone) & COMPONENT_STOP_RINGTONE_CHANGE)
return FALSE
ringtone = new_ringtone
return TRUE
return set_ringtone(new_ringtone, user)
if("PDA_toggleAlerts")
alert_silenced = !alert_silenced
@@ -451,18 +460,23 @@
message = emoji_sanitize(message)
// check message against filter
if(!check_pda_message_against_filter(message, sender))
if(sender && !check_pda_message_against_filter(message, sender))
return null
return message
/// Sends a message to targets via PDA. When sending to everyone, set `everyone` to true so the message is formatted accordingly
/datum/computer_file/program/messenger/proc/send_message(mob/living/sender, message, list/targets, everyone = FALSE)
/datum/computer_file/program/messenger/proc/send_message(atom/source, message, list/targets, everyone = FALSE)
var/mob/living/sender
if(isliving(source))
sender = source
else if(is_ic_filtered_for_pdas(message) || is_soft_ic_filtered_for_pdas(message))
return FALSE
message = sanitize_pda_message(message, sender)
if(!message)
return FALSE
// upgrade the image asset to a permanent key
var/photo_asset_key = selected_image
if(photo_asset_key == TEMP_IMAGE_PATH(REF(src)))
@@ -474,7 +488,7 @@
var/list/datum/computer_file/program/messenger/target_messengers = list()
var/list/datum/pda_chat/target_chats = list()
var/should_alert = length(targets) == 1
var/should_alert = length(targets) == 1 && sender
// filter out invalid targets
for(var/target in targets)
@@ -523,7 +537,7 @@
target_chats += target_chat
target_messengers += target_messenger
if(!send_message_signal(sender, message, target_messengers, photo_asset_key, everyone))
if(!send_message_signal(source, message, target_messengers, photo_asset_key, everyone))
return FALSE
// Log it in our logs
@@ -553,8 +567,11 @@
return send_message_signal(sender, message, targets, fake_photo, FALSE, TRUE, fake_name, fake_job)
/datum/computer_file/program/messenger/proc/send_message_signal(mob/sender, message, list/datum/computer_file/program/messenger/targets, photo_path = null, everyone = FALSE, rigged = FALSE, fake_name = null, fake_job = null)
if(!sender.can_perform_action(computer, ALLOW_RESTING))
/datum/computer_file/program/messenger/proc/send_message_signal(atom/source, message, list/datum/computer_file/program/messenger/targets, photo_path = null, everyone = FALSE, rigged = FALSE, fake_name = null, fake_job = null)
var/mob/sender
if(ismob(source))
sender = source
if(sender && !sender.can_perform_action(computer, ALLOW_RESTING))
return FALSE
if(!COOLDOWN_FINISHED(src, last_text))
@@ -566,7 +583,8 @@
// check for jammers
if(is_within_radio_jammer_range(computer) && !rigged)
// different message so people know it's a radio jammer
to_chat(sender, span_notice("ERROR: Network unavailable, please try again later."))
if(sender)
to_chat(sender, span_notice("ERROR: Network unavailable, please try again later."))
if(alert_able && !alert_silenced)
playsound(computer, 'sound/machines/terminal_error.ogg', 15, TRUE)
return FALSE
@@ -596,38 +614,46 @@
// If it didn't reach, note that fact
if (!signal.data["done"])
to_chat(sender, span_notice("ERROR: Server is not responding."))
if(sender)
to_chat(sender, span_notice("ERROR: Server is not responding."))
if(alert_able && !alert_silenced)
playsound(computer, 'sound/machines/terminal_error.ogg', 15, TRUE)
return FALSE
// SKYRAT EDIT BEGIN - PDA messages show a visible message; again!
sender.visible_message(span_notice("[sender]'s PDA rings out with the soft sound of keypresses"), vision_distance = COMBAT_MESSAGE_RANGE)
// SKYRAT EDIT END
var/shell_addendum = ""
if(istype(source, /obj/item/circuit_component))
var/obj/item/circuit_component/circuit = source
shell_addendum = "[circuit.parent.get_creator()] "
// Log in the talk log
sender.log_talk(message, LOG_PDA, tag="[rigged ? "Rigged" : ""] PDA: [computer.saved_identification] to [signal.format_target()]")
source.log_talk(message, LOG_PDA, tag="[shell_addendum][rigged ? "Rigged" : ""] PDA: [computer.saved_identification] to [signal.format_target()]")
if(rigged)
log_bomber(sender, "sent a rigged PDA message (Name: [fake_name]. Job: [fake_job]) to [english_list(stringified_targets)] [!is_special_character(sender) ? "(SENT BY NON-ANTAG)" : ""]")
message = emoji_parse(message) //already sent- this just shows the sent emoji as one to the sender in the to_chat
// Show it to ghosts
var/ghost_message = span_game_say("[span_name("[sender]")] [rigged ? "(as [span_name(fake_name)]) Rigged " : ""]PDA Message --> [span_name("[signal.format_target()]")]: \"[signal.format_message()]\"")
var/ghost_message = span_game_say("[span_name("[source]")] [rigged ? "(as [span_name(fake_name)]) Rigged " : ""]PDA Message --> [span_name("[signal.format_target()]")]: \"[signal.format_message()]\"")
var/list/message_listeners = GLOB.dead_player_list + GLOB.current_observers_list
for(var/mob/listener as anything in message_listeners)
if(!(get_chat_toggles(listener) & CHAT_GHOSTPDA))
continue
to_chat(listener, "[FOLLOW_LINK(listener, sender)] [ghost_message]")
to_chat(listener, "[FOLLOW_LINK(listener, source)] [ghost_message]")
to_chat(sender, span_info("PDA message sent to [signal.format_target()]: \"[message]\""))
if(sender)
to_chat(sender, span_info("PDA message sent to [signal.format_target()]: \"[message]\""))
if (alert_able && !alert_silenced)
computer.send_sound()
COOLDOWN_START(src, last_text, 1 SECONDS)
SEND_SIGNAL(computer, COMSIG_MODULAR_PDA_MESSAGE_SENT, source, signal)
selected_image = null
return TRUE
@@ -642,6 +668,7 @@
var/sender_ref = signal.data["ref"]
// don't create a new chat for rigged messages, make it a one off notif
if(!is_rigged)
var/datum/pda_message/message = new(signal.data["message"], FALSE, station_time_timestamp(PDA_MESSAGE_TIMESTAMP_FORMAT), signal.data["photo"], signal.data["everyone"])
@@ -662,6 +689,14 @@
if(computer.loc && isliving(computer.loc))
receievers += computer.loc
// resolving w/o nullcheck here, assume the messenger exists if a real person sent a message
var/datum/computer_file/program/messenger/sender_messenger = chat.recipient?.resolve()
var/sender_title = is_fake_user ? STRINGIFY_PDA_TARGET(fake_name, fake_job) : get_messenger_name(sender_messenger)
var/sender_name = is_fake_user ? fake_name : sender_messenger.computer.saved_identification
SEND_SIGNAL(computer, COMSIG_MODULAR_PDA_MESSAGE_RECEIVED, signal, fake_job || sender_messenger?.computer.saved_job , sender_name)
for(var/mob/living/messaged_mob as anything in receievers)
if(messaged_mob.stat >= UNCONSCIOUS)
continue
@@ -675,11 +710,6 @@
else
reply = "(<a href='byond://?src=[REF(src)];choice=[reply_href];skiprefresh=1;target=[REF(chat)]'>Reply</a>)"
// resolving w/o nullcheck here, assume the messenger exists if a real person sent a message
var/datum/computer_file/program/messenger/sender_messenger = chat.recipient?.resolve()
var/sender_title = is_fake_user ? STRINGIFY_PDA_TARGET(fake_name, fake_job) : get_messenger_name(sender_messenger)
var/sender_name = is_fake_user ? fake_name : sender_messenger.computer.saved_identification
if (isAI(messaged_mob))
sender_title = "<a href='?src=[REF(messaged_mob)];track=[html_encode(sender_name)]'>[sender_title]</a>"
@@ -27,4 +27,5 @@
return newscaster_ui.ui_static_data(user)
/datum/computer_file/program/newscaster/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
return newscaster_ui.ui_act(action, params, ui, state)
@@ -8,6 +8,7 @@
tgui_id = "NtosNotepad"
program_icon = "book"
can_run_on_flags = PROGRAM_ALL
circuit_comp_type = /obj/item/circuit_component/mod_program/notepad
var/written_note = "Congratulations on your station upgrading to the new NtOS and Thinktronic based collaboration effort, \
bringing you the best in electronics and software since 2467!\n\
@@ -19,7 +20,8 @@
Quarter - Either sides of Aft\n\
Bow - Either sides of Fore"
/datum/computer_file/program/notepad/ui_act(action, list/params, datum/tgui/ui)
/datum/computer_file/program/notepad/ui_act(action, list/params, datum/tgui/ui, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("UpdateNote")
written_note = params["newnote"]
@@ -31,3 +33,34 @@
data["note"] = written_note
return data
/obj/item/circuit_component/mod_program/notepad
associated_program = /datum/computer_file/program/notepad
///When the input is received, the written note will be set to its value.
var/datum/port/input/set_text
///The written note output, sent everytime notes are updated.
var/datum/port/output/updated_text
/obj/item/circuit_component/mod_program/notepad/populate_ports()
. = ..()
set_text = add_input_port("Set Notes", PORT_TYPE_STRING)
updated_text = add_output_port("Updated Notes", PORT_TYPE_STRING)
/obj/item/circuit_component/mod_program/notepad/register_shell(atom/movable/shell)
. = ..()
RegisterSignal(associated_program, COMSIG_UI_ACT, PROC_REF(on_note_updated))
/obj/item/circuit_component/mod_program/notepad/unregister_shell()
UnregisterSignal(associated_program, COMSIG_UI_ACT)
return ..()
/obj/item/circuit_component/mod_program/notepad/proc/on_note_updated(datum/source, mob/user, action, list/params)
SIGNAL_HANDLER
if(action == "UpdateNote")
updated_text.set_output(params["newnote"])
/obj/item/circuit_component/mod_program/notepad/input_received(datum/port/port)
var/datum/computer_file/program/notepad/pad = associated_program
pad.written_note = set_text.value
SStgui.update_uis(pad.computer)
updated_text.set_output(pad.written_note)
@@ -1,3 +1,10 @@
#define NT_PAY_STATUS_NO_ACCOUNT 0
#define NT_PAY_STATUS_DEPT_ACCOUNT 1
#define NT_PAY_STATUS_INVALID_TOKEN 2
#define NT_PAY_SATUS_SENDER_IS_RECEIVER 3
#define NT_PAY_STATUS_INVALID_MONEY 4
#define NT_PAY_STATUS_SUCCESS 5
/datum/computer_file/program/nt_pay
filename = "ntpay"
filedesc = "Nanotrasen Pay System"
@@ -8,45 +15,19 @@
tgui_id = "NtosPay"
program_icon = "money-bill-wave"
can_run_on_flags = PROGRAM_ALL
circuit_comp_type = /obj/item/circuit_component/mod_program/nt_pay
///Reference to the currently logged in user.
var/datum/bank_account/current_user
///Pay token, by which we can send credits
var/token
///Amount of credits, which we sends
var/money_to_send = 0
///Pay token what we want to find
var/wanted_token
/datum/computer_file/program/nt_pay/ui_act(action, list/params, datum/tgui/ui)
/datum/computer_file/program/nt_pay/ui_act(action, list/params, datum/tgui/ui, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("Transaction")
if(IS_DEPARTMENTAL_ACCOUNT(current_user))
return to_chat(usr, span_notice("The app is unable to withdraw from that card."))
token = params["token"]
money_to_send = params["amount"]
var/datum/bank_account/recipient
if(!token)
return to_chat(usr, span_notice("You need to enter your transfer target's pay token."))
if(!money_to_send)
return to_chat(usr, span_notice("You need to specify how much you're sending."))
if(token == current_user.pay_token)
return to_chat(usr, span_notice("You can't send credits to yourself."))
for(var/account as anything in SSeconomy.bank_accounts_by_id)
var/datum/bank_account/acc = SSeconomy.bank_accounts_by_id[account]
if(acc.pay_token == token)
recipient = acc
break
if(!recipient)
return to_chat(usr, span_notice("The app can't find who you're trying to pay. Did you enter the pay token right?"))
if(!current_user.has_money(money_to_send) || money_to_send < 1)
return current_user.bank_card_talk("You cannot afford it.")
recipient.bank_card_talk("You received [money_to_send] credit(s). Reason: transfer from [current_user.account_holder]")
recipient.transfer_money(current_user, money_to_send)
current_user.bank_card_talk("You send [money_to_send] credit(s) to [recipient.account_holder]. Now you have [current_user.account_balance] credit(s)")
var/token = params["token"]
var/money_to_send = params["amount"]
make_payment(token, money_to_send, usr)
if("GetPayToken")
wanted_token = null
@@ -58,8 +39,6 @@
if(!wanted_token)
return wanted_token = "Account \"[params["wanted_name"]]\" not found."
/datum/computer_file/program/nt_pay/ui_data(mob/user)
var/list/data = list()
@@ -74,3 +53,99 @@
data["transaction_list"] = current_user.transaction_history
return data
///Wrapper and signal for the main payment function of this program
/datum/computer_file/program/nt_pay/proc/make_payment(token, money_to_send, mob/user)
var/payment_result = _pay(token, money_to_send, user)
SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT, payment_result)
/datum/computer_file/program/nt_pay/proc/_pay(token, money_to_send, mob/user)
if(IS_DEPARTMENTAL_ACCOUNT(current_user))
if(user)
to_chat(user, span_notice("The app is unable to withdraw from that card."))
return NT_PAY_STATUS_DEPT_ACCOUNT
var/datum/bank_account/recipient
if(!token)
if(user)
to_chat(user, span_notice("You need to enter your transfer target's pay token."))
return NT_PAY_STATUS_INVALID_TOKEN
if(money_to_send <= 0)
if(user)
to_chat(user, span_notice("You need to specify how much you're sending."))
return NT_PAY_STATUS_INVALID_MONEY
if(token == current_user.pay_token)
if(user)
to_chat(user, span_notice("You can't send credits to yourself."))
return NT_PAY_SATUS_SENDER_IS_RECEIVER
for(var/account as anything in SSeconomy.bank_accounts_by_id)
var/datum/bank_account/acc = SSeconomy.bank_accounts_by_id[account]
if(acc.pay_token == token)
recipient = acc
break
if(!recipient)
if(user)
to_chat(user, span_notice("The app can't find who you're trying to pay. Did you enter the pay token right?"))
return NT_PAY_STATUS_INVALID_TOKEN
if(!current_user.has_money(money_to_send) || money_to_send < 1)
current_user.bank_card_talk("You cannot afford it.")
return NT_PAY_STATUS_INVALID_MONEY
recipient.bank_card_talk("You received [money_to_send] credit(s). Reason: transfer from [current_user.account_holder]")
recipient.transfer_money(current_user, money_to_send)
current_user.bank_card_talk("You send [money_to_send] credit(s) to [recipient.account_holder]. Now you have [current_user.account_balance] credit(s)")
return NT_PAY_STATUS_SUCCESS
/obj/item/circuit_component/mod_program/nt_pay
associated_program = /datum/computer_file/program/nt_pay
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
///Circuit variables. This one is for the token we want to pay
var/datum/port/input/token_port
///The port for the money to send
var/datum/port/input/money_port
///Let's us know if the payment has gone through or not.
var/datum/port/output/payment_status
/obj/item/circuit_component/mod_program/nt_pay/register_shell(atom/movable/shell)
. = ..()
RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT, PROC_REF(on_payment_result))
/obj/item/circuit_component/mod_program/nt_pay/unregister_shell()
UnregisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT)
return ..()
/obj/item/circuit_component/mod_program/nt_pay/populate_ports()
. = ..()
token_port = add_input_port("Token", PORT_TYPE_STRING)
money_port = add_input_port("Amount", PORT_TYPE_NUMBER)
payment_status = add_output_port("Status", PORT_TYPE_NUMBER)
/obj/item/circuit_component/mod_program/nt_pay/get_ui_notices()
. = ..()
. += create_ui_notice("NT-Pay Statuses:")
. += create_ui_notice("Fail (No Account) - [NT_PAY_STATUS_NO_ACCOUNT]", "red")
. += create_ui_notice("Fail (Dept Account) - [NT_PAY_STATUS_DEPT_ACCOUNT]", "red")
. += create_ui_notice("Fail (Invalid Token) - [NT_PAY_STATUS_INVALID_TOKEN]", "red")
. += create_ui_notice("Fail (Sender = Receiver) - [NT_PAY_SATUS_SENDER_IS_RECEIVER]", "red")
. += create_ui_notice("Fail (Invalid Amount) - [NT_PAY_STATUS_INVALID_MONEY]", "red")
. += create_ui_notice("Success - [NT_PAY_STATUS_SUCCESS]", "green")
/obj/item/circuit_component/mod_program/nt_pay/input_received(datum/port/port)
var/datum/computer_file/program/nt_pay/program = associated_program
program.make_payment(token_port.value, money_port.value)
/obj/item/circuit_component/mod_program/nt_pay/proc/on_payment_result(datum/source, payment_result)
SIGNAL_HANDLER
payment_status.set_output(payment_result)
#undef NT_PAY_STATUS_NO_ACCOUNT
#undef NT_PAY_STATUS_DEPT_ACCOUNT
#undef NT_PAY_STATUS_INVALID_TOKEN
#undef NT_PAY_SATUS_SENDER_IS_RECEIVER
#undef NT_PAY_STATUS_INVALID_MONEY
#undef NT_PAY_STATUS_SUCCESS
@@ -105,6 +105,7 @@
download_completion += download_netspeed
/datum/computer_file/program/ntnetdownload/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_downloadfile")
if(!downloaded_file)
@@ -44,6 +44,7 @@
)
/datum/computer_file/program/portrait_printer/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("search")
if(search_string != params["to_search"])
@@ -1,3 +1,13 @@
///The selected target is not trackable
#define RADAR_NOT_TRACKABLE 0
///The selected target is trackable
#define RADAR_TRACKABLE 1
///The selected target is trackable, even if subtypes would normally consider it untrackable.
#define RADAR_TRACKABLE_ANYWAY 2
///If the target is something it shouldn't be normally tracking, this is the maximum distance within with it an be tracked.
#define MAX_RADAR_CIRCUIT_DISTANCE 18
/datum/computer_file/program/radar //generic parent that handles most of the process
filename = "genericfinder"
filedesc = "debug_finder"
@@ -58,12 +68,13 @@
return data
/datum/computer_file/program/radar/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("selecttarget")
var/selected_new_ref = params["ref"]
if(selected_new_ref in trackable_object_refs())
selected = selected_new_ref
SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_RADAR_SELECTED, selected)
return TRUE
if("scan")
@@ -133,13 +144,22 @@
**arg1 is the atom being evaluated.
*/
/datum/computer_file/program/radar/proc/trackable(atom/movable/signal)
if(!signal || !computer)
return FALSE
SHOULD_CALL_PARENT(TRUE)
if(isnull(signal) || isnull(computer))
return RADAR_NOT_TRACKABLE
var/turf/here = get_turf(computer)
var/turf/there = get_turf(signal)
if(!here || !there)
return FALSE //I was still getting a runtime even after the above check while scanning, so fuck it
return (there.z == here.z) || (is_station_level(here.z) && is_station_level(there.z))
return RADAR_NOT_TRACKABLE //I was still getting a runtime even after the above check while scanning, so fuck it
if(there.z != here.z && (!is_station_level(here.z) || !is_station_level(there.z)))
return RADAR_NOT_TRACKABLE
var/trackable_signal = SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE, signal, here, there)
switch(trackable_signal)
if(COMPONENT_RADAR_TRACK_ANYWAY)
return RADAR_TRACKABLE_ANYWAY
if(COMPONENT_RADAR_DONT_TRACK)
return RADAR_NOT_TRACKABLE
return RADAR_TRACKABLE
/**
*
@@ -168,7 +188,10 @@
*return an atom reference.
*/
/datum/computer_file/program/radar/proc/find_atom()
return
SHOULD_CALL_PARENT(TRUE)
var/list/atom_container = list(null)
SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM, atom_container)
return atom_container[1]
//We use SSfastprocess for the program icon state because it runs faster than process_tick() does.
/datum/computer_file/program/radar/process()
@@ -221,9 +244,10 @@
program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET
download_access = list(ACCESS_MEDICAL)
program_icon = "heartbeat"
circuit_comp_type = /obj/item/circuit_component/mod_program/radar/medical
/datum/computer_file/program/radar/lifeline/find_atom()
return locate(selected) in GLOB.human_list
return ..() || (locate(selected) in GLOB.human_list)
/datum/computer_file/program/radar/lifeline/scan()
objects = list()
@@ -243,14 +267,16 @@
objects += list(crewinfo)
/datum/computer_file/program/radar/lifeline/trackable(mob/living/carbon/human/humanoid)
. = ..()
if(. == RADAR_TRACKABLE_ANYWAY)
return RADAR_TRACKABLE_ANYWAY
if(!humanoid || !istype(humanoid))
return FALSE
if(..())
if (istype(humanoid.w_uniform, /obj/item/clothing/under))
var/obj/item/clothing/under/uniform = humanoid.w_uniform
if(uniform.has_sensor && uniform.sensor_mode >= SENSOR_COORDS) // Suit sensors must be on maximum
return TRUE
return FALSE
return RADAR_NOT_TRACKABLE
if(!istype(humanoid.w_uniform, /obj/item/clothing/under))
return RADAR_NOT_TRACKABLE
var/obj/item/clothing/under/uniform = humanoid.w_uniform
if(uniform.has_sensor && uniform.sensor_mode >= SENSOR_COORDS) // Suit sensors must be on maximum
return RADAR_TRACKABLE
///Tracks all janitor equipment
/datum/computer_file/program/radar/custodial_locator
@@ -262,9 +288,10 @@
program_icon = "broom"
size = 2
detomatix_resistance = DETOMATIX_RESIST_MINOR
circuit_comp_type = /obj/item/circuit_component/mod_program/radar/janitor
/datum/computer_file/program/radar/custodial_locator/find_atom()
return locate(selected) in GLOB.janitor_devices
return ..() || (locate(selected) in GLOB.janitor_devices)
/datum/computer_file/program/radar/custodial_locator/scan()
objects = list()
@@ -306,6 +333,7 @@
program_icon = "bomb"
arrowstyle = "ntosradarpointerS.png"
pointercolor = "red"
circuit_comp_type = /obj/item/circuit_component/mod_program/radar/nukie
/datum/computer_file/program/radar/fission360/on_start(mob/living/user)
. = ..()
@@ -323,7 +351,7 @@
return ..()
/datum/computer_file/program/radar/fission360/find_atom()
return SSpoints_of_interest.get_poi_atom_by_ref(selected)
return ..() || SSpoints_of_interest.get_poi_atom_by_ref(selected)
/datum/computer_file/program/radar/fission360/scan()
objects = list()
@@ -379,3 +407,129 @@
span_danger("[computer] vibrates and lets out an ominous alarm. Uh oh."),
span_notice("[computer] begins to vibrate rapidly. Wonder what that means..."),
)
/**
* Base circuit for the radar program.
* The abstract radar doesn't have this, nor this one is associated to it, so
* make sure to specify the associate_program and circuit_comp_type of subtypes,
*/
/obj/item/circuit_component/mod_program/radar
///The target to track
var/datum/port/input/target
///The selected target, from the app
var/datum/port/output/selected_by_app
/// The result from the output
var/datum/port/output/x_pos
var/datum/port/output/y_pos
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/obj/item/circuit_component/mod_program/radar/populate_ports()
. = ..()
target = add_input_port("Target", PORT_TYPE_ATOM)
selected_by_app = add_output_port("Selected From Program", PORT_TYPE_ATOM)
x_pos = add_output_port("X", PORT_TYPE_NUMBER)
y_pos = add_output_port("Y", PORT_TYPE_NUMBER)
/obj/item/circuit_component/mod_program/radar/register_shell(atom/movable/shell)
. = ..()
RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE, PROC_REF(can_track))
RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM, PROC_REF(get_atom))
RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_RADAR_SELECTED, PROC_REF(on_selected))
/obj/item/circuit_component/mod_program/radar/unregister_shell()
UnregisterSignal(associated_program.computer, list(
COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE,
COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM,
COMSIG_MODULAR_COMPUTER_RADAR_SELECTED,
))
return ..()
/obj/item/circuit_component/mod_program/radar/get_ui_notices()
. = ..()
. += create_ui_notice("Max range for unsupported entities: [MAX_RADAR_CIRCUIT_DISTANCE] tiles", "orange", FA_ICON_BULLSEYE)
///Set the selected ref of the program to the target (if it exists) and update the x/y pos ports (if trackable) when triggered.
/obj/item/circuit_component/mod_program/radar/input_received(datum/port/port)
var/datum/computer_file/program/radar/radar = associated_program
var/atom/radar_atom = radar.find_atom()
if(target.value != radar_atom)
radar.selected = REF(target.value)
SStgui.update_uis(radar.computer)
if(radar.trackable(radar_atom))
var/turf/turf = get_turf(radar_atom)
x_pos.set_value(turf.x)
y_pos.set_value(turf.y)
else
x_pos.set_value(null)
y_pos.set_value(null)
/**
* Check if we can track the object. When making different definitions of this proc for subtypes, include typical
* targets as an exception to this (e.g humans for lifeline) so that even if they're coming from a circuit input
* they won't get filtered by the maximum distance, because they're "supported entities".
*/
/obj/item/circuit_component/mod_program/radar/proc/can_track(datum/source, atom/signal, signal_turf, computer_turf)
SIGNAL_HANDLER
if(target.value && get_dist_euclidian(computer_turf, signal_turf) > MAX_RADAR_CIRCUIT_DISTANCE)
return COMPONENT_RADAR_DONT_TRACK
return COMPONENT_RADAR_TRACK_ANYWAY
///Return the value of the target port.
/obj/item/circuit_component/mod_program/radar/proc/get_atom(datum/source, list/atom_container)
SIGNAL_HANDLER
atom_container[1] = target.value
/**
* When a target is selected by the app, reset the target port, update the x/pos ports (if trackable)
* and set selected_by_app port to the target atom.
*/
/obj/item/circuit_component/mod_program/radar/proc/on_selected(datum/source, selected_ref)
SIGNAL_HANDLER
target.set_value(null)
var/datum/computer_file/program/radar/radar = associated_program
var/atom/selected_atom = radar.find_atom()
selected_by_app.set_value(selected_atom)
if(radar.trackable(selected_atom))
var/turf/turf = get_turf(radar.selected)
x_pos.set_value(turf.x)
y_pos.set_value(turf.y)
else
x_pos.set_value(null)
y_pos.set_value(null)
/obj/item/circuit_component/mod_program/radar/medical
associated_program = /datum/computer_file/program/radar/lifeline
/obj/item/circuit_component/mod_program/radar/medical/can_track(datum/source, atom/signal, signal_turf, computer_turf)
if(target.value in GLOB.human_list)
return NONE
return ..()
/obj/item/circuit_component/mod_program/radar/janitor
associated_program = /datum/computer_file/program/radar/custodial_locator
/obj/item/circuit_component/mod_program/radar/janitor/can_track(datum/source, atom/signal, signal_turf, computer_turf)
if(target.value in GLOB.janitor_devices)
return NONE
return ..()
/obj/item/circuit_component/mod_program/radar/nukie
associated_program = /datum/computer_file/program/radar/fission360
/obj/item/circuit_component/mod_program/radar/nukie/can_track(datum/source, atom/signal, signal_turf, computer_turf)
if(target.value in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb))
return NONE
if(target.value in SSpoints_of_interest.real_nuclear_disks)
return NONE
if(target.value == SSshuttle.getShuttle("syndicate"))
return NONE
return ..()
#undef MAX_RADAR_CIRCUIT_DISTANCE
#undef RADAR_NOT_TRACKABLE
#undef RADAR_TRACKABLE
#undef RADAR_TRACKABLE_ANYWAY
@@ -83,7 +83,8 @@
return data
/datum/computer_file/program/robocontrol/ui_act(action, list/params, datum/tgui/ui)
/datum/computer_file/program/robocontrol/ui_act(action, list/params, datum/tgui/ui, datum/tgui/ui, datum/ui_state/state)
. = ..()
var/mob/current_user = ui.user
var/obj/item/card/id/id_card = computer?.computer_id_slot
@@ -84,6 +84,7 @@
return data
/datum/computer_file/program/robotact/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
//Implied type, memes
var/obj/item/modular_computer/pda/silicon/tablet = computer
var/mob/living/silicon/robot/cyborg = tablet.silicon_owner
@@ -8,6 +8,8 @@
tgui_id = "NtosSignaler"
program_icon = "satellite-dish"
can_run_on_flags = PROGRAM_PDA | PROGRAM_LAPTOP
program_flags = /datum/computer_file/program::program_flags | PROGRAM_CIRCUITS_RUN_WHEN_CLOSED
circuit_comp_type = /obj/item/circuit_component/mod_program/signaler
///What is the saved signal frequency?
var/signal_frequency = FREQ_SIGNALER
/// What is the saved signal code?
@@ -36,10 +38,11 @@
data["maxFrequency"] = MAX_FREE_FREQ
return data
/datum/computer_file/program/signal_commander/ui_act(action, list/params)
/datum/computer_file/program/signal_commander/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("signal")
INVOKE_ASYNC(src, PROC_REF(signal))
INVOKE_ASYNC(src, PROC_REF(signal), usr)
. = TRUE
if("freq")
var/new_signal_frequency = sanitize_frequency(unformat_frequency(params["freq"]), TRUE)
@@ -56,27 +59,65 @@
signal_code = initial(signal_code)
. = TRUE
/datum/computer_file/program/signal_commander/proc/signal()
/datum/computer_file/program/signal_commander/proc/signal(atom/source)
if(!radio_connection)
return
var/mob/user
var/obj/item/circuit_component/signaling
if(ismob(source))
user = source
else if(istype(source, /obj/item/circuit_component))
signaling = source
if(!COOLDOWN_FINISHED(src, signal_cooldown))
computer.balloon_alert(usr, "cooling down!")
if(user)
computer.balloon_alert(user, "cooling down!")
return
COOLDOWN_START(src, signal_cooldown, signal_cooldown_time)
computer.balloon_alert(usr, "signaled")
if(user)
computer.balloon_alert(user, "signaled")
var/time = time2text(world.realtime,"hh:mm:ss")
var/turf/T = get_turf(computer)
var/logging_data = "[time] <B>:</B> [key_name(usr)] used the computer '[initial(computer.name)]' @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(signal_frequency)]/[signal_code]"
var/user_deets
if(signaling)
user_deets = "[signaling.parent.get_creator()]"
else
user_deets = "[key_name(usr)]"
var/logging_data = "[time] <B>:</B> [user_deets] used the computer '[initial(computer.name)]' @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(signal_frequency)]/[signal_code]"
add_to_signaler_investigate_log(logging_data)
var/datum/signal/signal = new(list("code" = signal_code), logging_data = logging_data)
var/datum/signal/signal = new(list("code" = signal_code, "key" = signaling?.parent.owner_id), logging_data = logging_data)
radio_connection.post_signal(computer, signal)
/datum/computer_file/program/signal_commander/proc/set_frequency(new_frequency)
SSradio.remove_object(computer, signal_frequency)
signal_frequency = new_frequency
radio_connection = SSradio.add_object(computer, signal_frequency, RADIO_SIGNALER)
/obj/item/circuit_component/mod_program/signaler
associated_program = /datum/computer_file/program/signal_commander
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
/// Frequency input
var/datum/port/input/freq
/// Signal input
var/datum/port/input/code
/obj/item/circuit_component/mod_program/signaler/populate_ports()
. = ..()
freq = add_input_port("Frequency", PORT_TYPE_NUMBER, trigger = PROC_REF(set_freq), default = FREQ_SIGNALER)
code = add_input_port("Code", PORT_TYPE_NUMBER, trigger = PROC_REF(set_code), default = DEFAULT_SIGNALER_CODE)
/obj/item/circuit_component/mod_program/signaler/proc/set_freq(datum/port/port)
var/datum/computer_file/program/signal_commander/signaler = associated_program
signaler.set_frequency(clamp(freq.value, MIN_FREE_FREQ, MAX_FREE_FREQ))
/obj/item/circuit_component/mod_program/signaler/proc/set_code(datum/port/port)
var/datum/computer_file/program/signal_commander/signaler = associated_program
signaler.signal_code = round(clamp(code.value, 1, 100))
/obj/item/circuit_component/mod_program/signaler/input_received(datum/port/port)
INVOKE_ASYNC(associated_program, TYPE_PROC_REF(/datum/computer_file/program/signal_commander, signal), src)
@@ -50,7 +50,8 @@
return null
/datum/computer_file/program/skill_tracker/ui_act(action, params, datum/tgui/ui)
/datum/computer_file/program/skill_tracker/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_reward")
var/skill_type = find_skilltype(params["skill"])
@@ -55,6 +55,7 @@
return data
/datum/computer_file/program/supermatter_monitor/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_refresh")
refresh()
@@ -4,6 +4,7 @@
program_icon = "signal"
program_open_overlay = "generic"
size = 1
circuit_comp_type = /obj/item/circuit_component/mod_program/status
extended_desc = "An app used to change the message on the station status displays."
tgui_id = "NtosStatus"
@@ -42,16 +43,16 @@
* * upper - Top text
* * lower - Bottom text
*/
/datum/computer_file/program/status/proc/post_message(upper, lower)
/datum/computer_file/program/status/proc/post_message(upper, lower, log_usr = key_name(usr))
post_status("message", upper, lower)
log_game("[key_name(usr)] has changed the station status display message to \"[upper] [lower]\" [loc_name(usr)]")
log_game("[log_usr] has changed the station status display message to \"[upper] [lower]\" [loc_name(usr)]")
/**
* Post a picture to status displays
* Arguments:
* * picture - The picture name
*/
/datum/computer_file/program/status/proc/post_picture(picture)
/datum/computer_file/program/status/proc/post_picture(picture, log_usr = key_name(usr))
if (!(picture in GLOB.status_display_approved_pictures))
return
if(picture in GLOB.status_display_state_pictures)
@@ -80,9 +81,10 @@
else
post_status("alert", picture)
log_game("[key_name(usr)] has changed the station status display message to \"[picture]\" [loc_name(usr)]")
log_game("[log_usr] has changed the station status display message to \"[picture]\" [loc_name(usr)]")
/datum/computer_file/program/status/ui_act(action, list/params, datum/tgui/ui)
/datum/computer_file/program/status/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("setStatusMessage")
upper_text = reject_bad_text(params["upperText"] || "", MAX_STATUS_LINE_LENGTH)
@@ -104,3 +106,26 @@
data["lowerText"] = lower_text
return data
/obj/item/circuit_component/mod_program/status
associated_program = /datum/computer_file/program/status
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
///When the trigger is signaled, this will be the upper text of status displays.
var/datum/port/input/upper_text
///When the trigger is signaled, this will be the bottom text.
var/datum/port/input/bottom_text
///A list port that, when signaled, will set the status image to one of its values
var/datum/port/input/status_display_pics
/obj/item/circuit_component/mod_program/status/populate_options()
status_display_pics = add_option_port("Set Status Display Picture", GLOB.status_display_approved_pictures, trigger = PROC_REF(set_picture))
/obj/item/circuit_component/mod_program/status/proc/set_picture(datum/port/port)
var/datum/computer_file/program/status/status = associated_program
INVOKE_ASYNC(status, TYPE_PROC_REF(/datum/computer_file/program/status, post_picture), status_display_pics.value, parent.get_creator())
/obj/item/circuit_component/mod_program/status/input_received(datum/port/port)
var/datum/computer_file/program/status/status = associated_program
INVOKE_ASYNC(status, TYPE_PROC_REF(/datum/computer_file/program/status, post_message), upper_text.value, bottom_text.value, parent.get_creator())
@@ -88,7 +88,8 @@
)
return data
/datum/computer_file/program/science/ui_act(action, list/params)
/datum/computer_file/program/science/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
// Check if the console is locked to block any actions occuring
if (locked && action != "toggleLock")
computer.say("Console is locked, cannot perform further actions.")
@@ -23,6 +23,7 @@
return data
/datum/computer_file/program/themeify/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("PRG_change_theme")
var/selected_theme = params["selected_theme"]
@@ -9,8 +9,10 @@
program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET
tgui_id = "NtosNetMonitor"
program_icon = "network-wired"
circuit_comp_type = /obj/item/circuit_component/mod_program/ntnetmonitor
/datum/computer_file/program/ntnetmonitor/ui_act(action, list/params, datum/tgui/ui)
/datum/computer_file/program/ntnetmonitor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
switch(action)
if("resetIDS")
SSmodular_computers.intrusion_detection_alarm = FALSE
@@ -66,3 +68,64 @@
data["tablets"] += list(tablet_data)
return data
/obj/item/circuit_component/mod_program/ntnetmonitor
associated_program = /datum/computer_file/program/ntnetmonitor
///The stored NTnet relay or PDA to be used as the target of triggers
var/datum/port/input/target
///Sets `intrusion_detection_alarm` when triggered
var/datum/port/input/toggle_ids
///Toggles the target ntnet relay on/off when triggered
var/datum/port/input/toggle_relay
///Purges modpc logs when triggered
var/datum/port/input/purge_logs
///Toggles the spam mode of the target PDA when triggered
var/datum/port/input/toggle_mass_pda
///Toggle mime mode of the target PDA when triggered
var/datum/port/input/toggle_mime_mode
///Returns a list of all PDA Messengers when the "Get Messengers" input is pinged
var/datum/port/output/all_messengers
///See above
var/datum/port/input/get_pdas
/obj/item/circuit_component/mod_program/ntnetmonitor/populate_ports()
. = ..()
target = add_input_port("Target Messenger/Relay", PORT_TYPE_DATUM, order = 0.5)
toggle_ids = add_input_port("Toggle IDS Status", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_ids))
toggle_relay = add_input_port("Toggle NTnet Relay", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_relay))
purge_logs = add_input_port("Purge Logs", PORT_TYPE_SIGNAL, trigger = PROC_REF(purge_logs))
toggle_mass_pda = add_input_port("Toggle Mass Messenger", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_pda_stuff))
toggle_mime_mode = add_input_port("Toggle Mime Mode", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_pda_stuff))
get_pdas = add_input_port("Get PDAs", PORT_TYPE_SIGNAL, trigger = PROC_REF(get_pdas))
all_messengers = add_output_port("List of PDAs", PORT_TYPE_LIST(PORT_TYPE_ATOM))
/obj/item/circuit_component/mod_program/ntnetmonitor/proc/get_pdas(datum/port/port)
var/list/computers_with_messenger = list()
for(var/messenger_ref as anything in GLOB.pda_messengers)
var/datum/computer_file/program/messenger/messenger = GLOB.pda_messengers[messenger_ref]
computers_with_messenger += messenger.computer
all_messengers.set_value(computers_with_messenger)
/obj/item/circuit_component/mod_program/ntnetmonitor/proc/toggle_ids(datum/port/port)
SSmodular_computers.intrusion_detection_enabled = !SSmodular_computers.intrusion_detection_enabled
/obj/item/circuit_component/mod_program/ntnetmonitor/proc/toggle_relay(datum/port/port)
var/obj/machinery/ntnet_relay/target_relay = target.value
if(!istype(target_relay))
return
target_relay.set_relay_enabled(!target_relay.relay_enabled)
/obj/item/circuit_component/mod_program/ntnetmonitor/proc/purge_logs(datum/port/port)
SSmodular_computers.purge_logs()
/obj/item/circuit_component/mod_program/ntnetmonitor/proc/toggle_pda_stuff(datum/port/port)
var/obj/item/modular_computer/computer = target.value
if(!istype(computer))
return
var/datum/computer_file/program/messenger/target_messenger = locate() in computer.stored_files
if(isnull(target_messenger))
return
if(COMPONENT_TRIGGERED_BY(toggle_mass_pda, port))
target_messenger.spam_mode = !target_messenger.spam_mode
if(COMPONENT_TRIGGERED_BY(toggle_mime_mode, port))
target_messenger.mime_mode = !target_messenger.mime_mode
+1 -1
View File
@@ -86,7 +86,7 @@
*/
/datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_UI_ACT, usr, action)
SEND_SIGNAL(src, COMSIG_UI_ACT, usr, action, params)
// If UI is not interactive or usr calling Topic is not the UI user, bail.
if(!ui || ui.status != UI_INTERACTIVE)
return TRUE
+3
View File
@@ -174,6 +174,7 @@
qdel(input_port)
if(parent)
SStgui.update_uis(parent)
return null //explicitly set the port to null if used like this: `port = remove_input_port(port)`
/**
* Adds an output port and returns it
@@ -203,6 +204,7 @@
qdel(output_port)
if(parent)
SStgui.update_uis(parent)
return null //explicitly set the port to null if used like this: `port = remove_output_port(port)`
/**
@@ -340,6 +342,7 @@
if(length(input_ports))
. += create_ui_notice("Power Usage Per Input: [power_usage_per_input]", "orange", "bolt")
/**
* Called when a special button is pressed on this component in the UI.
*
+1
View File
@@ -271,3 +271,4 @@ You can use a machine in the vault to deposit cash or rob Cargo's department fun
You can use an upgraded microwave to charge your PDA!
You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people - it's a much better experience!
Some areas of the station use simple nautical directions to indicate their respective locations, like Fore (Front of the ship), Aft (Back), Port (Left side), Starboard (Right), Quarter and Bow (Either sides of Aft and Fore, respectively). You can review these terms on the Notepad App of your PDA.
Modular computers are compatible with integrated circuits, but most of the program-dependent circuits require them to be open/backgrounded to work. To install circuits on stationary consoles, you need to toggle interaction with the frame with right-click first.
+3
View File
@@ -5104,6 +5104,7 @@
#include "code\modules\mod\modules\modules_timeline.dm"
#include "code\modules\mod\modules\modules_visor.dm"
#include "code\modules\modular_computers\computers\item\computer.dm"
#include "code\modules\modular_computers\computers\item\computer_circuit.dm"
#include "code\modules\modular_computers\computers\item\computer_files.dm"
#include "code\modules\modular_computers\computers\item\computer_power.dm"
#include "code\modules\modular_computers\computers\item\computer_ui.dm"
@@ -5123,6 +5124,7 @@
#include "code\modules\modular_computers\file_system\data.dm"
#include "code\modules\modular_computers\file_system\picture_file.dm"
#include "code\modules\modular_computers\file_system\program.dm"
#include "code\modules\modular_computers\file_system\program_circuit.dm"
#include "code\modules\modular_computers\file_system\programs\airestorer.dm"
#include "code\modules\modular_computers\file_system\programs\alarm.dm"
#include "code\modules\modular_computers\file_system\programs\arcade.dm"
@@ -5168,6 +5170,7 @@
#include "code\modules\modular_computers\file_system\programs\maintenance\phys_scanner.dm"
#include "code\modules\modular_computers\file_system\programs\maintenance\spectre_meter.dm"
#include "code\modules\modular_computers\file_system\programs\maintenance\themes.dm"
#include "code\modules\modular_computers\file_system\programs\messenger\messenger_circuit.dm"
#include "code\modules\modular_computers\file_system\programs\messenger\messenger_data.dm"
#include "code\modules\modular_computers\file_system\programs\messenger\messenger_program.dm"
#include "code\modules\movespeed\_movespeed_modifier.dm"