Files
Bubberstation/code/modules/cargo/order.dm
necromanceranne cfd40aeef5 Imports and Contraband 2: Landfill Gacha Addiction (I put trash randomizers into cargo crates and called it content) (#76771)
## About The Pull Request

This is a followup on my previous PR involving cargo imports. I've made
a number of changes and new additions to cargo's imports and contraband.
But I've also changed how Smuggler's Satchels generate loot as well.

### New:
**Abandoned Crates:** You can now order in abandoned crates at a steep
price. Obviously these are just your standard fare abandoned crates, so
they've got a pretty long list of potential contents. Some great, some
utterly not worth the price you paid for the crate. Since they're quite
pricey, you can't order very many quickly. But this does allow cargo
techs the opportunity to spend the round solving puzzles to get
interesting loot.

**Dumpster of Maint Garbage:** This dumpster (similarly named to another
dumpster you can order) is filled with maint trash and potential maint
random spawns. This list is extensive enough that anything spawned in
this crate is likely to be mostly garbage. But, it is more affordable
than abandoned crates. I'd consider this the literally trashier version
of the abandoned crate.

**Shamber's Juice Eldritch Energy! Crate:** A crate with one can of the
extremely rare, short run edition of Shambler's Juice, Eldritch Energy!
This contains 5 units of eldritch essence. Heals heretics, hurts
everyone else! This is a VERY potent poison, but it also happens to be a
handy way for a Cargonian heretic to get a potent healing item without
having to waste knowledge points.

**Animal Hide Crate:** It's a cargo crate full of animal hides! This can
include fairly rare hides and some icebox creature hides as well, like
polar bear hides and wolf sinew. It's not too expensive, and mostly
spits out leather.

**Dreadnog Carton Crate:** A carton full of the worst eggnog imaginable.
This is just something to troll people with. Drink it and you'll get a
massive mood penalty. Dreadnog! May or may not contain space cola!

### Updated:

**Contraband Crate and Smuggler's Satchels:** This has had it's price
increased considerably. But, for good reason. It now contains some more
controlled random items, but also some more valuable contraband as well
as a very rare spawn. The upper end on his contraband can be extremely
valuable, but the majority of the items gained from contraband crates
will probably be either what you can get now (quite weak), or something
a bit more middle of the road (some more unique narcotics).

As a consequence, I've also passed this change onto smuggler's satchels,
as they used the crate to generate its contents. (it literally spawned
and then deleted a contraband crate to generate the contents hoo haa).

I've also increased the number of items in the smuggler's satchel. Since
the randomly spawned smuggler's satchels are quite a bit rarer now there
is only ever two spawned in the map, and spending actual TC on these is
somewhat counterproductive, I don't imagine this will be more beneficial
for scavenger hunters hoping for some interesting goodies.

**Russian Crate (the normal one):** The mosins now spawn in ancient gun
cases. These determine what kind of mosin you can get. 79% of the time,
you get the crap mosin. 20% of the time, you get a good mosin. And 1% of
the time, you get rations. This more tightly controls how many good
mosins are entering into the round and how much of a value purchase the
Russian crate actually is for getting ballistics. Since the process is
even more unlikely than before, it isn't necessarily as guaranteed that
you will get a good mosin. Hell, you might not even get a gun if you're
that unlucky.

**Shocktrooper Crate:** It now has an armor vest and helmet. So, not
only do you get some grenades, you get some protection as well. Since
this is the 'loud' crate, I felt it appropriate to make it slightly more
useful for enabling that.

**Special Ops Crate:** It now contains five mirage grenades and a
chameleon belt, and has had the survival knife improved to a
switchblade. This is probably the weakest of the two crates STILL, but
hopefully these make them a little more interesting and novel by giving
them pretty fun grenade to toy with.

## Why It's Good For The Game

My initial PR hoped to add in a few more interesting purchases for
cargo. I think currently cargo has a slight issue of not having enough
valuable or interesting uses for their money. I think it still has that
problem, but by including more unique crates that allow cargo to provide
some oddities into the round, that might slowly work itself out.

This PR hopes to provide another way to waste their money if they have
an excess amount. Landfill Trash Gambling. Spending it away on complete
junk, which I think is absolutely hilarious when it doesn't work out, as
it is soulful in its design. Definitely not inspired by my recent thrift
shop excursions this month buying and scrounging for furniture and
interesting clothing.

[Relevant](https://www.youtube.com/watch?v=QK8mJJJvaes)

Also, I wanted to buff some of the crates I introduced a bit last time,
and nerf the mosin production somewhat via a more controllable method
that I can actually adjust as necessary down the line.

## Changelog
🆑
fix: Stops manifest generation runtiming when a cargo crate is empty.
add: Abandoned crates are now available via cargo imports.
add: Dumpsters full of maintenance trash are now available via cargo
imports.
add: An ultra-rare can of Shambler's Juice is now available via cargo
imports.
add: Animal hides and leathers can be (unreliably) ordered via cargo
imports.
add: The Dreadnog has entered this realm. To consume, purchase it via
cargo imports.
balance: Contraband Crates (and as a consequence, smuggler's satchels)
now generate more varied goods. Mostly the same, but sometimes you get
something quite different or even valuable.
balance: Mosins generated via the Russian supply crate are a bit more
random, weighing more heavily towards bad mosins than good mosins.
balance: Buffed both the shocktrooper and special op crate. Shocktrooper
now has an armored helmet and vest, and special op now has 5 mirage
grenades and a chameleon belt. The survival knife in the special op
crate is now a switchblade.
/🆑
2023-07-15 16:27:39 +01:00

195 lines
7.0 KiB
Plaintext

/// The chance for a manifest or crate to be created with errors
#define MANIFEST_ERROR_CHANCE 5
// MANIFEST BITFLAGS
/// Determines if the station name will be incorrect on the manifest
#define MANIFEST_ERROR_NAME (1 << 0)
/// Determines if contents will be deleted from the manifest but still be present in the crate
#define MANIFEST_ERROR_CONTENTS (1 << 1)
/// Determines if contents will be deleted from the crate but still be present in the manifest
#define MANIFEST_ERROR_ITEM (1 << 2)
/obj/item/paper/fluff/jobs/cargo/manifest
var/order_cost = 0
var/order_id = 0
var/errors = 0
/obj/item/paper/fluff/jobs/cargo/manifest/Initialize(mapload, id, cost, manifest_can_fail = TRUE)
. = ..()
order_id = id
order_cost = cost
if(!manifest_can_fail)
return
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_NAME
investigate_log("Supply order #[order_id] generated a manifest with an incorrect station name.", INVESTIGATE_CARGO)
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_CONTENTS
investigate_log("Supply order #[order_id] generated a manifest missing listed contents.", INVESTIGATE_CARGO)
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_ITEM
investigate_log("Supply order #[order_id] generated with incorrect contents shipped.", INVESTIGATE_CARGO)
/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved()
return LAZYLEN(stamp_cache) && !is_denied()
/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied()
return LAZYLEN(stamp_cache) && ("stamp-deny" in stamp_cache)
/datum/supply_order
var/id
var/cost_type
var/orderer
var/orderer_rank
var/orderer_ckey
var/reason
var/discounted_pct
///If set to FALSE, we won't charge when the cargo shuttle arrives with this.
var/charge_on_purchase = TRUE
///area this order wants to reach, if not null then it will come with the deliver_first component set to this area
var/department_destination
var/datum/supply_pack/pack
var/datum/bank_account/paying_account
var/obj/item/coupon/applied_coupon
///Boolean on whether the manifest can fail or not.
var/manifest_can_fail = TRUE
///Boolean on whether the manifest can be cancelled through cargo consoles.
var/can_be_cancelled = TRUE
/datum/supply_order/New(
datum/supply_pack/pack,
orderer,
orderer_rank,
orderer_ckey,
reason,
paying_account,
department_destination,
coupon,
charge_on_purchase = TRUE,
manifest_can_fail = TRUE,
cost_type = "cr",
can_be_cancelled = TRUE,
)
id = SSshuttle.order_number++
src.cost_type = cost_type
src.pack = pack
src.orderer = orderer
src.orderer_rank = orderer_rank
src.orderer_ckey = orderer_ckey
src.reason = reason
src.paying_account = paying_account
src.department_destination = department_destination
src.applied_coupon = coupon
src.charge_on_purchase = charge_on_purchase
src.manifest_can_fail = manifest_can_fail
src.can_be_cancelled = can_be_cancelled
//returns the total cost of this order. Its not the total price paid by cargo but the total value of this order
/datum/supply_order/proc/get_final_cost()
var/cost = pack.get_cost()
if(applied_coupon) //apply discount price
cost -= (cost * applied_coupon.discount_pct_off)
if(!isnull(paying_account)) //privately purchased means 1.1x the cost
cost *= 1.1
return cost
/datum/supply_order/proc/generateRequisition(turf/T)
var/obj/item/paper/requisition_paper = new(T)
requisition_paper.name = "requisition form - #[id] ([pack.name])"
var/requisition_text = "<h2>[station_name()] Supply Requisition</h2>"
requisition_text += "<hr/>"
requisition_text += "Order #[id]<br/>"
requisition_text+= "Time of Order: [station_time_timestamp()]<br/>"
requisition_text += "Item: [pack.name]<br/>"
requisition_text += "Access Restrictions: [SSid_access.get_access_desc(pack.access)]<br/>"
requisition_text += "Requested by: [orderer]<br/>"
if(paying_account)
requisition_text += "Paid by: [paying_account.account_holder]<br/>"
requisition_text += "Rank: [orderer_rank]<br/>"
requisition_text += "Comment: [reason]<br/>"
requisition_paper.add_raw_text(requisition_text)
requisition_paper.update_appearance()
return requisition_paper
/datum/supply_order/proc/generateManifest(obj/container, owner, packname, cost) //generates-the-manifests.
var/obj/item/paper/fluff/jobs/cargo/manifest/manifest_paper = new(null, id, cost, manifest_can_fail)
var/station_name = (manifest_paper.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name()
manifest_paper.name = "shipping manifest - [packname?"#[id] ([pack.name])":"(Grouped Item Crate)"]"
var/manifest_text = "<h2>[command_name()] Shipping Manifest</h2>"
manifest_text += "<hr/>"
if(owner && !(owner == "Cargo"))
manifest_text += "Direct purchase from [owner]<br/>"
manifest_paper.name += " - Purchased by [owner]"
manifest_text += "Order[packname?"":"s"]: [id]<br/>"
manifest_text += "Destination: [station_name]<br/>"
if(packname)
manifest_text += "Item: [packname]<br/>"
manifest_text += "Contents: <br/>"
manifest_text += "<ul>"
var/container_contents = list() // Associative list with the format (item_name = nº of occurences, ...)
for(var/atom/movable/AM in container.contents - manifest_paper)
container_contents[AM.name]++
if((manifest_paper.errors & MANIFEST_ERROR_CONTENTS) && container_contents)
for(var/i = 1 to rand(1, round(container.contents.len * 0.5))) // Remove anywhere from one to half of the items
var/missing_item = pick(container_contents)
container_contents[missing_item]--
if(container_contents[missing_item] == 0) // To avoid 0s and negative values on the manifest
container_contents -= missing_item
for(var/item in container_contents)
manifest_text += "<li> [container_contents[item]] [item][container_contents[item] == 1 ? "" : "s"]</li>"
manifest_text += "</ul>"
manifest_text += "<h4>Stamp below to confirm receipt of goods:</h4>"
manifest_paper.add_raw_text(manifest_text)
if(manifest_paper.errors & MANIFEST_ERROR_ITEM)
if(HAS_TRAIT(container, TRAIT_NO_MISSING_ITEM_ERROR))
manifest_paper.errors &= ~MANIFEST_ERROR_ITEM
else
var/lost = max(round(container.contents.len / 10), 1)
while(--lost >= 0)
qdel(pick(container.contents))
manifest_paper.update_appearance()
manifest_paper.forceMove(container)
if(istype(container, /obj/structure/closet/crate))
var/obj/structure/closet/crate/C = container
C.manifest = manifest_paper
C.update_appearance()
else
container.contents += manifest_paper
return manifest_paper
/datum/supply_order/proc/generate(atom/A)
var/account_holder
if(paying_account)
account_holder = paying_account.account_holder
else
account_holder = "Cargo"
var/obj/structure/closet/crate/crate = pack.generate(A, paying_account)
if(department_destination)
crate.AddElement(/datum/element/deliver_first, department_destination, pack.cost)
generateManifest(crate, account_holder, pack, pack.cost)
return crate
/datum/supply_order/proc/generateCombo(miscbox, misc_own, misc_contents, misc_cost)
for (var/I in misc_contents)
new I(miscbox)
generateManifest(miscbox, misc_own, "", misc_cost)
return
#undef MANIFEST_ERROR_CHANCE
#undef MANIFEST_ERROR_NAME
#undef MANIFEST_ERROR_CONTENTS
#undef MANIFEST_ERROR_ITEM