Files
Bubberstation/code/modules/wiremod/core/component.dm
Watermelon914 0db2a23faf Adds a new power storage type: The Megacell. Drastically reduces power cell consumption/storage. [MDB Ignore] (#84079)
## About The Pull Request
As the title says. A standard power cell now only stores 10 KJ and
drains power similar to how it did before the refactor to all power
appliances.

The new standard megacell stock part stores 1 MJ (what cells store right
now). APCs and SMESs have had their power cells replaced with these
megacell stock parts instead. Megacells can only be used in APCs and
SMESs. It shouldn't be possible to use megacells in any typical
appliance.

This shouldn't change anything about how much 'use' you can get out of a
power cell in regular practice. Most should operate the same and you
should still get the same amount of shots out of a laser gun, and we can
look at expanding what can be switched over to megacells, e.g. if we
want mechs to require significantly more power than a typical appliance.

Thanks to Meyhazah for the megacell icon sprites.

## Why It's Good For The Game
Power cell consumption is way too high ever since the power appliance
refactor that converted most things to be in joules. It's a bit
ridiculous for most of our machinery to drain the station's power supply
this early on.

The reason it's like this is because regular appliances (laser guns,
borgs, lights) all have a cell type that is identical to the APC/SMES
cell type. And it means that if we want to provide an easy way to charge
these appliances without making it easy to charge APCs/SMESs through a
power bug exploit, we need to introduce a new cell type to differentiate
between what supplies power and regular appliances that use power. This
is primarily what the megacell stock part does.

This moves us back to what it was originally like before the power
refactor, where recharging power cells wouldn't drain an exorbitant
amount of energy. However, it maintains the goal of the original
refactor which was to prevent people from cheesing power generation to
produce an infinite amount of power, as the power that APCs and SMESs
operate at is drastically different from the power that a regular
appliance uses.

## Changelog
🆑 Watermelon, Mayhazah
balance: Drastically reduces the power consumption and max charge of
power cells
balance: Added a new stock part called the battery, used primarily in
the construction of APCs and SMESs.
add: Suiciding with a cell/battery will shock you and potentially dust
you/shock the people around you if the charge is great enough.
/🆑

---------

Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>
Co-authored-by: Pickle-Coding <58013024+Pickle-Coding@users.noreply.github.com>
2024-06-25 00:32:19 +00:00

419 lines
14 KiB
Plaintext

/**
* # 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/devices/circuitry_n_data.dmi'
icon_state = "component"
inhand_icon_state = "electronic"
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
/// The name of the component shown on the UI
var/display_name = "Generic"
/// The category of the component in the UI
var/category = COMPONENT_DEFAULT_CATEGORY
/// The colour this circuit component appears in the UI
var/ui_color = "blue"
/// 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 energy usage whenever this component receives an input.
var/energy_usage_per_input = 0.001 * STANDARD_CELL_CHARGE
/// 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
/// Determines the amount of space this circuit occupies in an integrated circuit.
var/circuit_size = 1
/// The UI buttons of this circuit component. An assoc list that has this format: "button_icon" = "action_name"
var/ui_buttons = null
/// Called when the option ports should be set up
/obj/item/circuit_component/proc/populate_options()
return
/// Called when the rest of the ports should be set up
/obj/item/circuit_component/proc/populate_ports()
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, order = 0, trigger = PROC_REF(input_received))
return add_input_port(name, PORT_TYPE_OPTION, order = order, trigger = trigger, port_type = /datum/port/input/option, extra_args = list("possible_options" = list_to_use))
/obj/item/circuit_component/Initialize(mapload)
. = ..()
if(name == COMPONENT_DEFAULT_NAME)
name = "[LOWER_TEXT(display_name)] [COMPONENT_DEFAULT_NAME]"
populate_options()
populate_ports()
if((circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL) && !trigger_input)
trigger_input = add_input_port("Trigger", PORT_TYPE_SIGNAL, order = 2)
if((circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL) && !trigger_output)
trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL, order = 2)
if(circuit_flags & CIRCUIT_FLAG_INSTANT)
ui_color = "orange"
/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 ..()
/obj/item/circuit_component/drop_location()
if(parent?.shell)
return parent.shell.drop_location()
return ..()
/obj/item/circuit_component/examine(mob/user)
. = ..()
if(circuit_flags & CIRCUIT_FLAG_REFUSE_MODULE)
. += span_notice("It's incompatible with module components.")
/**
* 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, order = 1, trigger = PROC_REF(input_received), default = 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))
input_ports += input_port
sortTim(input_ports, GLOBAL_PROC_REF(cmp_port_order_asc))
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)
return null //explicitly set the port to null if used like this: `port = remove_input_port(port)`
/**
* 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, order = 1)
var/list/arguments = list(src)
arguments += args
var/datum/port/output/output_port = new(arglist(arguments))
output_ports += output_port
sortTim(output_ports, GLOBAL_PROC_REF(cmp_port_order_asc))
if(parent)
SStgui.update_uis(parent)
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)
return null //explicitly set the port to null if used like this: `port = remove_output_port(port)`
/**
* Called whenever an input is received from one of the ports.
*
* Return value indicates whether the trigger was successful or not.
* Arguments:
* * port - Can be null. The port that sent the input
* * return_values - Only defined if the component is receiving an input due to instant execution. Contains the values to be returned once execution has stopped.
*/
/obj/item/circuit_component/proc/trigger_component(datum/port/input/port, list/return_values)
SHOULD_NOT_SLEEP(TRUE)
pre_input_received(port)
if(!should_receive_input(port))
return FALSE
var/result
if(port)
var/proc_to_call = port.trigger
if(!proc_to_call)
return FALSE
result = call(src, proc_to_call)(port, return_values)
else
result = input_received(null, return_values)
if(result)
return FALSE
if(circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL)
trigger_output.set_output(COMPONENT_SIGNAL)
return TRUE
/obj/item/circuit_component/proc/set_circuit_size(new_size)
if(parent)
parent.current_size -= circuit_size
circuit_size = new_size
if(parent)
parent.current_size += circuit_size
/**
* Called whether this circuit component should receive an input.
* If this returns false, the proc that is supposed to be triggered will not be called and an output signal will not be sent.
* This is to only return false if flow of execution should be stopped because something bad has happened (e.g. no power)
* Returning no value in input_received() is not an issue because it means flow of execution will continue even if the component failed to execute properly.
*
* Return value indicates whether or not
* Arguments:
* * port - Can be null. The port that sent the input
*/
/obj/item/circuit_component/proc/should_receive_input(datum/port/input/port)
SHOULD_NOT_SLEEP(TRUE)
if(!parent?.on)
return FALSE
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 FALSE
var/flags = SEND_SIGNAL(parent, COMSIG_CIRCUIT_PRE_POWER_USAGE, energy_usage_per_input)
if(!(flags & COMPONENT_OVERRIDE_POWER_USAGE))
var/obj/item/stock_parts/power_store/cell = parent.get_cell()
if(!cell?.use(energy_usage_per_input))
return FALSE
if((!port || port.trigger == PROC_REF(input_received)) && (circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL) && !COMPONENT_TRIGGERED_BY(trigger_input, port))
return FALSE
return TRUE
/// Called when trying to get the physical location of this object
/obj/item/circuit_component/proc/get_location()
return get_turf(src) || get_turf(parent?.shell)
/obj/item/circuit_component/balloon_alert(mob/viewer, text)
if(parent)
return parent.balloon_alert(viewer, text)
return ..()
/// Called before input_received and should_receive_input. Used to perform behaviour that shouldn't care whether the input should be received or not.
/obj/item/circuit_component/proc/pre_input_received(datum/port/input/port)
SHOULD_NOT_SLEEP(TRUE)
return
/**
* Called from trigger_component. General component behaviour should go in this proc. This is the default proc that is called if no trigger proc is specified.
*
* Return value indicates that the circuit should not send an output signal.
* Arguments:
* * port - Can be null. The port that sent the input
* * return_values - Only defined if the component is receiving an input due to instant execution. Contains the values to be returned once execution has stopped.
*/
/obj/item/circuit_component/proc/input_received(datum/port/input/port, list/return_values)
SHOULD_NOT_SLEEP(TRUE)
return
/// 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)
if(circuit_flags & CIRCUIT_FLAG_ADMIN)
ADD_TRAIT(added_to, TRAIT_CIRCUIT_UNDUPABLE, REF(src))
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)
REMOVE_TRAIT(removed_from, TRAIT_CIRCUIT_UNDUPABLE, REF(src))
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(circuit_flags & CIRCUIT_FLAG_INSTANT)
. += create_ui_notice("Instant Execution", "red", "tachometer-alt")
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("Energy Usage Per Input: [display_energy(energy_usage_per_input)]", "orange", "bolt")
/**
* Called when a special button is pressed on this component in the UI.
*
* Arguments:
* * user - Interacting mob
* * action - A string for which action is being performed. No parameters passed because it's only a button press.
*/
/obj/item/circuit_component/proc/ui_perform_action(mob/user, action)
return
/**
* 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,
))
/obj/item/circuit_component/ui_host(mob/user)
if(parent)
return parent.ui_host()
return ..()
/**
* 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, column_name = "Column", column_name_plural = "Columns")
SHOULD_BE_PURE(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
. = list()
. += create_ui_notice("Available [column_name_plural]:", "grey", "question-circle")
for(var/entry in entries)
. += create_ui_notice("[column_name] Name: '[entry]'", "grey", "columns")
/**
* Called when a circuit component is added to an object with a USB port.
*
* Arguments:
* * shell - The object that USB cables can connect to
*/
/obj/item/circuit_component/proc/register_usb_parent(atom/movable/shell)
return
/**
* Called when a circuit component is removed from an object with a USB port.
*
* Arguments:
* * shell - The object that USB cables can connect to
*/
/obj/item/circuit_component/proc/unregister_usb_parent(atom/movable/shell)
return
/**
* Called when a circuit component requests to send Ntnet data signal.
*
* Arguments:
* * port - The required list port needed by the Ntnet receive
* * key - The encryption key
* * signal_type - The signal type used for sending this global signal (optional, default is COMSIG_GLOB_CIRCUIT_NTNET_DATA_SENT)
*/
/obj/item/circuit_component/proc/send_ntnet_data(datum/port/input/port, key, signal_type = COMSIG_GLOB_CIRCUIT_NTNET_DATA_SENT)
SEND_GLOBAL_SIGNAL(signal_type, list("data" = port.value, "enc_key" = key, "port" = WEAKREF(port)))