mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
## About The Pull Request - Adds sanitization to windoor names and circuit shells. - Fixes a (mostly useless) href exploit with wizard's contracts. ## Why It's Good For The Game Sanitizing input is probably good. ## Changelog 🆑 fix: Fixed a few sanitization issues. /🆑
398 lines
14 KiB
Plaintext
398 lines
14 KiB
Plaintext
/// 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 <b>USB port</b> 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("<b>The integrated circuit is non-functional whilst the shell is unanchored.</b>")
|
|
|
|
|
|
/**
|
|
* 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
|