From 72f76caea025befde3e7043bd39e8d8bb1d075b2 Mon Sep 17 00:00:00 2001
From: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Date: Fri, 5 Dec 2025 20:42:32 -0600
Subject: [PATCH] Spy loot tweaks (#94189)
## About The Pull Request
1. Ammo will no longer show as a loot option until a weapon that uses
said ammo has been claimed. Some ammo, particularly shotgun ammo has
unique status bypassing this, since you could reasonably acquire a
shotgun from the armory, a bartender, or cargo. There is also a 10%
chance that some ammo will show up anyways, to keep people on their
toes.
2. If you claim another weapon of an already claimed ammo type, the ammo
in the pool will be refreshed, meaning obtainable once again.
3. Unclaimed items have a 40% chance of being re-added to the loot pool.
This is on top of the 80% chance for an item to not be removed from the
pool when selecting loot.
4. When hard bounties empty out their loot pool, they will instead draw
from the medium loot pool. Likewise medium bounties will draw from the
easy pool when they run out.
5. Modified the antag info panel text slightly.
6. Made more appropriate use of `SStraitor.uplink_items`, and checks for
abstract items more thoroughly.
7. Some weapons no longer come in gun cases for Spies
## Why It's Good For The Game
1. Initially the thought was Spies would just have dud bounties on
occasion. They could take the time to lie low or work on their plans
rather than prowl for loot. Alternatively, an aggressive spy would go
after them *just in case* they got the correct gun, or a passive spy
would go after them for the ability to later bribe someone carrying the
correct gun.
However, in practice it seems people largely just don't like having dud
bounties, as it hurts the pacing. Someone who is on a roll doesn't want
to sit there and do nothing.
Ghommie tried to implement this earlier but I disagreed with the
implementation, so here we are.
2. It would be pretty unappealing to go after a gun after realizing all
the gun's ammo has been looted already.
3. I noticed in testing pools would drain themselves pretty fast. This
serves as a bit of a stop-gap, and prevents items that rolled on
particularly difficult targets from being lost forever. Now you can roll
the dice and hope that it shows up again.
4. Might as well reward people with non-junk items if they manage to
drain a tier's loot pool, especially since the top tier pool is pretty
small.
5. People were oft confused about the wording of the text, thinking
they'd get banned.
6. No reason to use the indexed list.
7. Having a gun case thrust into your hands is not very inconspicuous
## Changelog
:cl: Melbert
balance: Ammo will no longer show up in the Spy loot pool until a weapon
that uses said ammo has been claimed. Some ammo for weapons easily
obtainable on station (shotgun shot) are not limited by this
restriction. Also, there's a rare chance ammo will show up anyways just
to keep people on their toes.
balance: Claiming a weapon will refresh ammo in the loot pool that has
already been claimed or cycled out.
balance: Bounty rewards that no Spy claimed have a chance to re-enter
the loot pool after a bounty refresh, rather than being lost for the
remainder of the round.
balance: When higher tier Spy bounties empty their loot pools, they will
attempt to draw from lower tier pools before defaulting to telecrystals.
balance: Spies no longer are rewarded with gun cases for bounties;
rather just the gun itself
spellcheck: Modified the Spy antag info text
/:cl:
---
code/modules/antagonists/spy/spy_bounty.dm | 37 +++++++++-
.../antagonists/spy/spy_bounty_handler.dm | 45 ++++++++++---
code/modules/antagonists/spy/spy_uplink.dm | 8 ++-
code/modules/cargo/packs/general.dm | 3 +-
.../research/techweb/nodes/syndicate_nodes.dm | 5 +-
code/modules/uplink/uplink_items.dm | 3 +
code/modules/uplink/uplink_items/bundle.dm | 6 +-
code/modules/uplink/uplink_items/dangerous.dm | 19 +++++-
code/modules/uplink/uplink_items/nukeops.dm | 67 +++++++++++++++++++
.../modules/uplink/uplink_items/spy_unique.dm | 50 ++++++++++++++
.../packages/tgui/interfaces/AntagInfoSpy.tsx | 4 +-
11 files changed, 220 insertions(+), 27 deletions(-)
diff --git a/code/modules/antagonists/spy/spy_bounty.dm b/code/modules/antagonists/spy/spy_bounty.dm
index c285362bcd2..975e42dd58a 100644
--- a/code/modules/antagonists/spy/spy_bounty.dm
+++ b/code/modules/antagonists/spy/spy_bounty.dm
@@ -31,6 +31,8 @@
VAR_FINAL/initalized = FALSE
/// Whether the bounty has been completed.
VAR_FINAL/claimed = FALSE
+ /// What pool we picked the loot from.
+ VAR_FINAL/loot_difficulty
/// What uplink item the bounty will reward on completion.
VAR_FINAL/datum/uplink_item/reward_item
@@ -70,16 +72,47 @@
/// Selects what uplink item the bounty will reward on completion.
/datum/spy_bounty/proc/select_reward(datum/spy_bounty_handler/handler)
- var/list/loot_pool = handler.possible_uplink_items[difficulty]
+ loot_difficulty = difficulty
+
+ var/list/loot_pool
+ // work backwards from the highest difficulty loot pool to find one that has items
+ for(var/i in length(handler.possible_uplink_items) to 1 step -1)
+ var/pool_tier = handler.possible_uplink_items[i]
+ // we're not looking for this difficult, skip
+ if(pool_tier != loot_difficulty)
+ continue
+ // we found our difficulty's loot pool, if it has items we're done
+ loot_pool = handler.possible_uplink_items[pool_tier]
+ if(length(loot_pool) || i == 1)
+ break
+ // if our difficult does not have items, and we're not at the lowest difficulty, step down and try again
+ loot_difficulty = handler.possible_uplink_items[i - 1]
if(!length(loot_pool))
- reward_item = /datum/uplink_item/bundles_tc/telecrystal
+ reward_item = SStraitor.uplink_items_by_type[/datum/uplink_item/bundles_tc/telecrystal]
return // future todo : add some junk items for when we run out of items
reward_item = pick(loot_pool)
+ // we remove here, rather than on claim, to reduce the chance of duplicate rewards in a single batch
+ // otherwise it would be not only possible, but *likely* to get the same reward simultaneously across bounties
+ // (though the reason this is a probability is so there is a rare chance this can happen anyways, for the fun of it)
if(prob(80))
loot_pool -= reward_item
+/**
+ * Called when the bounty gets cleared after the end of a bounty period
+ *
+ * * handler - The bounty handler that is handling this bounty.
+ */
+/datum/spy_bounty/proc/clear_bounty(datum/spy_bounty_handler/handler)
+ ASSERT(initalized, "Trying to clear an uninitialized bounty!")
+
+ // another chance to return unclaimed reward items to the bounty pool
+ if(!claimed && reward_item && prob(40))
+ handler.possible_uplink_items[loot_difficulty || difficulty] |= reward_item
+
+ qdel(src)
+
/**
* Checks if the passed movable is a valid target for this bounty.
*
diff --git a/code/modules/antagonists/spy/spy_bounty_handler.dm b/code/modules/antagonists/spy/spy_bounty_handler.dm
index 4b4ba9a835c..2f4cfc876c7 100644
--- a/code/modules/antagonists/spy/spy_bounty_handler.dm
+++ b/code/modules/antagonists/spy/spy_bounty_handler.dm
@@ -113,22 +113,43 @@
continue
bounty_types[difficulty][bounty] = weight
+ var/list/limited_items = list()
+ var/list/show_anyways = list()
+ show_anyways += typesof(/datum/uplink_item/spy_unique/shotgun_ammo) // acquiring a shotgun is not difficult
+
for(var/datum/uplink_item/item as anything in SStraitor.uplink_items)
- if(isnull(item.item) || item.item == ABSTRACT_UPLINK_ITEM)
+ // limited items is populated as we go
+ if(item in limited_items)
continue
- if(!(item.purchasable_from & UPLINK_SPY))
+ // proc handles checking if the item is valid
+ if(!try_add_to_loot_pool(item))
continue
- // This will have some overlap, and that's intentional -
- // Adds some variety, rare moments where you can get a hard reward for an easier bounty (or visa versa)
- if(item.cost <= CONFIG_GET(number/spy_easy_reward_tc_threshold))
- possible_uplink_items[SPY_DIFFICULTY_EASY] += item
- if(item.cost >= CONFIG_GET(number/spy_easy_reward_tc_threshold) && item.cost <= CONFIG_GET(number/spy_hard_reward_tc_threshold))
- possible_uplink_items[SPY_DIFFICULTY_MEDIUM] += item
- if(item.cost >= CONFIG_GET(number/spy_hard_reward_tc_threshold))
- possible_uplink_items[SPY_DIFFICULTY_HARD] += item
+ // any child items, such as ammo, are removed from the pool until the parent item is rewarded
+ for(var/child_item_type in (item.relevant_child_items || list()) - show_anyways)
+ if(prob(10)) // 10% chance to have it anyways though. teehee
+ continue
+ var/child_item = SStraitor.uplink_items_by_type[child_item_type]
+ for(var/difficulty in possible_uplink_items)
+ possible_uplink_items[difficulty] -= child_item
+ limited_items |= child_item
refresh_bounty_list()
+/// Helper to attempt to add the passed uplink item datum to the possible bounty pool(s).
+/datum/spy_bounty_handler/proc/try_add_to_loot_pool(datum/uplink_item/item)
+ if(isnull(item.item) || item.item == ABSTRACT_UPLINK_ITEM || !(item.purchasable_from & UPLINK_SPY))
+ return FALSE
+
+ // This will have some overlap, and that's intentional -
+ // Adds some variety, rare moments where you can get a hard reward for an easier bounty (or visa versa)
+ if(item.cost <= CONFIG_GET(number/spy_easy_reward_tc_threshold))
+ possible_uplink_items[SPY_DIFFICULTY_EASY] |= item
+ if(item.cost >= CONFIG_GET(number/spy_easy_reward_tc_threshold) && item.cost <= CONFIG_GET(number/spy_hard_reward_tc_threshold))
+ possible_uplink_items[SPY_DIFFICULTY_MEDIUM] |= item
+ if(item.cost >= CONFIG_GET(number/spy_hard_reward_tc_threshold))
+ possible_uplink_items[SPY_DIFFICULTY_HARD] |= item
+ return TRUE
+
/// Helper that returns a list of all active bounties in a single list, regardless of difficulty.
/datum/spy_bounty_handler/proc/get_all_bounties() as /list
var/list/all_bounties = list()
@@ -152,7 +173,9 @@
bounties_to_give[SPY_DIFFICULTY_MEDIUM] += converted_medium_bounties
for(var/difficulty in bounties)
- QDEL_LIST(bounties[difficulty])
+ for(var/datum/spy_bounty/bounty as anything in bounties[difficulty])
+ bounty.clear_bounty(src)
+ bounties[difficulty].Cut()
var/list/pool = bounty_types[difficulty]
var/amount_to_give = bounties_to_give[difficulty]
diff --git a/code/modules/antagonists/spy/spy_uplink.dm b/code/modules/antagonists/spy/spy_uplink.dm
index 7131a0028ca..879061ed25a 100644
--- a/code/modules/antagonists/spy/spy_uplink.dm
+++ b/code/modules/antagonists/spy/spy_uplink.dm
@@ -148,12 +148,16 @@
return FALSE
var/bounty_key = bounty.get_dupe_protection_key(stealing)
+ // record that we've claimed this type of bounty
handler.all_claimed_bounty_types[bounty_key] += 1
handler.claimed_bounties_from_last_pool[bounty_key] = TRUE
-
+ // clear up the bounty itself
bounty.clean_up_stolen_item(stealing, spy, handler)
bounty.claimed = TRUE
-
+ // adds child items to the bounty pool, ie ammo for a newly acquired gun
+ for(var/child_item_type in bounty.reward_item.relevant_child_items)
+ handler.try_add_to_loot_pool(SStraitor.uplink_items_by_type[child_item_type])
+ // and finally, spawn the reward
var/atom/movable/reward = bounty.reward_item.spawn_item_for_generic_use(spy)
if(isitem(reward))
spy.put_in_hands(reward)
diff --git a/code/modules/cargo/packs/general.dm b/code/modules/cargo/packs/general.dm
index 92364c33b8a..896649cc961 100644
--- a/code/modules/cargo/packs/general.dm
+++ b/code/modules/cargo/packs/general.dm
@@ -224,8 +224,7 @@
///Generate assorted uplink items, taking into account the same surplus modifiers used for surplus crates
/datum/supply_pack/misc/syndicate/fill(obj/structure/closet/crate/C)
var/list/uplink_items = list()
- for(var/datum/uplink_item/item_path as anything in SStraitor.uplink_items_by_type)
- var/datum/uplink_item/item = SStraitor.uplink_items_by_type[item_path]
+ for(var/datum/uplink_item/item as anything in SStraitor.uplink_items)
if(item.purchasable_from & contents_uplink_type && item.item)
uplink_items += item
diff --git a/code/modules/research/techweb/nodes/syndicate_nodes.dm b/code/modules/research/techweb/nodes/syndicate_nodes.dm
index f4317669597..d031cf7c848 100644
--- a/code/modules/research/techweb/nodes/syndicate_nodes.dm
+++ b/code/modules/research/techweb/nodes/syndicate_nodes.dm
@@ -30,9 +30,8 @@
SIGNAL_HANDLER
UnregisterSignal(SSearly_assets, COMSIG_SUBSYSTEM_POST_INITIALIZE)
required_items_to_unlock = list()
- for(var/datum/uplink_item/item_path as anything in SStraitor.uplink_items_by_type)
- var/datum/uplink_item/item = SStraitor.uplink_items_by_type[item_path]
- if(!item.item || !(item.uplink_item_flags & SYNDIE_ILLEGAL_TECH))
+ for(var/datum/uplink_item/item as anything in SStraitor.uplink_items)
+ if(isnull(item.item) || item.item == ABSTRACT_UPLINK_ITEM || !(item.uplink_item_flags & SYNDIE_ILLEGAL_TECH))
continue
required_items_to_unlock |= item.item //allows deconning to unlock.
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index b89fff956df..5644e058244 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -109,6 +109,9 @@
/// Uses the purchase log, so items purchased that are not visible in the purchase log will not count towards this.
/// However, they won't be purchasable afterwards.
var/lock_other_purchases = FALSE
+ /// A lazylist of typepaths to uplink items relevant to this this item
+ /// EX: a pistol would list its magazines or modifications here
+ var/list/relevant_child_items
/datum/uplink_item/New()
. = ..()
diff --git a/code/modules/uplink/uplink_items/bundle.dm b/code/modules/uplink/uplink_items/bundle.dm
index b6cdc2fd3d6..6247e32e3b8 100644
--- a/code/modules/uplink/uplink_items/bundle.dm
+++ b/code/modules/uplink/uplink_items/bundle.dm
@@ -18,8 +18,7 @@
/datum/uplink_item/bundles_tc/random/purchase(mob/user, datum/uplink_handler/handler, atom/movable/source)
var/list/possible_items = list()
- for(var/datum/uplink_item/item_path as anything in SStraitor.uplink_items_by_type)
- var/datum/uplink_item/uplink_item = SStraitor.uplink_items_by_type[item_path]
+ for(var/datum/uplink_item/uplink_item as anything in SStraitor.uplink_items)
if(src == uplink_item || !uplink_item.item)
continue
if(!handler.can_purchase_item(user, uplink_item))
@@ -81,8 +80,7 @@
/// generates items that can go inside crates, edit this proc to change what items could go inside your specialized crate
/datum/uplink_item/bundles_tc/surplus/proc/generate_possible_items(mob/user, datum/uplink_handler/handler)
var/list/possible_items = list()
- for(var/datum/uplink_item/item_path as anything in SStraitor.uplink_items_by_type)
- var/datum/uplink_item/uplink_item = SStraitor.uplink_items_by_type[item_path]
+ for(var/datum/uplink_item/uplink_item as anything in SStraitor.uplink_items)
if(src == uplink_item || !uplink_item.item)
continue
if(!handler.check_if_restricted(uplink_item))
diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm
index 59d5644778d..ad8509a3e7c 100644
--- a/code/modules/uplink/uplink_items/dangerous.dm
+++ b/code/modules/uplink/uplink_items/dangerous.dm
@@ -15,7 +15,10 @@
item = /obj/item/storage/toolbox/guncase/traitor/donksoft
cost = 6
surplus = 10
- purchasable_from = ~UPLINK_SERIOUS_OPS
+ purchasable_from = ~(UPLINK_SERIOUS_OPS | UPLINK_SPY)
+ relevant_child_items = list(
+ /datum/uplink_item/ammo/toydarts,
+ )
/datum/uplink_item/dangerous/pistol
name = "Makarov Pistol Case"
@@ -24,7 +27,13 @@
While not included in the kit, the pistol is compatible with suppressors, which can be purchased separately."
item = /obj/item/storage/toolbox/guncase/traitor
cost = 7
- purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
+ purchasable_from = ~(UPLINK_ALL_SYNDIE_OPS | UPLINK_SPY)
+ relevant_child_items = list(
+ /datum/uplink_item/ammo/pistol,
+ /datum/uplink_item/ammo/pistolap,
+ /datum/uplink_item/ammo/pistolhp,
+ /datum/uplink_item/ammo/pistolfire,
+ )
/datum/uplink_item/dangerous/throwingweapons
name = "Box of Throwing Weapons"
@@ -96,3 +105,9 @@
cost = 13
surplus = 50
purchasable_from = ~UPLINK_ALL_SYNDIE_OPS //only traitors get the original revolver
+ relevant_child_items = list(
+ /datum/uplink_item/ammo/revolver,
+ /datum/uplink_item/ammo_nuclear/basic/revolver,
+ /datum/uplink_item/ammo_nuclear/special/revolver/phasic,
+ /datum/uplink_item/ammo_nuclear/special/revolver/heartseeker,
+ )
diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm
index 887a5165ad4..37ee8997aee 100644
--- a/code/modules/uplink/uplink_items/nukeops.dm
+++ b/code/modules/uplink/uplink_items/nukeops.dm
@@ -72,6 +72,17 @@
desc = "A fully-loaded 2-round burst fire drum-fed shotgun, complete with a secondary magazine you can hotswap. The gun has a handy label to explain how. \
Compatible with all 12g rounds. Designed for close quarter anti-personnel engagements. Comes with three spare magazines."
item = /obj/item/storage/toolbox/guncase/bulldog
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/basic/buck,
+ /datum/uplink_item/ammo_nuclear/basic/donk,
+ /datum/uplink_item/ammo_nuclear/basic/flechette,
+ /datum/uplink_item/ammo_nuclear/basic/slug,
+ /datum/uplink_item/ammo_nuclear/incendiary/dragon,
+ /datum/uplink_item/ammo_nuclear/special/meteor,
+ /datum/uplink_item/spy_unique/shotgun_ammo,
+ /datum/uplink_item/spy_unique/shotgun_ammo/breacher_slug,
+ /datum/uplink_item/spy_unique/shotgun_ammo/slugs,
+ )
/datum/uplink_item/ammo_nuclear/basic/buck
name = "12g Buckshot Drum (Bulldog)"
@@ -122,6 +133,12 @@
desc = "A small, easily concealable handgun that uses 10mm auto rounds in 8-round magazines and is compatible \
with suppressors. Comes with three spare magazines."
item = /obj/item/storage/toolbox/guncase/clandestine
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/basic/m10mm,
+ /datum/uplink_item/ammo_nuclear/ap/m10mm,
+ /datum/uplink_item/ammo_nuclear/hp/m10mm,
+ /datum/uplink_item/ammo_nuclear/incendiary/m10mm,
+ )
/datum/uplink_item/ammo_nuclear/basic/m10mm
name = "10mm Handgun Magazine (Ansem)"
@@ -164,6 +181,12 @@
desc = "A fully-loaded Scarborough Arms bullpup submachine gun. The C-20r fires .45 rounds with a \
24-round magazine and is compatible with suppressors. Comes with spare three magazines."
item = /obj/item/storage/toolbox/guncase/c20r
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/ap/smg,
+ /datum/uplink_item/ammo_nuclear/basic/smg,
+ /datum/uplink_item/ammo_nuclear/hp/smg,
+ /datum/uplink_item/ammo_nuclear/incendiary/smg,
+ )
/datum/uplink_item/ammo_nuclear/basic/smg
name = ".45 SMG Magazine (C-20r)"
@@ -213,6 +236,12 @@
desc = "Waffle Corp's modernized Syndicate revolver. Fires 7 brutal rounds of .357 Magnum. \
A classic operative weapon, improved for the modern era. Comes with 3 additional speedloaders of .357."
item = /obj/item/storage/toolbox/guncase/revolver
+ relevant_child_items = list(
+ /datum/uplink_item/ammo/revolver,
+ /datum/uplink_item/ammo_nuclear/basic/revolver,
+ /datum/uplink_item/ammo_nuclear/special/revolver/phasic,
+ /datum/uplink_item/ammo_nuclear/special/revolver/heartseeker,
+ )
/datum/uplink_item/ammo_nuclear/basic/revolver
name = ".357 Speed Loader (Revolver)"
@@ -246,6 +275,10 @@
desc = "A reusable rocket propelled grenade launcher preloaded with a low-yield 84mm HE round. \
Guaranteed to take your target out with a bang, or your money back! Comes with a bouquet of additional rockets!"
item = /obj/item/storage/toolbox/guncase/rocketlauncher
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/ap/rocket,
+ /datum/uplink_item/ammo_nuclear/basic/rocket,
+ )
/datum/uplink_item/ammo_nuclear/basic/rocket
name = "84mm HE Rocket Bouquet (Rocket Launcher)"
@@ -272,6 +305,13 @@
desc = "A fully-loaded Aussec Armoury belt-fed machine gun. \
This deadly weapon has a massive 50-round magazine of devastating 7mm ammunition."
item = /obj/item/gun/ballistic/automatic/l6_saw
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/ap/machinegun,
+ /datum/uplink_item/ammo_nuclear/basic/machinegun,
+ /datum/uplink_item/ammo_nuclear/hp/machinegun,
+ /datum/uplink_item/ammo_nuclear/incendiary/machinegun,
+ /datum/uplink_item/ammo_nuclear/special/machinegun,
+ )
/datum/uplink_item/ammo_nuclear/basic/machinegun
name = "7mm Box Magazine (L6 SAW)"
@@ -311,6 +351,11 @@
Comes with a 40mm underbarrel grenade launcher. Use secondary-fire to fire the grenade launcher. Also comes with two spare magazines \
and a box of 40mm rubber slugs."
item = /obj/item/storage/toolbox/guncase/m90gl
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/basic/carbine,
+ /datum/uplink_item/ammo_nuclear/special/carbine,
+ /datum/uplink_item/ammo_nuclear/basic/carbine/a40mm,
+ )
/datum/uplink_item/ammo_nuclear/basic/carbine
name = ".223 Toploader Magazine (M-90gl)"
@@ -340,6 +385,13 @@
Can be fitted with a suppressor. If anyone asks how that even works, tell them it's Nanotrasen's fault. Comes with \
3 spare magazines; 2 regular magazines and 1 disruptor magazine. Also comes with a suit and tie."
item = /obj/item/storage/briefcase/sniper
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/ap/sniper/penetrator,
+ /datum/uplink_item/ammo_nuclear/basic/sniper,
+ /datum/uplink_item/ammo_nuclear/basic/sniper/disruptor,
+ /datum/uplink_item/ammo_nuclear/incendiary/sniper,
+ /datum/uplink_item/ammo_nuclear/special/sniper/marksman,
+ )
/datum/uplink_item/ammo_nuclear/basic/sniper
name = ".50 BMG Magazine (AMSR)"
@@ -398,6 +450,9 @@
item = /obj/item/gun/ballistic/automatic/smartgun
cost = 2
purchasable_from = UPLINK_SERIOUS_OPS
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/surplus_smg,
+ )
/datum/uplink_item/ammo_nuclear/surplus_smg
name = "Surplus Smart-SMG Magazine (Smartgun)"
@@ -629,6 +684,12 @@
item = /obj/item/storage/box/syndie_kit/cowboy
cost = 18
purchasable_from = UPLINK_SERIOUS_OPS
+ relevant_child_items = list(
+ /datum/uplink_item/ammo/revolver,
+ /datum/uplink_item/ammo_nuclear/basic/revolver,
+ /datum/uplink_item/ammo_nuclear/special/revolver/phasic,
+ /datum/uplink_item/ammo_nuclear/special/revolver/heartseeker,
+ )
// Mech related gear
@@ -650,6 +711,9 @@
for hit-and-run style attacks. Features a scattershot shotgun, armor boosters against melee and ranged attacks, and ion thrusters."
item = /obj/vehicle/sealed/mecha/gygax/dark/loaded
cost = 60
+ relevant_child_items = list(
+ /datum/uplink_item/mech/support_bag/mauler,
+ )
/datum/uplink_item/mech/mauler
name = "Mauler Exosuit"
@@ -657,6 +721,9 @@
and deployable smoke. Comes equipped with an LMG, scattershot carbine, missile rack, and an antiprojectile armor booster."
item = /obj/vehicle/sealed/mecha/marauder/mauler/loaded
cost = 100
+ relevant_child_items = list(
+ /datum/uplink_item/mech/support_bag/mauler,
+ )
// ~~ Mech Support ~~
diff --git a/code/modules/uplink/uplink_items/spy_unique.dm b/code/modules/uplink/uplink_items/spy_unique.dm
index e201b804df5..abc7449e567 100644
--- a/code/modules/uplink/uplink_items/spy_unique.dm
+++ b/code/modules/uplink/uplink_items/spy_unique.dm
@@ -76,6 +76,11 @@
item = /obj/item/gun/ballistic/shotgun/automatic/dual_tube/deadly
cost = SPY_UPPER_COST_THRESHOLD
uplink_item_flags = SYNDIE_ILLEGAL_TECH | SYNDIE_TRIPS_CONTRABAND
+ relevant_child_items = list(
+ /datum/uplink_item/spy_unique/shotgun_ammo,
+ /datum/uplink_item/spy_unique/shotgun_ammo/breacher_slug,
+ /datum/uplink_item/spy_unique/shotgun_ammo/slugs,
+ )
/datum/uplink_item/spy_unique/bulldog_shotgun
name = "Bulldog Shotgun"
@@ -83,6 +88,17 @@
item = /obj/item/gun/ballistic/shotgun/bulldog/unrestricted
cost = SPY_UPPER_COST_THRESHOLD
uplink_item_flags = SYNDIE_ILLEGAL_TECH | SYNDIE_TRIPS_CONTRABAND
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/basic/buck,
+ /datum/uplink_item/ammo_nuclear/basic/donk,
+ /datum/uplink_item/ammo_nuclear/basic/flechette,
+ /datum/uplink_item/ammo_nuclear/basic/slug,
+ /datum/uplink_item/ammo_nuclear/incendiary/dragon,
+ /datum/uplink_item/ammo_nuclear/special/meteor,
+ /datum/uplink_item/spy_unique/shotgun_ammo,
+ /datum/uplink_item/spy_unique/shotgun_ammo/breacher_slug,
+ /datum/uplink_item/spy_unique/shotgun_ammo/slugs,
+ )
/datum/uplink_item/spy_unique/ansem_pistol
name = "Ansem Pistol"
@@ -90,6 +106,36 @@
item = /obj/item/gun/ballistic/automatic/pistol/clandestine
cost = SPY_UPPER_COST_THRESHOLD
uplink_item_flags = SYNDIE_ILLEGAL_TECH | SYNDIE_TRIPS_CONTRABAND
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/basic/m10mm,
+ /datum/uplink_item/ammo_nuclear/ap/m10mm,
+ /datum/uplink_item/ammo_nuclear/hp/m10mm,
+ /datum/uplink_item/ammo_nuclear/incendiary/m10mm,
+ )
+
+/datum/uplink_item/spy_unique/makarov_pistol
+ name = "Makarov Pistol"
+ desc = "A Makarov pistol - Reliable and sturdy."
+ item = /obj/item/gun/ballistic/automatic/pistol
+ cost = SPY_LOWER_COST_THRESHOLD
+ uplink_item_flags = SYNDIE_ILLEGAL_TECH | SYNDIE_TRIPS_CONTRABAND
+ relevant_child_items = list(
+ /datum/uplink_item/ammo/pistol,
+ /datum/uplink_item/ammo/pistolap,
+ /datum/uplink_item/ammo/pistolhp,
+ /datum/uplink_item/ammo/pistolfire,
+ )
+
+/datum/uplink_item/spy_unique/toy_pistol
+ name = "Donksoft Riot Pistol"
+ desc = "A Donksoft Riot Pistol - A toy pistol that fires foam darts at higher than normal velocity."
+ item = /obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine
+ cost = SPY_LOWER_COST_THRESHOLD
+ cost = SPY_LOWER_COST_THRESHOLD
+ uplink_item_flags = SYNDIE_ILLEGAL_TECH | SYNDIE_TRIPS_CONTRABAND
+ relevant_child_items = list(
+ /datum/uplink_item/ammo/toydarts,
+ )
/datum/uplink_item/spy_unique/rocket_launcher
name = "Rocket Launcher"
@@ -97,6 +143,10 @@
item = /obj/item/gun/ballistic/rocketlauncher
cost = SPY_UPPER_COST_THRESHOLD - 1 // It's a meme item
uplink_item_flags = SYNDIE_ILLEGAL_TECH | SYNDIE_TRIPS_CONTRABAND
+ relevant_child_items = list(
+ /datum/uplink_item/ammo_nuclear/ap/rocket,
+ /datum/uplink_item/ammo_nuclear/basic/rocket,
+ )
/datum/uplink_item/spy_unique/shotgun_ammo
name = "Box of Buckshot"
diff --git a/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx b/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx
index ea1e142de19..3549e9fafd8 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx
@@ -56,7 +56,9 @@ export const AntagInfoSpy = () => {
Work together or work against them: The choice is yours, but{' '}
- you cannot share the rewards.
+
+ the same bounty cannot be claimed twice.
+