diff --git a/code/game/objects/items/mail.dm b/code/game/objects/items/mail.dm
index 149e4968402..c9fddbe19d6 100644
--- a/code/game/objects/items/mail.dm
+++ b/code/game/objects/items/mail.dm
@@ -107,25 +107,45 @@
var/tag = uppertext(GLOB.TAGGERLOCATIONS[destination_tag.currTag])
to_chat(user, span_notice("*[tag]*"))
sort_tag = destination_tag.currTag
- playsound(loc, 'sound/machines/twobeep_high.ogg', 100, TRUE)
+ playsound(loc, 'sound/machines/twobeep_high.ogg', vol = 100, vary = TRUE)
+
+/obj/item/mail/multitool_act(mob/living/user, obj/item/tool)
+ if(user.get_inactive_held_item() == src)
+ balloon_alert(user, "nothing to disable!")
+ return TRUE
+ balloon_alert(user, "hold it!")
+ return FALSE
+
/obj/item/mail/attack_self(mob/user)
+ if(!unwrap(user))
+ return FALSE
+ return after_unwrap(user)
+
+/// proc for unwrapping a mail. Goes just for an unwrapping procces, returns FALSE if it fails.
+/obj/item/mail/proc/unwrap(mob/user)
if(recipient_ref)
var/datum/mind/recipient = recipient_ref.resolve()
// If the recipient's mind has gone, then anyone can open their mail
// whether a mind can actually be qdel'd is an exercise for the reader
if(recipient && recipient != user?.mind)
to_chat(user, span_notice("You can't open somebody else's mail! That's illegal!"))
- return
+ return FALSE
- to_chat(user, span_notice("You start to unwrap the package..."))
+ balloon_alert(user, "unwrapping...")
if(!do_after(user, 1.5 SECONDS, target = user))
- return
- user.temporarilyRemoveItemFromInventory(src, TRUE)
- if(contents.len)
- user.put_in_hands(contents[1])
- playsound(loc, 'sound/items/poster_ripped.ogg', 50, TRUE)
+ return FALSE
+ return TRUE
+
+// proc that goes after unwrapping a mail.
+/obj/item/mail/proc/after_unwrap(mob/user)
+ user.temporarilyRemoveItemFromInventory(src, force = TRUE)
+ for(var/obj/item/stuff as anything in contents) // Mail and envelope actually can have more than 1 item.
+ user.put_in_hands(stuff)
+ playsound(loc, 'sound/items/poster_ripped.ogg', vol = 50, vary = TRUE)
qdel(src)
+ return TRUE
+
/obj/item/mail/examine_more(mob/user)
. = ..()
@@ -332,3 +352,153 @@
/obj/item/paper/fluff/junkmail_generic/Initialize(mapload)
default_raw_text = pick(GLOB.junkmail_messages)
return ..()
+
+/obj/item/mail/traitor
+ var/armed = FALSE
+ var/datum/weakref/made_by_ref
+ /// Cached information about who made it for logging purposes
+ var/made_by_cached_name
+ /// Cached information about who made it for logging purposes
+ var/made_by_cached_ckey
+ goodie_count = 0
+
+/obj/item/mail/traitor/envelope
+ name = "envelope"
+ icon_state = "mail_large"
+ stamp_max = 2
+ stamp_offset_y = 5
+
+/obj/item/mail/traitor/after_unwrap(mob/user)
+ user.temporarilyRemoveItemFromInventory(src, force = TRUE)
+ playsound(loc, 'sound/items/poster_ripped.ogg', vol = 50, vary = TRUE)
+ for(var/obj/item/stuff as anything in contents) // Mail and envelope actually can have more than 1 item.
+ if(user.put_in_hands(stuff) && armed)
+ log_bomber(user, "opened armed mail made by [made_by_cached_name] ([made_by_cached_ckey]), activating", stuff)
+ INVOKE_ASYNC(stuff, TYPE_PROC_REF(/obj/item, attack_self), user)
+ qdel(src)
+ return TRUE
+
+/obj/item/mail/traitor/multitool_act(mob/living/user, obj/item/tool)
+ if(armed == FALSE || user.get_inactive_held_item() != src)
+ return ..()
+ if(IS_WEAKREF_OF(user.mind, made_by_ref))
+ balloon_alert(user, "disarming trap...")
+ if(!do_after(user, 2 SECONDS, target = src))
+ return FALSE
+ balloon_alert(user, "disarmed")
+ playsound(src, 'sound/machines/defib_ready.ogg', vol = 100, vary = TRUE)
+ armed = FALSE
+ return TRUE
+ else
+ balloon_alert(user, "tinkering with something...")
+
+ if(!do_after(user, 2 SECONDS, target = src))
+ after_unwrap(user)
+ return FALSE
+ if(prob(50))
+ balloon_alert(user, "disarmed something...?")
+ playsound(src, 'sound/machines/defib_ready.ogg', vol = 100, vary = TRUE)
+ armed = FALSE
+ return TRUE
+ else
+ after_unwrap(user)
+ return TRUE
+
+/obj/item/storage/mail_counterfeit_device
+ name = "GLA-2 mail counterfeit device"
+ desc = "Device that actually able to counterfeit NT's mail. This device also able to place a trap inside of mail for malicious actions. Trap will \"activate\" any item inside of mail. Also it might be used for contraband purposes. Integrated micro-computer will give you great configuration optionality for your needs."
+ w_class = WEIGHT_CLASS_NORMAL
+ icon = 'icons/obj/device_syndie.dmi'
+ icon_state = "mail_counterfeit_device"
+
+/obj/item/storage/mail_counterfeit_device/Initialize(mapload)
+ . = ..()
+ atom_storage.max_slots = 1
+ atom_storage.allow_big_nesting = TRUE
+ atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL
+
+/obj/item/storage/mail_counterfeit_device/examine_more(mob/user)
+ . = ..()
+ . += span_notice("You notice the manufacture marking on the side of the device...")
+ . += "\t[span_info("Guerilla Letter Assembler")]"
+ . += "\t[span_info("GLA Postal Service, right on schedule.")]"
+ return .
+
+/obj/item/storage/mail_counterfeit_device/attack_self(mob/user, modifiers)
+ var/mail_type = tgui_alert(user, "Make it look like an envelope or like normal mail?", "Mail Counterfeiting", list("Mail", "Envelope"))
+ if(isnull(mail_type))
+ return FALSE
+ if(loc != user)
+ return FALSE
+ mail_type = lowertext(mail_type)
+
+ var/mail_armed = tgui_alert(user, "Arm it?", "Mail Counterfeiting", list("Yes", "No")) == "Yes"
+ if(isnull(mail_armed))
+ return FALSE
+ if(loc != user)
+ return FALSE
+
+ var/list/mail_recipients = list("Anyone")
+ var/list/mail_recipients_for_input = list("Anyone")
+ var/list/used_names = list()
+ for(var/datum/record/locked/person in sort_record(GLOB.manifest.locked))
+ if(isnull(person.mind_ref))
+ continue
+ mail_recipients += person.mind_ref
+ mail_recipients_for_input += avoid_assoc_duplicate_keys(person.name, used_names)
+
+ var/recipient = tgui_input_list(user, "Choose a recipient", "Mail Counterfeiting", mail_recipients_for_input)
+ if(isnull(recipient))
+ return FALSE
+ if(!(src in user.contents))
+ return FALSE
+
+ var/index = mail_recipients_for_input.Find(recipient)
+
+ var/obj/item/mail/traitor/shady_mail
+ if(mail_type == "mail")
+ shady_mail = new /obj/item/mail/traitor
+ else
+ shady_mail = new /obj/item/mail/traitor/envelope
+
+ shady_mail.made_by_cached_ckey = user.ckey
+ shady_mail.made_by_cached_name = user.mind.name
+
+ if(index == 1)
+ var/mail_name = tgui_input_text(user, "Enter mail title, or leave it blank", "Mail Counterfeiting")
+ if(!(src in user.contents))
+ return FALSE
+ if(reject_bad_text(mail_name, ascii_only = FALSE))
+ shady_mail.name = mail_name
+ else
+ shady_mail.name = mail_type
+ else
+ shady_mail.initialize_for_recipient(mail_recipients[index])
+
+ atom_storage.hide_contents(user)
+ user.temporarilyRemoveItemFromInventory(src, force = TRUE)
+ shady_mail.contents += contents
+ shady_mail.armed = mail_armed
+ shady_mail.made_by_ref = WEAKREF(user.mind)
+ user.put_in_hands(shady_mail)
+ qdel(src)
+
+/// Unobtainable item mostly for (b)admin purposes.
+/obj/item/storage/mail_counterfeit_device/advanced
+ name = "GLA-MACRO mail counterfeit device"
+
+/obj/item/storage/mail_counterfeit_device/advanced/Initialize(mapload)
+ . = ..()
+ desc += " This model is highly advanced and capable of compressing items, making mail's storage space comparable to standard backpack."
+ create_storage(max_slots = 21, max_total_storage = 21)
+ atom_storage.allow_big_nesting = TRUE
+
+/// Unobtainable item mostly for (b)admin purposes.
+/obj/item/storage/mail_counterfeit_device/bluespace
+ name = "GLA-ULTRA mail counterfeit device"
+
+/obj/item/storage/mail_counterfeit_device/bluespace/Initialize(mapload)
+ . = ..()
+ desc += " This model is the most advanced and capable of performing crazy bluespace compressions, making mail's storage space comparable to bluespace backpack."
+ create_storage(max_specific_storage = WEIGHT_CLASS_GIGANTIC, max_total_storage = 35, max_slots = 30)
+ atom_storage.allow_big_nesting = TRUE
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index c9ace922962..7fb901eebc5 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -426,6 +426,14 @@
new /obj/item/grenade/empgrenade(src)
new /obj/item/implanter/emp(src)
+/obj/item/storage/box/syndie_kit/mail_counterfeit
+ name = "mail counterfeit kit"
+ desc = "A box full of mail counterfeit devices. Nothing stops the mail."
+
+/obj/item/storage/box/syndie_kit/mail_counterfeit/PopulateContents()
+ for(var/i in 1 to 6)
+ new /obj/item/storage/mail_counterfeit_device(src)
+
/obj/item/storage/box/syndie_kit/chemical
name = "chemical kit"
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 22b1693f392..eaf25edf81f 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -230,6 +230,7 @@
#include "timer_sanity.dm"
#include "trait_addition_and_removal.dm"
#include "traitor.dm"
+#include "traitor_mail_content_check.dm"
#include "turf_icons.dm"
#include "tutorial_sanity.dm"
#include "unit_test.dm"
diff --git a/code/modules/unit_tests/traitor_mail_content_check.dm b/code/modules/unit_tests/traitor_mail_content_check.dm
new file mode 100644
index 00000000000..6d14d9d1428
--- /dev/null
+++ b/code/modules/unit_tests/traitor_mail_content_check.dm
@@ -0,0 +1,10 @@
+/// Makes sure that fake mail doesn't get random goods.
+/datum/unit_test/traitor_mail_content_check
+
+/datum/unit_test/traitor_mail_content_check/Run()
+ var/mob/living/carbon/human/person = allocate(/mob/living/carbon/human/consistent)
+ person.mind_initialize()
+ var/obj/item/mail/traitor/test_mail = allocate(/obj/item/mail/traitor)
+ person.mind.set_assigned_role(SSjob.GetJobType(/datum/job/captain))
+ test_mail.initialize_for_recipient(person.mind)
+ TEST_ASSERT_EQUAL(test_mail.contents.len, 0, "/obj/item/mail/traitor should not have items after initialize_for_recipient proc!")
diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm
index be21458ee87..32788fad169 100644
--- a/code/modules/uplink/uplink_items/job.dm
+++ b/code/modules/uplink/uplink_items/job.dm
@@ -16,6 +16,15 @@
limited_stock = 1 //please don't spam deadchat
surplus = 5
+/datum/uplink_item/role_restricted/mail_counterfeit_kit
+ name = "GLA Brand Mail Counterfeit Kit"
+ desc = "A box full of mail counterfeit devices. Devices that actually able to counterfeit NT's mail. Those devices also able to place a trap inside of mail for malicious actions. Trap will \"activate\" any item inside of mail. Also counterfieted mail might be used for contraband purposes. Integrated micro-computer will give you great configuration optionality for your needs. \nNothing stops the mail."
+ item = /obj/item/storage/box/syndie_kit/mail_counterfeit
+ cost = 2
+ illegal_tech = FALSE
+ restricted_roles = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER)
+ surplus = 5
+
/datum/uplink_item/role_restricted/bureaucratic_error
name = "Organic Capital Disturbance Virus"
desc = "Randomizes job positions presented to new hires. May lead to too many/too few security officers and/or clowns. Single use."
diff --git a/code/modules/uplink/uplink_items/stealthy_tools.dm b/code/modules/uplink/uplink_items/stealthy_tools.dm
index 6cc5a12bd97..60f007ebae7 100644
--- a/code/modules/uplink/uplink_items/stealthy_tools.dm
+++ b/code/modules/uplink/uplink_items/stealthy_tools.dm
@@ -92,6 +92,13 @@
surplus = 30
illegal_tech = FALSE
+/datum/uplink_item/stealthy_tools/mail_counterfeit
+ name = "GLA Brand Mail Counterfeit Device"
+ desc = "Device that actually able to counterfeit NT's mail. This device also able to place a trap inside of mail for malicious actions. Trap will \"activate\" any item inside of mail. Also it might be used for contraband purposes. Integrated micro-computer will give you great configuration optionality for your needs."
+ item = /obj/item/storage/mail_counterfeit_device
+ cost = 1
+ surplus = 30
+
/datum/uplink_item/stealthy_tools/telecomm_blackout
name = "Disable Telecomms"
desc = "When purchased, a virus will be uploaded to the telecommunication processing servers to temporarily disable themselves."
diff --git a/icons/obj/device_syndie.dmi b/icons/obj/device_syndie.dmi
index 5eaa05d0914..25ac0ef9b39 100644
Binary files a/icons/obj/device_syndie.dmi and b/icons/obj/device_syndie.dmi differ