Files
Bubberstation/code/modules/cargo/exports.dm
SkyratBot 85f883f22c [MIRROR] Export scanner no longer shows shipping manifest value + manifest error changes [MDB IGNORE] (#24478)
* Export scanner no longer shows shipping manifest value + manifest error changes (#78923)

## About The Pull Request

Adds a variable to exports called `scannable`. If you are trying to scan
it and its set to false, then it won't show the value. When the actual
sale is done, the value will still be added/subtracted.

I also reorganized the message the scanner sends because I didn't want
to put multiple inline if statements.

Lastly, made it so that locked crates and private order lockboxes can't
get the shipping manifest contents error by adding a new trait
`TRAIT_NO_MANIFEST_CONTENTS_ERROR`. For departmental orders, I just
entirely disabled the manifests having errors by setting it to
`MANIFEST_CAN_FAIL` to false.

This PR conflicts with #78911, sorry but that PR was what reminded me to
fix this bug.

## Why It's Good For The Game

Some may call it tedium but having to read paperwork is part of the
cargo experience. Since it is harder to determine the crates value,
there has been a few balance changes. The penalty for incorrect manifest
is half the crates price or 500 credits, whichever is less.

![image](https://github.com/tgstation/tgstation/assets/47338680/b7549d11-eb49-43a2-8bb2-70ca8817e04d)

Let me know if you have any suggestions for how the message is worded,
it might be too long.

Locked crates and private orders can no longer get the shipping manifest
contents error because it is basically impossible to get someone to tell
you all the contents of their purchase. It's only a 5% chance of being
wrong and if they're not in cargo they probably don't care about the
cargo budget.

## Changelog
🆑
balance: Export scanner no longer shows value of shipping manifests, now
you actually have to read them.
balance: Shipping manifest penalty is now only half crate cost as well
as capped to 500 credits.
balance: Shipping manifests for private orders or locked crates can no
longer have the incorrect contents error. Shipping manifests for
departmental orders can n longer have any error.
/🆑

---------

Co-authored-by: Jacquerel <hnevard@ gmail.com>
Co-authored-by: Ghom <42542238+Ghommie@ users.noreply.github.com>

* Export scanner no longer shows shipping manifest value + manifest error changes

---------

Co-authored-by: BlueMemesauce <47338680+BlueMemesauce@users.noreply.github.com>
Co-authored-by: Jacquerel <hnevard@ gmail.com>
Co-authored-by: Ghom <42542238+Ghommie@ users.noreply.github.com>
2023-10-20 22:48:13 -04:00

223 lines
8.3 KiB
Plaintext

/* How it works:
The shuttle arrives at CentCom dock and calls sell(), which recursively loops through all the shuttle contents that are unanchored.
Each object in the loop is checked for applies_to() of various export datums, except the invalid ones.
*/
/* The rule in figuring out item export cost:
Export cost of goods in the shipping crate must be always equal or lower than:
packcage cost - crate cost - manifest cost
Crate cost is 500cr for a regular plasteel crate and 100cr for a large wooden one. Manifest cost is always 200cr.
This is to avoid easy cargo points dupes.
Credit dupes that require a lot of manual work shouldn't be removed, unless they yield too much profit for too little work.
For example, if some player buys iron and glass sheets and uses them to make and sell reinforced glass:
100 glass + 50 iron-> 100 reinforced glass
1500cr -> 1600cr)
Then the player gets the profit from selling his own wasted time.
*/
// Simple holder datum to pass export results around
/datum/export_report
///names of atoms sold/deleted by export
var/list/exported_atoms = list()
///export instance => total count of sold objects of its type, only exists if any were sold
var/list/total_amount = list()
///export instance => total value of sold objects
var/list/total_value = list()
///set to false if any objects in a dry run were unscannable
var/all_contents_scannable = TRUE
/*
* Handles exporting a movable atom and its contents
* Arguments:
** apply_elastic: if the price will change based on amount sold, where applicable
** delete_unsold: if the items that were not sold should be deleted
** dry_run: if the item should be actually sold, or if its just a pirce test
** external_report: works as "transaction" object, pass same one in if you're doing more than one export in single go
** ignore_typecache: typecache containing types that should be completely ignored
*/
/proc/export_item_and_contents(atom/movable/exported_atom, apply_elastic = TRUE, delete_unsold = TRUE, dry_run = FALSE, datum/export_report/external_report, list/ignore_typecache)
if(!GLOB.exports_list.len)
setupExports()
var/list/contents = exported_atom.get_all_contents_ignoring(ignore_typecache)
var/datum/export_report/report = external_report
if(!report) //If we don't have any longer transaction going on
report = new
// We go backwards, so it'll be innermost objects sold first. We also make sure nothing is accidentally delete before everything is sold.
var/list/to_delete = list()
for(var/atom/movable/thing as anything in reverse_range(contents))
var/sold = FALSE
for(var/datum/export/export as anything in GLOB.exports_list)
if(export.applies_to(thing, apply_elastic))
if(!dry_run && (SEND_SIGNAL(thing, COMSIG_ITEM_PRE_EXPORT) & COMPONENT_STOP_EXPORT))
break
//Don't add value of unscannable items for a dry run report
if(dry_run && !export.scannable)
report.all_contents_scannable = FALSE
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)
to_delete += thing
for(var/atom/movable/thing as anything in to_delete)
if(!QDELETED(thing))
qdel(thing)
return report
/datum/export
/// Unit name. Only used in "Received [total_amount] [name]s [message]."
var/unit_name = ""
/// Message appended to the sale report
var/message = ""
/// Cost of item, in cargo credits. Must not allow for infinite price dupes, see above.
var/cost = 1
/// whether this export can have a negative impact on the cargo budget or not
var/allow_negative_cost = FALSE
/// coefficient used in marginal price calculation that roughly corresponds to the inverse of price elasticity, or "quantity elasticity"
var/k_elasticity = 1/30
/// The multiplier of the amount sold shown on the report. Useful for exports, such as material, which costs are not strictly per single units sold.
var/amount_report_multiplier = 1
/// Type of the exported object. If none, the export datum is considered base type.
var/list/export_types = list()
/// Set to FALSE to make the datum apply only to a strict type.
var/include_subtypes = TRUE
/// Types excluded from export
var/list/exclude_types = list()
/// Set to false if the cost shouldn't be determinable by an export scanner
var/scannable = TRUE
/// cost includes elasticity, this does not.
var/init_cost
/datum/export/New()
..()
SSprocessing.processing += src
init_cost = cost
export_types = typecacheof(export_types, only_root_path = !include_subtypes, ignore_root_path = FALSE)
exclude_types = typecacheof(exclude_types)
/datum/export/Destroy()
SSprocessing.processing -= src
return ..()
/datum/export/process()
..()
cost *= NUM_E**(k_elasticity * (1/30))
if(cost > init_cost)
cost = init_cost
/// Checks the cost. 0 cost items are skipped in export.
/datum/export/proc/get_cost(obj/exported_item, apply_elastic = TRUE)
var/amount = get_amount(exported_item)
if(apply_elastic)
if(k_elasticity != 0)
return round((cost/k_elasticity) * (1 - NUM_E**(-1 * k_elasticity * amount))) //anti-derivative of the marginal cost function
else
return round(cost * amount) //alternative form derived from L'Hopital to avoid division by 0
else
return round(init_cost * amount)
/*
* Checks the amount of exportable in object. Credits in the bill, sheets in the stack, etc.
* Usually acts as a multiplier for a cost, so item that has 0 amount will be skipped in export.
*/
/datum/export/proc/get_amount(obj/exported_item)
return 1
/// Checks if the item is fit for export datum.
/datum/export/proc/applies_to(obj/exported_item, apply_elastic = TRUE)
if(!is_type_in_typecache(exported_item, export_types))
return FALSE
if(include_subtypes && is_type_in_typecache(exported_item, exclude_types))
return FALSE
if(!get_cost(exported_item, apply_elastic))
return FALSE
if(exported_item.flags_1 & HOLOGRAM_1)
return FALSE
return TRUE
/**
* Calculates the exact export value of the object, while factoring in all the relivant variables.
*
* Called only once, when the object is actually sold by the datum.
* Adds item's cost and amount to the current export cycle.
* get_cost, get_amount and applies_to do not neccesary mean a successful sale.
*
*/
/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/export_value = get_cost(sold_item, apply_elastic)
///Quantity of the object in question.
var/export_amount = get_amount(sold_item)
if(export_amount <= 0 || (export_value <= 0 && !allow_negative_cost))
return FALSE
// 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 * 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.
* Called before the end of current export cycle.
* It must always return something if the datum adds or removes any credtis.
*/
/datum/export/proc/total_printout(datum/export_report/ex, notes = TRUE)
if(!ex.total_amount[src] || !ex.total_value[src])
return ""
var/total_value = ex.total_value[src]
var/total_amount = ex.total_amount[src]
var/msg = "[total_value] credits: Received [total_amount] "
if(total_value > 0)
msg = "+" + msg
if(unit_name)
msg += unit_name
if(total_amount > 1)
msg += "s"
if(message)
msg += " "
if(message)
msg += message
msg += "."
return msg
GLOBAL_LIST_EMPTY(exports_list)
/// Called when the global exports_list is empty, and sets it up.
/proc/setupExports()
for(var/subtype in subtypesof(/datum/export))
var/datum/export/export_datum = new subtype
if(export_datum.export_types && export_datum.export_types.len) // Exports without a type are invalid/base types
GLOB.exports_list += export_datum