Files
+37 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

385 lines
18 KiB
Plaintext

GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
/mob/living,
/obj/docking_port,
/obj/effect/hierophant,
/obj/effect/mob_spawn,
/obj/effect/portal,
/obj/effect/rune,
/obj/item/beacon,
/obj/item/disk/nuclear,
/obj/item/gps,
/obj/item/hilbertshotel,
/obj/item/mail,
/obj/item/shared_storage,
/obj/item/swapper,
/obj/item/warp_cube,
/obj/machinery/autolathe, // In case you manage to get it to print a beacon while in transit
/obj/machinery/camera,
/obj/machinery/disposal,
/obj/machinery/exodrone_launcher,
/obj/machinery/fax,
/obj/machinery/launchpad,
/obj/machinery/nuclearbomb,
/obj/machinery/quantumpad,
/obj/machinery/rnd/production,
/obj/machinery/syndicatebomb,
/obj/machinery/teleport/hub,
/obj/machinery/teleport/station,
/obj/narsie,
/obj/projectile/beam/wormhole,
/obj/structure/blob,
/obj/structure/checkoutmachine,
/obj/structure/disposalpipe,
/obj/structure/disposaloutlet,
/obj/structure/extraction_point,
/obj/structure/guardian_beacon,
/obj/tear_in_reality,
)))
/// How many goody orders we can fit in a lockbox before we upgrade to a crate
#define GOODY_FREE_SHIPPING_MAX 5
/// How much to charge oversized goody orders
#define CRATE_TAX 700
#define REFILL_RANGE_DEFAULT 10
/obj/docking_port/mobile/supply
name = "supply shuttle"
shuttle_id = "cargo"
callTime = 60 SECONDS
dir = WEST
port_direction = EAST
movement_force = list("KNOCKDOWN" = 0, "THROW" = 0)
/obj/docking_port/mobile/supply/register()
. = ..()
SSshuttle.supply = src
/obj/docking_port/mobile/supply/canMove()
. = ..()
if(!.)
return FALSE
if(is_station_level(z))
return check_blacklist(shuttle_areas)
/obj/docking_port/mobile/supply/proc/check_blacklist(areaInstances)
for(var/area/shuttle_area as anything in areaInstances)
for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists())
for(var/turf/shuttle_turf as anything in zlevel_turfs)
for(var/atom/passenger in shuttle_turf.get_all_contents())
if((is_type_in_typecache(passenger, GLOB.blacklisted_cargo_types) || HAS_TRAIT(passenger, TRAIT_BANNED_FROM_CARGO_SHUTTLE)) && !istype(passenger, /obj/docking_port))
return FALSE
return TRUE
/// Returns anything on the cargo blacklist found within areas_to_check back to the turf of the home docking port via Centcom branded supply pod.
/obj/docking_port/mobile/supply/proc/return_blacklisted_things_home(list/area/areas_to_check, obj/docking_port/stationary/home)
var/list/stuff_to_send_home = list()
for(var/area/shuttle_area as anything in areas_to_check)
for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists())
for(var/turf/shuttle_turf as anything in zlevel_turfs)
for(var/atom/passenger in shuttle_turf.get_all_contents())
if((is_type_in_typecache(passenger, GLOB.blacklisted_cargo_types) || HAS_TRAIT(passenger, TRAIT_BANNED_FROM_CARGO_SHUTTLE)) && !istype(passenger, /obj/docking_port))
stuff_to_send_home += passenger
if(!length(stuff_to_send_home))
return FALSE
podspawn(list(
"target" = get_turf(home),
"path" = /obj/structure/closet/supplypod/centcompod,
"spawn" = stuff_to_send_home,
))
return stuff_to_send_home
/obj/docking_port/mobile/supply/request(obj/docking_port/stationary/S)
if(mode != SHUTTLE_IDLE)
return 2
return ..()
/obj/docking_port/mobile/supply/check_dock(obj/docking_port/stationary/S, silent)
. = ..()
if(!.)
return
// If we're not trying to dock at Centcom, we don't care.
if(S.shuttle_id != "cargo_away")
return
// Else we are docking at Centcom, check the blacklist to make sure no contraband was put onto the shuttle mid-transit.
// If there's anything contrabandy, send these items back to the origin docking port.
// This is a sort of catch-all Centcom exploit check.
var/list/stuff_sent_home = return_blacklisted_things_home(shuttle_areas, previous)
if(!length(stuff_sent_home))
return
for(var/atom/thing_sent_home as anything in stuff_sent_home)
investigate_log("Blacklisted item found on in-transit Cargo Shuttle: [thing_sent_home] ([thing_sent_home.type])", INVESTIGATE_CARGO)
message_admins("Blacklisted item found on in-transit Cargo Shuttle. See cargo logs for more details.")
SSshuttle.centcom_message = "Contraband found on Cargo Shuttle. This has been returned via drop pod."
/obj/docking_port/mobile/supply/initiate_docking(obj/docking_port/stationary/new_dock, force=FALSE)
if(getDockedId() == "cargo_away") // Buy when we leave home.
buy()
create_mail()
if(SSshuttle.renew_cargo_air)
refill_air()
. = ..() // Fly/enter transit.
if(. != DOCKING_SUCCESS)
return
if(getDockedId() == "cargo_away") // Sell when we get home
sell()
if(SSshuttle.renew_cargo_air)
refill_air()
/obj/docking_port/mobile/supply/proc/buy()
SEND_SIGNAL(SSshuttle, COMSIG_SUPPLY_SHUTTLE_BUY)
var/list/obj/miscboxes = list() //miscboxes are combo boxes that contain all goody orders grouped
var/list/misc_order_num = list() //list of strings of order numbers, so that the manifest can show all orders in a box
var/list/misc_contents = list() //list of lists of items that each box will contain
var/list/misc_costs = list() //list of overall costs sustained by each buyer.
var/list/empty_turfs = list()
for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
for(var/turf/open/floor/shuttle_turf in shuttle_area.get_turfs_from_all_zlevels())
if(shuttle_turf.is_blocked_turf())
continue
empty_turfs += shuttle_turf
//quickly and greedily handle chef's grocery runs first, there are a few reasons why this isn't attached to the rest of cargo...
//but the biggest reason is that the chef requires produce to cook and do their job, and if they are using this system they
//already got let down by the botanists. So to open a new chance for cargo to also screw them over any more than is necessary is bad.
if(SSshuttle.chef_groceries.len)
var/obj/structure/closet/crate/freezer/grocery_crate = new(pick_n_take(empty_turfs))
grocery_crate.name = "kitchen produce freezer"
grocery_crate.desc = "Produce order for the Kitchen, deliver to the chef ASAP."
investigate_log("Chef's [SSshuttle.chef_groceries.len] sized produce order arrived. Cost was deducted from orderer, not cargo.", INVESTIGATE_CARGO)
for(var/datum/orderable_item/item as anything in SSshuttle.chef_groceries)//every order
for(var/amt in 1 to SSshuttle.chef_groceries[item])//every order amount
new item.purchase_path(grocery_crate)
SSshuttle.chef_groceries.Cut() //This lets the console know it can order another round.
if(!SSshuttle.shopping_list.len)
return
var/value = 0
var/purchases = 0
var/price
var/pack_cost
var/list/goodies_by_buyer = list() // if someone orders more than GOODY_FREE_SHIPPING_MAX goodies, we upcharge to a normal crate so they can't carry around 20 combat shotties
var/list/clean_up_orders = list() // orders to remove since we are done with them
for(var/datum/supply_order/spawning_order in SSshuttle.shopping_list)
if(!empty_turfs.len)
break
//adjust galactic material market based on the ordered quantities
var/datum/supply_pack/custom/minerals/sheets = astype(spawning_order.pack)
if(!isnull(sheets))
var/list/orders_adjusted = sheets.adjust_market()
if(orders_adjusted.len)
var/datum/bank_account/paying_for_this = spawning_order.paying_account || SSeconomy.get_dep_account(ACCOUNT_CAR)
if(!sheets.contains.len) //no sheets in the market at all
paying_for_this.bank_card_talk("Order #[spawning_order.id] ([spawning_order.pack.name]) was cancelled due to insufficient materials in the market!.")
SSshuttle.shopping_list -= spawning_order
clean_up_orders += spawning_order
continue
//some of the orders were adjusted(quantity changed or cancelled) according to the market
paying_for_this.bank_card_talk("Order #[spawning_order.id] ([spawning_order.pack.name]) had the following orders adjusted<br>[orders_adjusted.Join("<br>")]<br>.")
price = spawning_order.get_final_cost()
// department orders EARN money for cargo, not the other way around
var/datum/bank_account/paying_for_this
if(!spawning_order.department_destination && spawning_order.charge_on_purchase)
if(spawning_order.paying_account) //Someone paid out of pocket
paying_for_this = spawning_order.paying_account
// note this is before we increment, so this is the GOODY_FREE_SHIPPING_MAX + 1th goody to ship. also note we only increment off this step if they successfully pay the fee, so there's no way around it
if(spawning_order.pack.order_flags & ORDER_GOODY)
var/list/current_buyer_orders = goodies_by_buyer[paying_for_this] // BUBBER EDIT
if(LAZYLEN(current_buyer_orders) == GOODY_FREE_SHIPPING_MAX)
price = round(price + CRATE_TAX)
paying_for_this.bank_card_talk("Goody order size exceeds free shipping limit: Assessing [CRATE_TAX] [MONEY_NAME_SINGULAR] S&H fee.")
else
paying_for_this = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(paying_for_this)
if(!paying_for_this.adjust_money(-price, "Cargo: [spawning_order.pack.name]"))
if(spawning_order.paying_account)
paying_for_this.bank_card_talk("Cargo order #[spawning_order.id] rejected due to lack of funds. [MONEY_NAME_CAPITALIZED] required: [price]")
if(!spawning_order.can_be_cancelled) //only if it absolutly cannot be canceled by the player do we cancel it for them
SSshuttle.shopping_list -= spawning_order
clean_up_orders += spawning_order
continue
pack_cost = spawning_order.pack.get_cost()
// BUBBER EDIT START - include cargo-funded goodies in per-buyer goody packaging
if(spawning_order.charge_on_purchase && (spawning_order.pack.order_flags & ORDER_GOODY) && paying_for_this)
LAZYADD(goodies_by_buyer[paying_for_this], spawning_order)
// BUBBER EDIT END
if(spawning_order.paying_account && spawning_order.charge_on_purchase) // SKYRAT EDIT CHANGE - ORIGINAL: if(spawning_order.paying_account)
paying_for_this = spawning_order.paying_account
// BUBBER REMOVAL START
// if(spawning_order.pack.order_flags & ORDER_GOODY)
// LAZYADD(goodies_by_buyer[spawning_order.paying_account], spawning_order)
// BUBBER REMOVAL END
var/receiver_message = "Cargo order #[spawning_order.id] has shipped."
if(spawning_order.charge_on_purchase)
receiver_message += " [price] [MONEY_NAME] have been charged to your bank account"
paying_for_this.bank_card_talk(receiver_message)
SSeconomy.add_audit_entry(paying_for_this, price, spawning_order.pack.name)
var/datum/bank_account/department/cargo = SSeconomy.get_dep_account(ACCOUNT_CAR)
cargo.adjust_money(price - pack_cost) //Cargo gets the handling fee
value += pack_cost
if(!(spawning_order.pack.order_flags & ORDER_GOODY)) //we handle goody crates below
var/obj/structure/closet/crate = spawning_order.generate(pick_n_take(empty_turfs))
crate.name += " - #[spawning_order.id]"
SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[spawning_order.pack.get_cost()]", "[spawning_order.pack.name]", "[spawning_order.orderer_rank]"))
var/from_whom = paying_for_this?.account_holder || "nobody (department order)"
investigate_log("Order #[spawning_order.id] ([spawning_order.pack.name], placed by [key_name(spawning_order.orderer_ckey)]), paid by [from_whom] has shipped.", INVESTIGATE_CARGO)
if(spawning_order.pack.order_flags & ORDER_DANGEROUS)
message_admins("\A [spawning_order.pack.name] ordered by [ADMIN_LOOKUPFLW(spawning_order.orderer_ckey)], paid by [from_whom] has shipped.")
purchases++
// done dealing with order. Time to remove & delete it
SSshuttle.shopping_list -= spawning_order
clean_up_orders += spawning_order
// we handle packing all the goodies last, since the type of crate we use depends on how many goodies they ordered. If it's more than GOODY_FREE_SHIPPING_MAX
// then we send it in a crate (including the CRATE_TAX cost), otherwise send it in a free shipping case
for(var/buyer_key in goodies_by_buyer)
var/list/buying_account_orders = goodies_by_buyer[buyer_key]
var/datum/bank_account/buying_account = buyer_key
var/buyer = buying_account.account_holder || "Account ID: [buying_account.account_id]" // BUBBER EDIT - allow goodies to be bought privately
if(buying_account_orders.len > GOODY_FREE_SHIPPING_MAX) // no free shipping, send a crate
var/obj/structure/closet/crate/secure/owned/our_crate = new /obj/structure/closet/crate/secure/owned(pick_n_take(empty_turfs), buying_account) // BUBBER EDIT - add init arg
/// SKYRAT EDIT ADDITION START - FIXES COMMAND BUDGET CASES BEING UNOPENABLE
if(istype(our_crate.buyer_account, /datum/bank_account/department))
our_crate.department_purchase = TRUE
our_crate.department_account = our_crate.buyer_account
/// SKYRAT EDIT ADDITION END
our_crate.name = "goody crate - purchased by [buyer]"
miscboxes[buyer] = our_crate
else //free shipping in a case
miscboxes[buyer] = new /obj/item/storage/lockbox/order(pick_n_take(empty_turfs), buying_account) // BUBBER EDIT - add init arg
var/obj/item/storage/lockbox/order/our_case = miscboxes[buyer]
our_case.buyer_account = buying_account
/// SKYRAT EDIT ADDITION START - FIXES COMMAND BUDGET CASES BEING UNOPENABLE
if(istype(our_case.buyer_account, /datum/bank_account/department))
our_case.department_purchase = TRUE
our_case.department_account = our_case.buyer_account
/// SKYRAT EDIT ADDITION END
miscboxes[buyer].name = "goody case - purchased by [buyer]"
misc_contents[buyer] = list()
for(var/datum/supply_order/our_order as anything in buying_account_orders)
for (var/item in our_order.pack.contains)
misc_contents[buyer] += item
misc_costs[buyer] += our_order.pack.cost
misc_order_num[buyer] = "[misc_order_num[buyer]]#[our_order.id] "
for(var/miscbox in miscboxes)
var/datum/supply_order/order = new/datum/supply_order()
order.id = misc_order_num[miscbox]
order.generateCombo(miscboxes[miscbox], miscbox, misc_contents[miscbox], misc_costs[miscbox])
qdel(order)
//clean up all dealt with orders
for(var/datum/supply_order/completed_order in clean_up_orders)
qdel(completed_order)
var/datum/bank_account/cargo_budget = SSeconomy.get_dep_account(ACCOUNT_CAR)
investigate_log("[purchases] orders in this shipment, worth [value] [MONEY_NAME]. [cargo_budget.account_balance] [MONEY_NAME] left.", INVESTIGATE_CARGO)
/// Deletes and sells the items on the shuttle
/obj/docking_port/mobile/supply/proc/sell()
var/datum/bank_account/cargo_budget = SSeconomy.get_dep_account(ACCOUNT_CAR)
var/presale_points = cargo_budget.account_balance
var/msg = ""
var/datum/export_report/report = new
for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists())
for(var/turf/shuttle_turf as anything in zlevel_turfs)
for(var/atom/movable/exporting_atom in shuttle_turf)
if(iseyemob(exporting_atom))
continue
if(isobserver(exporting_atom))
continue
if(exporting_atom.anchored)
continue
export_item_and_contents(exporting_atom, apply_elastic = TRUE, dry_run = FALSE, external_report = report)
for(var/datum/export/exported_datum in report.total_amount)
var/export_text = exported_datum.total_printout(report)
if(!export_text)
continue
msg += export_text + "\n"
cargo_budget.adjust_money(report.total_value[exported_datum])
SSshuttle.centcom_message = msg
if(report.exported_atoms.len)
investigate_log("contents sold for [cargo_budget.account_balance - presale_points] [MONEY_NAME]. Contents: [report.exported_atoms.Join(",")]. Message: [msg]", INVESTIGATE_CARGO)
/*
Generates a box of mail depending on our exports and imports.
Applied in the cargo shuttle sending/arriving, by building the crate if the round is ready to introduce mail based on the economy subsystem.
Then, fills the mail crate with mail, by picking applicable crew who can receive mail at the time to sending.
*/
/obj/docking_port/mobile/supply/proc/create_mail()
//Early return if there's no mail waiting to prevent taking up a slot. We also don't send mails on sundays or holidays.
if(!SSeconomy.mail_waiting || SSeconomy.mail_blocked || SSsecurity_level.current_security_level.disables_mail)
return
//spawn crate
var/list/empty_turfs = list()
for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
for(var/turf/open/floor/shuttle_floor in shuttle_area.get_turfs_from_all_zlevels())
if(shuttle_floor.is_blocked_turf())
continue
empty_turfs += shuttle_floor
new /obj/structure/closet/crate/mail/economy(pick(empty_turfs))
/// Takes a supply pack, returns the amount we currently have on order (or OVER_ORDER_LIMIT if we are over the hardcap on orders of this type)
/obj/docking_port/mobile/supply/proc/get_order_count(datum/supply_pack/ordering)
var/similar_count = 0
for(var/datum/supply_order/order as anything in (SSshuttle.shopping_list | SSshuttle.request_list))
if(order.pack == ordering)
similar_count += 1
if(similar_count >= CARGO_MAX_ORDER)
return OVER_ORDER_LIMIT
return similar_count
/**
* This proc collects all turfs on a shuttle, then replaces the air with the initial gas mix across the shuttle.
* Used by the shuttle air upgrade.
*/
/obj/docking_port/mobile/supply/proc/refill_air()
for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
for(var/turf/open/floor/shuttle_floor in shuttle_area.get_turfs_from_all_zlevels())
if(shuttle_floor.blocks_air)
//skip walls
continue
var/datum/gas_mixture/GM = SSair.parse_gas_string(shuttle_floor.initial_gas_mix, /datum/gas_mixture/turf)
shuttle_floor.copy_air(GM)
shuttle_floor.temperature = initial(shuttle_floor.temperature)
shuttle_floor.update_visuals()
#undef GOODY_FREE_SHIPPING_MAX
#undef CRATE_TAX
#undef REFILL_RANGE_DEFAULT