diff --git a/code/__DEFINES/dcs/signals/signals_circuit.dm b/code/__DEFINES/dcs/signals/signals_circuit.dm index c0676d768ce..3fe4e80fdb2 100644 --- a/code/__DEFINES/dcs/signals/signals_circuit.dm +++ b/code/__DEFINES/dcs/signals/signals_circuit.dm @@ -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" diff --git a/code/__DEFINES/dcs/signals/signals_modular_computer.dm b/code/__DEFINES/dcs/signals/signals_modular_computer.dm index 950262a5fe1..7fc2a61636a 100644 --- a/code/__DEFINES/dcs/signals/signals_modular_computer.dm +++ b/code/__DEFINES/dcs/signals/signals_modular_computer.dm @@ -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" diff --git a/code/__DEFINES/modular_computer.dm b/code/__DEFINES/modular_computer.dm index f8f666cfc63..a8e5c38ffc6 100644 --- a/code/__DEFINES/modular_computer.dm +++ b/code/__DEFINES/modular_computer.dm @@ -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" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index b3cd1921a64..8f8ce8dadbb 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -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" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index acbca5ffcce..d567f152c06 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -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, ), diff --git a/code/datums/components/shell.dm b/code/datums/components/shell.dm index 806e2585add..4646d30fc31 100644 --- a/code/datums/components/shell.dm +++ b/code/datums/components/shell.dm @@ -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) diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index adb10f128ae..e418988c058 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -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. diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 420bfa74b54..1749fc4a40c 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -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() diff --git a/code/modules/modular_computers/computers/item/computer_circuit.dm b/code/modules/modular_computers/computers/item/computer_circuit.dm new file mode 100644 index 00000000000..f659048b1d0 --- /dev/null +++ b/code/modules/modular_computers/computers/item/computer_circuit.dm @@ -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)) diff --git a/code/modules/modular_computers/computers/item/computer_power.dm b/code/modules/modular_computers/computers/item/computer_power.dm index b68df9a4671..db3ecdb7747 100644 --- a/code/modules/modular_computers/computers/item/computer_power.dm +++ b/code/modules/modular_computers/computers/item/computer_power.dm @@ -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() diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm index f5ca1d74765..4313bf2efbd 100644 --- a/code/modules/modular_computers/computers/item/computer_ui.dm +++ b/code/modules/modular_computers/computers/item/computer_ui.dm @@ -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") diff --git a/code/modules/modular_computers/computers/item/pda.dm b/code/modules/modular_computers/computers/item/pda.dm index 0c2580a32d4..24e304fd5ef 100644 --- a/code/modules/modular_computers/computers/item/pda.dm +++ b/code/modules/modular_computers/computers/item/pda.dm @@ -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 ..() diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm index ea4839ac944..1d947a5574b 100644 --- a/code/modules/modular_computers/computers/item/processor.dm +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -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) diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm index 7fd019c1bee..63a1d005169 100644 --- a/code/modules/modular_computers/computers/machinery/modular_computer.dm +++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/computer_file.dm b/code/modules/modular_computers/file_system/computer_file.dm index 948257aa83c..7b4fc676091 100644 --- a/code/modules/modular_computers/file_system/computer_file.dm +++ b/code/modules/modular_computers/file_system/computer_file.dm @@ -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) /** diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index 5ddb064f319..1b18e5a2924 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/program_circuit.dm b/code/modules/modular_computers/file_system/program_circuit.dm new file mode 100644 index 00000000000..5ced6e03e09 --- /dev/null +++ b/code/modules/modular_computers/file_system/program_circuit.dm @@ -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") diff --git a/code/modules/modular_computers/file_system/programs/airestorer.dm b/code/modules/modular_computers/file_system/programs/airestorer.dm index 9fc51a00f57..57c9e730072 100644 --- a/code/modules/modular_computers/file_system/programs/airestorer.dm +++ b/code/modules/modular_computers/file_system/programs/airestorer.dm @@ -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) diff --git a/code/modules/modular_computers/file_system/programs/antagonist/contractor_program.dm b/code/modules/modular_computers/file_system/programs/antagonist/contractor_program.dm index def1aa655d8..d357d0f12f7 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/contractor_program.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/contractor_program.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm index d3b7e9d5b86..986c2e10f58 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm @@ -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)) diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm index 3e0ac275c14..5163557eb04 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/arcade.dm b/code/modules/modular_computers/file_system/programs/arcade.dm index 9f4db1963c5..fd52792bc8b 100644 --- a/code/modules/modular_computers/file_system/programs/arcade.dm +++ b/code/modules/modular_computers/file_system/programs/arcade.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/atmosscan.dm b/code/modules/modular_computers/file_system/programs/atmosscan.dm index fd520fbb385..8f29af43510 100644 --- a/code/modules/modular_computers/file_system/programs/atmosscan.dm +++ b/code/modules/modular_computers/file_system/programs/atmosscan.dm @@ -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) diff --git a/code/modules/modular_computers/file_system/programs/borg_monitor.dm b/code/modules/modular_computers/file_system/programs/borg_monitor.dm index 196ab033408..6f5b116a930 100644 --- a/code/modules/modular_computers/file_system/programs/borg_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm @@ -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, "

[span_notice("Message from [ID] -- \"[message]\"")]
") - 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, "

[span_notice("Message from [ID] to [R] -- \"[message]\"")]
") - 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, "

[span_notice("Message from [ID] -- \"[message]\"")]
") + 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, "

[span_notice("Message from [ID] to [robot] -- \"[message]\"")]
") + 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 diff --git a/code/modules/modular_computers/file_system/programs/bounty_board.dm b/code/modules/modular_computers/file_system/programs/bounty_board.dm index 4bbad8d1d8b..86590192041 100644 --- a/code/modules/modular_computers/file_system/programs/bounty_board.dm +++ b/code/modules/modular_computers/file_system/programs/bounty_board.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/budgetordering.dm b/code/modules/modular_computers/file_system/programs/budgetordering.dm index b88636a7de8..a5986aa3816 100644 --- a/code/modules/modular_computers/file_system/programs/budgetordering.dm +++ b/code/modules/modular_computers/file_system/programs/budgetordering.dm @@ -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()) diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm index c41aa8ae29a..238d05704e2 100644 --- a/code/modules/modular_computers/file_system/programs/card.dm +++ b/code/modules/modular_computers/file_system/programs/card.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/cargoship.dm b/code/modules/modular_computers/file_system/programs/cargoship.dm index 92c6e152f71..fa73149dc85 100644 --- a/code/modules/modular_computers/file_system/programs/cargoship.dm +++ b/code/modules/modular_computers/file_system/programs/cargoship.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm index 1067c3f392d..b3566591e12 100644 --- a/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm @@ -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)) diff --git a/code/modules/modular_computers/file_system/programs/coupon.dm b/code/modules/modular_computers/file_system/programs/coupon.dm index 72e3ff6ec06..d8ebec7510a 100644 --- a/code/modules/modular_computers/file_system/programs/coupon.dm +++ b/code/modules/modular_computers/file_system/programs/coupon.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm index f7e3df473ea..d635a12000f 100644 --- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm +++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm index c9133515507..74af88ac870 100644 --- a/code/modules/modular_computers/file_system/programs/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/file_browser.dm @@ -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"]) diff --git a/code/modules/modular_computers/file_system/programs/frontier.dm b/code/modules/modular_computers/file_system/programs/frontier.dm index 884e635364c..c41cf642ffb 100644 --- a/code/modules/modular_computers/file_system/programs/frontier.dm +++ b/code/modules/modular_computers/file_system/programs/frontier.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm index d9805f4012e..b27ae687bf3 100644 --- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm +++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/mafia_ntos.dm b/code/modules/modular_computers/file_system/programs/mafia_ntos.dm index c970349bd20..bf092ed1d4e 100644 --- a/code/modules/modular_computers/file_system/programs/mafia_ntos.dm +++ b/code/modules/modular_computers/file_system/programs/mafia_ntos.dm @@ -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() diff --git a/code/modules/modular_computers/file_system/programs/maintenance/camera.dm b/code/modules/modular_computers/file_system/programs/maintenance/camera.dm index c729e3ed5c4..cfa346d347c 100644 --- a/code/modules/modular_computers/file_system/programs/maintenance/camera.dm +++ b/code/modules/modular_computers/file_system/programs/maintenance/camera.dm @@ -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.")) diff --git a/code/modules/modular_computers/file_system/programs/maintenance/modsuit.dm b/code/modules/modular_computers/file_system/programs/maintenance/modsuit.dm index c67b001942c..0d094ced4f5 100644 --- a/code/modules/modular_computers/file_system/programs/maintenance/modsuit.dm +++ b/code/modules/modular_computers/file_system/programs/maintenance/modsuit.dm @@ -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) diff --git a/code/modules/modular_computers/file_system/programs/maintenance/spectre_meter.dm b/code/modules/modular_computers/file_system/programs/maintenance/spectre_meter.dm index 8a3a3e6cb6e..9806d6bb8cf 100644 --- a/code/modules/modular_computers/file_system/programs/maintenance/spectre_meter.dm +++ b/code/modules/modular_computers/file_system/programs/maintenance/spectre_meter.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/maintenance/themes.dm b/code/modules/modular_computers/file_system/programs/maintenance/themes.dm index fe6a25f15b1..26717e33d3a 100644 --- a/code/modules/modular_computers/file_system/programs/maintenance/themes.dm +++ b/code/modules/modular_computers/file_system/programs/maintenance/themes.dm @@ -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) diff --git a/code/modules/modular_computers/file_system/programs/messenger/messenger_circuit.dm b/code/modules/modular_computers/file_system/programs/messenger/messenger_circuit.dm new file mode 100644 index 00000000000..82228267db1 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/messenger/messenger_circuit.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm index 0b69fd5230f..de45c4260e8 100644 --- a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm +++ b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm @@ -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 = "(Reply)" - // 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 = "[sender_title]" diff --git a/code/modules/modular_computers/file_system/programs/newscasterapp.dm b/code/modules/modular_computers/file_system/programs/newscasterapp.dm index a19f38fb777..ed1c440f411 100644 --- a/code/modules/modular_computers/file_system/programs/newscasterapp.dm +++ b/code/modules/modular_computers/file_system/programs/newscasterapp.dm @@ -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) diff --git a/code/modules/modular_computers/file_system/programs/notepad.dm b/code/modules/modular_computers/file_system/programs/notepad.dm index a2fdd5929e8..fb0401f921b 100644 --- a/code/modules/modular_computers/file_system/programs/notepad.dm +++ b/code/modules/modular_computers/file_system/programs/notepad.dm @@ -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) diff --git a/code/modules/modular_computers/file_system/programs/nt_pay.dm b/code/modules/modular_computers/file_system/programs/nt_pay.dm index abaf03fa1cc..9aabf94f255 100644 --- a/code/modules/modular_computers/file_system/programs/nt_pay.dm +++ b/code/modules/modular_computers/file_system/programs/nt_pay.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm index 78320e9009f..3fbc7843f18 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -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) diff --git a/code/modules/modular_computers/file_system/programs/portrait_printer.dm b/code/modules/modular_computers/file_system/programs/portrait_printer.dm index 6fc1358ccaf..0e69dd4969d 100644 --- a/code/modules/modular_computers/file_system/programs/portrait_printer.dm +++ b/code/modules/modular_computers/file_system/programs/portrait_printer.dm @@ -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"]) diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index 5e32a212bac..3b3964d3fd8 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index 37ae20f5260..396b8aa8417 100644 --- a/code/modules/modular_computers/file_system/programs/robocontrol.dm +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/robotact.dm b/code/modules/modular_computers/file_system/programs/robotact.dm index d7a7122090d..8a2a824d004 100644 --- a/code/modules/modular_computers/file_system/programs/robotact.dm +++ b/code/modules/modular_computers/file_system/programs/robotact.dm @@ -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 diff --git a/code/modules/modular_computers/file_system/programs/signalcommander.dm b/code/modules/modular_computers/file_system/programs/signalcommander.dm index 2032ad5461b..1e6e3e54051 100644 --- a/code/modules/modular_computers/file_system/programs/signalcommander.dm +++ b/code/modules/modular_computers/file_system/programs/signalcommander.dm @@ -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] : [key_name(usr)] used the computer '[initial(computer.name)]' @ location ([T.x],[T.y],[T.z]) : [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] : [user_deets] used the computer '[initial(computer.name)]' @ location ([T.x],[T.y],[T.z]) : [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) diff --git a/code/modules/modular_computers/file_system/programs/skill_tracker.dm b/code/modules/modular_computers/file_system/programs/skill_tracker.dm index 9063e3e239d..bd208dcef52 100644 --- a/code/modules/modular_computers/file_system/programs/skill_tracker.dm +++ b/code/modules/modular_computers/file_system/programs/skill_tracker.dm @@ -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"]) diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index 0002aa983cc..72ab4d094c0 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -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() diff --git a/code/modules/modular_computers/file_system/programs/statusdisplay.dm b/code/modules/modular_computers/file_system/programs/statusdisplay.dm index 9d21b477971..fdee0d98724 100644 --- a/code/modules/modular_computers/file_system/programs/statusdisplay.dm +++ b/code/modules/modular_computers/file_system/programs/statusdisplay.dm @@ -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()) diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm index 3d9af432b69..1b2e29baef7 100644 --- a/code/modules/modular_computers/file_system/programs/techweb.dm +++ b/code/modules/modular_computers/file_system/programs/techweb.dm @@ -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.") diff --git a/code/modules/modular_computers/file_system/programs/theme_selector.dm b/code/modules/modular_computers/file_system/programs/theme_selector.dm index 5fdf1c8d365..6190f9b15ab 100644 --- a/code/modules/modular_computers/file_system/programs/theme_selector.dm +++ b/code/modules/modular_computers/file_system/programs/theme_selector.dm @@ -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"] diff --git a/code/modules/modular_computers/file_system/programs/wirecarp.dm b/code/modules/modular_computers/file_system/programs/wirecarp.dm index 64ef7137b00..fc3b181d524 100644 --- a/code/modules/modular_computers/file_system/programs/wirecarp.dm +++ b/code/modules/modular_computers/file_system/programs/wirecarp.dm @@ -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 diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index bcd97a91245..1168b6c619a 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -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 diff --git a/code/modules/wiremod/core/component.dm b/code/modules/wiremod/core/component.dm index ebabd211f70..39f33809073 100644 --- a/code/modules/wiremod/core/component.dm +++ b/code/modules/wiremod/core/component.dm @@ -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. * diff --git a/strings/tips.txt b/strings/tips.txt index 69128714f1f..31ae19a1cad 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -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. diff --git a/tgstation.dme b/tgstation.dme index 6e2d1f7211f..6197f82f996 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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"