diff --git a/code/datums/components/label.dm b/code/datums/components/label.dm
new file mode 100644
index 00000000000..26cb99c7e3c
--- /dev/null
+++ b/code/datums/components/label.dm
@@ -0,0 +1,87 @@
+/**
+ The label component.
+
+ This component is used to manage labels applied by the hand labeler.
+
+ Atoms can only have one instance of this component, and therefore only one label at a time.
+ This is to avoid having names like "Backpack (label1) (label2) (label3)". This is annoying and abnoxious to read.
+
+ When a player clicks the atom with a hand labeler to apply a label, this component gets applied to it.
+ If the labeler is off, the component will be removed from it, and the label will be removed from its name.
+ */
+/datum/component/label
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ /// The name of the label the player is applying to the parent.
+ var/label_name
+
+/datum/component/label/Initialize(_label_name)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ label_name = _label_name
+ apply_label()
+
+/datum/component/label/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackby)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/Examine)
+
+/datum/component/label/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))
+
+/**
+ This proc will fire after the parent is hit by a hand labeler which is trying to apply another label.
+ Since the parent already has a label, it will remove the old one from the parent's name, and apply the new one.
+*/
+/datum/component/label/InheritComponent(datum/component/label/new_comp , i_am_original, list/arguments)
+ remove_label()
+ if(new_comp)
+ label_name = new_comp.label_name
+ else
+ label_name = arguments[1]
+ apply_label()
+
+/**
+ This proc will trigger when any object is used to attack the parent.
+
+ If the attacking object is not a hand labeler, it will return.
+ If the attacking object is a hand labeler it will restore the name of the parent to what it was before this component was added to it, and the component will be deleted.
+
+ Arguments:
+ * source: The parent.
+ * attacker: The object that is hitting the parent.
+ * user: The mob who is wielding the attacking object.
+*/
+/datum/component/label/proc/OnAttackby(datum/source, obj/item/attacker, mob/user)
+ // If the attacking object is not a hand labeler or its mode is 1 (has a label ready to apply), return.
+ // The hand labeler should be off (mode is 0), in order to remove a label.
+ var/obj/item/hand_labeler/labeler = attacker
+ if(!istype(labeler) || labeler.mode)
+ return
+
+ remove_label()
+ playsound(parent, 'sound/items/poster_ripped.ogg', 20, TRUE)
+ to_chat(user, "You remove the label from [parent].")
+ qdel(src) // Remove the component from the object.
+
+/**
+ This proc will trigger when someone examines the parent.
+ It will attach the text found in the body of the proc to the `examine_list` and display it to the player examining the parent.
+
+ Arguments:
+ * source: The parent.
+ * user: The mob exmaining the parent.
+ * examine_list: The current list of text getting passed from the parent's normal examine() proc.
+*/
+/datum/component/label/proc/Examine(datum/source, mob/user, list/examine_list)
+ examine_list += "It has a label with some words written on it. Use a hand labeler to remove it."
+
+/// Applies a label to the name of the parent in the format of: "parent_name (label)"
+/datum/component/label/proc/apply_label()
+ var/atom/owner = parent
+ owner.name += " ([label_name])"
+
+/// Removes the label from the parent's name
+/datum/component/label/proc/remove_label()
+ var/atom/owner = parent
+ owner.name = replacetext(owner.name, "([label_name])", "") // Remove the label text from the parent's name, wherever it's located.
+ owner.name = trim(owner.name) // Shave off any white space from the beginning or end of the parent's name.
diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm
index 732c55e3fbe..14bc959fbb1 100644
--- a/code/datums/outfits/outfit_admin.dm
+++ b/code/datums/outfits/outfit_admin.dm
@@ -709,7 +709,8 @@
pda = /obj/item/pda
backpack_contents = list(
/obj/item/storage/box/survival = 1,
- /obj/item/hand_labeler = 1
+ /obj/item/hand_labeler = 1,
+ /obj/item/hand_labeler_refill = 1
)
/datum/outfit/admin/sol_trader/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm
index 15c6f3fb2c0..a18a9d74f28 100644
--- a/code/datums/supplypacks.dm
+++ b/code/datums/supplypacks.dm
@@ -1434,6 +1434,8 @@ GLOBAL_LIST_INIT(all_supply_groups, list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY
contains = list(/obj/structure/filingcabinet/chestdrawer,
/obj/item/camera_film,
/obj/item/hand_labeler,
+ /obj/item/hand_labeler_refill,
+ /obj/item/hand_labeler_refill,
/obj/item/stack/tape_roll,
/obj/item/paper_bin,
/obj/item/pen,
diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm
index 74bee9867ab..7722dd2b53f 100644
--- a/code/modules/paperwork/handlabeler.dm
+++ b/code/modules/paperwork/handlabeler.dm
@@ -1,11 +1,12 @@
/obj/item/hand_labeler
name = "hand labeler"
+ desc = "A combined label printer, applicator, and remover, all in a single portable device. Designed to be easy to operate and use."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "labeler0"
item_state = "flight"
var/label = null
var/labels_left = 30
- var/mode = 0 //off or on.
+ var/mode = 0
/obj/item/hand_labeler/afterattack(atom/A, mob/user, proximity)
if(!proximity)
@@ -29,7 +30,9 @@
user.visible_message("[user] labels [A] as [label].", \
"You label [A] as [label].")
investigate_log("[key_name(user)] labelled [A] as [label].", INVESTIGATE_LABEL) // Investigate goes BEFORE rename so the original name is preserved in the log
- A.name = "[A.name] ([label])"
+ A.AddComponent(/datum/component/label, label)
+ playsound(A, 'sound/items/handling/component_pickup.ogg', 20, TRUE)
+ labels_left--
/obj/item/hand_labeler/attack_self(mob/user as mob)
mode = !mode
@@ -45,3 +48,20 @@
to_chat(user, "You set the text to '[str]'.")
else
to_chat(user, "You turn off \the [src].")
+
+/obj/item/hand_labeler/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/hand_labeler_refill))
+ to_chat(user, "You insert [I] into [src].")
+ user.drop_item()
+ qdel(I)
+ labels_left = initial(labels_left) //Yes, it's capped at its initial value
+ else
+ return ..()
+
+/obj/item/hand_labeler_refill
+ name = "hand labeler paper roll"
+ icon = 'icons/obj/bureaucracy.dmi'
+ desc = "A roll of paper. Use it on a hand labeler to refill it."
+ icon_state = "labeler_refill"
+ item_state = "electropack"
+ w_class = WEIGHT_CLASS_TINY
diff --git a/icons/obj/bureaucracy.dmi b/icons/obj/bureaucracy.dmi
index adf304513c7..3d287a2365a 100644
Binary files a/icons/obj/bureaucracy.dmi and b/icons/obj/bureaucracy.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 3ecbfb6b3ee..a913628c28e 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -296,6 +296,7 @@
#include "code\datums\components\decal.dm"
#include "code\datums\components\ducttape.dm"
#include "code\datums\components\jestosterone.dm"
+#include "code\datums\components\label.dm"
#include "code\datums\components\material_container.dm"
#include "code\datums\components\paintable.dm"
#include "code\datums\components\spawner.dm"
diff --git a/sound/items/handling/component_pickup.ogg b/sound/items/handling/component_pickup.ogg
new file mode 100644
index 00000000000..cfaba1dd193
Binary files /dev/null and b/sound/items/handling/component_pickup.ogg differ