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