From 36e2c1c1c7d404ea30706751dcdf1e4b50ba4d5b Mon Sep 17 00:00:00 2001
From: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Date: Tue, 11 Jan 2022 15:22:52 -0600
Subject: [PATCH] Refactors pricetag component: no getcomponent, no ugly
signals, fixes cubes a bit (#63954)
Refactors the pricetag component
Removes a getcomponent for the pricetag component in cube export handling (replaced with inherit component behavior)
Removes some nasty signals which were effectively just send signal, get 1
Deletes the internal radio within bounty cubes from before exporting
Disallows bounty cubes from being barcoded with TRAIT_NO_BARCODES
Prevents bounty cube pricetag component from being deleted by unwrapping
Closes #63921 technically
---
code/__DEFINES/dcs/signals/signals_object.dm | 9 +-
code/__DEFINES/traits.dm | 2 +
code/datums/components/pricetag.dm | 114 ++++++++++++++-----
code/game/machinery/civilian_bounties.dm | 21 +++-
code/modules/cargo/export_scanner.dm | 9 +-
code/modules/cargo/exports.dm | 42 ++++---
code/modules/recycling/sortingmachinery.dm | 28 +++--
7 files changed, 156 insertions(+), 69 deletions(-)
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 9a9c3db887f..868cc08a43e 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -197,13 +197,18 @@
#define COMSIG_OBJ_ATTEMPT_CHARGE_CHANGE "obj_attempt_simple_charge_change"
// /obj/item signals for economy
+///called before an item is sold by the exports system.
+#define COMSIG_ITEM_PRE_EXPORT "item_pre_sold"
+ /// Stops the export from calling sell_object() on the item, so you can handle it manually.
+ #define COMPONENT_STOP_EXPORT (1<<0)
///called when an item is sold by the exports subsystem
-#define COMSIG_ITEM_SOLD "item_sold"
+#define COMSIG_ITEM_EXPORTED "item_sold"
+ /// Stops the export from adding the export information to the report, so you can handle it manually.
+ #define COMPONENT_STOP_EXPORT_REPORT (1<<0)
///called when a wrapped up structure is opened by hand
#define COMSIG_STRUCTURE_UNWRAPPED "structure_unwrapped"
///called when a wrapped up item is opened by hand
#define COMSIG_ITEM_UNWRAPPED "item_unwrapped"
- #define COMSIG_ITEM_SPLIT_VALUE (1<<0)
///called when getting the item's exact ratio for cargo's profit.
#define COMSIG_ITEM_SPLIT_PROFIT "item_split_profits"
///called when getting the item's exact ratio for cargo's profit, without selling the item.
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index e479d3a0a00..b984f55c63b 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -500,6 +500,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NO_IMMOBILIZE "no_immobilize"
/// Prevents stripping this equipment
#define TRAIT_NO_STRIP "no_strip"
+/// Disallows this item from being pricetagged with a barcode
+#define TRAIT_NO_BARCODES "no_barcode"
//quirk traits
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
diff --git a/code/datums/components/pricetag.dm b/code/datums/components/pricetag.dm
index bd29c3199c0..0a5a37c3367 100644
--- a/code/datums/components/pricetag.dm
+++ b/code/datums/components/pricetag.dm
@@ -1,40 +1,102 @@
+/*
+ * Pricetag component.
+ *
+ * Used when exporting items via the cargo system.
+ * Gives a cut of the profit to one or multiple bank accounts.
+ */
/datum/component/pricetag
- ///Payee gets 100% of the value if no ratio has been set.
- var/default_profit_ratio = 1
- ///List of bank accounts this pricetag pays out to. Format is payees[bank_account] = profit_ratio.
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ /// Whether we qdel ourself when our parent is unwrapped or not.
+ var/delete_on_unwrap = TRUE
+ /// List of bank accounts this pricetag pays out to. Format is payees[bank_account] = profit_ratio.
var/list/payees = list()
-/datum/component/pricetag/Initialize(_owner,_profit_ratio)
+/datum/component/pricetag/Initialize(pay_to_account, profit_ratio = 1, delete_on_unwrap = TRUE)
if(!isobj(parent)) //Has to account for both objects and sellable structures like crates.
return COMPONENT_INCOMPATIBLE
- if(_profit_ratio)
- payees[_owner] = _profit_ratio
- else
- payees[_owner] = default_profit_ratio
+ if(isnull(pay_to_account))
+ stack_trace("[type] component was added to something without a pay_to_account!")
+ return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, list(COMSIG_ITEM_SOLD), .proc/split_profit)
- RegisterSignal(parent, list(COMSIG_STRUCTURE_UNWRAPPED, COMSIG_ITEM_UNWRAPPED), .proc/Unwrapped)
- RegisterSignal(parent, list(COMSIG_ITEM_SPLIT_PROFIT, COMSIG_ITEM_SPLIT_PROFIT_DRY), .proc/return_ratio)
+ payees[pay_to_account] = profit_ratio
+ src.delete_on_unwrap = delete_on_unwrap
-/datum/component/pricetag/proc/Unwrapped()
+/datum/component/pricetag/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ITEM_EXPORTED, .proc/on_parent_sold)
+ // Register this regardless of delete_on_unwrap because it could change by inherited components.
+ RegisterSignal(parent, list(COMSIG_STRUCTURE_UNWRAPPED, COMSIG_ITEM_UNWRAPPED), .proc/on_parent_unwrap)
+
+/datum/component/pricetag/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_ITEM_EXPORTED,
+ COMSIG_STRUCTURE_UNWRAPPED,
+ COMSIG_ITEM_UNWRAPPED,
+ ))
+
+/*
+ * Inheriting an incoming / new version of price tag:
+ *
+ * If the account passed in the incoming version is already in our list,
+ * only override it if the ratio is better for the payee
+ *
+ * If the account passed in the incoming version is not in our list, add it like normal.
+ *
+ * If the incoming version shouldn't delete when unwrapped,
+ * our version shouldn't either.
+ * We don't care about the other way around
+ * (Don't go from non-deleting to deleting)
+ */
+/datum/component/pricetag/InheritComponent(datum/component/pricetag/new_comp, i_am_original, pay_to_account, profit_ratio = 1, delete_on_unwrap = TRUE)
+ if(!isnull(payees[pay_to_account]) && payees[pay_to_account] >= profit_ratio) // They're already getting a better ratio, don't scam them
+ return
+
+ payees[pay_to_account] = profit_ratio
+ if(!delete_on_unwrap)
+ src.delete_on_unwrap = delete_on_unwrap
+
+
+/*
+ * Signal proc for [COMSIG_STRUCTURE_UNWRAPPED] and [COMSIG_ITEM_UNWRAPPED].
+ *
+ * Once it leaves its wrapped container,
+ * the parent should loses its pricetag component
+ * (if delete_on_unwrap is TRUE)
+ */
+/datum/component/pricetag/proc/on_parent_unwrap(obj/source)
SIGNAL_HANDLER
- qdel(src) //Once it leaves it's wrapped container, the object in question should lose it's pricetag component.
+ if(!delete_on_unwrap)
+ return
-/datum/component/pricetag/proc/split_profit(item_value)
+ qdel(src)
+
+/*
+ * Signal proc for [COMSIG_ITEM_EXPORTED].
+ *
+ * Pays out money to everyone in the payees list.
+ */
+/datum/component/pricetag/proc/on_parent_sold(obj/source, datum/export/export, datum/export_report/report, item_value)
SIGNAL_HANDLER
- var/price = item_value
- if(price)
- for(var/datum/bank_account/payee in payees)
- var/profit_ratio = payees[payee]
- var/adjusted_value = price * profit_ratio
- var/datum/bank_account/bank_account = payee
- bank_account.adjust_money(adjusted_value)
- bank_account.bank_card_talk("Sale of [parent] recorded. [adjusted_value] credits added to account.")
- return TRUE
+ if(!isnum(item_value))
+ return
-/datum/component/pricetag/proc/return_ratio()
- SIGNAL_HANDLER
- return default_profit_ratio
+ // Gotta see how much money we've lost by the end of things.
+ var/overall_item_price = item_value
+
+ for(var/datum/bank_account/payee as anything in payees)
+ // Every payee with a ratio gets a cut based on the item's total value
+ var/payee_cut = round(item_value * payees[payee])
+ // And of course, the cut is removed from what cargo gets. (But not below zero, just in case)
+ overall_item_price = max(0, overall_item_price - payee_cut)
+
+ payee.adjust_money(payee_cut)
+ payee.bank_card_talk("Sale of [source] recorded. [payee_cut] credits added to account.")
+
+ // Update the report with the modified final price
+ report.total_value[export] += overall_item_price
+ report.total_amount[export] += export.get_amount(source) * export.amount_report_multiplier
+
+ // And ensure we don't double-add to the report
+ return COMPONENT_STOP_EXPORT_REPORT
diff --git a/code/game/machinery/civilian_bounties.dm b/code/game/machinery/civilian_bounties.dm
index e4cbeda0e57..add748fc4dd 100644
--- a/code/game/machinery/civilian_bounties.dm
+++ b/code/game/machinery/civilian_bounties.dm
@@ -275,14 +275,17 @@
/obj/item/bounty_cube/Initialize(mapload)
. = ..()
+ ADD_TRAIT(src, TRAIT_NO_BARCODES, INNATE_TRAIT) // Don't allow anyone to override our pricetag component with a barcode
radio = new(src)
radio.keyslot = new radio_key
radio.set_listening(FALSE)
radio.recalculateChannels()
+ RegisterSignal(radio, COMSIG_ITEM_PRE_EXPORT, .proc/on_export)
/obj/item/bounty_cube/Destroy()
+ UnregisterSignal(radio, COMSIG_ITEM_PRE_EXPORT)
QDEL_NULL(radio)
- . = ..()
+ return ..()
/obj/item/bounty_cube/examine()
. = ..()
@@ -291,6 +294,20 @@
if(handler_tip && !bounty_handler_account)
. += span_notice("Scan this in the cargo shuttle with an export scanner to register your bank account for the [bounty_value * handler_tip] credit handling tip.")
+/*
+ * Signal proc for [COMSIG_ITEM_EXPORTED], registered on the internal radio.
+ *
+ * Deletes the internal radio before being exported,
+ * to stop it from bring counted as an export.
+ *
+ * No 4 free credits for you!
+ */
+/obj/item/bounty_cube/proc/on_export(datum/source)
+ SIGNAL_HANDLER
+
+ QDEL_NULL(radio)
+ return COMPONENT_STOP_EXPORT // stops the radio from exporting, not the cube
+
/obj/item/bounty_cube/process(delta_time)
//if our nag cooldown has finished and we aren't on Centcom or in transit, then nag
if(COOLDOWN_FINISHED(src, next_nag_time) && !is_centcom_level(z) && !is_reserved_level(z))
@@ -320,7 +337,7 @@
bounty_holder_account = holder_id.registered_account
name = "\improper [bounty_value] cr [name]"
desc += " The sales tag indicates it was [bounty_holder] ([bounty_holder_job])'s reward for completing the [bounty_name] bounty."
- AddComponent(/datum/component/pricetag, holder_id.registered_account, holder_cut)
+ AddComponent(/datum/component/pricetag, holder_id.registered_account, holder_cut, FALSE)
AddComponent(/datum/component/gps, "[src]")
START_PROCESSING(SSobj, src)
COOLDOWN_START(src, next_nag_time, nag_cooldown)
diff --git a/code/modules/cargo/export_scanner.dm b/code/modules/cargo/export_scanner.dm
index 86d0a0dbae2..26d0422c7f8 100644
--- a/code/modules/cargo/export_scanner.dm
+++ b/code/modules/cargo/export_scanner.dm
@@ -29,6 +29,7 @@
var/mob/living/carbon/human/scan_human = user
if(istype(O, /obj/item/bounty_cube))
var/obj/item/bounty_cube/cube = O
+ var/datum/bank_account/scanner_account = scan_human.get_bank_account()
if(!istype(get_area(cube), /area/shuttle/supply))
to_chat(user, span_warning("Shuttle placement not detected. Handling tip not registered."))
@@ -36,12 +37,10 @@
else if(cube.bounty_handler_account)
to_chat(user, span_warning("Bank account for handling tip already registered!"))
- else if(scan_human.get_bank_account() && cube.GetComponent(/datum/component/pricetag))
- var/datum/component/pricetag/pricetag = cube.GetComponent(/datum/component/pricetag)
-
- cube.bounty_handler_account = scan_human.get_bank_account()
- pricetag.payees[cube.bounty_handler_account] += cube.handler_tip
+ else if(scanner_account)
+ cube.AddComponent(/datum/component/pricetag, scanner_account, cube.handler_tip, FALSE)
+ cube.bounty_handler_account = scanner_account
cube.bounty_handler_account.bank_card_talk("Bank account for [price ? "[price * cube.handler_tip] credit " : ""]handling tip successfully registered.")
if(cube.bounty_holder_account != cube.bounty_handler_account) //No need to send a tracking update to the person scanning it
diff --git a/code/modules/cargo/exports.dm b/code/modules/cargo/exports.dm
index 3ffec5f9f0b..e0f9f028deb 100644
--- a/code/modules/cargo/exports.dm
+++ b/code/modules/cargo/exports.dm
@@ -26,12 +26,10 @@ Then the player gets the profit from selling his own wasted time.
var/list/total_value = list() //export instance => total value of sold objects
// external_report works as "transaction" object, pass same one in if you're doing more than one export in single go
-/proc/export_item_and_contents(atom/movable/AM, apply_elastic = TRUE, delete_unsold = TRUE, dry_run=FALSE, datum/export_report/external_report)
+/proc/export_item_and_contents(atom/movable/AM, apply_elastic = TRUE, delete_unsold = TRUE, dry_run = FALSE, datum/export_report/external_report)
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.get_all_contents()
var/datum/export_report/report = external_report
@@ -45,12 +43,14 @@ Then the player gets the profit from selling his own wasted time.
var/sold = FALSE
for(var/datum/export/export as anything in GLOB.exports_list)
if(export.applies_to(thing, apply_elastic))
- sold = export.sell_object(thing, report, dry_run, apply_elastic, profit_ratio)
+ if(!dry_run && (SEND_SIGNAL(thing, COMSIG_ITEM_PRE_EXPORT) & COMPONENT_STOP_EXPORT))
+ break
+ sold = export.sell_object(thing, report, dry_run, apply_elastic)
report.exported_atoms += " [thing.name]"
break
if(!dry_run && (sold || delete_unsold))
if(ismob(thing))
- thing.investigate_log("deleted through cargo export",INVESTIGATE_CARGO)
+ thing.investigate_log("deleted through cargo export", INVESTIGATE_CARGO)
to_delete += thing
for(var/atom/movable/thing as anything in to_delete)
@@ -136,31 +136,29 @@ Then the player gets the profit from selling his own wasted time.
* get_cost, get_amount and applies_to do not neccesary mean a successful sale.
*
*/
-/datum/export/proc/sell_object(obj/O, datum/export_report/report, dry_run = TRUE, apply_elastic = TRUE)
+/datum/export/proc/sell_object(obj/sold_item, datum/export_report/report, dry_run = TRUE, apply_elastic = TRUE)
///This is the value of the object, as derived from export datums.
- var/the_cost = get_cost(O, apply_elastic)
+ var/export_value = get_cost(sold_item, apply_elastic)
///Quantity of the object in question.
- var/amount = get_amount(O)
- ///Utilized in the pricetag component. Splits the object's profit when it has a pricetag by the specified amount.
- var/profit_ratio = 0
+ var/export_amount = get_amount(sold_item)
- if(amount <=0 || (the_cost <=0 && !allow_negative_cost))
+ if(export_amount <= 0 || (export_value <= 0 && !allow_negative_cost))
return FALSE
- if(dry_run == FALSE)
- if(SEND_SIGNAL(O, COMSIG_ITEM_SOLD, item_value = get_cost(O, apply_elastic)) & COMSIG_ITEM_SPLIT_VALUE)
- profit_ratio = SEND_SIGNAL(O, COMSIG_ITEM_SPLIT_PROFIT_DRY)
- the_cost = the_cost * ((100 - profit_ratio) * 0.01)
- else
- profit_ratio = SEND_SIGNAL(O, COMSIG_ITEM_SPLIT_PROFIT)
- the_cost = the_cost * ((100 - profit_ratio) * 0.01)
- report.total_value[src] += the_cost
- report.total_amount[src] += amount*amount_report_multiplier
+ // If we're not doing a dry run, send COMSIG_ITEM_EXPORTED to the sold item
+ var/export_result
+ if(!dry_run)
+ export_result = SEND_SIGNAL(sold_item, COMSIG_ITEM_EXPORTED, src, report, export_value)
+
+ // If the signal handled adding it to the report, don't do it now
+ if(!(export_result & COMPONENT_STOP_EXPORT_REPORT))
+ report.total_value[src] += export_value
+ report.total_amount[src] += export_amount * amount_report_multiplier
if(!dry_run)
if(apply_elastic)
- cost *= NUM_E**(-1*k_elasticity*amount) //marginal cost modifier
- SSblackbox.record_feedback("nested tally", "export_sold_cost", 1, list("[O.type]", "[the_cost]"))
+ cost *= NUM_E**(-1 * k_elasticity * export_amount) //marginal cost modifier
+ SSblackbox.record_feedback("nested tally", "export_sold_cost", 1, list("[sold_item.type]", "[export_value]"))
return TRUE
// Total printout for the cargo console.
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index be68673e532..62d79074ef6 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -114,9 +114,10 @@
sticker.payments_acc = tagger.payments_acc //new tag gets the tagger's current account.
sticker.cut_multiplier = tagger.cut_multiplier //same, but for the percentage taken.
- var/list/wrap_contents = src.get_all_contents()
- for(var/obj/I in wrap_contents)
- I.AddComponent(/datum/component/pricetag, sticker.payments_acc, tagger.cut_multiplier)
+ for(var/obj/wrapped_item in get_all_contents())
+ if(HAS_TRAIT(wrapped_item, TRAIT_NO_BARCODES))
+ continue
+ wrapped_item.AddComponent(/datum/component/pricetag, sticker.payments_acc, tagger.cut_multiplier)
var/overlaystring = "[icon_state]_tag"
if(giftwrapped)
overlaystring = copytext(overlaystring, 5)
@@ -133,9 +134,10 @@
to_chat(user, span_warning("For some reason, you can't attach [W]!"))
return
sticker = stickerA
- var/list/wrap_contents = src.get_all_contents()
- for(var/obj/I in wrap_contents)
- I.AddComponent(/datum/component/pricetag, sticker.payments_acc, sticker.cut_multiplier)
+ for(var/obj/wrapped_item in get_all_contents())
+ if(HAS_TRAIT(wrapped_item, TRAIT_NO_BARCODES))
+ continue
+ wrapped_item.AddComponent(/datum/component/pricetag, sticker.payments_acc, sticker.cut_multiplier)
var/overlaystring = "[icon_state]_tag"
if(giftwrapped)
overlaystring = copytext_char(overlaystring, 5) //5 == length("gift") + 1
@@ -307,9 +309,10 @@
sticker.payments_acc = tagger.payments_acc //new tag gets the tagger's current account.
sticker.cut_multiplier = tagger.cut_multiplier //as above, as before.
- var/list/wrap_contents = src.get_all_contents()
- for(var/obj/I in wrap_contents)
- I.AddComponent(/datum/component/pricetag, sticker.payments_acc, tagger.cut_multiplier)
+ for(var/obj/wrapped_item in get_all_contents())
+ if(HAS_TRAIT(wrapped_item, TRAIT_NO_BARCODES))
+ continue
+ wrapped_item.AddComponent(/datum/component/pricetag, sticker.payments_acc, tagger.cut_multiplier)
var/overlaystring = "[icon_state]_tag"
if(giftwrapped)
overlaystring = copytext(overlaystring, 5)
@@ -327,9 +330,10 @@
to_chat(user, span_warning("For some reason, you can't attach [W]!"))
return
sticker = stickerA
- var/list/wrap_contents = src.get_all_contents()
- for(var/obj/I in wrap_contents)
- I.AddComponent(/datum/component/pricetag, sticker.payments_acc, sticker.cut_multiplier)
+ for(var/obj/wrapped_item in get_all_contents())
+ if(HAS_TRAIT(wrapped_item, TRAIT_NO_BARCODES))
+ continue
+ wrapped_item.AddComponent(/datum/component/pricetag, sticker.payments_acc, sticker.cut_multiplier)
var/overlaystring = "[icon_state]_tag"
if(giftwrapped)
overlaystring = copytext_char(overlaystring, 5) //5 == length("gift") + 1