tgui backend
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/// An admin verb to view all circuits, plus useful information
|
||||
/datum/admins/proc/view_all_circuits()
|
||||
set category = "Admin.Game"
|
||||
set name = "View All Circuits"
|
||||
|
||||
var/static/datum/circuit_admin_panel/circuit_admin_panel = new
|
||||
circuit_admin_panel.ui_interact(usr)
|
||||
|
||||
/datum/circuit_admin_panel
|
||||
|
||||
/datum/circuit_admin_panel/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["circuits"] = list()
|
||||
|
||||
for (var/obj/item/integrated_circuit/circuit as anything in GLOB.integrated_circuits)
|
||||
var/datum/mind/inserter = circuit.inserter_mind?.resolve()
|
||||
|
||||
data["circuits"] += list(list(
|
||||
"ref" = REF(circuit),
|
||||
"name" = "[circuit.name] in [loc_name(circuit)]",
|
||||
"creator" = circuit.get_creator(),
|
||||
"has_inserter" = !isnull(inserter),
|
||||
))
|
||||
|
||||
return data
|
||||
|
||||
/datum/circuit_admin_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return .
|
||||
|
||||
if (!istext(params["circuit"]))
|
||||
return FALSE
|
||||
|
||||
var/obj/item/integrated_circuit/circuit = locate(params["circuit"])
|
||||
if (!istype(circuit))
|
||||
to_chat(usr, span_warning("That circuit no longer exists."))
|
||||
return FALSE
|
||||
|
||||
switch (action)
|
||||
if ("duplicate_circuit")
|
||||
if (alert(usr, "This will spawn the new circuit at where you are, are you sure?", "Confirm", "Yes", "No") != "Yes")
|
||||
return FALSE
|
||||
|
||||
var/list/errors = list()
|
||||
|
||||
var/obj/item/integrated_circuit/loaded/new_circuit = new(usr.drop_location())
|
||||
new_circuit.load_circuit_data(circuit.convert_to_json(), errors)
|
||||
|
||||
if (length(errors))
|
||||
to_chat(usr, span_warning("Somehow, duplicating the circuit failed:"))
|
||||
for (var/error in errors)
|
||||
to_chat(usr, span_warning(error))
|
||||
if ("follow_circuit")
|
||||
usr.client?.admin_follow(circuit)
|
||||
if ("save_circuit")
|
||||
circuit.attempt_save_to(usr.client)
|
||||
if ("vv_circuit")
|
||||
usr.client?.debug_variables(circuit)
|
||||
if ("open_circuit")
|
||||
circuit.ui_interact(usr)
|
||||
if ("open_player_panel")
|
||||
var/datum/mind/inserter = circuit.inserter_mind?.resolve()
|
||||
usr.client?.holder?.show_player_panel(inserter?.current)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/circuit_admin_panel/ui_state(mob/user)
|
||||
return GLOB.admin_state
|
||||
|
||||
/datum/circuit_admin_panel/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "CircuitAdminPanel")
|
||||
ui.open()
|
||||
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* # Integrated Circuit Component
|
||||
*
|
||||
* A component that performs a function when given an input
|
||||
*
|
||||
* Can be attached to an integrated circuitboard, where it can then
|
||||
* be connected between other components to provide an output or to receive
|
||||
* an input. This is the base type of all components
|
||||
*/
|
||||
/obj/item/circuit_component
|
||||
name = COMPONENT_DEFAULT_NAME
|
||||
icon = 'icons/obj/module.dmi'
|
||||
icon_state = "component"
|
||||
inhand_icon_state = "electronic"
|
||||
|
||||
/// The name of the component shown on the UI
|
||||
var/display_name = "Generic"
|
||||
|
||||
/// The integrated_circuit that this component is attached to.
|
||||
var/obj/item/integrated_circuit/parent
|
||||
|
||||
/// A list that contains the outpurt ports on this component
|
||||
/// Used to connect between the ports
|
||||
var/list/datum/port/output/output_ports = list()
|
||||
|
||||
/// A list that contains the components the input ports on this component
|
||||
/// Used to connect between the ports
|
||||
var/list/datum/port/input/input_ports = list()
|
||||
|
||||
/// Generic trigger input for triggering this component
|
||||
var/datum/port/input/trigger_input
|
||||
var/datum/port/output/trigger_output
|
||||
|
||||
/// The flags of the circuit to control basic generalised behaviour.
|
||||
var/circuit_flags = NONE
|
||||
|
||||
/// Used to determine the x position of the component within the UI
|
||||
var/rel_x = 0
|
||||
/// Used to determine the y position of the component within the UI
|
||||
var/rel_y = 0
|
||||
|
||||
/// The power usage whenever this component receives an input
|
||||
var/power_usage_per_input = 1
|
||||
|
||||
// Whether the component is removable or not. Only affects user UI
|
||||
var/removable = TRUE
|
||||
|
||||
// Defines which shells support this component. Only used as an informational guide, does not restrict placing these components in circuits.
|
||||
var/required_shells = null
|
||||
|
||||
/// Called when the option ports should be set up
|
||||
/obj/item/circuit_component/proc/populate_options()
|
||||
return
|
||||
|
||||
/// Extension of add_input_port. Simplifies the code to make an option port to reduce boilerplate
|
||||
/obj/item/circuit_component/proc/add_option_port(name, list/list_to_use)
|
||||
return add_input_port(name, PORT_TYPE_OPTION, port_type = /datum/port/input/option, extra_args = list("possible_options" = list_to_use))
|
||||
|
||||
/obj/item/circuit_component/Initialize()
|
||||
. = ..()
|
||||
if(name == COMPONENT_DEFAULT_NAME)
|
||||
name = "[lowertext(display_name)] [COMPONENT_DEFAULT_NAME]"
|
||||
populate_options()
|
||||
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
/obj/item/circuit_component/LateInitialize()
|
||||
. = ..()
|
||||
if(circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL)
|
||||
trigger_input = add_input_port("Trigger", PORT_TYPE_SIGNAL)
|
||||
if(circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL)
|
||||
trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL)
|
||||
|
||||
/obj/item/circuit_component/Destroy()
|
||||
if(parent)
|
||||
// Prevents a Destroy() recursion
|
||||
var/obj/item/integrated_circuit/old_parent = parent
|
||||
parent = null
|
||||
old_parent.remove_component(src)
|
||||
|
||||
trigger_input = null
|
||||
trigger_output = null
|
||||
|
||||
QDEL_LIST(output_ports)
|
||||
QDEL_LIST(input_ports)
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Called when a shell is registered from the component/the component is added to a circuit.
|
||||
*
|
||||
* Register all signals here on the shell.
|
||||
* Arguments:
|
||||
* * shell - Shell being registered
|
||||
*/
|
||||
/obj/item/circuit_component/proc/register_shell(atom/movable/shell)
|
||||
return
|
||||
|
||||
/**
|
||||
* Called when a shell is unregistered from the component/the component is removed from a circuit.
|
||||
*
|
||||
* Unregister all signals here on the shell.
|
||||
* Arguments:
|
||||
* * shell - Shell being unregistered
|
||||
*/
|
||||
/obj/item/circuit_component/proc/unregister_shell(atom/movable/shell)
|
||||
return
|
||||
|
||||
/**
|
||||
* Disconnects a component from other components
|
||||
*
|
||||
* Disconnects both the input and output ports of the component
|
||||
*/
|
||||
/obj/item/circuit_component/proc/disconnect()
|
||||
for(var/datum/port/output/port_to_disconnect as anything in output_ports)
|
||||
port_to_disconnect.disconnect_all()
|
||||
|
||||
for(var/datum/port/input/port_to_disconnect as anything in input_ports)
|
||||
port_to_disconnect.disconnect_all()
|
||||
|
||||
/**
|
||||
* Adds an input port and returns it
|
||||
*
|
||||
* Arguments:
|
||||
* * name - The name of the input port
|
||||
* * type - The datatype it handles
|
||||
* * trigger - Whether this input port triggers an update on the component when updated.
|
||||
*/
|
||||
/obj/item/circuit_component/proc/add_input_port(name, type, trigger = TRUE, default = null, index = null, port_type = /datum/port/input, extra_args = null)
|
||||
var/list/arguments = list(src)
|
||||
arguments += args
|
||||
if(extra_args)
|
||||
arguments += extra_args
|
||||
var/datum/port/input/input_port = new port_type(arglist(arguments))
|
||||
if(index)
|
||||
input_ports.Insert(index, input_port)
|
||||
else
|
||||
input_ports += input_port
|
||||
if(parent)
|
||||
SStgui.update_uis(parent)
|
||||
return input_port
|
||||
|
||||
/**
|
||||
* Removes an input port and deletes it. This will not cleanup any references made by derivatives of the circuit component
|
||||
*
|
||||
* Arguments:
|
||||
* * input_port - The input port to remove.
|
||||
*/
|
||||
/obj/item/circuit_component/proc/remove_input_port(datum/port/input/input_port)
|
||||
input_ports -= input_port
|
||||
qdel(input_port)
|
||||
if(parent)
|
||||
SStgui.update_uis(parent)
|
||||
|
||||
/**
|
||||
* Adds an output port and returns it
|
||||
*
|
||||
* Arguments:
|
||||
* * name - The name of the output port
|
||||
* * type - The datatype it handles.
|
||||
*/
|
||||
/obj/item/circuit_component/proc/add_output_port(name, type)
|
||||
var/list/arguments = list(src)
|
||||
arguments += args
|
||||
var/datum/port/output/output_port = new(arglist(arguments))
|
||||
output_ports += output_port
|
||||
return output_port
|
||||
|
||||
/**
|
||||
* Removes an output port and deletes it. This will not cleanup any references made by derivatives of the circuit component
|
||||
*
|
||||
* Arguments:
|
||||
* * output_port - The output port to remove.
|
||||
*/
|
||||
/obj/item/circuit_component/proc/remove_output_port(datum/port/output/output_port)
|
||||
output_ports -= output_port
|
||||
qdel(output_port)
|
||||
if(parent)
|
||||
SStgui.update_uis(parent)
|
||||
|
||||
/**
|
||||
* Called whenever an input is received from one of the ports.
|
||||
*
|
||||
* Return value indicates that the circuit should not do anything. Also prevents an output signal.
|
||||
* Arguments:
|
||||
* * port - Can be null. The port that sent the input
|
||||
*/
|
||||
/obj/item/circuit_component/proc/input_received(datum/port/input/port)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
if(!parent?.on)
|
||||
return TRUE
|
||||
|
||||
if(!parent.admin_only)
|
||||
if(circuit_flags & CIRCUIT_FLAG_ADMIN)
|
||||
message_admins("[display_name] tried to execute on [parent.get_creator_admin()] that has admin_only set to 0")
|
||||
return TRUE
|
||||
|
||||
var/obj/item/stock_parts/cell/cell = parent.get_cell()
|
||||
if(!cell?.use(power_usage_per_input))
|
||||
return TRUE
|
||||
|
||||
if((circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL) && !COMPONENT_TRIGGERED_BY(trigger_input, port))
|
||||
return TRUE
|
||||
|
||||
/// Called when this component is about to be added to an integrated_circuit.
|
||||
/obj/item/circuit_component/proc/add_to(obj/item/integrated_circuit/added_to)
|
||||
return TRUE
|
||||
|
||||
/// Called when this component is removed from an integrated_circuit.
|
||||
/obj/item/circuit_component/proc/removed_from(obj/item/integrated_circuit/removed_from)
|
||||
return
|
||||
|
||||
/**
|
||||
* Gets the UI notices to be displayed on the CircuitInfo panel.
|
||||
*
|
||||
* Returns a list of buttons in the following format
|
||||
* list(
|
||||
* "icon" = ICON(string)
|
||||
* "content" = CONTENT(string)
|
||||
* "color" = COLOR(string, not a hex)
|
||||
* )
|
||||
*/
|
||||
/obj/item/circuit_component/proc/get_ui_notices()
|
||||
. = list()
|
||||
|
||||
if(!removable)
|
||||
. += create_ui_notice("Unremovable", "red", "lock")
|
||||
|
||||
if(length(required_shells))
|
||||
. += create_ui_notice("Supported Shells:", "green", "notes-medical")
|
||||
for(var/atom/movable/shell as anything in required_shells)
|
||||
. += create_ui_notice(initial(shell.name), "green", "plus-square")
|
||||
|
||||
if(length(input_ports))
|
||||
. += create_ui_notice("Power Usage Per Input: [power_usage_per_input]", "orange", "bolt")
|
||||
|
||||
/**
|
||||
* Creates a UI notice entry to be used in get_ui_notices()
|
||||
*
|
||||
* Returns a list that can then be added to the return list in get_ui_notices()
|
||||
*/
|
||||
/obj/item/circuit_component/proc/create_ui_notice(content, color, icon)
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
return list(list(
|
||||
"icon" = icon,
|
||||
"content" = content,
|
||||
"color" = color,
|
||||
))
|
||||
|
||||
/**
|
||||
* Creates a table UI notice entry to be used in get_ui_notices()
|
||||
*
|
||||
* Returns a list that can then be added to the return list in get_ui_notices()
|
||||
* Used by components to list their available columns. Recommended to use at the end of get_ui_notices()
|
||||
*/
|
||||
/obj/item/circuit_component/proc/create_table_notices(list/entries)
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
. = list()
|
||||
. += create_ui_notice("Available Columns:", "grey", "question-circle")
|
||||
|
||||
|
||||
for(var/entry in entries)
|
||||
. += create_ui_notice("Column Name: '[entry]'", "grey", "columns")
|
||||
|
||||
/obj/item/circuit_component/proc/register_usb_parent(atom/movable/parent)
|
||||
return
|
||||
|
||||
/obj/item/circuit_component/proc/unregister_usb_parent(atom/movable/parent)
|
||||
return
|
||||
@@ -0,0 +1,394 @@
|
||||
/// Component printer, creates components for integrated circuits.
|
||||
/obj/machinery/component_printer
|
||||
name = "component printer"
|
||||
desc = "Produces components for the creation of integrated circuits."
|
||||
icon = 'icons/obj/wiremod_fab.dmi'
|
||||
icon_state = "fab-idle"
|
||||
circuit = /obj/item/circuitboard/machine/component_printer
|
||||
|
||||
/// The internal material bus
|
||||
var/datum/component/remote_materials/materials
|
||||
|
||||
density = TRUE
|
||||
|
||||
/// The techweb the printer will get researched designs from
|
||||
var/datum/techweb/techweb
|
||||
|
||||
/obj/machinery/component_printer/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
techweb = SSresearch.science_tech
|
||||
|
||||
materials = AddComponent( \
|
||||
/datum/component/remote_materials, \
|
||||
"component_printer", \
|
||||
mapload, \
|
||||
mat_container_flags = BREAKDOWN_FLAGS_LATHE, \
|
||||
)
|
||||
|
||||
/obj/machinery/component_printer/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ComponentPrinter", name)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/component_printer/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
|
||||
)
|
||||
|
||||
/obj/machinery/component_printer/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
|
||||
switch (action)
|
||||
if ("print")
|
||||
var/design_id = params["designId"]
|
||||
if (!techweb.researched_designs[design_id])
|
||||
return TRUE
|
||||
|
||||
var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
|
||||
if (!(design.build_type & COMPONENT_PRINTER))
|
||||
return TRUE
|
||||
|
||||
if (materials.on_hold())
|
||||
say("Mineral access is on hold, please contact the quartermaster.")
|
||||
return TRUE
|
||||
|
||||
if (!materials.mat_container?.has_materials(design.materials))
|
||||
say("Not enough materials.")
|
||||
return TRUE
|
||||
|
||||
balloon_alert_to_viewers("printed [design.name]")
|
||||
materials.mat_container?.use_materials(design.materials)
|
||||
materials.silo_log(src, "printed", -1, design.name, design.materials)
|
||||
var/atom/printed_design = new design.build_path(drop_location())
|
||||
printed_design.pixel_x = printed_design.base_pixel_x + rand(-5, 5)
|
||||
printed_design.pixel_y = printed_design.base_pixel_y + rand(-5, 5)
|
||||
if ("remove_mat")
|
||||
var/datum/material/material = locate(params["ref"])
|
||||
var/amount = text2num(params["amount"])
|
||||
|
||||
if (!amount)
|
||||
return TRUE
|
||||
|
||||
// SAFETY: eject_sheets checks for valid mats
|
||||
materials.eject_sheets(material, amount)
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/component_printer/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["materials"] = materials.mat_container.ui_data()
|
||||
return data
|
||||
|
||||
/obj/machinery/component_printer/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/designs = list()
|
||||
|
||||
// for (var/datum/design/component/component_design_type as anything in subtypesof(/datum/design/component))
|
||||
for (var/researched_design_id in techweb.researched_designs)
|
||||
var/datum/design/design = SSresearch.techweb_design_by_id(researched_design_id)
|
||||
if (!(design.build_type & COMPONENT_PRINTER))
|
||||
continue
|
||||
|
||||
designs[researched_design_id] = list(
|
||||
"name" = design.name,
|
||||
"description" = design.desc,
|
||||
"materials" = get_material_cost_data(design.materials),
|
||||
"categories" = design.category,
|
||||
)
|
||||
|
||||
data["designs"] = designs
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/component_printer/crowbar_act(mob/living/user, obj/item/tool)
|
||||
if(..())
|
||||
return TRUE
|
||||
return default_deconstruction_crowbar(tool)
|
||||
|
||||
/obj/machinery/component_printer/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
if(..())
|
||||
return TRUE
|
||||
return default_deconstruction_screwdriver(user, "fab-o", "fab-idle", tool)
|
||||
|
||||
/obj/machinery/component_printer/proc/get_material_cost_data(list/materials)
|
||||
var/list/data = list()
|
||||
|
||||
for (var/datum/material/material_type as anything in materials)
|
||||
data[initial(material_type.name)] = materials[material_type]
|
||||
|
||||
return data
|
||||
|
||||
/obj/item/circuitboard/machine/component_printer
|
||||
name = "\improper Component Printer (Machine Board)"
|
||||
greyscale_colors = CIRCUIT_COLOR_SCIENCE
|
||||
build_path = /obj/machinery/component_printer
|
||||
req_components = list(
|
||||
/obj/item/stock_parts/matter_bin = 2,
|
||||
/obj/item/stock_parts/manipulator = 2,
|
||||
/obj/item/reagent_containers/glass/beaker = 2,
|
||||
)
|
||||
|
||||
/obj/machinery/debug_component_printer
|
||||
name = "debug component printer"
|
||||
desc = "Produces components for the creation of integrated circuits."
|
||||
icon = 'icons/obj/wiremod_fab.dmi'
|
||||
icon_state = "fab-idle"
|
||||
|
||||
/// All of the possible circuit designs stored by this debug printer
|
||||
var/list/all_circuit_designs
|
||||
|
||||
density = TRUE
|
||||
|
||||
/obj/machinery/debug_component_printer/Initialize()
|
||||
. = ..()
|
||||
all_circuit_designs = list()
|
||||
|
||||
for(var/id in SSresearch.techweb_designs)
|
||||
var/datum/design/design = SSresearch.techweb_design_by_id(id)
|
||||
if((design.build_type & COMPONENT_PRINTER) && design.build_path)
|
||||
all_circuit_designs[design.build_path] = list(
|
||||
"name" = design.name,
|
||||
"description" = design.desc,
|
||||
"materials" = design.materials,
|
||||
"categories" = design.category
|
||||
)
|
||||
|
||||
for(var/obj/item/circuit_component/component as anything in subtypesof(/obj/item/circuit_component))
|
||||
var/categories = list("Inaccessible")
|
||||
if(initial(component.circuit_flags) & CIRCUIT_FLAG_ADMIN)
|
||||
categories = list("Admin")
|
||||
if(!(component in all_circuit_designs))
|
||||
all_circuit_designs[component] = list(
|
||||
"name" = initial(component.display_name),
|
||||
"description" = initial(component.desc),
|
||||
"materials" = list(),
|
||||
"categories" = categories,
|
||||
)
|
||||
|
||||
/obj/machinery/debug_component_printer/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ComponentPrinter", name)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/debug_component_printer/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
|
||||
)
|
||||
|
||||
/obj/machinery/debug_component_printer/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
|
||||
switch (action)
|
||||
if ("print")
|
||||
var/build_path = text2path(params["designId"])
|
||||
if (!build_path)
|
||||
return TRUE
|
||||
|
||||
var/list/design = all_circuit_designs[build_path]
|
||||
if(!design)
|
||||
return TRUE
|
||||
|
||||
balloon_alert_to_viewers("printed [design["name"]]")
|
||||
var/atom/printed_design = new build_path(drop_location())
|
||||
printed_design.pixel_x = printed_design.base_pixel_x + rand(-5, 5)
|
||||
printed_design.pixel_y = printed_design.base_pixel_y + rand(-5, 5)
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/debug_component_printer/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["materials"] = list()
|
||||
data["designs"] = all_circuit_designs
|
||||
|
||||
return data
|
||||
|
||||
/// Module duplicator, allows you to save and recreate module components.
|
||||
/obj/machinery/module_duplicator
|
||||
name = "module duplicator"
|
||||
desc = "Allows you to duplicate module components so that you don't have to recreate them. Scan a module component over this machine to add it as an entry."
|
||||
icon = 'icons/obj/wiremod_fab.dmi'
|
||||
icon_state = "module-fab-idle"
|
||||
circuit = /obj/item/circuitboard/machine/module_duplicator
|
||||
|
||||
/// The internal material bus
|
||||
var/datum/component/remote_materials/materials
|
||||
|
||||
density = TRUE
|
||||
|
||||
var/list/scanned_designs = list()
|
||||
|
||||
var/cost_per_component = 1000
|
||||
|
||||
/obj/machinery/module_duplicator/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
materials = AddComponent( \
|
||||
/datum/component/remote_materials, \
|
||||
"module_duplicator", \
|
||||
mapload, \
|
||||
mat_container_flags = BREAKDOWN_FLAGS_LATHE, \
|
||||
)
|
||||
|
||||
/obj/machinery/module_duplicator/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ComponentPrinter", name)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/module_duplicator/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
|
||||
)
|
||||
|
||||
/obj/machinery/module_duplicator/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
|
||||
switch (action)
|
||||
if ("print")
|
||||
var/design_id = text2num(params["designId"])
|
||||
|
||||
if (design_id < 1 || design_id > length(scanned_designs))
|
||||
return TRUE
|
||||
|
||||
var/list/design = scanned_designs[design_id]
|
||||
|
||||
if (materials.on_hold())
|
||||
say("Mineral access is on hold, please contact the quartermaster.")
|
||||
return TRUE
|
||||
|
||||
if (!materials.mat_container?.has_materials(design["materials"]))
|
||||
say("Not enough materials.")
|
||||
return TRUE
|
||||
|
||||
balloon_alert_to_viewers("printed [design["name"]]")
|
||||
materials.mat_container?.use_materials(design["materials"])
|
||||
materials.silo_log(src, "printed", -1, design["name"], design["materials"])
|
||||
print_module(design)
|
||||
if ("remove_mat")
|
||||
var/datum/material/material = locate(params["ref"])
|
||||
var/amount = text2num(params["amount"])
|
||||
|
||||
if (!amount)
|
||||
return TRUE
|
||||
|
||||
// SAFETY: eject_sheets checks for valid mats
|
||||
materials.eject_sheets(material, amount)
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/module_duplicator/proc/print_module(list/design)
|
||||
flick("module-fab-print", src)
|
||||
addtimer(CALLBACK(src, .proc/finish_module_print, design), 1.6 SECONDS)
|
||||
|
||||
/obj/machinery/module_duplicator/proc/finish_module_print(list/design)
|
||||
var/obj/item/circuit_component/module/module = new(drop_location())
|
||||
module.load_data_from_list(design["dupe_data"])
|
||||
module.pixel_x = module.base_pixel_x + rand(-5, 5)
|
||||
module.pixel_y = module.base_pixel_y + rand(-5, 5)
|
||||
|
||||
/obj/machinery/module_duplicator/attackby(obj/item/weapon, mob/user, params)
|
||||
if(!istype(weapon, /obj/item/circuit_component/module))
|
||||
return ..()
|
||||
|
||||
var/obj/item/circuit_component/module/module = weapon
|
||||
if(module.circuit_flags & CIRCUIT_FLAG_UNDUPEABLE)
|
||||
balloon_alert(user, "module cannot be saved!")
|
||||
return ..()
|
||||
|
||||
if(module.display_name == initial(module.display_name))
|
||||
balloon_alert(user, "module needs a name!")
|
||||
return ..()
|
||||
|
||||
for(var/list/component_data as anything in scanned_designs)
|
||||
if(component_data["name"] == module.display_name)
|
||||
balloon_alert(user, "module name already exists!")
|
||||
return ..()
|
||||
|
||||
var/total_cost = 0
|
||||
for(var/obj/item/circuit_component/component as anything in module.internal_circuit.attached_components)
|
||||
if(component.circuit_flags & CIRCUIT_FLAG_UNDUPEABLE)
|
||||
balloon_alert(user, "module contains prohibited components!")
|
||||
return ..()
|
||||
|
||||
total_cost += cost_per_component
|
||||
|
||||
var/list/data = list()
|
||||
|
||||
data["dupe_data"] = list()
|
||||
module.save_data_to_list(data["dupe_data"])
|
||||
|
||||
data["name"] = module.display_name
|
||||
data["desc"] = "A module that has been loaded in by [user]."
|
||||
data["materials"] = list(/datum/material/glass = total_cost)
|
||||
|
||||
flick("module-fab-scan", src)
|
||||
addtimer(CALLBACK(src, .proc/finish_module_scan, user, data), 1.4 SECONDS)
|
||||
|
||||
/obj/machinery/module_duplicator/proc/finish_module_scan(mob/user, data)
|
||||
scanned_designs += list(data)
|
||||
|
||||
balloon_alert(user, "module has been saved.")
|
||||
playsound(src, 'sound/machines/ping.ogg', 50)
|
||||
|
||||
/obj/machinery/module_duplicator/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["materials"] = materials.mat_container.ui_data()
|
||||
return data
|
||||
|
||||
/obj/machinery/module_duplicator/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/designs = list()
|
||||
|
||||
var/index = 1
|
||||
for (var/list/design as anything in scanned_designs)
|
||||
designs["[index]"] = list(
|
||||
"name" = design["name"],
|
||||
"description" = design["desc"],
|
||||
"materials" = get_material_cost_data(design["materials"]),
|
||||
"categories" = list("Circuitry"),
|
||||
)
|
||||
index++
|
||||
|
||||
data["designs"] = designs
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/module_duplicator/crowbar_act(mob/living/user, obj/item/tool)
|
||||
if(..())
|
||||
return TRUE
|
||||
return default_deconstruction_crowbar(tool)
|
||||
|
||||
/obj/machinery/module_duplicator/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
if(..())
|
||||
return TRUE
|
||||
return default_deconstruction_screwdriver(user, "module-fab-o", "module-fab-idle", tool)
|
||||
|
||||
/obj/machinery/module_duplicator/proc/get_material_cost_data(list/materials)
|
||||
var/list/data = list()
|
||||
|
||||
for (var/datum/material/material_type as anything in materials)
|
||||
data[initial(material_type.name)] = materials[material_type]
|
||||
|
||||
return data
|
||||
|
||||
/obj/item/circuitboard/machine/module_duplicator
|
||||
name = "\improper Module Duplicator (Machine Board)"
|
||||
greyscale_colors = CIRCUIT_COLOR_SCIENCE
|
||||
build_path = /obj/machinery/module_duplicator
|
||||
req_components = list(
|
||||
/obj/item/stock_parts/matter_bin = 2,
|
||||
/obj/item/stock_parts/manipulator = 2,
|
||||
/obj/item/reagent_containers/glass/beaker = 2,
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
// An assoc list of all the possible datatypes.
|
||||
GLOBAL_LIST_INIT(circuit_datatypes, generate_circuit_datatypes())
|
||||
|
||||
/proc/generate_circuit_datatypes()
|
||||
var/list/datatypes_by_key = list()
|
||||
for(var/datum/circuit_datatype/type as anything in subtypesof(/datum/circuit_datatype))
|
||||
if(!initial(type.datatype))
|
||||
continue
|
||||
datatypes_by_key[initial(type.datatype)] = new type()
|
||||
return datatypes_by_key
|
||||
|
||||
/**
|
||||
* A circuit datatype. Used to determine the datatype of a port and also handle any additional behaviour.
|
||||
*/
|
||||
/datum/circuit_datatype
|
||||
/// The key. Used to identify the datatype. Should be a define.
|
||||
var/datatype
|
||||
|
||||
/// The color of the port in the UI. Doesn't work with hex colours.
|
||||
var/color = "blue"
|
||||
|
||||
/// The flags of the circuit datatype
|
||||
var/datatype_flags = 0
|
||||
|
||||
/**
|
||||
* Returns the value to be set for the port
|
||||
*
|
||||
* Used for implicit conversions between outputs and inputs (e.g. number -> string)
|
||||
* and applying/removing signals on inputs
|
||||
*/
|
||||
/datum/circuit_datatype/proc/convert_value(datum/port/port, value_to_convert)
|
||||
return value_to_convert
|
||||
|
||||
/**
|
||||
* Determines if a datatype is compatible with another port of a different type.
|
||||
* Note: This is ALWAYS called on the input port, never on the output port.
|
||||
* Inputs need to care about what types they're receiving, output ports don't have to care.
|
||||
*
|
||||
* Arguments:
|
||||
* * datatype_to_check - The datatype to check
|
||||
*/
|
||||
/datum/circuit_datatype/proc/can_receive_from_datatype(datatype_to_check)
|
||||
return datatype == datatype_to_check // This is already done by default on the input port.
|
||||
|
||||
/**
|
||||
* Called when the datatype is given to a port.
|
||||
*
|
||||
* Arguments:
|
||||
* * gained_port - The gained port.
|
||||
*/
|
||||
/datum/circuit_datatype/proc/on_gain(datum/port/gained_port)
|
||||
return
|
||||
|
||||
/**
|
||||
* Called when the datatype is removed from a port.
|
||||
*
|
||||
* Arguments:
|
||||
* * lost_port - The removed port.
|
||||
*/
|
||||
/datum/circuit_datatype/proc/on_loss(datum/port/lost_port)
|
||||
return
|
||||
|
||||
/**
|
||||
* Determines if a port is compatible with this datatype.
|
||||
* This WILL throw a runtime if it returns false. This is for sanity checking and it should not return false
|
||||
* unless under extraordinary circumstances or people fail to write proper code.
|
||||
*
|
||||
* Arguments:
|
||||
* * port - The port to check if it is compatible.
|
||||
*/
|
||||
/datum/circuit_datatype/proc/is_compatible(datum/port/port)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* The data to send to the UI attached to the port. Received by the type in FUNDAMENTAL_PORT_TYPES
|
||||
*
|
||||
* Arguments:
|
||||
* * port - The port sending the data.
|
||||
*/
|
||||
/datum/circuit_datatype/proc/datatype_ui_data(datum/port/port)
|
||||
return
|
||||
|
||||
/**
|
||||
* When an input is manually set by a player. This is where extra sanitizing can happen. Will still call convert_value()
|
||||
*
|
||||
* Arguments:
|
||||
* * port - The port sending the data.
|
||||
* *
|
||||
*/
|
||||
/datum/circuit_datatype/proc/handle_manual_input(datum/port/input/port, mob/user, user_input)
|
||||
return user_input
|
||||
@@ -0,0 +1,227 @@
|
||||
#define LOG_ERROR(list, error) if(list) { list.Add(error) }
|
||||
|
||||
// Determines if a port can have a predefined input value if it is of this type.
|
||||
GLOBAL_LIST_INIT(circuit_dupe_whitelisted_types, list(
|
||||
PORT_TYPE_NUMBER,
|
||||
PORT_TYPE_STRING,
|
||||
PORT_TYPE_LIST,
|
||||
PORT_TYPE_ANY,
|
||||
PORT_TYPE_OPTION,
|
||||
))
|
||||
|
||||
/// Loads a circuit based on json data at a location. Can also load usb connections, such as arrest consoles.
|
||||
/obj/item/integrated_circuit/proc/load_circuit_data(json_data, list/errors)
|
||||
var/list/general_data = json_decode(json_data)
|
||||
|
||||
if(!general_data)
|
||||
LOG_ERROR(errors, "Invalid json format!")
|
||||
return
|
||||
|
||||
if(general_data["display_name"])
|
||||
set_display_name(general_data["display_name"])
|
||||
|
||||
var/list/variable_data = general_data["variables"]
|
||||
for(var/list/variable as anything in variable_data)
|
||||
var/variable_name = variable["name"]
|
||||
circuit_variables[variable_name] = new /datum/circuit_variable(variable_name, variable["datatype"])
|
||||
|
||||
admin_only = general_data["admin_only"]
|
||||
|
||||
var/list/circuit_data = general_data["components"]
|
||||
var/list/identifiers_to_circuit = list()
|
||||
for(var/identifier in circuit_data)
|
||||
var/list/component_data = circuit_data[identifier]
|
||||
var/type = text2path(component_data["type"])
|
||||
if(!ispath(type, /obj/item/circuit_component))
|
||||
LOG_ERROR(errors, "Invalid path for circuit component, expected [/obj/item/circuit_component], got [type]")
|
||||
continue
|
||||
var/obj/item/circuit_component/component = load_component(type)
|
||||
identifiers_to_circuit[identifier] = component
|
||||
component.load_data_from_list(component_data)
|
||||
|
||||
var/list/input_ports_data = component_data["input_ports_stored_data"]
|
||||
for(var/port_name in input_ports_data)
|
||||
var/datum/port/input/port
|
||||
var/list/port_data = input_ports_data[port_name]
|
||||
for(var/datum/port/input/port_to_check as anything in component.input_ports)
|
||||
if(port_to_check.name == port_name)
|
||||
port = port_to_check
|
||||
break
|
||||
|
||||
port.set_input(port_data["stored_data"])
|
||||
|
||||
var/list/external_objects = general_data["external_objects"]
|
||||
for(var/identifier in external_objects)
|
||||
var/list/object_data = external_objects[identifier]
|
||||
var/type = text2path(object_data["type"])
|
||||
if(!ispath(type))
|
||||
LOG_ERROR(errors, "Invalid path for external object, expected a path, got [type]")
|
||||
continue
|
||||
var/atom/movable/object = new type(drop_location())
|
||||
var/list/connected_components = list()
|
||||
for(var/component_id in object_data["connected_components"])
|
||||
var/obj/item/circuit_component/component = identifiers_to_circuit[component_id]
|
||||
if(!component)
|
||||
continue
|
||||
connected_components += component
|
||||
SEND_SIGNAL(object, COMSIG_MOVABLE_CIRCUIT_LOADED, src, connected_components)
|
||||
|
||||
for(var/identifier in identifiers_to_circuit)
|
||||
var/obj/item/circuit_component/component = identifiers_to_circuit[identifier]
|
||||
var/list/component_data = circuit_data[identifier]
|
||||
|
||||
var/list/connections = component_data["connections"]
|
||||
for(var/port_name in connections)
|
||||
var/datum/port/input/port
|
||||
var/list/connection_data = connections[port_name]
|
||||
for(var/datum/port/input/port_to_check as anything in component.input_ports)
|
||||
if(port_to_check.name == port_name)
|
||||
port = port_to_check
|
||||
break
|
||||
|
||||
if(!port)
|
||||
LOG_ERROR(errors, "Port [port_name] not found for [component.type].")
|
||||
continue
|
||||
|
||||
if(connection_data["stored_data"])
|
||||
if(!(port.datatype in GLOB.circuit_dupe_whitelisted_types))
|
||||
continue
|
||||
port.set_input(connection_data["stored_data"])
|
||||
continue
|
||||
|
||||
// The || list(connected_data) is for backwards compatibility with when inputs could only be connected to up to one output.
|
||||
for(var/list/output_data in (connection_data["connected_ports"] || list(connection_data)))
|
||||
var/obj/item/circuit_component/connected_component = identifiers_to_circuit[output_data["component_id"]]
|
||||
if(!connected_component)
|
||||
LOG_ERROR(errors, "No connected component found for [component.type] for port [connection_data["port_name"]]. (connected component identifier: [connection_data["component_id"]])")
|
||||
continue
|
||||
|
||||
var/datum/port/output/output_port
|
||||
var/output_port_name = output_data["port_name"]
|
||||
for(var/datum/port/output/port_to_check as anything in connected_component.output_ports)
|
||||
if(port_to_check.name == output_port_name)
|
||||
output_port = port_to_check
|
||||
break
|
||||
|
||||
if(!output_port)
|
||||
LOG_ERROR(errors, "No output port found for [component.type] for port [output_port_name] on component [connected_component.type]")
|
||||
continue
|
||||
|
||||
port.connect(output_port)
|
||||
|
||||
#undef LOG_ERROR
|
||||
|
||||
/// Converts a circuit into json.
|
||||
/obj/item/integrated_circuit/proc/convert_to_json()
|
||||
var/list/circuit_to_identifiers = list()
|
||||
var/list/identifiers = list()
|
||||
var/list/external_objects = list() // Objects that are connected to a component. These objects will be linked to the components.
|
||||
for(var/obj/item/circuit_component/component as anything in attached_components)
|
||||
var/identifier = "[component.type][length(identifiers)]"
|
||||
identifiers += identifier
|
||||
circuit_to_identifiers[component] = identifier
|
||||
var/list/objects = list()
|
||||
SEND_SIGNAL(component, COMSIG_CIRCUIT_COMPONENT_SAVE, objects)
|
||||
|
||||
for(var/atom/movable/object as anything in objects)
|
||||
if(object in external_objects)
|
||||
external_objects[object] += identifier
|
||||
continue
|
||||
external_objects[object] = list(identifier)
|
||||
|
||||
var/list/circuit_data = list()
|
||||
for(var/obj/item/circuit_component/component as anything in circuit_to_identifiers)
|
||||
var/identifier = circuit_to_identifiers[component]
|
||||
var/list/component_data = list()
|
||||
|
||||
component_data["type"] = component.type
|
||||
|
||||
var/list/connections = list()
|
||||
var/list/input_ports_stored_data = list()
|
||||
for(var/datum/port/input/input as anything in component.input_ports)
|
||||
var/list/connection_data = list()
|
||||
if(!length(input.connected_ports))
|
||||
if(isnull(input.value) || !(input.datatype in GLOB.circuit_dupe_whitelisted_types))
|
||||
continue
|
||||
connection_data["stored_data"] = input.value
|
||||
input_ports_stored_data[input.name] = connection_data
|
||||
continue
|
||||
connection_data["connected_ports"] = list()
|
||||
for(var/datum/port/output/output as anything in input.connected_ports)
|
||||
connection_data["connected_ports"] += list(list(
|
||||
"component_id" = circuit_to_identifiers[output.connected_component],
|
||||
"port_name" = output.name,
|
||||
))
|
||||
connections[input.name] = connection_data
|
||||
component_data["connections"] = connections
|
||||
component_data["input_ports_stored_data"] = input_ports_stored_data
|
||||
|
||||
component.save_data_to_list(component_data)
|
||||
circuit_data[identifier] = component_data
|
||||
|
||||
var/external_objects_key = list()
|
||||
for(var/atom/movable/object as anything in external_objects)
|
||||
var/list/new_data = list()
|
||||
new_data["type"] = object.type
|
||||
new_data["connected_components"] = external_objects[object]
|
||||
external_objects_key["[object.type][length(external_objects_key)]"] = new_data
|
||||
|
||||
var/list/general_data = list()
|
||||
general_data["components"] = circuit_data
|
||||
general_data["external_objects"] = external_objects_key
|
||||
general_data["display_name"] = display_name
|
||||
general_data["admin_only"] = admin_only
|
||||
|
||||
var/list/variables = list()
|
||||
for(var/variable_identifier in circuit_variables)
|
||||
var/list/new_data = list()
|
||||
var/datum/circuit_variable/variable = circuit_variables[variable_identifier]
|
||||
new_data["name"] = variable.name
|
||||
new_data["datatype"] = variable.datatype
|
||||
variables += list(new_data)
|
||||
general_data["variables"] = variables
|
||||
|
||||
return json_encode(general_data)
|
||||
|
||||
/obj/item/integrated_circuit/proc/load_component(type)
|
||||
var/obj/item/circuit_component/component = new type(src)
|
||||
add_component(component)
|
||||
return component
|
||||
|
||||
/// Saves data to a list. Shouldn't be used unless you are quite literally saving the data of a component to a list. Input value is the list to save the data to
|
||||
/obj/item/circuit_component/proc/save_data_to_list(list/component_data)
|
||||
component_data["rel_x"] = rel_x
|
||||
component_data["rel_y"] = rel_y
|
||||
|
||||
/// Loads data from a list
|
||||
/obj/item/circuit_component/proc/load_data_from_list(list/component_data)
|
||||
rel_x = component_data["rel_x"]
|
||||
rel_y = component_data["rel_y"]
|
||||
|
||||
/client/proc/load_circuit()
|
||||
set name = "Load Circuit"
|
||||
set category = "Admin.Fun"
|
||||
|
||||
if(!check_rights(R_VAREDIT))
|
||||
return
|
||||
|
||||
var/list/errors = list()
|
||||
|
||||
var/option = alert(usr, "Load by file or direct input?", "Load by file or string", "File", "Direct Input")
|
||||
var/txt
|
||||
switch(option)
|
||||
if("File")
|
||||
txt = file2text(tgui_input_num(usr, "Input File") as file|null)
|
||||
if("Direct Input")
|
||||
txt = tgui_input_num(usr, "Input JSON", "Input JSON") as text|null
|
||||
|
||||
if(!txt)
|
||||
return
|
||||
|
||||
var/obj/item/integrated_circuit/loaded/circuit = new(mob.drop_location())
|
||||
circuit.load_circuit_data(txt, errors)
|
||||
|
||||
if(length(errors))
|
||||
to_chat(src, span_warning("The following errors were found whilst compiling the circuit data:"))
|
||||
for(var/error in errors)
|
||||
to_chat(src, span_warning(error))
|
||||
@@ -0,0 +1,600 @@
|
||||
/// A list of all integrated circuits
|
||||
GLOBAL_LIST_EMPTY_TYPED(integrated_circuits, /obj/item/integrated_circuit)
|
||||
|
||||
/**
|
||||
* # Integrated Circuitboard
|
||||
*
|
||||
* A circuitboard that holds components that work together
|
||||
*
|
||||
* Has a limited amount of power.
|
||||
*/
|
||||
/obj/item/integrated_circuit
|
||||
name = "integrated circuit"
|
||||
desc = "By inserting components and a cell into this, wiring them up, and putting them into a shell, anyone can pretend to be a programmer."
|
||||
icon = 'icons/obj/module.dmi'
|
||||
icon_state = "integrated_circuit"
|
||||
inhand_icon_state = "electronic"
|
||||
|
||||
/// The name that appears on the shell.
|
||||
var/display_name = ""
|
||||
|
||||
/// The max length of the name.
|
||||
var/label_max_length = 24
|
||||
|
||||
/// The power of the integrated circuit
|
||||
var/obj/item/stock_parts/cell/cell
|
||||
|
||||
/// The shell that this circuitboard is attached to. Used by components.
|
||||
var/atom/movable/shell
|
||||
|
||||
/// The attached components
|
||||
var/list/obj/item/circuit_component/attached_components = list()
|
||||
|
||||
/// Whether the integrated circuit is on or not. Handled by the shell.
|
||||
var/on = FALSE
|
||||
|
||||
/// Whether the integrated circuit is locked or not. Handled by the shell.
|
||||
var/locked = FALSE
|
||||
|
||||
/// Whether the integrated circuit is admin only. Disables power usage and allows admin circuits to be attached, at the cost of making it inaccessible to regular users.
|
||||
var/admin_only = FALSE
|
||||
|
||||
/// The ID that is authorized to unlock/lock the shell so that the circuit can/cannot be removed.
|
||||
var/datum/weakref/owner_id
|
||||
|
||||
/// The current examined component. Used in IntegratedCircuit UI
|
||||
var/datum/weakref/examined_component
|
||||
|
||||
/// Set by the shell. Holds the reference to the owner who inserted the component into the shell.
|
||||
var/datum/weakref/inserter_mind
|
||||
|
||||
/// Variables stored on this integrated circuit. with a `variable_name = value` structure
|
||||
var/list/datum/circuit_variable/circuit_variables = list()
|
||||
|
||||
/// The maximum amount of setters and getters a circuit can have
|
||||
var/max_setters_and_getters = 30
|
||||
|
||||
/// The current setter and getter count the circuit has.
|
||||
var/setter_and_getter_count = 0
|
||||
|
||||
/// X position of the examined_component
|
||||
var/examined_rel_x = 0
|
||||
|
||||
/// Y position of the examined component
|
||||
var/examined_rel_y = 0
|
||||
|
||||
/// The X position of the screen. Used for adding components
|
||||
var/screen_x = 0
|
||||
|
||||
/// The Y position of the screen. Used for adding components.
|
||||
var/screen_y = 0
|
||||
|
||||
/obj/item/integrated_circuit/Initialize()
|
||||
. = ..()
|
||||
|
||||
GLOB.integrated_circuits += src
|
||||
|
||||
RegisterSignal(src, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
|
||||
|
||||
/obj/item/integrated_circuit/loaded/Initialize()
|
||||
. = ..()
|
||||
set_cell(new /obj/item/stock_parts/cell/high(src))
|
||||
|
||||
/obj/item/integrated_circuit/Destroy()
|
||||
for(var/obj/item/circuit_component/to_delete in attached_components)
|
||||
remove_component(to_delete)
|
||||
qdel(to_delete)
|
||||
QDEL_LIST(circuit_variables)
|
||||
attached_components.Cut()
|
||||
shell = null
|
||||
examined_component = null
|
||||
owner_id = null
|
||||
QDEL_NULL(cell)
|
||||
GLOB.integrated_circuits -= src
|
||||
return ..()
|
||||
|
||||
/obj/item/integrated_circuit/examine(mob/user)
|
||||
. = ..()
|
||||
if(cell)
|
||||
. += span_notice("The charge meter reads [cell ? round(cell.percent(), 1) : 0]%.")
|
||||
else
|
||||
. += span_notice("There is no power cell installed.")
|
||||
|
||||
/obj/item/integrated_circuit/proc/set_cell(obj/item/stock_parts/cell_to_set)
|
||||
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_CELL, cell_to_set)
|
||||
cell = cell_to_set
|
||||
|
||||
/obj/item/integrated_circuit/attackby(obj/item/I, mob/living/user, params)
|
||||
. = ..()
|
||||
if(istype(I, /obj/item/circuit_component))
|
||||
add_component_manually(I, user)
|
||||
return
|
||||
|
||||
if(istype(I, /obj/item/stock_parts/cell))
|
||||
if(cell)
|
||||
balloon_alert(user, "there already is a cell inside!")
|
||||
return
|
||||
if(!user.transferItemToLoc(I, src))
|
||||
return
|
||||
set_cell(I)
|
||||
I.add_fingerprint(user)
|
||||
user.visible_message(span_notice("[user] inserts a power cell into [src]."), span_notice("You insert the power cell into [src]."))
|
||||
return
|
||||
|
||||
if(istype(I, /obj/item/card/id))
|
||||
balloon_alert(user, "owner id set for [I]")
|
||||
owner_id = WEAKREF(I)
|
||||
return
|
||||
|
||||
if(I.tool_behaviour == TOOL_SCREWDRIVER)
|
||||
if(!cell)
|
||||
return
|
||||
I.play_tool_sound(src)
|
||||
user.visible_message(span_notice("[user] unscrews the power cell from [src]."), span_notice("You unscrew the power cell from [src]."))
|
||||
cell.forceMove(drop_location())
|
||||
set_cell(null)
|
||||
return
|
||||
|
||||
/**
|
||||
* Registers an movable atom as a shell
|
||||
*
|
||||
* No functionality is done here. This is so that input components
|
||||
* can properly register any signals on the shell.
|
||||
* Arguments:
|
||||
* * new_shell - The new shell to register.
|
||||
*/
|
||||
/obj/item/integrated_circuit/proc/set_shell(atom/movable/new_shell)
|
||||
remove_current_shell()
|
||||
set_on(TRUE)
|
||||
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_SHELL, new_shell)
|
||||
shell = new_shell
|
||||
RegisterSignal(shell, COMSIG_PARENT_QDELETING, .proc/remove_current_shell)
|
||||
for(var/obj/item/circuit_component/attached_component as anything in attached_components)
|
||||
attached_component.register_shell(shell)
|
||||
// Their input ports may be updated with user values, but the outputs haven't updated
|
||||
// because on is FALSE
|
||||
TRIGGER_CIRCUIT_COMPONENT(attached_component, null)
|
||||
|
||||
/**
|
||||
* Unregisters the current shell attached to this circuit.
|
||||
*/
|
||||
/obj/item/integrated_circuit/proc/remove_current_shell()
|
||||
SIGNAL_HANDLER
|
||||
if(!shell)
|
||||
return
|
||||
shell.name = initial(shell.name)
|
||||
for(var/obj/item/circuit_component/attached_component as anything in attached_components)
|
||||
attached_component.unregister_shell(shell)
|
||||
UnregisterSignal(shell, COMSIG_PARENT_QDELETING)
|
||||
shell = null
|
||||
set_on(FALSE)
|
||||
SEND_SIGNAL(src, COMSIG_CIRCUIT_SHELL_REMOVED)
|
||||
|
||||
/obj/item/integrated_circuit/proc/set_on(new_value)
|
||||
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_ON, new_value)
|
||||
on = new_value
|
||||
|
||||
/**
|
||||
* Adds a component to the circuitboard
|
||||
*
|
||||
* Once the component is added, the ports can be attached to other components
|
||||
*/
|
||||
/obj/item/integrated_circuit/proc/add_component(obj/item/circuit_component/to_add, mob/living/user)
|
||||
if(to_add.parent)
|
||||
return
|
||||
|
||||
if(SEND_SIGNAL(src, COMSIG_CIRCUIT_ADD_COMPONENT, to_add, user) & COMPONENT_CANCEL_ADD_COMPONENT)
|
||||
return
|
||||
|
||||
if(!to_add.add_to(src))
|
||||
return
|
||||
|
||||
var/success = FALSE
|
||||
if(user)
|
||||
success = user.transferItemToLoc(to_add, src)
|
||||
else
|
||||
success = to_add.forceMove(src)
|
||||
|
||||
if(!success)
|
||||
return
|
||||
|
||||
to_add.rel_x = rand(COMPONENT_MIN_RANDOM_POS, COMPONENT_MAX_RANDOM_POS) - screen_x
|
||||
to_add.rel_y = rand(COMPONENT_MIN_RANDOM_POS, COMPONENT_MAX_RANDOM_POS) - screen_y
|
||||
to_add.parent = src
|
||||
attached_components += to_add
|
||||
RegisterSignal(to_add, COMSIG_MOVABLE_MOVED, .proc/component_move_handler)
|
||||
SStgui.update_uis(src)
|
||||
|
||||
if(shell)
|
||||
to_add.register_shell(shell)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Adds a component to the circuitboard through a manual action.
|
||||
*/
|
||||
/obj/item/integrated_circuit/proc/add_component_manually(obj/item/circuit_component/to_add, mob/living/user)
|
||||
if (SEND_SIGNAL(src, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, to_add, user) & COMPONENT_CANCEL_ADD_COMPONENT)
|
||||
return
|
||||
|
||||
return add_component(to_add, user)
|
||||
|
||||
/obj/item/integrated_circuit/proc/component_move_handler(obj/item/circuit_component/source)
|
||||
SIGNAL_HANDLER
|
||||
if(source.loc != src)
|
||||
remove_component(source)
|
||||
|
||||
/**
|
||||
* Removes a component to the circuitboard
|
||||
*
|
||||
* This removes all connects between the ports
|
||||
*/
|
||||
/obj/item/integrated_circuit/proc/remove_component(obj/item/circuit_component/to_remove)
|
||||
if(shell)
|
||||
to_remove.unregister_shell(shell)
|
||||
|
||||
UnregisterSignal(to_remove, COMSIG_MOVABLE_MOVED)
|
||||
attached_components -= to_remove
|
||||
to_remove.disconnect()
|
||||
to_remove.parent = null
|
||||
SEND_SIGNAL(to_remove, COMSIG_CIRCUIT_COMPONENT_REMOVED, src)
|
||||
SStgui.update_uis(src)
|
||||
to_remove.removed_from(src)
|
||||
|
||||
/obj/item/integrated_circuit/get_cell()
|
||||
return cell
|
||||
|
||||
/obj/item/integrated_circuit/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/simple/circuit_assets)
|
||||
)
|
||||
|
||||
/obj/item/integrated_circuit/ui_static_data(mob/user)
|
||||
. = list()
|
||||
.["global_basic_types"] = GLOB.wiremod_basic_types
|
||||
.["screen_x"] = screen_x
|
||||
.["screen_y"] = screen_y
|
||||
|
||||
/obj/item/integrated_circuit/ui_data(mob/user)
|
||||
. = list()
|
||||
.["components"] = list()
|
||||
for(var/obj/item/circuit_component/component as anything in attached_components)
|
||||
if (component.circuit_flags & CIRCUIT_FLAG_HIDDEN)
|
||||
.["components"] += null
|
||||
continue
|
||||
|
||||
var/list/component_data = list()
|
||||
component_data["input_ports"] = list()
|
||||
for(var/datum/port/input/port as anything in component.input_ports)
|
||||
var/current_data = port.value
|
||||
if(isatom(current_data)) // Prevent passing the name of the atom.
|
||||
current_data = null
|
||||
var/list/connected_to = list()
|
||||
for(var/datum/port/output/output as anything in port.connected_ports)
|
||||
connected_to += REF(output)
|
||||
component_data["input_ports"] += list(list(
|
||||
"name" = port.name,
|
||||
"type" = port.datatype,
|
||||
"ref" = REF(port), // The ref is the identifier to work out what it is connected to
|
||||
"connected_to" = connected_to,
|
||||
"color" = port.color,
|
||||
"current_data" = current_data,
|
||||
"datatype_data" = port.datatype_ui_data(user),
|
||||
))
|
||||
component_data["output_ports"] = list()
|
||||
for(var/datum/port/output/port as anything in component.output_ports)
|
||||
component_data["output_ports"] += list(list(
|
||||
"name" = port.name,
|
||||
"type" = port.datatype,
|
||||
"ref" = REF(port),
|
||||
"color" = port.color,
|
||||
))
|
||||
|
||||
component_data["name"] = component.display_name
|
||||
component_data["x"] = component.rel_x
|
||||
component_data["y"] = component.rel_y
|
||||
component_data["removable"] = component.removable
|
||||
.["components"] += list(component_data)
|
||||
|
||||
.["variables"] = list()
|
||||
for(var/variable_name in circuit_variables)
|
||||
var/datum/circuit_variable/variable = circuit_variables[variable_name]
|
||||
var/list/variable_data = list()
|
||||
variable_data["name"] = variable.name
|
||||
variable_data["datatype"] = variable.datatype
|
||||
variable_data["color"] = variable.color
|
||||
.["variables"] += list(variable_data)
|
||||
|
||||
|
||||
.["display_name"] = display_name
|
||||
|
||||
var/obj/item/circuit_component/examined
|
||||
if(examined_component)
|
||||
examined = examined_component.resolve()
|
||||
|
||||
.["examined_name"] = examined?.display_name
|
||||
.["examined_desc"] = examined?.desc
|
||||
.["examined_notices"] = examined?.get_ui_notices()
|
||||
.["examined_rel_x"] = examined_rel_x
|
||||
.["examined_rel_y"] = examined_rel_y
|
||||
|
||||
.["is_admin"] = check_rights_for(user.client, R_VAREDIT)
|
||||
|
||||
/obj/item/integrated_circuit/ui_host(mob/user)
|
||||
if(shell)
|
||||
return shell
|
||||
return ..()
|
||||
|
||||
/obj/item/integrated_circuit/can_interact(mob/user)
|
||||
if(locked)
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/obj/item/integrated_circuit/ui_status(mob/user)
|
||||
. = ..()
|
||||
|
||||
if (isobserver(user))
|
||||
. = max(., UI_UPDATE)
|
||||
|
||||
// Extra protection because ui_state will not close the UI if they already have the ui open,
|
||||
// as ui_state is only set during
|
||||
if(admin_only)
|
||||
if(!check_rights_for(user.client, R_VAREDIT))
|
||||
return UI_CLOSE
|
||||
else
|
||||
return UI_INTERACTIVE
|
||||
|
||||
/obj/item/integrated_circuit/ui_state(mob/user)
|
||||
if(!shell)
|
||||
return GLOB.hands_state
|
||||
return GLOB.physical_obscured_state
|
||||
|
||||
/obj/item/integrated_circuit/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "IntegratedCircuit", name)
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
#define WITHIN_RANGE(id, table) (id >= 1 && id <= length(table))
|
||||
|
||||
/obj/item/integrated_circuit/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("add_connection")
|
||||
var/input_component_id = text2num(params["input_component_id"])
|
||||
var/output_component_id = text2num(params["output_component_id"])
|
||||
var/input_port_id = text2num(params["input_port_id"])
|
||||
var/output_port_id = text2num(params["output_port_id"])
|
||||
if(!WITHIN_RANGE(input_component_id, attached_components) || !WITHIN_RANGE(output_component_id, attached_components))
|
||||
return
|
||||
var/obj/item/circuit_component/input_component = attached_components[input_component_id]
|
||||
var/obj/item/circuit_component/output_component = attached_components[output_component_id]
|
||||
|
||||
if(!WITHIN_RANGE(input_port_id, input_component.input_ports) || !WITHIN_RANGE(output_port_id, output_component.output_ports))
|
||||
return
|
||||
var/datum/port/input/input_port = input_component.input_ports[input_port_id]
|
||||
var/datum/port/output/output_port = output_component.output_ports[output_port_id]
|
||||
|
||||
if(!input_port.can_receive_from_datatype(output_port.datatype))
|
||||
return
|
||||
input_port.connect(output_port)
|
||||
. = TRUE
|
||||
if("remove_connection")
|
||||
var/component_id = text2num(params["component_id"])
|
||||
var/is_input = params["is_input"]
|
||||
var/port_id = text2num(params["port_id"])
|
||||
|
||||
if(!WITHIN_RANGE(component_id, attached_components))
|
||||
return
|
||||
var/obj/item/circuit_component/component = attached_components[component_id]
|
||||
|
||||
var/list/port_table
|
||||
if(is_input)
|
||||
port_table = component.input_ports
|
||||
else
|
||||
port_table = component.output_ports
|
||||
|
||||
if(!WITHIN_RANGE(port_id, port_table))
|
||||
return
|
||||
|
||||
var/datum/port/port = port_table[port_id]
|
||||
port.disconnect_all()
|
||||
. = TRUE
|
||||
if("detach_component")
|
||||
var/component_id = text2num(params["component_id"])
|
||||
if(!WITHIN_RANGE(component_id, attached_components))
|
||||
return
|
||||
var/obj/item/circuit_component/component = attached_components[component_id]
|
||||
if(!component.removable)
|
||||
return
|
||||
component.disconnect()
|
||||
remove_component(component)
|
||||
if(component.loc == src)
|
||||
usr.put_in_hands(component)
|
||||
. = TRUE
|
||||
if("set_component_coordinates")
|
||||
var/component_id = text2num(params["component_id"])
|
||||
if(!WITHIN_RANGE(component_id, attached_components))
|
||||
return
|
||||
var/obj/item/circuit_component/component = attached_components[component_id]
|
||||
component.rel_x = min(max(-COMPONENT_MAX_POS, text2num(params["rel_x"])), COMPONENT_MAX_POS)
|
||||
component.rel_y = min(max(-COMPONENT_MAX_POS, text2num(params["rel_y"])), COMPONENT_MAX_POS)
|
||||
. = TRUE
|
||||
if("set_component_input")
|
||||
var/component_id = text2num(params["component_id"])
|
||||
var/port_id = text2num(params["port_id"])
|
||||
if(!WITHIN_RANGE(component_id, attached_components))
|
||||
return
|
||||
var/obj/item/circuit_component/component = attached_components[component_id]
|
||||
if(!WITHIN_RANGE(port_id, component.input_ports))
|
||||
return
|
||||
var/datum/port/input/port = component.input_ports[port_id]
|
||||
|
||||
if(params["set_null"])
|
||||
port.set_input(null)
|
||||
return TRUE
|
||||
|
||||
if(params["marked_atom"])
|
||||
if(port.datatype != PORT_TYPE_ATOM && port.datatype != PORT_TYPE_ANY)
|
||||
return
|
||||
var/obj/item/multitool/circuit/marker = usr.get_active_held_item()
|
||||
if(!istype(marker))
|
||||
var/client/user = usr.client
|
||||
if(!check_rights_for(user, R_VAREDIT))
|
||||
return TRUE
|
||||
var/atom/marked_atom = user.holder.marked_datum
|
||||
if(!marked_atom)
|
||||
return TRUE
|
||||
port.set_input(marked_atom)
|
||||
balloon_alert(usr, "updated [port.name]'s value to marked object.")
|
||||
return TRUE
|
||||
if(!marker.marked_atom)
|
||||
port.set_input(null)
|
||||
marker.say("Cleared port ('[port.name]')'s value.")
|
||||
return TRUE
|
||||
marker.say("Updated port ('[port.name]')'s value to the marked entity.")
|
||||
port.set_input(marker.marked_atom)
|
||||
return TRUE
|
||||
|
||||
var/user_input = port.handle_manual_input(usr, params["input"])
|
||||
if(isnull(user_input))
|
||||
return TRUE
|
||||
port.set_input(user_input)
|
||||
. = TRUE
|
||||
if("get_component_value")
|
||||
var/component_id = text2num(params["component_id"])
|
||||
var/port_id = text2num(params["port_id"])
|
||||
if(!WITHIN_RANGE(component_id, attached_components))
|
||||
return
|
||||
var/obj/item/circuit_component/component = attached_components[component_id]
|
||||
if(!WITHIN_RANGE(port_id, component.output_ports))
|
||||
return
|
||||
|
||||
var/datum/port/output/port = component.output_ports[port_id]
|
||||
var/value = port.value
|
||||
if(isatom(value))
|
||||
value = PORT_TYPE_ATOM
|
||||
else if(isnull(value))
|
||||
value = "null"
|
||||
var/string_form = copytext("[value]", 1, PORT_MAX_STRING_DISPLAY)
|
||||
if(length(string_form) >= PORT_MAX_STRING_DISPLAY-1)
|
||||
string_form += "..."
|
||||
balloon_alert(usr, "[port.name] value: [string_form]")
|
||||
. = TRUE
|
||||
if("set_display_name")
|
||||
var/new_name = params["display_name"]
|
||||
|
||||
if(new_name)
|
||||
set_display_name(strip_html(params["display_name"], label_max_length))
|
||||
else
|
||||
set_display_name("")
|
||||
|
||||
if(shell)
|
||||
if(display_name != "")
|
||||
shell.name = "[initial(shell.name)] ([display_name])"
|
||||
else
|
||||
shell.name = initial(shell.name)
|
||||
|
||||
. = TRUE
|
||||
if("set_examined_component")
|
||||
var/component_id = text2num(params["component_id"])
|
||||
if(!WITHIN_RANGE(component_id, attached_components))
|
||||
return
|
||||
examined_component = WEAKREF(attached_components[component_id])
|
||||
examined_rel_x = text2num(params["x"])
|
||||
examined_rel_y = text2num(params["y"])
|
||||
. = TRUE
|
||||
if("remove_examined_component")
|
||||
examined_component = null
|
||||
. = TRUE
|
||||
if("save_circuit")
|
||||
return attempt_save_to(usr.client)
|
||||
if("add_variable")
|
||||
var/variable_identifier = trim(copytext(params["variable_name"], 1, PORT_MAX_NAME_LENGTH))
|
||||
if(variable_identifier in circuit_variables)
|
||||
return TRUE
|
||||
if(variable_identifier == "")
|
||||
return TRUE
|
||||
var/variable_datatype = params["variable_datatype"]
|
||||
if(!(variable_datatype in GLOB.wiremod_basic_types))
|
||||
return
|
||||
|
||||
circuit_variables[variable_identifier] = new /datum/circuit_variable(variable_identifier, variable_datatype)
|
||||
. = TRUE
|
||||
if("remove_variable")
|
||||
var/variable_identifier = params["variable_name"]
|
||||
if(!(variable_identifier in circuit_variables))
|
||||
return
|
||||
var/datum/circuit_variable/variable = circuit_variables[variable_identifier]
|
||||
if(!variable)
|
||||
return
|
||||
circuit_variables -= variable_identifier
|
||||
qdel(variable)
|
||||
. = TRUE
|
||||
if("add_setter_or_getter")
|
||||
if(setter_and_getter_count >= max_setters_and_getters)
|
||||
balloon_alert(usr, "setter and getter count at maximum capacity")
|
||||
return
|
||||
var/designated_type = /obj/item/circuit_component/getter
|
||||
if(params["is_setter"])
|
||||
designated_type = /obj/item/circuit_component/setter
|
||||
var/obj/item/circuit_component/component = new designated_type(src)
|
||||
if(!add_component(component, usr))
|
||||
qdel(component)
|
||||
return
|
||||
RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED, .proc/clear_setter_or_getter)
|
||||
setter_and_getter_count++
|
||||
if("move_screen")
|
||||
screen_x = text2num(params["screen_x"])
|
||||
screen_y = text2num(params["screen_y"])
|
||||
|
||||
/obj/item/integrated_circuit/proc/clear_setter_or_getter(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
// This'll also be called in the Destroy() override of /obj/item/circuit_component
|
||||
if(!QDELING(source))
|
||||
qdel(source)
|
||||
setter_and_getter_count--
|
||||
|
||||
/obj/item/integrated_circuit/proc/on_atom_usb_cable_try_attach(datum/source, obj/item/usb_cable/usb_cable, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
usb_cable.balloon_alert(user, "circuit needs to be in a compatible shell")
|
||||
return COMSIG_CANCEL_USB_CABLE_ATTACK
|
||||
|
||||
#undef WITHIN_RANGE
|
||||
|
||||
/// Sets the display name that appears on the shell.
|
||||
/obj/item/integrated_circuit/proc/set_display_name(new_name)
|
||||
display_name = new_name
|
||||
|
||||
/**
|
||||
* Returns the creator of the integrated circuit. Used in admin messages and other related things.
|
||||
*/
|
||||
/obj/item/integrated_circuit/proc/get_creator_admin()
|
||||
return get_creator(include_link = TRUE)
|
||||
|
||||
/**
|
||||
* Returns the creator of the integrated circuit. Used in admin logs and other related things.
|
||||
*/
|
||||
/obj/item/integrated_circuit/proc/get_creator(include_link = FALSE)
|
||||
var/datum/mind/inserter
|
||||
if(inserter_mind)
|
||||
inserter = inserter_mind.resolve()
|
||||
|
||||
var/obj/item/card/id/id_card
|
||||
if(owner_id)
|
||||
id_card = owner_id.resolve()
|
||||
|
||||
return "[src] (Shell: [shell || "*null*"], Inserter: [key_name(inserter, include_link)], Owner ID: [id_card?.name || "*null*"])"
|
||||
|
||||
/// Attempts to save a circuit to a given client
|
||||
/obj/item/integrated_circuit/proc/attempt_save_to(client/saver)
|
||||
if(!check_rights_for(saver, R_VAREDIT))
|
||||
return FALSE
|
||||
var/temp_file = file("data/CircuitDownloadTempFile")
|
||||
fdel(temp_file)
|
||||
WRITE_FILE(temp_file, convert_to_json())
|
||||
DIRECT_OUTPUT(saver, ftp(temp_file, "[display_name || "circuit"].json"))
|
||||
return TRUE
|
||||
@@ -0,0 +1,60 @@
|
||||
/obj/item/multitool/circuit
|
||||
name = "circuit multitool"
|
||||
desc = "A circuit multitool. Used to mark entities which can then be uploaded to components by pressing the upload button on a port. \
|
||||
Acts as a normal multitool otherwise. Use in hand to clear marked entity so that you can mark another entity."
|
||||
icon_state = "multitool_circuit"
|
||||
|
||||
/// The marked atom of this multitool
|
||||
var/atom/marked_atom
|
||||
|
||||
/obj/item/multitool/circuit/Destroy()
|
||||
marked_atom = null
|
||||
return ..()
|
||||
|
||||
/obj/item/multitool/circuit/examine(mob/user)
|
||||
. = ..()
|
||||
. += span_notice("It has [marked_atom? "a" : "no"] marked entity registered.")
|
||||
|
||||
/obj/item/multitool/circuit/attack_self(mob/user, modifiers)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(!marked_atom)
|
||||
return
|
||||
|
||||
say("Cleared marked targets.")
|
||||
clear_marked_atom()
|
||||
return TRUE
|
||||
|
||||
/obj/item/multitool/circuit/melee_attack_chain(mob/user, atom/target, params)
|
||||
var/is_right_clicking = LAZYACCESS(params2list(params), RIGHT_CLICK)
|
||||
|
||||
if(marked_atom || !user.Adjacent(target) || is_right_clicking)
|
||||
return ..()
|
||||
|
||||
say("Marked [target].")
|
||||
marked_atom = target
|
||||
RegisterSignal(marked_atom, COMSIG_PARENT_QDELETING, .proc/cleanup_marked_atom)
|
||||
update_icon()
|
||||
flick("multitool_circuit_flick", src)
|
||||
playsound(src.loc, 'sound/misc/compiler-stage2.ogg', 30, TRUE)
|
||||
return TRUE
|
||||
|
||||
/obj/item/multitool/circuit/update_overlays()
|
||||
. = ..()
|
||||
cut_overlays()
|
||||
if(marked_atom)
|
||||
. += "marked_overlay"
|
||||
|
||||
/// Clears the current marked atom
|
||||
/obj/item/multitool/circuit/proc/clear_marked_atom()
|
||||
if(!marked_atom)
|
||||
return
|
||||
UnregisterSignal(marked_atom, COMSIG_PARENT_QDELETING)
|
||||
marked_atom = null
|
||||
update_icon()
|
||||
|
||||
/obj/item/multitool/circuit/proc/cleanup_marked_atom(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
if(source == marked_atom)
|
||||
clear_marked_atom()
|
||||
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* # Component Port
|
||||
*
|
||||
* A port used by a component. Connects to other ports.
|
||||
*/
|
||||
/datum/port
|
||||
/// The component this port is attached to
|
||||
var/obj/item/circuit_component/connected_component
|
||||
|
||||
/// Name of the port. Used when displaying the port.
|
||||
var/name
|
||||
|
||||
/// The port type. Ports can only connect to each other if the type matches
|
||||
var/datatype
|
||||
|
||||
/// The value that's currently in the port. It's of the above type.
|
||||
var/value
|
||||
|
||||
/// The default port type. Stores the original datatype of the port set on Initialize.
|
||||
var/datum/circuit_datatype/datatype_handler
|
||||
|
||||
/// The port color. If unset, appears as blue.
|
||||
var/color
|
||||
|
||||
/datum/port/New(obj/item/circuit_component/to_connect, name, datatype)
|
||||
if(!to_connect)
|
||||
qdel(src)
|
||||
return
|
||||
. = ..()
|
||||
connected_component = to_connect
|
||||
src.name = name
|
||||
set_datatype(datatype)
|
||||
|
||||
/datum/port/Destroy(force)
|
||||
disconnect_all()
|
||||
connected_component = null
|
||||
datatype_handler = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Sets the port's value to value.
|
||||
* Casts to the port's datatype (e.g. number -> string), and assumes this can be done.
|
||||
*/
|
||||
/datum/port/proc/set_value(value, force = FALSE)
|
||||
if(src.value != value || force)
|
||||
if(isatom(value))
|
||||
UnregisterSignal(value, COMSIG_PARENT_QDELETING)
|
||||
src.value = datatype_handler.convert_value(src, value)
|
||||
if(isatom(value))
|
||||
RegisterSignal(value, COMSIG_PARENT_QDELETING, .proc/null_value)
|
||||
SEND_SIGNAL(src, COMSIG_PORT_SET_VALUE, value)
|
||||
|
||||
/**
|
||||
* Updates the value of the input and calls input_received on the connected component
|
||||
*/
|
||||
/datum/port/input/proc/set_input(value)
|
||||
if(QDELETED(src)) //Pain
|
||||
return
|
||||
set_value(value)
|
||||
if(trigger)
|
||||
TRIGGER_CIRCUIT_COMPONENT(connected_component, src)
|
||||
|
||||
/datum/port/output/proc/set_output(value)
|
||||
set_value(value)
|
||||
|
||||
/**
|
||||
* Sets the datatype of the port.
|
||||
*
|
||||
* Arguments:
|
||||
* * new_type - The type this port is to be set to.
|
||||
*/
|
||||
/datum/port/proc/set_datatype(type_to_set)
|
||||
if(type_to_set == datatype)
|
||||
return
|
||||
|
||||
if(datatype_handler)
|
||||
datatype_handler.on_loss(src)
|
||||
datatype_handler = null
|
||||
|
||||
var/datum/circuit_datatype/handler = GLOB.circuit_datatypes[type_to_set]
|
||||
if(!handler || !handler.is_compatible(src))
|
||||
type_to_set = PORT_TYPE_ANY
|
||||
handler = GLOB.circuit_datatypes[type_to_set]
|
||||
// We can't leave this port without a type or else it'll just keep spewing out unnecessary and unneeded runtimes as well as leaving the circuit in a broken state.
|
||||
stack_trace("[src] port attempted to be set to an incompatible datatype! (target datatype to set: [type_to_set])")
|
||||
|
||||
datatype = type_to_set
|
||||
datatype_handler = handler
|
||||
color = datatype_handler.color
|
||||
datatype_handler.on_gain(src)
|
||||
src.value = datatype_handler.convert_value(src, value)
|
||||
SEND_SIGNAL(src, COMSIG_PORT_SET_TYPE, type_to_set)
|
||||
if(connected_component?.parent)
|
||||
SStgui.update_uis(connected_component.parent)
|
||||
|
||||
/datum/port/input/set_datatype(new_type)
|
||||
for(var/datum/port/output/output as anything in connected_ports)
|
||||
check_type(output)
|
||||
..()
|
||||
|
||||
/**
|
||||
* Returns the data from the datatype
|
||||
*/
|
||||
/datum/port/proc/datatype_ui_data()
|
||||
return datatype_handler.datatype_ui_data(src)
|
||||
|
||||
/**
|
||||
* # Output Port
|
||||
*
|
||||
* An output port that many input ports can connect to
|
||||
*
|
||||
* Sends a signal whenever the output value is changed
|
||||
*/
|
||||
/datum/port/output
|
||||
|
||||
/**
|
||||
* Disconnects a port from all other ports.
|
||||
*
|
||||
* Called by [/obj/item/circuit_component] whenever it is disconnected from
|
||||
* an integrated circuit
|
||||
*/
|
||||
/datum/port/proc/disconnect_all()
|
||||
SEND_SIGNAL(src, COMSIG_PORT_DISCONNECT)
|
||||
|
||||
/datum/port/input/disconnect_all()
|
||||
..()
|
||||
for(var/datum/port/output/output as anything in connected_ports)
|
||||
disconnect(output)
|
||||
|
||||
/datum/port/input/proc/disconnect(datum/port/output/output)
|
||||
connected_ports -= output
|
||||
UnregisterSignal(output, COMSIG_PORT_SET_VALUE)
|
||||
UnregisterSignal(output, COMSIG_PORT_SET_TYPE)
|
||||
UnregisterSignal(output, COMSIG_PORT_DISCONNECT)
|
||||
|
||||
/// Do our part in setting all source references anywhere to null.
|
||||
/datum/port/proc/on_value_qdeleting(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
if(value == source)
|
||||
value = null
|
||||
else
|
||||
stack_trace("Impossible? [src] should only receive COMSIG_PARENT_QDELETING from an atom currently in the port, not [source].")
|
||||
|
||||
/**
|
||||
* # Input Port
|
||||
*
|
||||
* An input port remembers connected output ports.
|
||||
*
|
||||
* Registers the PORT_SET_VALUE signal on each connected port,
|
||||
* and keeps its value equal to the last such signal received.
|
||||
*/
|
||||
/datum/port/input
|
||||
/// Whether this port triggers an update whenever an output is received.
|
||||
var/trigger = FALSE
|
||||
|
||||
/// The ports this port is wired to.
|
||||
var/list/datum/port/output/connected_ports
|
||||
|
||||
/datum/port/input/New(obj/item/circuit_component/to_connect, name, datatype, trigger, default)
|
||||
. = ..()
|
||||
set_value(default)
|
||||
src.trigger = trigger
|
||||
src.connected_ports = list()
|
||||
|
||||
/**
|
||||
* Introduces two ports to one another.
|
||||
*/
|
||||
/datum/port/input/proc/connect(datum/port/output/output)
|
||||
connected_ports |= output
|
||||
RegisterSignal(output, COMSIG_PORT_SET_VALUE, .proc/receive_value)
|
||||
RegisterSignal(output, COMSIG_PORT_SET_TYPE, .proc/check_type)
|
||||
RegisterSignal(output, COMSIG_PORT_DISCONNECT, .proc/disconnect)
|
||||
// For signals, we don't update the input to prevent sending a signal when connecting ports.
|
||||
if(!(datatype_handler.datatype_flags & DATATYPE_FLAG_AVOID_VALUE_UPDATE))
|
||||
set_input(output.value)
|
||||
|
||||
/**
|
||||
* Determines if a datatype is compatible with another port of a different type.
|
||||
*
|
||||
* Arguments:
|
||||
* * other_datatype - The datatype to check
|
||||
*/
|
||||
/datum/port/input/proc/can_receive_from_datatype(datatype_to_check)
|
||||
return datatype_handler.can_receive_from_datatype(datatype_to_check)
|
||||
|
||||
/**
|
||||
* Determines if a datatype is compatible with another port of a different type.
|
||||
*
|
||||
* Arguments:
|
||||
* * other_datatype - The datatype to check
|
||||
*/
|
||||
/datum/port/input/proc/handle_manual_input(mob/user, manual_input)
|
||||
if(datatype_handler.datatype_flags & DATATYPE_FLAG_ALLOW_MANUAL_INPUT)
|
||||
return datatype_handler.handle_manual_input(src, user, manual_input)
|
||||
return null
|
||||
|
||||
/**
|
||||
* Mirror value updates from connected output ports after an input_receive_delay.
|
||||
*/
|
||||
/datum/port/input/proc/receive_value(datum/port/output/output, value)
|
||||
SIGNAL_HANDLER
|
||||
SScircuit_component.add_callback(CALLBACK(src, .proc/set_input, value))
|
||||
|
||||
/// Signal handler proc to null the input if an atom is deleted. An update is not sent because this was not set by anything.
|
||||
/datum/port/proc/null_value(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
if(value == source)
|
||||
value = null
|
||||
|
||||
/**
|
||||
* Handle type updates from connected output ports, breaking uncastable connections.
|
||||
*/
|
||||
/datum/port/input/proc/check_type(datum/port/output/output)
|
||||
SIGNAL_HANDLER
|
||||
if(!can_receive_from_datatype(output.datatype))
|
||||
disconnect(output)
|
||||
@@ -0,0 +1,123 @@
|
||||
/// A cable that can connect integrated circuits to anything with a USB port, such as computers and machines.
|
||||
/obj/item/usb_cable
|
||||
name = "usb cable"
|
||||
desc = "A cable that can connect integrated circuits to anything with a USB port, such as computers and machines."
|
||||
icon = 'icons/obj/wiremod.dmi'
|
||||
icon_state = "usb_cable"
|
||||
inhand_icon_state = "coil"
|
||||
base_icon_state = "coil"
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
custom_materials = list(/datum/material/iron = 75)
|
||||
|
||||
/// The currently connected circuit
|
||||
var/obj/item/integrated_circuit/attached_circuit
|
||||
|
||||
/obj/item/usb_cable/Destroy()
|
||||
attached_circuit = null
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return ..()
|
||||
|
||||
/obj/item/usb_cable/Initialize()
|
||||
. = ..()
|
||||
RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/on_moved)
|
||||
|
||||
/obj/item/usb_cable/examine(mob/user)
|
||||
. = ..()
|
||||
|
||||
if (!isnull(attached_circuit))
|
||||
. += span_notice("It is attached to [attached_circuit.shell || attached_circuit].")
|
||||
|
||||
// Look, I'm not happy about this either, but moving an object doesn't call Moved if it's inside something else.
|
||||
// There's good reason for this, but there's no element or similar yet to track it as far as I know.
|
||||
// SSobj runs infrequently, this is only ran while there's an attached circuit, its performance cost is negligible.
|
||||
/obj/item/usb_cable/process(delta_time)
|
||||
if (!check_in_range())
|
||||
return PROCESS_KILL
|
||||
|
||||
/obj/item/usb_cable/pre_attack(atom/target, mob/living/user, params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
|
||||
if (prob(1))
|
||||
balloon_alert(user, "wrong way, god damnit")
|
||||
return TRUE
|
||||
|
||||
var/signal_result = SEND_SIGNAL(target, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, src, user)
|
||||
|
||||
var/last_attached_circuit = attached_circuit
|
||||
if (signal_result & COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT)
|
||||
if (isnull(attached_circuit))
|
||||
CRASH("Producers of COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT must set attached_circuit")
|
||||
balloon_alert(user, "connected to circuit\nconnect to a port")
|
||||
|
||||
playsound(src, 'sound/machines/pda_button1.ogg', 20, TRUE)
|
||||
|
||||
if (last_attached_circuit != attached_circuit)
|
||||
if (!isnull(last_attached_circuit))
|
||||
unregister_circuit_signals(last_attached_circuit)
|
||||
register_circuit_signals()
|
||||
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
return TRUE
|
||||
|
||||
if (signal_result & COMSIG_USB_CABLE_ATTACHED)
|
||||
// Short messages are better to read
|
||||
var/connection_description = "port"
|
||||
if (istype(target, /obj/machinery/computer))
|
||||
connection_description = "computer"
|
||||
else if (ismachinery(target))
|
||||
connection_description = "machine"
|
||||
|
||||
balloon_alert(user, "connected to [connection_description]")
|
||||
playsound(src, 'sound/items/screwdriver2.ogg', 20, TRUE)
|
||||
|
||||
return TRUE
|
||||
|
||||
if (signal_result & COMSIG_CANCEL_USB_CABLE_ATTACK)
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/obj/item/usb_cable/suicide_act(mob/user)
|
||||
user.visible_message(span_suicide("[user] is wrapping [src] around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!"))
|
||||
return OXYLOSS
|
||||
|
||||
/obj/item/usb_cable/proc/register_circuit_signals()
|
||||
RegisterSignal(attached_circuit, COMSIG_MOVABLE_MOVED, .proc/on_moved)
|
||||
RegisterSignal(attached_circuit, COMSIG_PARENT_QDELETING, .proc/on_circuit_qdeling)
|
||||
RegisterSignal(attached_circuit.shell, COMSIG_MOVABLE_MOVED, .proc/on_moved)
|
||||
|
||||
/obj/item/usb_cable/proc/unregister_circuit_signals(obj/item/integrated_circuit/old_circuit)
|
||||
UnregisterSignal(attached_circuit, list(
|
||||
COMSIG_MOVABLE_MOVED,
|
||||
COMSIG_PARENT_QDELETING,
|
||||
))
|
||||
|
||||
UnregisterSignal(attached_circuit.shell, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
/obj/item/usb_cable/proc/on_moved()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
check_in_range()
|
||||
|
||||
/obj/item/usb_cable/proc/check_in_range()
|
||||
if (isnull(attached_circuit))
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return FALSE
|
||||
|
||||
if (!IN_GIVEN_RANGE(attached_circuit, src, USB_CABLE_MAX_RANGE))
|
||||
balloon_alert_to_viewers("detached, too far away")
|
||||
unregister_circuit_signals(attached_circuit)
|
||||
attached_circuit = null
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/item/usb_cable/proc/on_circuit_qdeling()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
attached_circuit = null
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* A circuit variable that holds the name, the datatype and the colour of the variable (taken from the datatype).
|
||||
*
|
||||
* Used in integrated circuits for setter and getter circuit components.
|
||||
*/
|
||||
/datum/circuit_variable
|
||||
/// The display name of the circuit variable
|
||||
var/name
|
||||
|
||||
/// The datatype of the circuit variable. Used by the setter and getter circuit components
|
||||
var/datatype
|
||||
|
||||
/// The colour that appears in the UI. The value is set to the datatype's matching colour
|
||||
var/color
|
||||
|
||||
/// The current value held by the variable.
|
||||
var/value
|
||||
|
||||
/// The components that are currently listening. Triggers them when the value is updated.
|
||||
var/list/obj/item/circuit_component/listeners
|
||||
|
||||
/datum/circuit_variable/New(name, datatype)
|
||||
. = ..()
|
||||
src.name = name
|
||||
src.datatype = datatype
|
||||
|
||||
var/datum/circuit_datatype/circuit_datatype = GLOB.circuit_datatypes[datatype]
|
||||
|
||||
src.listeners = list()
|
||||
src.color = circuit_datatype.color
|
||||
|
||||
|
||||
/datum/circuit_variable/Destroy(force, ...)
|
||||
listeners = null
|
||||
return ..()
|
||||
|
||||
/// Sets the value of the circuit component and triggers the appropriate listeners
|
||||
/datum/circuit_variable/proc/set_value(new_value)
|
||||
value = new_value
|
||||
for(var/obj/item/circuit_component/component as anything in listeners)
|
||||
TRIGGER_CIRCUIT_COMPONENT(component, null)
|
||||
|
||||
/// Adds a listener to receive inputs when the variable has a value that is set.
|
||||
/datum/circuit_variable/proc/add_listener(obj/item/circuit_component/to_add)
|
||||
listeners += to_add
|
||||
|
||||
/// Removes a listener to receive inputs when the variable has a value that is set. Listener will usually clean themselves up
|
||||
/datum/circuit_variable/proc/remove_listener(obj/item/circuit_component/to_remove)
|
||||
listeners -= to_remove
|
||||
Reference in New Issue
Block a user