Arc feature - Persistent supplies (#22368)

# Summary

This PR adds persistent supply packages and a specialized charge card
for lore arcs.
Both can only be created using admin permissions and are not intended
for general availability.

## Changes

- Added a new package type, persistent supplies.
- Abstracted existing orion deliveries for shared code.
- Added a new persistent charge card variant.
- Added exceptions and specific logging for new card variant.

## Notes

Depends on PR #22367. Specific merge order not relevant.

Icons by @Fyniiy.

Relevant information for the current arc can be found on the forum
thread [Operation Deep
Dive](https://forums.aurorastation.org/topic/22921-all-crew-thread-operation-deep-dive/).

---------

Signed-off-by: FabianK3 <21039694+FabianK3@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: SleepyGemmy <99297919+SleepyGemmy@users.noreply.github.com>
This commit is contained in:
FabianK3
2026-05-05 01:25:06 +00:00
committed by GitHub
co-authored by Copilot SleepyGemmy
parent d717a9e8eb
commit 328c56be11
25 changed files with 209 additions and 124 deletions
+6 -6
View File
@@ -1,7 +1,7 @@
/obj/item/cargo_backpack
name = "cargo pack"
desc = "A robust set of rigs and buckles that allows the wearer to carry two additional Orion Express delivery packages on their back."
icon = 'icons/obj/orion_delivery.dmi'
icon = 'icons/obj/package.dmi'
icon_state = "package_pack"
item_state = "package_pack"
contained_sprite = TRUE
@@ -41,7 +41,7 @@
var/list/data = list()
data["cargo_pack_details"] = list()
for(var/obj/item/cargo_package/package in contained_packages)
for(var/obj/item/package/delivery/package in contained_packages)
var/delivery_site = "Unknown"
if(package.delivery_point_sector)
var/obj/effect/overmap/visitable/delivery_sector = package.delivery_point_sector.resolve()
@@ -82,7 +82,7 @@
return
for(var/package_index = 1 to length(contained_packages))
var/obj/item/cargo_package/package = contained_packages[package_index]
var/obj/item/package/delivery/package = contained_packages[package_index]
var/image/package_tag = image(icon, icon_state = "package_pack_[package_index]_tag")
package_tag.color = package.accent_color
mob_overlay.AddOverlays(package_tag)
@@ -110,7 +110,7 @@
to_chat(user, SPAN_WARNING("Your other hand is occupied!"))
return
user.visible_message("<b>[user]</b> unloads a package from \the [src]!", SPAN_NOTICE("You unload a package from \the [src]!"))
var/obj/item/cargo_package/package = contained_packages[1]
var/obj/item/package/delivery/package = contained_packages[1]
user.put_in_hands(package)
if(user.species.mob_size < 12)
package.wield(user)
@@ -121,11 +121,11 @@
if(!ishuman(user))
return ..()
if(user.back != src)
if(istype(attacking_item, /obj/item/cargo_package))
if(istype(attacking_item, /obj/item/package/delivery))
to_chat(user, SPAN_WARNING("Put \the [src] on your back before you load packages onto it!"))
return
return ..()
if(!istype(attacking_item, /obj/item/cargo_package))
if(!istype(attacking_item, /obj/item/package/delivery))
return ..()
if(LAZYLEN(contained_packages) >= 2)
+77 -49
View File
@@ -1,4 +1,42 @@
/obj/item/cargo_package
ABSTRACT_TYPE(/obj/item/package)
name = "package"
icon = 'icons/obj/package.dmi'
contained_sprite = TRUE
update_icon_on_init = TRUE
has_accents = TRUE
w_class = WEIGHT_CLASS_HUGE
force = 15
slowdown = 0.5
/obj/item/package/proc/wield(var/mob/living/carbon/human/user)
var/obj/A = user.get_inactive_hand()
if(A)
to_chat(user, SPAN_WARNING("Your other hand is occupied!"))
return
item_state += "_wielded"
var/obj/item/offhand/O = new(user)
O.name = "[initial(name)] - offhand"
O.desc = "Your second grip on \the [initial(name)]."
user.put_in_inactive_hand(O)
/obj/item/package/dropped(mob/user)
..()
item_state = initial(item_state)
if(user)
var/obj/item/offhand/O = user.get_inactive_hand()
if(istype(O))
O.unwield()
/obj/item/package/can_swap_hands(var/mob/user)
var/obj/item/offhand/O = user.get_inactive_hand()
if(istype(O))
return FALSE
return TRUE
/obj/item/package/too_heavy_to_throw()
return TRUE
/obj/item/package/delivery
name = "cargo package"
desc = "\
An Orion Express cargo package. \
@@ -8,17 +46,8 @@
This package makes use of the small-scale shipping network of Orion Express. \
It is a common sight all over the Spur, where Orion Express services depend on ordinary people and ships picking up and delivering packages for each other, \
with Orion Express only delivering to automated stations and other distribution points."
icon = 'icons/obj/orion_delivery.dmi'
icon_state = "express_package"
item_state = "express_package"
contained_sprite = TRUE
update_icon_on_init = TRUE
has_accents = TRUE
w_class = WEIGHT_CLASS_HUGE
force = 15
slowdown = 0.5
var/delivery_point_id = ""
var/datum/weakref/delivery_point_sector
@@ -32,12 +61,12 @@
/// If true, pay_amount goes into Operations Account
var/pays_horizon_account = TRUE
/obj/item/cargo_package/mechanics_hints(mob/user, distance, is_adjacent)
/obj/item/package/delivery/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "You can deliver this package to a cargo delivery point."
. += "An additional 2% is added to your account on delivery, or paid to you directly. Can be loaded into a cargo pack."
/obj/item/cargo_package/feedback_hints(mob/user, distance, is_adjacent)
/obj/item/package/delivery/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(delivery_point_id)
// if name not already set by cargo receptacle, acquire the sector name instead
@@ -49,7 +78,7 @@
. += SPAN_NOTICE("The label on the package reads: SITE: <b>[delivery_site]</b> | COORD: <b>[delivery_point_coordinates]</b> | ID: <b>[delivery_point_id]</b>")
. += SPAN_NOTICE("The price tag on the package reads: <b>[pay_amount]电</b>.")
/obj/item/cargo_package/Initialize(mapload, obj/structure/cargo_receptacle/delivery_point)
/obj/item/package/delivery/Initialize(mapload, obj/structure/cargo_receptacle/delivery_point)
. = ..()
pay_amount = rand(4, 7) * 100
if(prob(3))
@@ -58,7 +87,7 @@
setup_delivery_point(delivery_point)
accent_color = pick(COLOR_RED, COLOR_AMBER, COLOR_PINK, COLOR_YELLOW, COLOR_LIME)
/obj/item/cargo_package/proc/setup_delivery_point(var/obj/structure/cargo_receptacle/delivery_point)
/obj/item/package/delivery/proc/setup_delivery_point(var/obj/structure/cargo_receptacle/delivery_point)
associated_delivery_point = WEAKREF(delivery_point)
delivery_point_id = delivery_point.delivery_id
delivery_point_sector = delivery_point.delivery_sector
@@ -67,7 +96,7 @@
delivery_point_coordinates = "[delivery_point.x]-[delivery_point.y]"
pay_amount = pay_amount * delivery_point.payment_modifier
/obj/item/cargo_package/do_additional_pickup_checks(var/mob/living/carbon/human/user)
/obj/item/package/delivery/do_additional_pickup_checks(var/mob/living/carbon/human/user)
if(!ishuman(user))
return FALSE
@@ -92,58 +121,57 @@
return TRUE
return FALSE
/obj/item/cargo_package/proc/wield(var/mob/living/carbon/human/user)
var/obj/A = user.get_inactive_hand()
if(A)
to_chat(user, SPAN_WARNING("Your other hand is occupied!"))
return
item_state += "_wielded"
var/obj/item/offhand/O = new(user)
O.name = "[initial(name)] - offhand"
O.desc = "Your second grip on \the [initial(name)]."
user.put_in_inactive_hand(O)
/obj/item/cargo_package/dropped(mob/user)
..()
item_state = initial(item_state)
if(user)
var/obj/item/offhand/O = user.get_inactive_hand()
if(istype(O))
O.unwield()
/obj/item/cargo_package/can_swap_hands(var/mob/user)
var/obj/item/offhand/O = user.get_inactive_hand()
if(istype(O))
return FALSE
return TRUE
/obj/item/cargo_package/too_heavy_to_throw()
return TRUE
/obj/item/cargo_package/offship
/obj/item/package/delivery/offship
pays_horizon_account = FALSE
/// Whether this package is guaranteed to deliver to the horizon or not
var/horizon_delivery = FALSE
/obj/item/cargo_package/offship/feedback_hints(mob/user, distance, is_adjacent)
/obj/item/package/delivery/offship/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(!delivery_point_id)
. += SPAN_NOTICE("Delivery site still being calculated, please check back later!")
/obj/item/cargo_package/offship/Initialize(mapload, obj/structure/cargo_receptacle/delivery_point)
/obj/item/package/delivery/offship/Initialize(mapload, obj/structure/cargo_receptacle/delivery_point)
. = ..()
if(!delivery_point)
// add a timer before we pick the delivery point, in case any ships or ruins still need to load
addtimer(CALLBACK(src, PROC_REF(get_delivery_point)), 3 MINUTES)
/obj/item/cargo_package/offship/proc/get_delivery_point()
/obj/item/package/delivery/offship/proc/get_delivery_point()
var/obj/structure/cargo_receptacle/selected_delivery_point = get_cargo_package_delivery_point(src, horizon_delivery)
if(!selected_delivery_point)
qdel(src)
return
setup_delivery_point(selected_delivery_point)
/obj/item/cargo_package/offship/to_horizon
/obj/item/package/delivery/offship/to_horizon
horizon_delivery = TRUE
// Persistent supply packages. These are meant to be spawned by admins and feature no mechanics like the orion deliveries.
/obj/item/package/persistent_supply
name = "supply package"
desc = "A supply package. Doesn't qualify for deliveries."
desc_extended = "\
Usually each package contains a different set of things one might need as supplies. \
Unlike the Orion deliveries, these packages are not tied to a specific delivery network and are not eligible for deliveries. \
Probably more expensive then it should be."
icon_state = "supply_package"
item_state = "supply_package"
persistant_objects_expiration_time_days = 360
/obj/item/package/persistent_supply/Initialize()
. = ..()
SSpersistence.objectsRegisterTrack(src)
/obj/item/package/persistent_supply/persistent_objects_get_content()
return list()
/obj/item/package/persistent_supply/persistent_objects_apply_content(content, x, y, z)
src.x = x
src.y = y
src.z = z
/obj/item/package/persistent_supply/Destroy()
log_and_message_admins("Persistent supply package at [src] was destroyed!", null, get_turf(src))
. = ..()
+6 -6
View File
@@ -10,7 +10,7 @@ GLOBAL_LIST_INIT_TYPED(all_cargo_receptacles, /obj/structure/cargo_receptacle, l
These are set up by Orion to expand its small-scale shipping network, especially in more remote areas, like outer edges of the Frontier or Coalition. \
It is a common sight all over the Spur, however, where Orion Express services depend on ordinary people and ships picking up and delivering packages for each other, \
with Orion Express only delivering to automated stations and other distribution points."
icon = 'icons/obj/orion_delivery.dmi'
icon = 'icons/obj/package.dmi'
icon_state = "delivery_point"
density = TRUE
@@ -86,7 +86,7 @@ GLOBAL_LIST_INIT_TYPED(all_cargo_receptacles, /obj/structure/cargo_receptacle, l
for(var/i = 1 to package_amount)
var/turf/random_turf = pick_n_take(warehouse_turfs)
if(random_turf)
new /obj/item/cargo_package(random_turf, src)
new /obj/item/package/delivery(random_turf, src)
if(late_spawner)
playsound(pick(warehouse_turfs), 'sound/machines/twobeep.ogg', 50, 1)
GLOB.global_announcer.autosay(announcement_message, capitalize_first_letters(announcer_name), channel)
@@ -96,8 +96,8 @@ GLOBAL_LIST_INIT_TYPED(all_cargo_receptacles, /obj/structure/cargo_receptacle, l
return ..()
/obj/structure/cargo_receptacle/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/cargo_package))
var/obj/item/cargo_package/package = attacking_item
if(istype(attacking_item, /obj/item/package/delivery))
var/obj/item/package/delivery/package = attacking_item
if(!package.associated_delivery_point)
to_chat(user, SPAN_WARNING("Something's wrong with the cargo package, submit a bug report. ERRCODE: 0"))
return
@@ -120,14 +120,14 @@ GLOBAL_LIST_INIT_TYPED(all_cargo_receptacles, /obj/structure/cargo_receptacle, l
visible_message("\The [src] beeps, \"[SPAN_NOTICE("New package available for delivery.")]\"")
playsound(src, SFX_PRINT, 50, TRUE)
var/obj/item/cargo_package/printed_package = new /obj/item/cargo_package/offship(get_turf(user), selected_delivery_point)
var/obj/item/package/delivery/printed_package = new /obj/item/package/delivery/offship(get_turf(user), selected_delivery_point)
printed_package.pays_horizon_account = pays_horizon_account
animate(printed_package, alpha = 0, alpha = 255, time = 1 SECOND) // Makes them fade in
return
return ..()
/obj/structure/cargo_receptacle/proc/pay_account(var/mob/living/carbon/human/courier, var/obj/item/cargo_package/package)
/obj/structure/cargo_receptacle/proc/pay_account(var/mob/living/carbon/human/courier, var/obj/item/package/delivery/package)
if(package.pays_horizon_account)
var/datum/money_account/supply_account = SScargo.supply_account
if(supply_account && !supply_account.suspended)
+3
View File
@@ -104,6 +104,9 @@
update_icon()
else if(authenticated_account)
if(istype(attacking_item,/obj/item/spacecash))
if(istype(attacking_item, /obj/item/spacecash/ewallet/persistent_charge_card))
to_chat(user, SPAN_WARNING("You insert the [attacking_item] into [src], but the machine immediately rejects it!"))
return
var/obj/item/spacecash/cash = attacking_item
//consume the money
authenticated_account.money += cash.worth
+2
View File
@@ -133,6 +133,8 @@
//transfer the money
E.worth -= transaction_amount
if(istype(E, /obj/item/spacecash/ewallet/persistent_charge_card))
log_and_message_admins("[E] used for a transaction at [src] with amount [transaction_amount]. Remaining balance: [E.worth]", user, get_turf(src))
linked_account.money += transaction_amount
//create entry in the EFTPOS linked account transaction log
+37 -2
View File
@@ -297,7 +297,7 @@
name = "charge card"
icon_state = "efundcard"
desc = "A card that holds an amount of money."
var/owner_name = "" //So the ATM can set it so the EFTPOS can put a valid name on transactions.
var/owner_name = "unknown" //So the ATM can set it so the EFTPOS can put a valid name on transactions.
drop_sound = 'sound/items/drop/card.ogg'
pickup_sound = 'sound/items/pickup/card.ogg'
@@ -305,7 +305,8 @@
. = ..()
if(distance > 2 && user != loc)
return
. += SPAN_NOTICE("The charge card's owner is [src.owner_name].")
if(src.owner_name)
. += SPAN_NOTICE("The charge card's owner is [src.owner_name].")
. += SPAN_NOTICE("It has [src.worth]电 left.")
/obj/item/spacecash/ewallet/c2000
@@ -316,3 +317,37 @@
/obj/item/spacecash/ewallet/c10000
worth = 10000
// Persistent ewallet that keeps it's value across rounds.
// When spawned, using VV, set "worth", "initial_worth", "owner_name" and "name".
/obj/item/spacecash/ewallet/persistent_charge_card
name = "specialized charge card"
desc = "A specialized charge card that holds a certain amount of money. This type of charge card is in use for special purposes and not generally available."
icon_state = "efundcard_special"
var/initial_worth = 0 // Used for calculating how much cash was spend, needs to be set using VV after spawning it.
persistant_objects_expiration_time_days = 360
/obj/item/spacecash/ewallet/persistent_charge_card/Initialize()
. = ..()
SSpersistence.objectsRegisterTrack(src)
/obj/item/spacecash/ewallet/persistent_charge_card/persistent_objects_get_content()
var/list/content = list()
content["name"] = src.name
content["initial_worth"] = src.initial_worth
content["worth"] = src.worth
content["owner_name"] = src.owner_name
return content
/obj/item/spacecash/ewallet/persistent_charge_card/persistent_objects_apply_content(content, x, y, z)
name = isnull(content["name"]) ? src.name : content["name"]
initial_worth = isnull(content["initial_worth"]) ? 0 : content["initial_worth"]
worth = isnull(content["worth"]) ? 0 : content["worth"]
owner_name = isnull(content["owner_name"]) ? src.owner_name : content["owner_name"]
src.x = x
src.y = y
src.z = z
/obj/item/spacecash/ewallet/persistent_charge_card/Destroy()
log_and_message_admins("Persistent charge card ([src.name]) at [src] was destroyed!", null, get_turf(src))
. = ..()
@@ -107,9 +107,10 @@
return TRUE
var/obj/item/spacecash/ewallet/charge_card
var/mob/living/L
if(!using_id)
if(isliving(usr))
var/mob/living/L = usr
L = usr
charge_card = L.get_active_hand()
if(!istype(charge_card))
return TRUE
@@ -137,6 +138,8 @@
status_message = "Account Error: Failed to Deposit Credits into Cargo Account"
return TRUE
charge_card.worth -= transaction_amount
if(istype(charge_card, /obj/item/spacecash/ewallet/persistent_charge_card))
log_and_message_admins("[charge_card] used for a transaction at [src] with amount [transaction_amount]. Remaining balance: [charge_card.worth]", L, get_turf(src))
playsound(computer, 'sound/machines/chime.ogg', 50, TRUE)
status_message = co.set_paid(GetNameAndAssignmentFromId(id_card), usr.character_id, destinationact)