mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
Revamps security bounties (#94545)
## About The Pull Request <img width="565" height="201" alt="image" src="https://github.com/user-attachments/assets/f747992c-82d7-4cd2-9d5c-b94b7de37cdd" /> <img width="618" height="108" alt="image" src="https://github.com/user-attachments/assets/8d5c4e25-87ea-4e53-b9e6-e95e26b3e69f" /> - N-spect scanners can no longer print reports - Clown N-spect scanners have been removed as printing reports was their primary function - Security no longer get bounties to loot the brig's equipment. The contraband bounty is still available. - Patrol bounties have been reworked. - A patrol bounty will give you an area and a number of steps that you must take in an area. - To complete the bounty, you must walk to the area and take that many steps. It's that simple. - Your ID card will update you as you progress the bounty. - You are rewarded more for larger areas, and less for teeny tiny areas. - Walking back and forth the same two tiles will not count towards progress. - When done, all you need to do is go back to the civ console and press "send". You don't need to add any items to the pad. - All security officers can get general patrol bounties (service + maint + hallways). Departmental officers can get patrol bounties for their department. - And yes, it tracks if your *id card* moves. This means you can strap your ID card to a drone and it'll count. Get creative if you're lazy. - ID trims how handle bounty generation. This changes very little, besides allowing certain trims for certain jobs to add specific bounties. - There's now setters for bounties and bank accounts. - Fix Bountious Bounty trait by having a `get_reward` ## Why It's Good For The Game Sec bounties to loot a bunch of miscellaneous things from the brig is... odd. All it does is deprive your team of equipment should you need it. On the other hand, patrol bounties are really flavorful, but a bit cumbersome thanks to needing a hand scanner. By integrating the process of patrolling *into* the officer's ID card, it means you can just grab a bounty and go about your business. The idea is that this'll streamline the process of patrolling a bit and make it more natural and fun (well, as fun as "walking around" can be. Which is fun to me...) ## Changelog 🆑 Melbert del: N-spect scanners can no longer print reports. All it does now is scan for contraband. del: Clown N-spect scanners have been removed. del: Security no longer get bounties to loot the brig's equipment. Though the contraband bounty is still available. add: Security's patrol bounties have been reworked. Now, they just require you to walk around an area for a bit. No scanning necessary. refactor: Adds setters for bounties and bank accounts. Report any situations where your bank account is not set correctly. refactor: ID trims now handle bounty generation. Report any situations where you get a weird pool of bounties. fix: Bountious Bounties station trait works again /🆑
This commit is contained in:
@@ -12,8 +12,11 @@
|
||||
..()
|
||||
wanted_types = string_assoc_list(zebra_typecacheof(wanted_types, only_root_path = !include_subtypes))
|
||||
|
||||
/datum/bounty/item/print_required()
|
||||
return "[shipped_count]/[required_count]"
|
||||
|
||||
/datum/bounty/item/can_claim()
|
||||
return ..() && shipped_count >= required_count
|
||||
return shipped_count >= required_count
|
||||
|
||||
/datum/bounty/item/applies_to(obj/shipped)
|
||||
if(!is_type_in_typecache(shipped, wanted_types))
|
||||
@@ -55,7 +58,7 @@
|
||||
return
|
||||
var/choice = tgui_input_list(living_user, "Choose a bounty.", "New Bounty", subtypesof(/datum/bounty))
|
||||
var/datum/bounty/new_chore = text2path("[choice]")
|
||||
id.registered_account.civilian_bounty = new new_chore
|
||||
id.registered_account.set_bounty(new new_chore, id)
|
||||
balloon_alert(user, "new bounty acquired!")
|
||||
playsound(src, 'sound/effects/coin2.ogg', 30, TRUE)
|
||||
qdel(src)
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
/datum/bounty/reagent/can_claim()
|
||||
return ..() && shipped_volume >= required_volume
|
||||
|
||||
/datum/bounty/reagent/print_required()
|
||||
return "[shipped_volume]/[required_volume] u"
|
||||
|
||||
/datum/bounty/reagent/applies_to(obj/shipped)
|
||||
if(!is_reagent_container(shipped))
|
||||
return FALSE
|
||||
|
||||
@@ -1,93 +1,227 @@
|
||||
/datum/bounty/item/security/recharger
|
||||
name = "Weapon Recharger"
|
||||
description = "Nanotrasen military academy is conducting marksmanship exercises. They request that a recharger be shipped."
|
||||
reward = CARGO_CRATE_VALUE * 4
|
||||
required_count = 1
|
||||
wanted_types = list(/obj/machinery/recharger = TRUE)
|
||||
|
||||
/datum/bounty/item/security/pepperspray
|
||||
name = "Pepperspray"
|
||||
description = "We've been having a bad run of riots on Space Station 76. We could use some new pepperspray cans."
|
||||
reward = CARGO_CRATE_VALUE * 6
|
||||
required_count = 4
|
||||
wanted_types = list(/obj/item/reagent_containers/spray/pepper = TRUE)
|
||||
|
||||
/datum/bounty/item/security/prison_clothes
|
||||
name = "Prison Uniforms"
|
||||
description = "TerraGov has been unable to source any new prisoner uniforms, so if you have any spares, we'll take them off your hands."
|
||||
reward = CARGO_CRATE_VALUE * 4
|
||||
required_count = 4
|
||||
wanted_types = list(/obj/item/clothing/under/rank/prisoner = TRUE)
|
||||
|
||||
/datum/bounty/item/security/plates
|
||||
name = "License Plates"
|
||||
description = "As a result of a bad clown car crash, we could use an advance on some of your prisoners' license plates."
|
||||
reward = CARGO_CRATE_VALUE * 2
|
||||
required_count = 10
|
||||
wanted_types = list(/obj/item/stack/license_plates/filled = TRUE)
|
||||
|
||||
/datum/bounty/item/security/earmuffs
|
||||
name = "Earmuffs"
|
||||
description = "Central Command is getting tired of your station's messages. They've ordered that you ship some earmuffs to lessen the annoyance."
|
||||
reward = CARGO_CRATE_VALUE * 2
|
||||
wanted_types = list(/obj/item/clothing/ears/earmuffs = TRUE)
|
||||
|
||||
/datum/bounty/item/security/handcuffs
|
||||
name = "Handcuffs"
|
||||
description = "A large influx of escaped convicts have arrived at Central Command. Now is the perfect time to ship out spare handcuffs (or restraints)."
|
||||
reward = CARGO_CRATE_VALUE * 2
|
||||
required_count = 5
|
||||
wanted_types = list(/obj/item/restraints/handcuffs = TRUE)
|
||||
|
||||
|
||||
///Bounties that require you to perform documentation and inspection of your department to send to centcom.
|
||||
/datum/bounty/item/security/paperwork
|
||||
name = "Routine Security Inspection"
|
||||
description = "Perform a routine security inspection using an N-spect scanner on the following station area:"
|
||||
required_count = 1
|
||||
wanted_types = list(/obj/item/paper/report = TRUE)
|
||||
/datum/bounty/patrol
|
||||
name = "Patrol Station"
|
||||
description = "Perform a routine patrol of %AREA_NAME%. \
|
||||
You must travel at least %AREA_COVERAGE% meters within the area. \
|
||||
Your ID card will update you as you progress."
|
||||
reward = CARGO_CRATE_VALUE * 5
|
||||
var/area/demanded_area
|
||||
allow_duplicate = TRUE
|
||||
|
||||
/datum/bounty/item/security/paperwork/New()
|
||||
///list of areas for security to choose from to perform an inspection. Pulls the list and cross references it to the map to make sure the area is on the map before assigning.
|
||||
var/static/list/possible_areas
|
||||
if(!possible_areas)
|
||||
possible_areas = typecacheof(list(\
|
||||
/area/station/maintenance,\
|
||||
/area/station/commons,\
|
||||
/area/station/service,\
|
||||
/area/station/hallway/primary,\
|
||||
/area/station/security/office,\
|
||||
/area/station/security/prison,\
|
||||
/area/station/security/range,\
|
||||
/area/station/security/checkpoint,\
|
||||
/area/station/security/tram,\
|
||||
/area/station/security/breakroom,\
|
||||
/area/station/security/interrogation))
|
||||
for (var/area_type in possible_areas)
|
||||
if(GLOB.areas_by_type[area_type])
|
||||
continue
|
||||
possible_areas -= area_type
|
||||
demanded_area = pick(possible_areas)
|
||||
name = name + ": [initial(demanded_area.name)]"
|
||||
description = initial(description) + " [initial(demanded_area.name)]"
|
||||
/// Ref to the component applied to the ID card to track movement.
|
||||
VAR_PRIVATE/datum/tracker
|
||||
|
||||
/datum/bounty/item/security/paperwork/applies_to(obj/O)
|
||||
. = ..()
|
||||
if(!istype(O, /obj/item/paper/report))
|
||||
return FALSE
|
||||
var/obj/item/paper/report/slip = O
|
||||
if(istype(slip.scanned_area, demanded_area))
|
||||
return TRUE
|
||||
return FALSE
|
||||
/// Typepath of what area we want patrolled.
|
||||
VAR_FINAL/area/demanded_area
|
||||
/// How much area coverage is needed to complete the patrol.
|
||||
VAR_FINAL/needed_coverage = -1
|
||||
/// List of turf IDs that have been walked on during the patrol.
|
||||
VAR_FINAL/list/walked_turfs
|
||||
/// Number 0-5 tracking which progress alerts have been sent.
|
||||
VAR_FINAL/alerted = 0
|
||||
|
||||
/datum/bounty/item/security/contraband
|
||||
/datum/bounty/patrol/New()
|
||||
demanded_area = pick(get_patrol_area_types() & GLOB.areas_by_type)
|
||||
|
||||
var/total_coverage = 0
|
||||
for(var/turf/open/floor/walkable in GLOB.areas_by_type[demanded_area].get_turfs_from_all_zlevels())
|
||||
total_coverage += 1
|
||||
|
||||
needed_coverage = round(total_coverage * rand(4, 8) * 0.1, 1)
|
||||
|
||||
name += ": [initial(demanded_area.name)]"
|
||||
description = replacetext(description, "%AREA_NAME%", initial(demanded_area.name))
|
||||
description = replacetext(description, "%AREA_COVERAGE%", needed_coverage)
|
||||
|
||||
// scale the reward based on how big the area is, so you don't feel like you're wasting time
|
||||
// central primary hallway can have somewhere in the ballpark of 500 turfs
|
||||
// but something like the bar only sits around 100-200
|
||||
reward *= (needed_coverage / 100)
|
||||
|
||||
/datum/bounty/patrol/can_get()
|
||||
// only give out bounties worth completing.
|
||||
return needed_coverage >= 20
|
||||
|
||||
/datum/bounty/patrol/proc/get_patrol_area_types()
|
||||
return typecacheof(list(
|
||||
/area/station/commons,
|
||||
/area/station/hallway,
|
||||
/area/station/maintenance,
|
||||
/area/station/security/checkpoint,
|
||||
/area/station/security/prison,
|
||||
/area/station/service,
|
||||
))
|
||||
|
||||
/datum/bounty/patrol/proc/get_progress()
|
||||
var/progress = 0
|
||||
for(var/turf_id, count in walked_turfs)
|
||||
progress += count
|
||||
return progress
|
||||
|
||||
/datum/bounty/patrol/print_required()
|
||||
return "[get_progress()]/[needed_coverage] meters"
|
||||
|
||||
/datum/bounty/patrol/can_claim()
|
||||
return get_progress() >= needed_coverage
|
||||
|
||||
/datum/bounty/patrol/on_selected(obj/item/card/id/id_card)
|
||||
start_tracking(id_card)
|
||||
|
||||
/datum/bounty/patrol/proc/start_tracking(obj/item/card/id/id_card)
|
||||
tracker = AddComponent(/datum/component/connect_containers, id_card, list(COMSIG_MOVABLE_MOVED = PROC_REF(on_card_moved)))
|
||||
RegisterSignal(id_card, COMSIG_MOVABLE_MOVED, PROC_REF(on_card_moved))
|
||||
|
||||
/datum/bounty/patrol/on_reset(obj/item/card/id/id_card)
|
||||
stop_tracking(id_card)
|
||||
|
||||
/datum/bounty/patrol/proc/stop_tracking(obj/item/card/id/id_card)
|
||||
QDEL_NULL(tracker)
|
||||
UnregisterSignal(id_card, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
/datum/bounty/patrol/proc/on_card_moved(atom/movable/moving, atom/old_loc, ...)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/turf/new_turf = get_turf(moving)
|
||||
if(!isfloorturf(new_turf) || new_turf == get_turf(old_loc) || new_turf.loc.type != demanded_area)
|
||||
return
|
||||
|
||||
var/turf_id = "[new_turf.x],[new_turf.y],[new_turf.z]"
|
||||
// you can retread the same tiles, but only up to three times.
|
||||
// prevents cheese (walking between the same two tiles over and over),
|
||||
// but still allows smaller rooms to be completable without being frustrating.
|
||||
if(LAZYACCESS(walked_turfs, turf_id) >= 3)
|
||||
return
|
||||
|
||||
var/obj/item/card/id/id_card
|
||||
if(isliving(moving))
|
||||
var/mob/living/living_mover = moving
|
||||
id_card = living_mover.get_idcard()
|
||||
else if(isidcard(moving))
|
||||
id_card = moving
|
||||
if(isnull(id_card))
|
||||
id_card = locate() in moving.get_all_contents()
|
||||
|
||||
if(id_card?.registered_account?.civilian_bounty != src)
|
||||
return
|
||||
|
||||
LAZYADDASSOC(walked_turfs, turf_id, 1)
|
||||
var/progress = get_progress()
|
||||
if(alerted == 0)
|
||||
alerted = 1
|
||||
id_card.registered_account.bank_card_talk("Patrol started. \
|
||||
Travel [needed_coverage] meters in the area to complete your patrol.", force = TRUE)
|
||||
return
|
||||
|
||||
var/progress_percent = progress / needed_coverage
|
||||
if(progress_percent >= 0.25 && alerted < 2)
|
||||
alerted = 2
|
||||
id_card.registered_account.bank_card_talk("Patrol 25% complete.", force = TRUE)
|
||||
return
|
||||
|
||||
if(progress_percent >= 0.5 && alerted < 3)
|
||||
alerted = 3
|
||||
id_card.registered_account.bank_card_talk("Patrol 50% complete.", force = TRUE)
|
||||
return
|
||||
|
||||
if(progress_percent >= 0.75 && alerted < 4)
|
||||
alerted = 4
|
||||
id_card.registered_account.bank_card_talk("Patrol 75% complete.", force = TRUE)
|
||||
return
|
||||
|
||||
if(progress >= needed_coverage && alerted < 5)
|
||||
alerted = 5
|
||||
id_card.registered_account.bank_card_talk("Patrol complete. \
|
||||
Return to the bounty terminal to claim your reward.", force = TRUE)
|
||||
stop_tracking(id_card) // don't need this anymore
|
||||
return
|
||||
|
||||
/datum/bounty/patrol/supply
|
||||
name = "Patrol Cargo"
|
||||
allow_duplicate = FALSE
|
||||
|
||||
/datum/bounty/patrol/supply/get_patrol_area_types()
|
||||
return typecacheof(list(
|
||||
/area/station/cargo/breakroom,
|
||||
/area/station/cargo/lobby,
|
||||
/area/station/cargo/lower,
|
||||
/area/station/cargo/mining_breakroom,
|
||||
/area/station/cargo/miningdock,
|
||||
/area/station/cargo/miningfoundry,
|
||||
/area/station/cargo/miningoffice,
|
||||
/area/station/cargo/office,
|
||||
/area/station/cargo/sorting,
|
||||
/area/station/cargo/storage,
|
||||
/area/station/cargo/warehouse,
|
||||
/area/station/maintenance/department/cargo,
|
||||
)) + list(
|
||||
/area/station/cargo, // base cargo type as well
|
||||
/area/mine/lounge, // and public mining on lavaland
|
||||
)
|
||||
|
||||
/datum/bounty/patrol/medical
|
||||
name = "Patrol Medical"
|
||||
allow_duplicate = FALSE
|
||||
|
||||
/datum/bounty/patrol/medical/get_patrol_area_types()
|
||||
return typecacheof(list(
|
||||
/area/station/maintenance/department/medical,
|
||||
/area/station/medical/abandoned,
|
||||
/area/station/medical/break_room,
|
||||
/area/station/medical/cryo,
|
||||
/area/station/medical/exam_room,
|
||||
/area/station/medical/lower,
|
||||
/area/station/medical/medbay,
|
||||
/area/station/medical/morgue,
|
||||
/area/station/medical/office,
|
||||
/area/station/medical/storage,
|
||||
/area/station/medical/surgery,
|
||||
/area/station/medical/treatment_center,
|
||||
)) + list(
|
||||
/area/station/medical, // base medical type as well
|
||||
)
|
||||
|
||||
/datum/bounty/patrol/science
|
||||
name = "Patrol Science"
|
||||
allow_duplicate = FALSE
|
||||
|
||||
/datum/bounty/patrol/science/get_patrol_area_types()
|
||||
return typecacheof(list(
|
||||
/area/station/science/auxlab,
|
||||
/area/station/science/breakroom,
|
||||
/area/station/science/explab,
|
||||
/area/station/science/lab,
|
||||
/area/station/science/lobby,
|
||||
/area/station/science/lower,
|
||||
/area/station/science/research,
|
||||
/area/station/science/research/abandoned,
|
||||
)) + list(
|
||||
/area/station/science, // base science type as well
|
||||
/area/station/maintenance/department/science, // and only base sci maint - not xenobio maint
|
||||
)
|
||||
|
||||
/datum/bounty/patrol/engineering
|
||||
name = "Patrol Engineering"
|
||||
allow_duplicate = FALSE
|
||||
|
||||
/datum/bounty/patrol/engineering/get_patrol_area_types()
|
||||
return typecacheof(list(
|
||||
/area/station/engineering/break_room,
|
||||
/area/station/engineering/lobby,
|
||||
/area/station/engineering/main,
|
||||
/area/station/engineering/storage,
|
||||
/area/station/engineering/storage_shared,
|
||||
/area/station/maintenance/department/engine,
|
||||
/area/station/maintenance/department/electrical,
|
||||
)) + list(
|
||||
/area/station/engineering, // plus base engineering
|
||||
/area/station/engineering/atmos, // and base atmospherics
|
||||
)
|
||||
|
||||
/datum/bounty/item/contraband
|
||||
name = "Confiscated Contraband"
|
||||
description = "The Syndicate is constantly acting to subvert crewmates of Nanotrasen-affiliated stations. Ship us your latest batch of confiscated contraband."
|
||||
reward = CARGO_CRATE_VALUE * 4
|
||||
required_count = 5
|
||||
wanted_types = list(/obj/item = TRUE)
|
||||
|
||||
/datum/bounty/item/security/contraband/applies_to(obj/O)
|
||||
/datum/bounty/item/contraband/applies_to(obj/O)
|
||||
return HAS_TRAIT(O, TRAIT_CONTRABAND)
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
description = "Nanotrasen is interested in a virus with a [stat_name] stat of exactly [stat_value]. Central Command will pay handsomely for such a virus."
|
||||
reward += rand(0, 4) * CARGO_CRATE_VALUE
|
||||
|
||||
/datum/bounty/virus/print_required()
|
||||
return "At least 1u"
|
||||
|
||||
/datum/bounty/virus/can_claim()
|
||||
return ..() && shipped
|
||||
|
||||
@@ -61,4 +64,3 @@
|
||||
|
||||
/datum/bounty/virus/transmit/accepts_virus(datum/disease/advance/virus)
|
||||
return virus.totalTransmittable() == stat_value
|
||||
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
/// How many jobs have bounties, minus the random civ bounties. PLEASE INCREASE THIS NUMBER AS MORE DEPTS ARE ADDED TO BOUNTIES.
|
||||
#define MAXIMUM_BOUNTY_JOBS 14
|
||||
|
||||
/datum/bounty
|
||||
var/name
|
||||
var/description
|
||||
var/reward = 1000 // In credits.
|
||||
var/claimed = FALSE
|
||||
var/high_priority = FALSE
|
||||
VAR_PROTECTED/reward = CARGO_CRATE_VALUE * 5 // In credits.
|
||||
var/allow_duplicate = FALSE
|
||||
|
||||
/// Can this bounty be claimed right now?
|
||||
/datum/bounty/proc/can_claim()
|
||||
return !claimed
|
||||
|
||||
/// Called when the claim button is clicked. Override to provide fancy rewards.
|
||||
/datum/bounty/proc/claim()
|
||||
if(can_claim())
|
||||
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
|
||||
if(D)
|
||||
D.adjust_money(reward * SSeconomy.bounty_modifier)
|
||||
claimed = TRUE
|
||||
return FALSE
|
||||
|
||||
/// If an item in question can satisfy the bounty.
|
||||
/datum/bounty/proc/applies_to(obj/O)
|
||||
/datum/bounty/proc/applies_to(obj/shipped)
|
||||
return FALSE
|
||||
|
||||
/// Called when an object is sent on the bounty pad.
|
||||
/datum/bounty/proc/ship(obj/O)
|
||||
/datum/bounty/proc/ship(obj/shipped)
|
||||
return
|
||||
|
||||
/// Formats the text for what is required to complete the bounty, for display purposes.
|
||||
/datum/bounty/proc/print_required()
|
||||
return ""
|
||||
|
||||
/// Returns the adjusted reward for this bounty, taking into account any global modifiers.
|
||||
/datum/bounty/proc/get_bounty_reward()
|
||||
return reward * SSeconomy.bounty_modifier
|
||||
|
||||
/// Called when this bounty is selected by the passed ID card
|
||||
/datum/bounty/proc/on_selected(obj/item/card/id/id_card)
|
||||
return
|
||||
|
||||
/// Called when this bounty is successfully claimed by the passed ID card
|
||||
/datum/bounty/proc/on_claimed(obj/item/card/id/id_card)
|
||||
return
|
||||
|
||||
/// Called when this bounty is reset from the passed ID card, either from successful claim or from being replaced by another bounty
|
||||
/datum/bounty/proc/on_reset(obj/item/card/id/id_card)
|
||||
return
|
||||
|
||||
/** Returns a new bounty of random type, but does not add it to GLOB.bounties_list.
|
||||
@@ -49,7 +58,10 @@
|
||||
if(CIV_JOB_CHEF)
|
||||
chosen_type = pick(subtypesof(/datum/bounty/item/chef) + subtypesof(/datum/bounty/reagent/chef))
|
||||
if(CIV_JOB_SEC)
|
||||
chosen_type = pick(subtypesof(/datum/bounty/item/security))
|
||||
if(prob(75))
|
||||
chosen_type = /datum/bounty/patrol
|
||||
else
|
||||
chosen_type = /datum/bounty/item/contraband
|
||||
if(CIV_JOB_DRINK)
|
||||
if(prob(50))
|
||||
chosen_type = /datum/bounty/reagent/simple_drink
|
||||
@@ -85,5 +97,3 @@
|
||||
else
|
||||
qdel(bounty_ref)
|
||||
return bounty_ref
|
||||
|
||||
#undef MAXIMUM_BOUNTY_JOBS
|
||||
|
||||
@@ -84,22 +84,6 @@
|
||||
stock_max = 3
|
||||
availability_prob = 30
|
||||
|
||||
/**
|
||||
* # Fake N-spect scanner black market entry
|
||||
*/
|
||||
/datum/market_item/tool/fake_scanner
|
||||
name = "Clowny N-spect scanner"
|
||||
desc = "This UPGRADED N-spect scanner can play FIVE HIGH-QUALITY SOUNDS (fork required for sound adjustment not included) and print reports \
|
||||
LIGHTNING FAST (screwdriver necessary to activate maximum speed not included). We make no claims as to the usefulness of the reports printed by this. \
|
||||
Any and all implied warranties are void if the device is touched, moved, kicked, thrown or modified with bananium sheets. Batteries included. \
|
||||
Crowbar necessary to change batteries and adjust settings not included."
|
||||
item = /obj/item/inspector/clown
|
||||
|
||||
price_min = CARGO_CRATE_VALUE * 1.15
|
||||
price_max = CARGO_CRATE_VALUE * 1.615
|
||||
stock_max = 2
|
||||
availability_prob = 50
|
||||
|
||||
/datum/market_item/tool/program_disk
|
||||
name = "Bootleg Data Disk"
|
||||
desc = "A data disk containing EXCLUSIVE and LIMITED modular programs. Legally, we're not allowed to tell you how we acquired them."
|
||||
|
||||
Reference in New Issue
Block a user