diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 9de0dbe7c5..fa86b909ea 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -474,6 +474,14 @@
#define COMPONENT_BLOCK_SHARPEN_ALREADY 4
#define COMPONENT_BLOCK_SHARPEN_MAXED 8
#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M)
+
+// /obj/item signals for economy
+#define COMSIG_ITEM_SOLD "item_sold" //called when an item is sold by the exports subsystem
+#define COMSIG_STRUCTURE_UNWRAPPED "structure_unwrapped" //called when a wrapped up structure is opened by hand
+#define COMSIG_ITEM_UNWRAPPED "item_unwrapped" //called when a wrapped up item is opened by hand
+ #define COMSIG_ITEM_SPLIT_VALUE 1
+#define COMSIG_ITEM_SPLIT_PROFIT "item_split_profits" //Called when getting the item's exact ratio for cargo's profit.
+
#define COMSIG_ITEM_WORN_OVERLAYS "item_worn_overlays" //from base of obj/item/worn_overlays(): (isinhands, icon_file, used_state, style_flags, list/overlays)
#define COMSIG_ARMOR_PLATED "armor_plated" //called when an armor plate is successfully applied to an object
// THE FOLLOWING TWO BLOCKS SHOULD RETURN BLOCK FLAGS AS DEFINED IN __DEFINES/combat.dm!
diff --git a/code/datums/components/pricetag.dm b/code/datums/components/pricetag.dm
new file mode 100644
index 0000000000..3220dc425a
--- /dev/null
+++ b/code/datums/components/pricetag.dm
@@ -0,0 +1,28 @@
+/datum/component/pricetag
+ var/datum/bank_account/owner
+ var/profit_ratio = 1
+
+/datum/component/pricetag/Initialize(_owner,_profit_ratio)
+ if(!isobj(parent)) //Has to account for both objects and sellable structures like crates.
+ return COMPONENT_INCOMPATIBLE
+ owner = _owner
+ if(_profit_ratio)
+ profit_ratio = _profit_ratio
+ RegisterSignal(parent, COMSIG_ITEM_SOLD, .proc/split_profit)
+ RegisterSignal(parent, COMSIG_STRUCTURE_UNWRAPPED, .proc/Unwrapped)
+ RegisterSignal(parent, COMSIG_ITEM_UNWRAPPED, .proc/Unwrapped)
+ RegisterSignal(parent, COMSIG_ITEM_SPLIT_PROFIT, .proc/return_ratio)
+
+/datum/component/pricetag/proc/Unwrapped()
+ qdel(src) //Once it leaves it's wrapped container, the object in question should lose it's pricetag component.
+
+/datum/component/pricetag/proc/split_profit(var/item_value)
+ var/price = item_value
+ if(price)
+ var/adjusted_value = price*(profit_ratio/100)
+ owner.adjust_money(adjusted_value)
+ owner.bank_card_talk("Sale recorded. [adjusted_value] credits added to account.")
+ return TRUE
+
+/datum/component/pricetag/proc/return_ratio()
+ return profit_ratio
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index cc9fc8c2b3..7033284a86 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -311,3 +311,9 @@
random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7")
mergeable_decal = TRUE
persistent = TRUE
+
+/obj/effect/decal/cleanable/wrapping
+ name = "wrapping shreds"
+ desc = "Torn pieces of cardboard and paper, left over from a package."
+ icon = 'icons/obj/objects.dmi'
+ icon_state = "paper_shreds"
diff --git a/code/game/objects/items/stacks/wrap.dm b/code/game/objects/items/stacks/wrap.dm
index 6ae63e640f..c969e2d1d4 100644
--- a/code/game/objects/items/stacks/wrap.dm
+++ b/code/game/objects/items/stacks/wrap.dm
@@ -20,6 +20,9 @@
if(QDELETED(src) && !transfer)
new /obj/item/c_tube(T)
+/obj/item/stack/wrapping_paper/small
+ desc = "Wrap packages with this festive paper to make gifts. This roll looks a bit skimpy."
+ amount = 10
/*
* Package Wrap
@@ -121,6 +124,11 @@
if(QDELETED(src) && !transfer)
new /obj/item/c_tube(T)
+/obj/item/stack/packageWrap/small
+ desc = "You can use this to wrap items in. This roll looks a bit skimpy."
+ w_class = WEIGHT_CLASS_SMALL
+ amount = 5
+
/obj/item/c_tube
name = "cardboard tube"
desc = "A tube... of cardboard."
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index 79280faca6..a2ab0c2ff4 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -1456,3 +1456,18 @@
/obj/item/storage/box/strange_seeds_5pack/PopulateContents()
for(var/i in 1 to 5)
new /obj/item/seeds/random(src)
+
+/obj/item/storage/box/shipping
+ name = "box of shipping supplies"
+ desc = "Contains several scanners and labelers for shipping things. Wrapping Paper not included."
+ illustration = "shipping"
+
+/obj/item/storage/box/shipping/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/destTagger=1,\
+ /obj/item/sales_tagger=1,\
+ /obj/item/export_scanner=1,\
+ /obj/item/stack/packageWrap/small=2,\
+ /obj/item/stack/wrapping_paper/small=1
+ )
+ generate_items_inside(items_inside,src)
diff --git a/code/modules/cargo/bounties/assistant.dm b/code/modules/cargo/bounties/assistant.dm
index 35c7c787b6..a009ef20af 100644
--- a/code/modules/cargo/bounties/assistant.dm
+++ b/code/modules/cargo/bounties/assistant.dm
@@ -101,22 +101,22 @@
required_count = 15
wanted_types = list(/obj/item/shard)
-/*
/datum/bounty/item/assistant/comfy_chair
name = "Comfortable Chairs"
description = "Commander Pat is unhappy with his chair. He claims it hurts his back. Ship some alternatives out to humor him."
reward = 900
required_count = 5
wanted_types = list(/obj/structure/chair/comfy)
-*/
- /datum/bounty/item/assistant/geranium
+/*
+/datum/bounty/item/assistant/geranium
name = "Geraniums"
description = "Commander Zot has the hots for Commander Zena. Send a shipment of geraniums - her favorite flower - and he'll happily reward you."
reward = 1000
required_count = 3
wanted_types = list(/obj/item/reagent_containers/food/snacks/grown/poppy/geranium)
include_subtypes = FALSE
+*/
/datum/bounty/item/assistant/poppy
name = "Poppies"
diff --git a/code/modules/cargo/exports.dm b/code/modules/cargo/exports.dm
index e6ed245294..66713a3053 100644
--- a/code/modules/cargo/exports.dm
+++ b/code/modules/cargo/exports.dm
@@ -33,6 +33,8 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they
if(!GLOB.exports_list.len)
setupExports()
+ var/profit_ratio = 1 //Percentage that gets sent to the seller, rest goes to cargo.
+
var/list/contents = AM.GetAllContents()
var/datum/export_report/report = external_report
@@ -47,7 +49,7 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they
if(!E)
continue
if(E.applies_to(thing, allowed_categories, apply_elastic))
- sold = E.sell_object(thing, report, dry_run, allowed_categories , apply_elastic)
+ sold = E.sell_object(thing, report, dry_run, allowed_categories , apply_elastic, profit_ratio)
report.exported_atoms += " [thing.name]"
break
if(thing.reagents?.value_multiplier)
@@ -138,9 +140,19 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they
var/the_cost = get_cost(O, allowed_categories , apply_elastic)
var/amount = get_amount(O)
+ var/profit_ratio = 0
+
if(amount <=0 || the_cost <=0)
return FALSE
+ if(dry_run == FALSE)
+ if(SEND_SIGNAL(O, COMSIG_ITEM_SOLD, item_value = get_cost(O, allowed_categories , apply_elastic)) & COMSIG_ITEM_SPLIT_VALUE)
+ profit_ratio = SEND_SIGNAL(O, COMSIG_ITEM_SPLIT_PROFIT)
+ the_cost = the_cost*((100-profit_ratio)/100)
+ else
+ profit_ratio = SEND_SIGNAL(O, COMSIG_ITEM_SPLIT_PROFIT)
+ the_cost = the_cost*((100-profit_ratio)/100)
+
report.total_value[src] += the_cost
if(istype(O, /datum/export/material))
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index 008e093b74..28176e5ed4 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -7,9 +7,15 @@
mouse_drag_pointer = MOUSE_ACTIVE_POINTER
var/giftwrapped = FALSE
var/sortTag = 0
+ var/obj/item/barcode/sticker
/obj/structure/bigDelivery/interact(mob/user)
+ to_chat(user, "You start to unwrap the package...")
+ if(!do_after(user, 15, target = user))
+ return
playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ new /obj/effect/decal/cleanable/wrapping(get_turf(user))
+ unwrap_contents()
qdel(src)
/obj/structure/bigDelivery/Destroy()
@@ -18,6 +24,11 @@
AM.forceMove(T)
return ..()
+/obj/structure/bigDelivery/examine(mob/user)
+ . = ..()
+ if(sticker)
+ . += "There's a barcode attached to the side."
+
/obj/structure/bigDelivery/contents_explosion(severity, target, origin)
for(var/atom/movable/AM in contents)
AM.ex_act(severity, target, origin)
@@ -53,6 +64,47 @@
icon_state = "gift[icon_state]"
else
to_chat(user, "You need more paper!")
+ else if(istype(W, /obj/item/sales_tagger))
+ var/obj/item/sales_tagger/tagger = W
+ if(sticker)
+ to_chat(user, "This package already has a barcode attached!")
+ return
+ if(!(tagger.payments_acc))
+ to_chat(user, "Swipe an ID on [tagger] first!")
+ return
+ if(tagger.paper_count <= 0)
+ to_chat(user, "[tagger] is out of paper!")
+ return
+ user.visible_message("[user] attaches a barcode to [src].", "You attach a barcode to [src].")
+ tagger.paper_count -= 1
+ sticker = new /obj/item/barcode(src)
+ sticker.payments_acc = tagger.payments_acc //new tag gets the tagger's current account.
+ sticker.percent_cut = tagger.percent_cut //same, but for the percentage taken.
+
+ var/list/wrap_contents = src.GetAllContents()
+ for(var/obj/I in wrap_contents)
+ I.AddComponent(/datum/component/pricetag, sticker.payments_acc, tagger.percent_cut)
+ var/overlaystring = "[icon_state]_tag"
+ if(giftwrapped)
+ overlaystring = copytext(overlaystring, 5)
+ add_overlay(overlaystring)
+ else if(istype(W, /obj/item/barcode))
+ var/obj/item/barcode/stickerA = W
+ if(sticker)
+ to_chat(user, "This package already has a barcode attached!")
+ return
+ if(!(stickerA.payments_acc))
+ to_chat(user, "This barcode seems to be invalid. Guess it's trash now.")
+ return
+ if(!user.transferItemToLoc(W, src))
+ to_chat(user, "For some reason, you can't attach [W]!")
+ return
+ sticker = stickerA
+ var/overlaystring = "[icon_state]_tag"
+ if(giftwrapped)
+ overlaystring = copytext_char(overlaystring, 5) //5 == length("gift") + 1
+ add_overlay(overlaystring)
+
else
return ..()
@@ -68,11 +120,18 @@
to_chat(user, "You successfully removed [O]'s wrapping !")
O.forceMove(loc)
playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ new /obj/effect/decal/cleanable/wrapping(get_turf(user))
+ unwrap_contents()
qdel(src)
else
if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
to_chat(user, "You fail to remove [O]'s wrapping!")
+/obj/structure/bigDelivery/proc/unwrap_contents()
+ if(!sticker)
+ return
+ for(var/obj/I in src.GetAllContents())
+ SEND_SIGNAL(I, COMSIG_STRUCTURE_UNWRAPPED)
/obj/item/smallDelivery
name = "parcel"
@@ -81,17 +140,23 @@
icon_state = "deliverypackage3"
var/giftwrapped = 0
var/sortTag = 0
+ var/obj/item/barcode/sticker
/obj/item/smallDelivery/contents_explosion(severity, target, origin)
for(var/atom/movable/AM in contents)
AM.ex_act(severity, target, origin)
/obj/item/smallDelivery/attack_self(mob/user)
+ to_chat(user, "You start to unwrap the package...")
+ if(!do_after(user, 15, target = user))
+ return
user.temporarilyRemoveItemFromInventory(src, TRUE)
+ unwrap_contents()
for(var/X in contents)
var/atom/movable/AM = X
user.put_in_hands(AM)
playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ new /obj/effect/decal/cleanable/wrapping(get_turf(user))
qdel(src)
/obj/item/smallDelivery/attack_self_tk(mob/user)
@@ -106,6 +171,8 @@
var/atom/movable/AM = X
AM.forceMove(src.loc)
playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ new /obj/effect/decal/cleanable/wrapping(get_turf(user))
+ unwrap_contents()
qdel(src)
/obj/item/smallDelivery/attackby(obj/item/W, mob/user, params)
@@ -140,6 +207,59 @@
else
to_chat(user, "You need more paper!")
+ else if(istype(W, /obj/item/sales_tagger))
+ var/obj/item/sales_tagger/tagger = W
+ if(sticker)
+ to_chat(user, "This package already has a barcode attached!")
+ return
+ if(!(tagger.payments_acc))
+ to_chat(user, "Swipe an ID on [tagger] first!")
+ return
+ if(tagger.paper_count <= 0)
+ to_chat(user, "[tagger] is out of paper!")
+ return
+ user.visible_message("[user] attaches a barcode to [src].", "You attach a barcode to [src].")
+ tagger.paper_count -= 1
+ sticker = new /obj/item/barcode(src)
+ sticker.payments_acc = tagger.payments_acc //new tag gets the tagger's current account.
+ sticker.percent_cut = tagger.percent_cut //as above, as before.
+
+ var/list/wrap_contents = src.GetAllContents()
+ for(var/obj/I in wrap_contents)
+ I.AddComponent(/datum/component/pricetag, sticker.payments_acc, tagger.percent_cut)
+ var/overlaystring = "[icon_state]_tag"
+ if(giftwrapped)
+ overlaystring = copytext(overlaystring, 5)
+ add_overlay(overlaystring)
+
+ else if(istype(W, /obj/item/barcode))
+ var/obj/item/barcode/stickerA = W
+ if(sticker)
+ to_chat(user, "This package already has a barcode attached!")
+ return
+ if(!(stickerA.payments_acc))
+ to_chat(user, "This barcode seems to be invalid. Guess it's trash now.")
+ return
+ if(!user.transferItemToLoc(W, src))
+ to_chat(user, "For some reason, you can't attach [W]!")
+ return
+ sticker = stickerA
+ var/overlaystring = "[icon_state]_tag"
+ if(giftwrapped)
+ overlaystring = copytext_char(overlaystring, 5) //5 == length("gift") + 1
+ add_overlay(overlaystring)
+
+/obj/item/smallDelivery/proc/unwrap_contents()
+ if(!sticker)
+ return
+ for(var/obj/I in src.GetAllContents())
+ SEND_SIGNAL(I, COMSIG_ITEM_UNWRAPPED)
+
+/obj/item/smallDelivery/examine(mob/user)
+ . = ..()
+ if(sticker)
+ . += "There's a barcode attached to the side."
+
/obj/item/destTagger
name = "destination tagger"
@@ -194,3 +314,90 @@
var/n = text2num(href_list["nextTag"])
currTag = n
openwindow(usr)
+
+/obj/item/sales_tagger
+ name = "sales tagger"
+ desc = "A scanner that lets you tag wrapped items for sale, splitting the profit between you and cargo. Ctrl-Click to clear the registered account."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "salestagger"
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ w_class = WEIGHT_CLASS_TINY
+ slot_flags = ITEM_SLOT_BELT
+ ///The account which is recieving the split profits.
+ var/datum/bank_account/payments_acc = null
+ var/paper_count = 10
+ var/max_paper_count = 20
+ ///Details the percentage the scanned account recieves off the final sale.
+ var/percent_cut = 20
+
+/obj/item/sales_tagger/examine(mob/user)
+ . = ..()
+ . += "[src] has [paper_count]/[max_paper_count] available barcodes. Refill with paper."
+ . += "Profit split on sale is currently set to [percent_cut]%."
+
+/obj/item/sales_tagger/attackby(obj/item/I, mob/living/user, params)
+ . = ..()
+ if(istype(I, /obj/item/card/id))
+ var/obj/item/card/id/potential_acc = I
+ if(potential_acc.registered_account)
+ payments_acc = potential_acc.registered_account
+ playsound(src, 'sound/machines/ping.ogg', 40, TRUE)
+ to_chat(user, "[src] registers the ID card. Tag a wrapped item to create a barcode.")
+ else if(!potential_acc.registered_account)
+ to_chat(user, "This ID card has no account registered!")
+ return
+ else if(payments_acc != potential_acc.registered_account)
+ to_chat(user, "ID card already registered.")
+ if(istype(I, /obj/item/paper))
+ if (!(paper_count >= max_paper_count))
+ paper_count += 10
+ qdel(I)
+ if (paper_count >= max_paper_count)
+ paper_count = max_paper_count
+ to_chat(user, "[src]'s paper supply is now full.")
+ return
+ to_chat(user, "You refill [src]'s paper supply, you have [paper_count] left.")
+ return
+ else
+ to_chat(user, "[src]'s paper supply is full.")
+ return
+
+/obj/item/sales_tagger/attack_self(mob/user)
+ . = ..()
+ if(paper_count <= 0)
+ to_chat(user, "You're out of paper!'.")
+ return
+ if(!payments_acc)
+ to_chat(user, "You need to swipe [src] with an ID card first.")
+ return
+ paper_count -= 1
+ playsound(src, 'sound/machines/click.ogg', 40, TRUE)
+ to_chat(user, "You print a new barcode.")
+ var/obj/item/barcode/new_barcode = new /obj/item/barcode(src)
+ new_barcode.payments_acc = payments_acc //The sticker gets the scanner's registered account.
+ user.put_in_hands(new_barcode)
+
+/obj/item/sales_tagger/CtrlClick(mob/user)
+ . = ..()
+ payments_acc = null
+ to_chat(user, "You clear the registered account.")
+
+/obj/item/sales_tagger/AltClick(mob/user)
+ . = ..()
+ var/potential_cut = input("How much would you like to payout to the registered card?","Percentage Profit") as num|null
+ if(!potential_cut)
+ percent_cut = 50
+ percent_cut = clamp(round(potential_cut, 1), 1, 50)
+ to_chat(user, "[percent_cut]% profit will be recieved if a package with a barcode is sold.")
+
+/obj/item/barcode
+ name = "Barcode tag"
+ desc = "A tiny tag, associated with a crewmember's account. Attach to a wrapped item to give that account a portion of the wrapped item's profit."
+ icon = 'icons/obj/bureaucracy.dmi'
+ icon_state = "barcode"
+ w_class = WEIGHT_CLASS_TINY
+ ///All values inheirited from the sales tagger it came from.
+ var/datum/bank_account/payments_acc = null
+ var/percent_cut = 5
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_electronics.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_electronics.dm
index 78b0a365c5..fea67a921f 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_electronics.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_electronics.dm
@@ -90,3 +90,12 @@
materials = list(/datum/material/iron = 2010, /datum/material/glass = 5)
build_path = /obj/item/electronics/electrochromatic_kit
category = list("initial", "Electronics")
+
+/datum/design/salestagger
+ name = "Sales Tagger"
+ id = "salestagger"
+ build_type = AUTOLATHE | PROTOLATHE
+ materials = list(/datum/material/iron = 700, /datum/material/glass = 200)
+ build_path = /obj/item/sales_tagger
+ category = list("initial", "Electronics")
+ departmental_flags = DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SERVICE
diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm
index 8c91c09abb..92eaa2bd41 100644
--- a/code/modules/research/techweb/_techweb_node.dm
+++ b/code/modules/research/techweb/_techweb_node.dm
@@ -106,4 +106,4 @@
"destructive_analyzer", "circuit_imprinter", "experimentor", "rdconsole", "bepis", "design_disk", "tech_disk", "rdserver", "rdservercontrol", "mechfab", "paystand",
"space_heater", "beaker", "large_beaker", "xlarge_beaker", "bucket", "hypovial", "large_hypovial", "syringe", "pillbottle",
"sec_shellclip", "sec_beanbag", "sec_rshot", "sec_bshot", "sec_slug", "sec_islug", "sec_dart", "sec_38", "sec_38lethal",
- "rglass","plasteel","plastitanium","plasmaglass","plasmareinforcedglass","titaniumglass","plastitaniumglass")
+ "rglass","plasteel","plastitanium","plasmaglass","plasmareinforcedglass","titaniumglass","plastitaniumglass", "salestagger")
diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi
index 33cd46d15f..36402ad2d5 100644
Binary files a/icons/obj/computer.dmi and b/icons/obj/computer.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index 5254b53d2b..62fe5bbf92 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index 2c926ef639..27ad84b1a4 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 0cbed7f72e..90f21bf649 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -562,6 +562,7 @@
#include "code\datums\components\paintable.dm"
#include "code\datums\components\pellet_cloud.dm"
#include "code\datums\components\phantomthief.dm"
+#include "code\datums\components\pricetag.dm"
#include "code\datums\components\rad_insulation.dm"
#include "code\datums\components\radioactive.dm"
#include "code\datums\components\remote_materials.dm"