Files
Bubberstation/code/datums/components/shell.dm
leaKsi 917b68cba9 A bunch of sanitization fixes (#89477)
## 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.
/🆑
2025-02-15 10:42:08 +01:00

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