mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-07 23:42:44 +00:00
## About The Pull Request This PR does a few things but centrally it's all centered around mechanically enforcing what items are and are-not considered contraband in-game. ### What does something being contraband MEAN? Contraband items are visually indistinguishable from non-contraband. If an item is Contraband, it can only be detected in two ways: * After being scanned by an N-Spect scanner, which is a standard item security item, assuming it still has a charge to do so. * Via a scanner gate, which can now be upgraded with an N-spect scanner to allow for it to scan a person and all their contents for contraband. ### What items ARE contraband? Contraband items are intended to be determined both logically and through other relevant examine text. However, here's the short list of items that are considered contraband, reserving the right to expand the list. <details> <summary>In hindsight it's kind of a long list.</summary> * Items that have "contraband" or "illegal" in the name or description. * Items that allow for the player to obtain other illegal items, that are NOT particularly stealthy. * This means that a syndicate uplink is NOT considered contraband, as they're typically hidden on your person as something else. * Stealth items under the syndicate uplink, the revolutionary flash, and some mapped in dangerous items that can come from both syndicate and company-aligned resources are not considered dangerous. * Items that are purchased from cargo after emagging or switching to extended cargo range. * Items purchased FROM syndicate uplinks, the wizard knowledge scroll, or other antagonist shops. * Cursed artifacts/tools magically produced by cultists or heretics. * Items purchased from the blackmarket. * Items purchased from the contraband section of vending machines. * Some drugs and overtly dangerous or criminal byproducts. </details> ### How does this interact with the round? Well, primarily, this is an aid for in-game enforcement of space law. Based on the length of the above list, we have a LONG, LONG list of items in-game that are technically considered, in one way or another, illegal to have on the station, and yet without either metaknowledge of what those items are, or how they're used, security officers lack some of the certainty of how to deal with these kinds of encounters. Additionally to the knowledge aspect of this trait, security officers may now receive a new civilian bounty to collect items that are considered contraband, also giving them an incentive to look for and confiscate contraband that's been found across the station while upholding space law. ### Other minor changes that I rolled into this Security has a bounty for 3 different rechargers, and considering access limitations, most security players aren't going to make this exchange, so I've lowered the required amount down to 1. Adjusted the N-spect scanner's description to match it's new functionality. The Civilian bounty TGUI now has an additional 1 point of padding to make it feel less cramped. https://github.com/tgstation/tgstation/assets/41715314/c3cd4752-b03a-4e0b-959e-1252fcc2369d **Updated as of 6/19/2024:** Additionally, some storage items will block the presence of contraband when going through a contraband aligned scanning gate. These items include the infiltrator modsuit core, storage implant, void cloak, the aptly named smuggler's satchel, and the chameleon kit's backpack. **Updated as of 6/23/2024:** N-spect scanner now has contextual screentips. **Updated as of 6/29/2024:** Scanner gates are now available in all lathes that have a feature specific to how scanner gates function. So, includes cargo (contraband), security (weapons), and medbay (diseases). ## Why It's Good For The Game Originally, this started out as a way to be able to provide more in-character and in-flavor bounties for security officers, because they suck! Most security bounties as they exist right now do the worst possible things from all respective bounties: * They detract away from a job's actual responsibilities as opposed to working with them. * They're best completed while sitting next to your lathe and running items back to the bounty pad. * They exist with such esoteric rarity of high quantity of items that it's miserable to fulfil. As a result, I started work on this as a framework to allow security officers to be further incentivized to collect contraband across the station, either as a result of the gamemode or just through routine patrols across the station. Implementing it as a learning tool for security as well just happened to work out as an additional bonus, and having a function in-game allowing newer or less experienced players to know if an item is considered dangerous or conspicuous also works as a particularly good way to provide information where a player may not know what they're up against. If nothing else, this might be interesting to try, and if not, I'll just snip out the QOL changes from it and we'll see how it goes. Going forward, I am a bit hesitant about the contraband scanner gate mode, and as such, will try working with the admin team to determine if that's a good feature to keep around for game health, while hoping to give it a chance in the fullness of time. ## Changelog 🆑 add: Items spawned via traitor uplinks or are known illegal contraband on the station can now be scanned and identified as such by the N-spect scanners in security. These only applies to overt traitor or antagonist items, and "stealth" items will not be seen as such. add: Scanner gates can now be upgraded by using an N-spect scanner on it to unlock "contraband scanning" mode. add: Security officers can now be offered a bounty to turn in pieces of contraband. add: Some stealthy storage items like storage implants, smuggler's satchels, void cloaks, the infiltrator modsuit, and the chameleon backpack will block the presence of contraband on your person when placed inside. qol: N-spect scanner contextual screentips. balance: Recharger security bounties ask for a quantity of 1, down from 3. qol: security, cargo, and medbay have access to scanner gate boards. /🆑 --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
228 lines
8.3 KiB
Plaintext
228 lines
8.3 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) && (world.time-SSticker.round_start_time > STATION_RENAME_TIME_LIMIT)) //Too confusing if station name gets changed
|
|
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)
|
|
else if(prob(MANIFEST_ERROR_CHANCE)) //Content and item errors could remove the same items, so only one at a time
|
|
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
|
|
|
|
/datum/supply_order/Destroy(force)
|
|
QDEL_NULL(applied_coupon)
|
|
return ..()
|
|
|
|
//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 *= (1 - applied_coupon.discount_pct_off)
|
|
if(paying_account && !pack.goody) //privately purchased and not a goody means 1.1x the cost
|
|
cost *= 1.1
|
|
return round(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)
|
|
if(HAS_TRAIT(container, TRAIT_NO_MANIFEST_CONTENTS_ERROR))
|
|
manifest_paper.errors &= ~MANIFEST_ERROR_CONTENTS
|
|
else
|
|
for(var/iteration in 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(pack.contraband)
|
|
for(var/atom/movable/item_within as anything in crate.get_all_contents())
|
|
ADD_TRAIT(item_within, TRAIT_CONTRABAND, INNATE_TRAIT)
|
|
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
|
|
|
|
/datum/supply_order/proc/append_order(list/new_contents, cost_increase)
|
|
for(var/i as anything in new_contents)
|
|
if(pack.contains[i])
|
|
pack.contains[i] += new_contents[i]
|
|
else
|
|
pack.contains += i
|
|
pack.contains[i] = new_contents[i]
|
|
pack.cost += cost_increase
|
|
|
|
/// Custom type of order who's supply pack can be safely deleted
|
|
/datum/supply_order/disposable
|
|
|
|
/datum/supply_order/disposable/Destroy(force)
|
|
QDEL_NULL(pack)
|
|
return ..()
|
|
|
|
/// Custom material order to append cargo crate value to the final order cost
|
|
/datum/supply_order/disposable/materials
|
|
|
|
/datum/supply_order/disposable/materials/get_final_cost()
|
|
return (..() + CARGO_CRATE_VALUE)
|
|
|
|
#undef MANIFEST_ERROR_CHANCE
|
|
#undef MANIFEST_ERROR_NAME
|
|
#undef MANIFEST_ERROR_CONTENTS
|
|
#undef MANIFEST_ERROR_ITEM
|