/// Makes an atom a shell that is able to take in an attached circuit. /datum/component/shell dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS /// The circuitboard attached to this shell var/obj/item/integrated_circuit/attached_circuit /// Flags containing what this shell can do var/shell_flags = NONE /// The capacity of the shell. var/capacity = INFINITY /// A list of components that cannot be removed var/list/obj/item/circuit_component/unremovable_circuit_components /// Whether the shell is locked or not var/locked = FALSE // The variables below are used only for anchored shells /// The amount of power used in the last minute var/power_used_in_minute = 0 /// The cooldown time to reset the power_used_in_minute to 0 COOLDOWN_DECLARE(power_used_cooldown) /// The maximum power that the shell can use in a minute before entering overheating and destroying itself. var/max_power_use_in_minute = 20 * STANDARD_CELL_CHARGE /datum/component/shell/Initialize(unremovable_circuit_components, capacity, shell_flags, starting_circuit) . = ..() if(!ismovable(parent)) return COMPONENT_INCOMPATIBLE src.shell_flags = shell_flags || src.shell_flags src.capacity = capacity || src.capacity set_unremovable_circuit_components(unremovable_circuit_components) if(starting_circuit) attach_circuit(starting_circuit) /datum/component/shell/RegisterWithParent() RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, PROC_REF(on_attack_ghost)) if(!(shell_flags & SHELL_FLAG_CIRCUIT_UNMODIFIABLE)) RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_multitool_act)) RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attack_by)) if(!(shell_flags & SHELL_FLAG_CIRCUIT_UNREMOVABLE)) RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), PROC_REF(on_screwdriver_act)) RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, PROC_REF(on_object_deconstruct)) if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR) RegisterSignal(parent, COMSIG_MOVABLE_SET_ANCHORED, PROC_REF(on_set_anchored)) RegisterSignal(parent, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, PROC_REF(on_atom_usb_cable_try_attach)) RegisterSignal(parent, COMSIG_MOVABLE_CIRCUIT_LOADED, PROC_REF(on_load)) /datum/component/shell/proc/set_unremovable_circuit_components(list/components) if(unremovable_circuit_components) QDEL_LIST(unremovable_circuit_components) unremovable_circuit_components = list() for(var/obj/item/circuit_component/circuit_component as anything in components) add_unremovable_circuit_component(circuit_component) /datum/component/shell/proc/add_unremovable_circuit_component(obj/item/circuit_component/component) if(ispath(component)) component = new component() component.removable = FALSE component.set_circuit_size(0) RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_SAVE, PROC_REF(save_component)) unremovable_circuit_components += component /datum/component/shell/proc/save_component(datum/source, list/objects) SIGNAL_HANDLER objects += parent /datum/component/shell/proc/on_load(datum/source, obj/item/integrated_circuit/circuit, list/components) SIGNAL_HANDLER var/list/components_in_list = list() for(var/obj/item/circuit_component/component as anything in components) components_in_list += component.type for(var/obj/item/circuit_component/component as anything in unremovable_circuit_components) if(component.type in components_in_list) continue var/new_type = component.type components += new new_type() set_unremovable_circuit_components(components) attach_circuit(circuit) /datum/component/shell/UnregisterFromParent() UnregisterSignal(parent, list( COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), COMSIG_OBJ_DECONSTRUCT, COMSIG_MOVABLE_SET_ANCHORED, COMSIG_ATOM_EXAMINE, COMSIG_ATOM_ATTACK_GHOST, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, COMSIG_MOVABLE_CIRCUIT_LOADED, )) QDEL_NULL(attached_circuit) /datum/component/shell/Destroy(force) QDEL_LIST(unremovable_circuit_components) return ..() /datum/component/shell/proc/on_object_deconstruct() SIGNAL_HANDLER if(!attached_circuit) return if(attached_circuit.admin_only) return if(shell_flags & SHELL_FLAG_CIRCUIT_UNREMOVABLE) return remove_circuit() /datum/component/shell/proc/on_attack_ghost(datum/source, mob/dead/observer/ghost) SIGNAL_HANDLER if(!is_authorized(ghost)) return if(attached_circuit) INVOKE_ASYNC(attached_circuit, TYPE_PROC_REF(/datum, ui_interact), ghost) /datum/component/shell/proc/on_examine(atom/movable/source, mob/user, list/examine_text) SIGNAL_HANDLER if(!is_authorized(user)) return if(!attached_circuit) examine_text += span_notice("There is no integrated circuit attached.") return examine_text += span_notice("There is an integrated circuit attached. Use a multitool to access the wiring. Use a screwdriver to remove it from [source].") examine_text += span_notice("The cover panel to the integrated circuit is [locked? "locked" : "unlocked"].") var/obj/item/stock_parts/power_store/cell = attached_circuit.cell examine_text += span_notice("The charge meter reads [cell ? round(cell.percent(), 1) : 0]%.") if (shell_flags & SHELL_FLAG_USB_PORT) examine_text += span_notice("There is a USB port on the front.") if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR) examine_text += span_notice("The shell does not require a battery to function and will draw from the area's APC whenever possible.") if(!source.anchored) examine_text += span_danger("The integrated circuit is non-functional whilst the shell is unanchored.") /** * Called when the shell is anchored. * * Only applies if the shell has SHELL_FLAG_REQUIRE_ANCHOR. * Disables the integrated circuit if unanchored, otherwise enable the circuit. */ /datum/component/shell/proc/on_set_anchored(atom/movable/source, previous_value) SIGNAL_HANDLER attached_circuit?.set_on(source.anchored) /** * Called when an item hits the parent. This is the method to add the circuitboard to the component. */ /datum/component/shell/proc/on_attack_by(atom/source, obj/item/item, mob/living/attacker) SIGNAL_HANDLER if(!is_authorized(attacker)) return if(istype(item, /obj/item/stock_parts/power_store/cell)) source.balloon_alert(attacker, "can't put cell in directly!") return if(istype(item, /obj/item/inducer)) var/obj/item/inducer/inducer = item INVOKE_ASYNC(inducer, TYPE_PROC_REF(/obj/item, interact_with_atom), attached_circuit || parent, attacker, list()) return COMPONENT_NO_AFTERATTACK if(attached_circuit) if(attached_circuit.owner_id && item == attached_circuit.owner_id.resolve()) set_locked(!locked) source.balloon_alert(attacker, "[locked ? "locked" : "unlocked"] [source]") return COMPONENT_NO_AFTERATTACK if(!attached_circuit.owner_id && isidcard(item)) source.balloon_alert(attacker, "owner id set for [item]") attached_circuit.owner_id = WEAKREF(item) return COMPONENT_NO_AFTERATTACK if(istype(item, /obj/item/circuit_component)) attached_circuit.add_component_manually(item, attacker) return COMPONENT_NO_AFTERATTACK if(!istype(item, /obj/item/integrated_circuit)) return var/obj/item/integrated_circuit/logic_board = item . = COMPONENT_NO_AFTERATTACK if(logic_board.shell) // I'll be surprised if this ever happens return if(attached_circuit) source.balloon_alert(attacker, "there is already a circuitboard inside!") return if(logic_board.current_size > capacity) source.balloon_alert(attacker, "this is too large to fit into [parent]!") return logic_board.inserter_mind = WEAKREF(attacker.mind) attach_circuit(logic_board, attacker) /// Sets whether the shell is locked or not /datum/component/shell/proc/set_locked(new_value) locked = new_value attached_circuit?.set_locked(new_value) /datum/component/shell/proc/on_multitool_act(atom/source, mob/user, obj/item/tool) SIGNAL_HANDLER if(!is_authorized(user)) return if(!attached_circuit) return if(locked) if(shell_flags & SHELL_FLAG_ALLOW_FAILURE_ACTION) return source.balloon_alert(user, "it's locked!") return ITEM_INTERACT_BLOCKING attached_circuit.interact(user) return ITEM_INTERACT_BLOCKING /** * Called when a screwdriver is used on the parent. Removes the circuitboard from the component. */ /datum/component/shell/proc/on_screwdriver_act(atom/source, mob/user, obj/item/tool) SIGNAL_HANDLER if(!is_authorized(user)) return if(!attached_circuit) return if(locked) if(shell_flags & SHELL_FLAG_ALLOW_FAILURE_ACTION) return source.balloon_alert(user, "it's locked!") return ITEM_INTERACT_BLOCKING tool.play_tool_sound(parent) source.balloon_alert(user, "you unscrew [attached_circuit] from [parent].") remove_circuit() return ITEM_INTERACT_BLOCKING /** * Checks for when the circuitboard moves. If it moves, removes it from the component. */ /datum/component/shell/proc/on_circuit_moved(obj/item/integrated_circuit/circuit, atom/old_loc) SIGNAL_HANDLER if(circuit.loc != parent) remove_circuit() /** * Checks for when the circuitboard deletes so that it can be unassigned. */ /datum/component/shell/proc/on_circuit_delete(datum/source) SIGNAL_HANDLER remove_circuit() /datum/component/shell/proc/on_circuit_add_component_manually(atom/source, obj/item/circuit_component/added_comp, mob/living/user) SIGNAL_HANDLER if(locked) source.balloon_alert(user, "it's locked!") return COMPONENT_CANCEL_ADD_COMPONENT if(attached_circuit.current_size + added_comp.circuit_size > capacity) source.balloon_alert(user, "it won't fit!") return COMPONENT_CANCEL_ADD_COMPONENT /datum/component/shell/proc/override_power_usage(datum/source, power_to_use) SIGNAL_HANDLER if(COOLDOWN_FINISHED(src, power_used_cooldown)) power_used_in_minute = 0 var/area/location = get_area(parent) if(!location.powered(AREA_USAGE_EQUIP)) return if(power_used_in_minute > max_power_use_in_minute) explosion(parent, light_impact_range = 1, explosion_cause = attached_circuit) if(attached_circuit) remove_circuit() return location.apc?.terminal?.use_energy(power_to_use, channel = AREA_USAGE_EQUIP) power_used_in_minute += power_to_use COOLDOWN_START(src, power_used_cooldown, 1 MINUTES) return COMPONENT_OVERRIDE_POWER_USAGE /** * Attaches a circuit to the parent. Doesn't do any checks to see for any existing circuits so that should be done beforehand. */ /datum/component/shell/proc/attach_circuit(obj/item/integrated_circuit/circuitboard, mob/living/user) var/atom/movable/parent_atom = parent if(user && !user.transferItemToLoc(circuitboard, parent_atom)) return locked = FALSE attached_circuit = circuitboard SEND_SIGNAL(src, COMSIG_SHELL_CIRCUIT_ATTACHED) if(!(shell_flags & SHELL_FLAG_CIRCUIT_UNREMOVABLE) && !circuitboard.admin_only) RegisterSignal(circuitboard, COMSIG_MOVABLE_MOVED, PROC_REF(on_circuit_moved)) if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR) RegisterSignal(circuitboard, COMSIG_CIRCUIT_PRE_POWER_USAGE, PROC_REF(override_power_usage)) RegisterSignal(circuitboard, COMSIG_QDELETING, PROC_REF(on_circuit_delete)) for(var/obj/item/circuit_component/to_add as anything in unremovable_circuit_components) to_add.forceMove(attached_circuit) attached_circuit.add_component(to_add) RegisterSignal(circuitboard, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, PROC_REF(on_circuit_add_component_manually)) if(attached_circuit.display_name != "") parent_atom.name = "[initial(parent_atom.name)] ([strip_html(attached_circuit.display_name)])" attached_circuit.set_locked(FALSE) if((shell_flags & SHELL_FLAG_CIRCUIT_UNREMOVABLE) || circuitboard.admin_only) circuitboard.moveToNullspace() else if(circuitboard.loc != parent_atom) circuitboard.forceMove(parent_atom) attached_circuit.set_shell(parent_atom) // call after set_shell() sets on to true if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR) attached_circuit.set_on(parent_atom.anchored) /** * Removes the circuit from the component. Doesn't do any checks to see for an existing circuit so that should be done beforehand. */ /datum/component/shell/proc/remove_circuit() // remove_current_shell() also turns off the circuit attached_circuit.remove_current_shell() UnregisterSignal(attached_circuit, list( COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, COMSIG_CIRCUIT_PRE_POWER_USAGE, )) if(attached_circuit.loc == parent || (!QDELETED(attached_circuit) && attached_circuit.loc == null)) var/atom/parent_atom = parent var/drop_location = parent_atom.drop_location() if(drop_location) attached_circuit.forceMove(drop_location) else attached_circuit.moveToNullspace() for(var/obj/item/circuit_component/to_remove as anything in unremovable_circuit_components) attached_circuit.remove_component(to_remove) to_remove.moveToNullspace() attached_circuit.set_locked(FALSE) SEND_SIGNAL(src, COMSIG_SHELL_CIRCUIT_REMOVED) attached_circuit = null /datum/component/shell/proc/on_atom_usb_cable_try_attach(atom/source, obj/item/usb_cable/usb_cable, mob/user) SIGNAL_HANDLER if(!is_authorized(user)) return if (!(shell_flags & SHELL_FLAG_USB_PORT)) source.balloon_alert(user, "this shell has no usb ports") return COMSIG_CANCEL_USB_CABLE_ATTACK if (isnull(attached_circuit)) source.balloon_alert(user, "no circuit inside") return COMSIG_CANCEL_USB_CABLE_ATTACK if(attached_circuit.locked) source.balloon_alert(user, "circuit is locked!") return COMSIG_CANCEL_USB_CABLE_ATTACK usb_cable.attached_circuit = attached_circuit return COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT /** * Determines if a user is authorized to see the existence of this shell. Returns false if they are not * * Arguments: * * user - The user to check if they are authorized */ /datum/component/shell/proc/is_authorized(mob/user) if((shell_flags & SHELL_FLAG_CIRCUIT_UNREMOVABLE) && (shell_flags & SHELL_FLAG_CIRCUIT_UNMODIFIABLE)) return FALSE if(attached_circuit?.admin_only) if(check_rights_for(user.client, R_VAREDIT)) return TRUE return FALSE return TRUE