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:
MrMelbert
2026-01-11 14:20:11 -05:00
committed by GitHub
parent 9f4bd535c3
commit b804e1df79
22 changed files with 446 additions and 592 deletions
-14
View File
@@ -1,20 +1,6 @@
// Used to stringify message targets before sending the signal datum.
#define STRINGIFY_PDA_TARGET(name, job) "[name] ([job])"
//N-spect scanner defines
#define INSPECTOR_PRINT_SOUND_MODE_NORMAL 1
#define INSPECTOR_PRINT_SOUND_MODE_CLASSIC 2
#define INSPECTOR_PRINT_SOUND_MODE_HONK 3
#define INSPECTOR_PRINT_SOUND_MODE_FAFAFOGGY 4
#define BANANIUM_CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST 4
#define CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST 4
#define INSPECTOR_ENERGY_USAGE_HONK (0.015 * STANDARD_CELL_CHARGE)
#define INSPECTOR_ENERGY_USAGE_NORMAL (0.005 * STANDARD_CELL_CHARGE)
#define INSPECTOR_ENERGY_USAGE_LOW (0.001 * STANDARD_CELL_CHARGE)
#define INSPECTOR_TIME_MODE_SLOW 1
#define INSPECTOR_TIME_MODE_FAST 2
#define INSPECTOR_TIME_MODE_HONK 3
// Health scan modes
/// Healthscan prints health of the target
#define SCANNER_CONDENSED 0
+2
View File
@@ -70,6 +70,8 @@
#define CIV_JOB_BITRUN 14
#define CIV_JOB_RANDOM 15
#define MAXIMUM_BOUNTY_JOBS 14 //Should be equal to the highest numbered non-random job above.
//These defines are to be used to with the payment component, determines which lines will be used during a transaction. If in doubt, go with clinical.
#define PAYMENT_CLINICAL "clinical"
#define PAYMENT_FRIENDLY "friendly"
@@ -254,21 +254,6 @@
result = /obj/item/clothing/gloves/tackler/offbrand
category = CAT_EQUIPMENT
/**
* Recipe used for upgrading fake N-spect scanners to bananium HONK-spect scanners
*/
/datum/crafting_recipe/clown_scanner_upgrade
name = "Bananium HONK-spect scanner"
result = /obj/item/inspector/clown/bananium
reqs = list(
/obj/item/inspector/clown = 1,
/obj/item/stack/sticky_tape = 3,
/obj/item/stack/sheet/mineral/bananium = 5,
) //the chainsaw of prank tools
tool_paths = list(/obj/item/bikehorn)
time = 40 SECONDS
category = CAT_EQUIPMENT
/datum/crafting_recipe/rebar_quiver
name = "Rebar Storage Quiver"
result = /obj/item/storage/bag/rebar_quiver
+12
View File
@@ -1133,6 +1133,8 @@
var/department_access = list()
/// List of bonus departmental accesses that departmental security officers can in relation to how many overall security officers there are if the scaling system is set up. These can otherwise be granted via config settings.
var/elevated_access = list()
/// Typepath for unique patrol bounty available to this type of officer
var/patrol_type
/datum/id_trim/job/security_officer/refresh_trim_access()
. = ..()
@@ -1166,6 +1168,12 @@
if(CONFIG_GET(number/depsec_access_level) == ALWAYS_GETS_ACCESS)
access |= elevated_access
/datum/id_trim/job/security_officer/get_random_bounty_type(input_bounty_type)
if(input_bounty_type != CIV_JOB_SEC || prob(33) || isnull(patrol_type))
return ..()
return patrol_type
/datum/id_trim/job/security_officer/supply
assignment = JOB_SECURITY_OFFICER_SUPPLY
subdepartment_color = COLOR_CARGO_BROWN
@@ -1179,6 +1187,7 @@
ACCESS_AUX_BASE,
ACCESS_MINING_STATION,
)
patrol_type = /datum/bounty/patrol/supply
/datum/id_trim/job/security_officer/engineering
assignment = JOB_SECURITY_OFFICER_ENGINEERING
@@ -1193,6 +1202,7 @@
ACCESS_ENGINE_EQUIP,
ACCESS_TCOMMS,
)
patrol_type = /datum/bounty/patrol/engineering
/datum/id_trim/job/security_officer/medical
assignment = JOB_SECURITY_OFFICER_MEDICAL
@@ -1209,6 +1219,7 @@
ACCESS_PARAMEDIC,
)
honorifics = list("Orderly", "Officer")
patrol_type = /datum/bounty/patrol/medical
/datum/id_trim/job/security_officer/science
assignment = JOB_SECURITY_OFFICER_SCIENCE
@@ -1225,6 +1236,7 @@
ACCESS_ROBOTICS,
ACCESS_XENOBIOLOGY,
)
patrol_type = /datum/bounty/patrol/science
/datum/id_trim/job/shaft_miner
assignment = JOB_SHAFT_MINER
+1 -1
View File
@@ -1,4 +1,4 @@
// When adding a new area to the security areas, make sure to add it to /datum/bounty/item/security/paperwork as well!
// When adding a new area to the security areas, make sure to add it to /datum/bounty/patrol as well!
/area/station/security
name = "Security"
+97 -27
View File
@@ -134,9 +134,10 @@
stop_sending()
if(current_bounty.can_claim())
//Pay for the bounty with the ID's department funds.
status_report += "Bounty completed! Please give your bounty cube to cargo for your automated payout shortly."
status_report += " Bounty completed! Please give your bounty cube to cargo for your automated payout shortly."
SSblackbox.record_feedback("tally", "bounties_completed", 1, current_bounty.type)
id_account.reset_bounty()
current_bounty.on_claimed(inserted_scan_id)
id_account.reset_bounty(inserted_scan_id)
SSeconomy.civ_bounty_tracker++
var/obj/item/bounty_cube/reward = new /obj/item/bounty_cube(drop_location())
@@ -157,36 +158,106 @@
var/time_left = DisplayTimeText(COOLDOWN_TIMELEFT(id_account, bounty_timer), round_seconds_to = 1)
balloon_alert(user, "try again in [time_left]!")
return FALSE
if(!id_account.account_job)
if(!inserted_scan_id.trim)
say("Requesting ID card has no job assignment registered!")
return FALSE
var/list/datum/bounty/crumbs = generate_bounty_list(id_account.account_job.bounty_types)
var/list/datum/bounty/crumbs = inserted_scan_id.trim.generate_bounty_list()
COOLDOWN_START(id_account, bounty_timer, (5 MINUTES) - cooldown_reduction)
id_account.bounties = crumbs
/**
* Generates a list of bounties for use with the civilian bounty pad.
*
* @param bounty_types the define taken from a job for selection of a random_bounty() proc.
* @param bounty_rolls the number of bounties to be selected from.
* @param assistant_failsafe Do we guarentee one assistant bounty per generated list? Used for non-assistant jobs to give an easier alternative to that job's default bounties.
*/
/obj/machinery/computer/piratepad_control/civilian/proc/generate_bounty_list(bounty_types, bounty_rolls = 3, assistant_failsafe = TRUE)
/datum/id_trim/proc/generate_bounty_list(bounty_rolls = 3, assistant_failsafe = TRUE)
var/datum/job/our_job = find_job()
var/bounty_type = our_job?.bounty_types || CIV_JOB_RANDOM
var/list/rolling_list = list()
if(assistant_failsafe)
rolling_list += random_bounty(CIV_JOB_BASIC)
while(bounty_rolls > 1)
var/datum/bounty/potential_bounty = random_bounty(bounty_types)
var/repeats_bool = FALSE
for(var/datum/iterator in rolling_list)
if(iterator.type == potential_bounty.type)
repeats_bool = TRUE
if(repeats_bool)
var/random_assistant = get_random_bounty_type(CIV_JOB_BASIC)
var/datum/bounty/assistant_bounty = new random_assistant()
if(assistant_bounty.can_get())
rolling_list += assistant_bounty
else
qdel(assistant_bounty)
var/attempts = 20
while(length(rolling_list) < bounty_rolls && attempts > 0)
var/random_job = get_random_bounty_type(attempts <= 5 ? CIV_JOB_BASIC : bounty_type)
var/datum/bounty/job_bounty = new random_job()
attempts -= 1
if(!job_bounty.can_get() || has_duplicate_bounty(rolling_list, job_bounty))
qdel(job_bounty)
continue
rolling_list += potential_bounty
bounty_rolls -= 1
rolling_list += job_bounty
return rolling_list
/// Helper to see if there's a duplicate bounty in a list of bounties
/datum/id_trim/proc/has_duplicate_bounty(list/datum/bounty/bounty_list, datum/bounty/check_bounty)
PRIVATE_PROC(TRUE)
for(var/datum/bounty/existing as anything in bounty_list)
if(existing.type != check_bounty.type)
continue
if(existing.allow_duplicate && check_bounty.allow_duplicate)
continue
return TRUE
return FALSE
/// Returns a /datum/bounty typepath for a given bounty type
/datum/id_trim/proc/get_random_bounty_type(input_bounty_type)
if(!input_bounty_type || input_bounty_type == CIV_JOB_RANDOM)
input_bounty_type = rand(1, MAXIMUM_BOUNTY_JOBS)
switch(input_bounty_type)
if(CIV_JOB_BASIC)
return pick(subtypesof(/datum/bounty/item/assistant))
if(CIV_JOB_ROBO)
return pick(subtypesof(/datum/bounty/item/mech))
if(CIV_JOB_CHEF)
return pick(subtypesof(/datum/bounty/item/chef) + subtypesof(/datum/bounty/reagent/chef))
if(CIV_JOB_SEC)
if(prob(75))
return /datum/bounty/patrol
return /datum/bounty/item/contraband
if(CIV_JOB_DRINK)
if(prob(50))
return /datum/bounty/reagent/simple_drink
return /datum/bounty/reagent/complex_drink
if(CIV_JOB_CHEM)
if(prob(50))
return /datum/bounty/reagent/chemical_simple
return/datum/bounty/reagent/chemical_complex
if(CIV_JOB_VIRO)
return pick(subtypesof(/datum/bounty/virus))
if(CIV_JOB_SCI)
if(prob(50))
return pick(subtypesof(/datum/bounty/item/science))
return pick(subtypesof(/datum/bounty/item/slime))
if(CIV_JOB_ENG)
return pick(subtypesof(/datum/bounty/item/engineering))
if(CIV_JOB_MINE)
return pick(subtypesof(/datum/bounty/item/mining))
if(CIV_JOB_MED)
return pick(subtypesof(/datum/bounty/item/medical))
if(CIV_JOB_GROW)
return pick(subtypesof(/datum/bounty/item/botany))
if(CIV_JOB_ATMOS)
return pick(subtypesof(/datum/bounty/item/atmospherics))
if(CIV_JOB_BITRUN)
return pick(subtypesof(/datum/bounty/item/bitrunning))
stack_trace("Failed to get random bounty type for input type [input_bounty_type]")
return null
/**
* Proc that assigned a civilian bounty to an ID card, from the list of potential bounties that that bank account currently has available.
* Available choices are assigned during add_bounties, and one is locked in here.
@@ -198,7 +269,7 @@
if(!id_account?.bounties?[choice])
playsound(loc, 'sound/machines/synth/synth_no.ogg', 40 , TRUE)
return
id_account.civilian_bounty = id_account.bounties[choice]
id_account.set_bounty(id_account.bounties[choice], inserted_scan_id)
id_account.bounties = null
SSblackbox.record_feedback("tally", "bounties_assigned", 1, id_account.civilian_bounty.type)
return id_account.civilian_bounty
@@ -218,18 +289,17 @@
if(inserted_scan_id.registered_account.civilian_bounty)
data["id_bounty_info"] = inserted_scan_id.registered_account.civilian_bounty.description
data["id_bounty_num"] = inserted_scan_id.registered_account.bounty_num()
data["id_bounty_value"] = (inserted_scan_id.registered_account.civilian_bounty.reward) * (CIV_BOUNTY_SPLIT/100)
data["id_bounty_value"] = (inserted_scan_id.registered_account.civilian_bounty.get_bounty_reward()) * (CIV_BOUNTY_SPLIT / 100)
if(inserted_scan_id.registered_account.bounties)
data["picking"] = TRUE
data["id_bounty_names"] = list(inserted_scan_id.registered_account.bounties[1].name,
inserted_scan_id.registered_account.bounties[2].name,
inserted_scan_id.registered_account.bounties[3].name)
data["id_bounty_infos"] = list(inserted_scan_id.registered_account.bounties[1].description,
inserted_scan_id.registered_account.bounties[2].description,
inserted_scan_id.registered_account.bounties[3].description)
data["id_bounty_values"] = list(inserted_scan_id.registered_account.bounties[1].reward * (CIV_BOUNTY_SPLIT/100),
inserted_scan_id.registered_account.bounties[2].reward * (CIV_BOUNTY_SPLIT/100),
inserted_scan_id.registered_account.bounties[3].reward * (CIV_BOUNTY_SPLIT/100))
data["id_bounty_names"] = list()
data["id_bounty_infos"] = list()
data["id_bounty_values"] = list()
for(var/datum/bounty/bounty as anything in inserted_scan_id.registered_account.bounties)
data["id_bounty_names"] += bounty.name
data["id_bounty_infos"] += bounty.description
data["id_bounty_values"] += bounty.get_bounty_reward() * (CIV_BOUNTY_SPLIT / 100)
else
data["picking"] = FALSE
@@ -367,7 +437,7 @@
COOLDOWN_START(src, next_nag_time, nag_cooldown)
/obj/item/bounty_cube/proc/set_up(datum/bounty/my_bounty, obj/item/card/id/holder_id)
bounty_value = my_bounty.reward
bounty_value = my_bounty.get_bounty_reward()
bounty_name = my_bounty.name
bounty_holder = holder_id.registered_name
bounty_holder_job = holder_id.assignment
+30 -16
View File
@@ -142,7 +142,7 @@
. = ..()
var/datum/bank_account/blank_bank_account = new("Unassigned", SSjob.get_job_type(/datum/job/unassigned), player_account = FALSE)
registered_account = blank_bank_account
set_account(blank_bank_account)
registered_account.replaceable = TRUE
// Applying the trim updates the label and icon, so don't do this twice.
@@ -164,10 +164,8 @@
ADD_TRAIT(src, TRAIT_TASTEFULLY_THICK_ID_CARD, ROUNDSTART_TRAIT)
/obj/item/card/id/Destroy()
if (registered_account)
LAZYREMOVE(registered_account.bank_cards, src)
if (my_store)
QDEL_NULL(my_store)
clear_account()
QDEL_NULL(my_store)
if (isitem(loc))
UnregisterSignal(loc, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
return ..()
@@ -475,8 +473,31 @@
// Hard reset access
access.Cut()
/// Sets the bank account for the ID card.
/obj/item/card/id/proc/set_account(datum/bank_account/account, transfer_funds = FALSE)
if(registered_account == account)
return
if(!isnull(registered_account))
if(transfer_funds)
account?.transfer_money(registered_account, registered_account.account_balance, "Account transfer")
clear_account()
if(isnull(account))
return
if(src in account.bank_cards)
stack_trace("Despite [src] not being registered to [account], the account already has it within the bank_cards list.")
registered_account = account
LAZYOR(registered_account.bank_cards, src)
registered_account.civilian_bounty?.on_selected(src)
/// Clears the economy account from the ID card.
/obj/item/card/id/proc/clear_account()
if(isnull(registered_account))
return
registered_account.civilian_bounty?.on_reset(src)
LAZYREMOVE(registered_account.bank_cards, src)
registered_account = null
@@ -750,7 +771,6 @@
/// Attempts to set a new bank account on the ID card.
/obj/item/card/id/proc/set_new_account(mob/living/user)
. = FALSE
var/datum/bank_account/old_account = registered_account
if(loc != user)
to_chat(user, span_warning("You must be holding the ID to continue!"))
return FALSE
@@ -767,11 +787,7 @@
if(isnull(account))
to_chat(user, span_warning("The account ID number provided is invalid."))
return FALSE
if(old_account)
LAZYREMOVE(old_account.bank_cards, src)
account.account_balance += old_account.account_balance
LAZYADD(account.bank_cards, src)
registered_account = account
set_account(account, transfer_funds = TRUE)
to_chat(user, span_notice("The provided account has been linked to this ID card. It contains [account.account_balance] [MONEY_NAME]."))
return TRUE
@@ -1097,8 +1113,7 @@
. = ..()
var/datum/bank_account/department_account = SSeconomy.get_dep_account(department_ID)
if(department_account)
registered_account = department_account
LAZYOR(department_account.bank_cards, src)
set_account(department_account)
name = "departmental card ([department_name])"
desc = "Provides access to the [department_name]."
SSeconomy.dep_cards += src
@@ -1470,7 +1485,7 @@
/obj/item/card/id/advanced/debug/Initialize(mapload)
. = ..()
registered_account = new(player_account = FALSE)
set_account(new /datum/bank_account(player_account = FALSE))
registered_account.account_id = ADMIN_ACCOUNT_ID // this is so bank_card_talk() can work.
registered_account.account_job = SSjob.get_job_type(/datum/job/admin)
registered_account.account_balance += 999999 // MONEY! We add more money to the account every time we spawn because it's a debug item and infinite money whoopie
@@ -1971,8 +1986,7 @@
var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[owner.account_id]"]
if(account)
LAZYADD(account.bank_cards, src)
registered_account = account
set_account(account)
to_chat(user, span_notice("Your account number has been automatically assigned."))
/obj/item/card/id/advanced/chameleon/add_item_context(obj/item/source, list/context, atom/target, mob/living/user,)
+16 -334
View File
@@ -4,13 +4,12 @@
/**
* # N-spect scanner
*
* Creates reports for area inspection bounties.
* Determines if an item or its contents are contraband.
*/
/obj/item/inspector
name = "\improper N-spect scanner"
desc = "Central Command standard issue inspection device. \
Performs wide area scan reports for inspectors to use to verify the security and integrity of the station. \
Can additionally be used for precision scans to determine if an item contains, or is itself, contraband."
Used for precision scans to determine if an item contains, or is itself, contraband."
icon = 'icons/obj/devices/scanner.dmi'
icon_state = "inspector"
worn_icon_state = "salestagger"
@@ -25,18 +24,10 @@
sound_vary = TRUE
pickup_sound = SFX_GENERIC_DEVICE_PICKUP
drop_sound = SFX_GENERIC_DEVICE_DROP
///How long it takes to print on time each mode, ordered NORMAL, FAST, HONK
var/list/time_list = list(5 SECONDS, 1 SECONDS, 0.1 SECONDS)
///Which print time mode we're on.
var/time_mode = INSPECTOR_TIME_MODE_SLOW
///determines the sound that plays when printing a report
var/print_sound_mode = INSPECTOR_PRINT_SOUND_MODE_NORMAL
///Power cell used to power the scanner. Paths g
var/obj/item/stock_parts/power_store/cell = /obj/item/stock_parts/power_store/cell/crap
///Cell cover status
var/cell_cover_open = FALSE
///Energy used per print.
var/energy_per_print = INSPECTOR_ENERGY_USAGE_NORMAL
///Does this item scan for contraband correctly? If not, will provide a flipped response.
var/scans_correctly = TRUE
@@ -57,29 +48,22 @@
/obj/item/inspector/get_cell()
return cell
/obj/item/inspector/attack_self(mob/user)
. = ..()
if(do_after(user, time_list[time_mode], target = user, progress=TRUE))
print_report(user)
/obj/item/inspector/crowbar_act(mob/living/user, obj/item/tool)
. = ..()
if(user.combat_mode)
return
cell_cover_open = !cell_cover_open
balloon_alert(user, "[cell_cover_open ? "opened" : "closed"] cell cover")
return TRUE
return ITEM_INTERACT_SUCCESS
/obj/item/inspector/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers)
if(cell_cover_open && istype(I, /obj/item/stock_parts/power_store/cell))
/obj/item/inspector/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(cell_cover_open && istype(tool, /obj/item/stock_parts/power_store/cell))
if(cell)
to_chat(user, span_warning("[src] already has a cell installed."))
return
if(user.transferItemToLoc(I, src))
cell = I
return ITEM_INTERACT_BLOCKING
if(user.transferItemToLoc(tool, src))
cell = tool
to_chat(user, span_notice("You successfully install \the [cell] into [src]."))
return
return ..()
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
return NONE
/obj/item/inspector/item_ctrl_click(mob/user)
if(!cell_cover_open || !cell)
@@ -110,22 +94,21 @@
if(cell_cover_open)
balloon_alert(user, "close cover first!")
return ITEM_INTERACT_BLOCKING
if(!cell || !cell.use(INSPECTOR_ENERGY_USAGE_LOW))
if(!cell || !cell.use(0.001 * STANDARD_CELL_CHARGE))
balloon_alert(user, "check cell!")
return ITEM_INTERACT_BLOCKING
if(iscarbon(interacting_with)) // Prevents scanning people
return
return ITEM_INTERACT_BLOCKING
if(contraband_scan(interacting_with, user))
playsound(src, 'sound/machines/uplink/uplinkerror.ogg', 40)
balloon_alert(user, "contraband detected!")
return ITEM_INTERACT_SUCCESS
else
playsound(src, 'sound/machines/ping.ogg', 20)
balloon_alert(user, "clear")
return ITEM_INTERACT_SUCCESS
playsound(src, 'sound/machines/ping.ogg', 20)
balloon_alert(user, "clear")
return ITEM_INTERACT_SUCCESS
/obj/item/inspector/add_context(atom/source, list/context, obj/item/held_item, mob/user)
var/update_context = FALSE
@@ -176,305 +159,4 @@
return FALSE
/**
* Create our report
*
* Arguments:
*/
/obj/item/inspector/proc/create_slip()
var/obj/item/paper/report/slip = new(get_turf(src))
slip.generate_report(get_area(src))
/**
* Prints out a report for bounty purposes, and plays a short audio blip.
*
* Arguments:
*/
/obj/item/inspector/proc/print_report(mob/user)
if(!cell)
to_chat(user, span_info("\The [src] doesn't seem to be on... It feels quite light. Perhaps it lacks a power cell?"))
return
if(cell.charge == 0)
to_chat(user, span_info("\The [src] doesn't seem to be on... Perhaps it ran out of power?"))
return
if(!cell.use(energy_per_print))
if(cell.use(ENERGY_TO_SPEAK))
say("ERROR! POWER CELL CHARGE LEVEL TOO LOW TO PRINT REPORT!")
return
create_slip()
switch(print_sound_mode)
if(INSPECTOR_PRINT_SOUND_MODE_NORMAL)
playsound(src, 'sound/machines/high_tech_confirm.ogg', 50, FALSE)
if(INSPECTOR_PRINT_SOUND_MODE_CLASSIC)
playsound(src, 'sound/items/biddledeep.ogg', 50, FALSE)
if(INSPECTOR_PRINT_SOUND_MODE_HONK)
playsound(src, 'sound/items/bikehorn.ogg', 50, FALSE)
if(INSPECTOR_PRINT_SOUND_MODE_FAFAFOGGY)
playsound(src, pick(list('sound/items/robofafafoggy.ogg', 'sound/items/robofafafoggy2.ogg')), 50, FALSE)
/obj/item/paper/report
name = "encrypted station inspection"
desc = "Contains no information about the station's current status."
icon = 'icons/obj/service/bureaucracy.dmi'
icon_state = "slip"
///What area the inspector scanned when the report was made. Used to verify the security bounty.
var/area/scanned_area
show_written_words = FALSE
/obj/item/paper/report/proc/generate_report(area/scan_area)
scanned_area = scan_area
icon_state = "slipfull"
desc = "Contains detailed information about the station's current status."
var/list/characters = list()
characters += GLOB.alphabet
characters += GLOB.alphabet_upper
characters += GLOB.numerals
var/report_text = random_string(rand(180,220), characters)
report_text += "[prob(50) ? "=" : "=="]" //Based64 encoding
add_raw_text(report_text)
update_appearance()
/obj/item/paper/report/examine(mob/user)
. = ..()
if(scanned_area?.name)
. += span_notice("\The [src] contains data on [scanned_area.name].")
else if(scanned_area)
. += span_notice("\The [src] contains data on a vague area on station, you should throw it away.")
else if(get_total_length())
icon_state = "slipfull"
. += span_notice("Wait a minute, this isn't an encrypted inspection report! You should throw it away.")
else
. += span_notice("Wait a minute, this thing's blank! You should throw it away.")
/**
* # Fake N-spect scanner
*
* A clown variant of the N-spect scanner
*
* This prints fake reports with garbage in them,
* can be set to print them instantly with a screwdriver.
* By default it plays the old "woody" scanning sound, scanning sounds can be cycled by clicking with a multitool.
* Can be crafted into a bananium HONK-spect scanner
*/
/obj/item/inspector/clown
scans_correctly = FALSE
///will only cycle through modes with numbers lower than this
var/max_mode = CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST
///names of modes, ordered first to last
var/list/mode_names = list("normal", "classic", "honk", "fafafoggy")
/obj/item/inspector/clown/attack(mob/living/M, mob/living/user)
. = ..()
print_report(user)
/obj/item/inspector/clown/screwdriver_act(mob/living/user, obj/item/tool)
if(!cell_cover_open)
return ..()
cycle_print_time(user)
return TRUE
/obj/item/inspector/clown/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers)
if(cell_cover_open && istype(I, /obj/item/kitchen/fork))
cycle_sound(user)
return
return ..()
/obj/item/inspector/clown/examine(mob/user)
. = ..()
if(cell_cover_open)
. += "Two weird settings dials are visible within the battery compartment."
/obj/item/inspector/clown/examine_more(mob/user)
if(!cell_cover_open)
return ..()
. = list(span_notice("Both setting dials are flush with the surface of the battery compartment, and seem to be impossible to move with bare hands."))
. += "\t[span_info("The first dial is labeled \"SPEED\" and looks a bit like a <strong>screw</strong> head.")]"
. += "\t[span_info("The second dial is labeled \"SOUND\". It has four small holes in it. Perhaps it can be turned with a fork?")]"
. += "\t[span_info("A small bananium part labeled \"ADVANCED WATER CHIP 23000000\" is visible within the battery compartment. It looks completely unlike normal modern electronics, disturbing it would be rather unwise.")]"
/obj/item/inspector/clown/proc/cycle_print_time(mob/user)
var/message
if(time_mode == INSPECTOR_TIME_MODE_FAST)
time_mode = INSPECTOR_TIME_MODE_SLOW
message = "SLOW."
else
time_mode = INSPECTOR_TIME_MODE_FAST
message = "LIGHTNING FAST."
balloon_alert(user, "scanning speed set to [message]")
/obj/item/inspector/clown/proc/cycle_sound(mob/user)
print_sound_mode++
if(print_sound_mode > max_mode)
print_sound_mode = INSPECTOR_PRINT_SOUND_MODE_NORMAL
balloon_alert(user, "bleep setting set to [mode_names[print_sound_mode]]")
/obj/item/inspector/clown/create_slip()
var/obj/item/paper/fake_report/slip = new(get_turf(src))
slip.generate_report(get_area(src))
/**
* # Bananium HONK-spect scanner
*
* An upgraded version of the fake N-spect scanner
*
* Can print things way faster, at full power the reports printed by this will destroy
* themselves and leave water behind when folding is attempted by someone who isn't an
* origami master. Printing at full power costs INSPECTOR_ENERGY_USAGE_HONK cell units
* instead of INSPECTOR_ENERGY_USAGE_NORMAL cell units.
*/
/obj/item/inspector/clown/bananium
name = "\improper Bananium HONK-spect scanner"
desc = "Honkmother-blessed inspection device. Performs inspections according to Clown protocols when activated, then \
prints a clowncrypted report regarding the maintenance of the station. Hard to replace."
icon = 'icons/obj/tools.dmi'
icon_state = "bananium_inspector"
w_class = WEIGHT_CLASS_SMALL
max_mode = BANANIUM_CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST
custom_materials = list(/datum/material/bananium = SHEET_MATERIAL_AMOUNT * 5)
///How many more times can we print?
var/paper_charges = 32
///Max value of paper_charges
var/max_paper_charges = 32
///How much charges are restored per paper consumed
var/charges_per_paper = 1
/obj/item/inspector/clown/bananium/proc/check_settings_legality()
if(print_sound_mode == INSPECTOR_PRINT_SOUND_MODE_NORMAL && time_mode == INSPECTOR_TIME_MODE_HONK)
if(cell.use(ENERGY_TO_SPEAK))
say("Setting combination forbidden by Geneva convention revision CCXXIII selected, reverting to defaults")
time_mode = INSPECTOR_TIME_MODE_SLOW
print_sound_mode = INSPECTOR_PRINT_SOUND_MODE_NORMAL
energy_per_print = INSPECTOR_ENERGY_USAGE_NORMAL
/obj/item/inspector/clown/bananium/screwdriver_act(mob/living/user, obj/item/tool)
. = ..()
check_settings_legality()
return TRUE
/obj/item/inspector/clown/bananium/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers)
. = ..()
if(cell_cover_open)
check_settings_legality()
if(istype(I, /obj/item/paper/fake_report) || paper_charges >= max_paper_charges)
to_chat(user, span_info("\The [src] refuses to consume \the [I]!"))
return
if(istype(I, /obj/item/paper))
to_chat(user, span_info("\The [src] consumes \the [I]!"))
paper_charges = min(paper_charges + charges_per_paper, max_paper_charges)
qdel(I)
/obj/item/inspector/clown/bananium/Initialize(mapload)
. = ..()
playsound(src, 'sound/effects/angryboat.ogg', 150, FALSE)
/obj/item/inspector/clown/bananium/create_slip()
if(time_mode == INSPECTOR_TIME_MODE_HONK)
var/obj/item/paper/fake_report/water/slip = new(get_turf(src))
slip.generate_report(get_area(src))
return
return ..()
/obj/item/inspector/clown/bananium/print_report(mob/user)
if(time_mode != INSPECTOR_TIME_MODE_HONK)
return ..()
if(paper_charges == 0)
if(cell.use(ENERGY_TO_SPEAK))
say("ERROR! OUT OF PAPER! MAXIMUM PRINTING SPEED UNAVAIBLE! SWITCH TO A SLOWER SPEED TO OR PROVIDE PAPER!")
else
to_chat(user, span_info("\The [src] doesn't seem to be on... Perhaps it ran out of power?"))
return
paper_charges--
return ..()
/obj/item/inspector/clown/bananium/cycle_print_time(mob/user)
var/message
switch(time_mode)
if(INSPECTOR_TIME_MODE_HONK)
energy_per_print = INSPECTOR_ENERGY_USAGE_NORMAL
time_mode = INSPECTOR_TIME_MODE_SLOW
message = "SLOW."
if(INSPECTOR_TIME_MODE_SLOW)
time_mode = INSPECTOR_TIME_MODE_FAST
message = "LIGHTNING FAST."
else
time_mode = INSPECTOR_TIME_MODE_HONK
energy_per_print = INSPECTOR_ENERGY_USAGE_HONK
message = "HONK!"
balloon_alert(user, "scanning speed set to [message]")
/**
* Reports printed by fake N-spect scanner
*
* Not valid for the bounty.
*/
/obj/item/paper/fake_report
name = "encrypted station inspection"
desc = "Contains no information about the station's current status."
icon = 'icons/obj/service/bureaucracy.dmi'
icon_state = "slip"
show_written_words = FALSE
///What area the inspector scanned when the report was made. Used to generate the examine text of the report
var/area/scanned_area
/obj/item/paper/fake_report/proc/generate_report(area/scan_area)
scanned_area = scan_area
icon_state = "slipfull"
var/list/new_info = list()
for(var/i in 1 to rand(23, 123))
var/roll = rand(0, 1000)
switch(roll)
if(0 to 900)
new_info += pick_list_replacements(CLOWN_NONSENSE_FILE, "honk")
if(901 to 999)
new_info += pick_list_replacements(CLOWN_NONSENSE_FILE, "non-honk-clown-words")
if(1000)
new_info += pick_list_replacements(CLOWN_NONSENSE_FILE, "rare")
add_raw_text(new_info.Join())
update_appearance()
/obj/item/paper/fake_report/examine(mob/user)
. = ..()
if(scanned_area?.name)
. += span_notice("\The [src] contains no data on [scanned_area.name].")
else if(scanned_area)
. += span_notice("\The [src] contains no data on a vague area on station, you should throw it away.")
else if(get_total_length())
. += span_notice("Wait a minute, this isn't an encrypted inspection report! You should throw it away.")
else
. += span_notice("Wait a minute, this thing's blank! You should throw it away.")
/**
* # Fake report made of water
*
* Fake report but it turns into water under certain circumstances.
*
* If someone who isn't an origami master tries to fold it into a paper plane, it will make the floor it's on wet and disappear.
* If it is ground, it will turn into 5u water.
*/
/obj/item/paper/fake_report/water
interaction_flags_click = NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING
/obj/item/paper/fake_report/water/grind_results()
return list(/datum/reagent/water = 5)
/obj/item/paper/fake_report/water/click_alt(mob/living/user)
var/datum/action/innate/origami/origami_action = locate() in user.actions
if(origami_action?.active) //Origami masters can fold water
make_plane(user, /obj/item/paperplane/syndicate)
return CLICK_ACTION_SUCCESS
if(!do_after(user, 1 SECONDS, target = src, progress=TRUE))
return CLICK_ACTION_BLOCKING
var/turf/open/target = get_turf(src)
target.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
to_chat(user, span_notice("As you try to fold [src] into the shape of a plane, it disintegrates into water!"))
qdel(src)
return CLICK_ACTION_SUCCESS
#undef ENERGY_TO_SPEAK
@@ -85,7 +85,7 @@
if(isnull(outfit_id))
return
outfit_id.registered_account = new()
outfit_id.set_account(new /datum/bank_account)
outfit_id.registered_account.replaceable = FALSE
outfit_id.registered_account.account_id = null
outfit_id.registered_name = player.name
@@ -79,7 +79,7 @@
var/obj/item/card/id/outfit_id = avatar.wear_id
if(outfit_id)
outfit_id.registered_account = new()
outfit_id.set_account(new /datum/bank_account)
outfit_id.registered_account.replaceable = FALSE
SSid_access.apply_trim_to_card(outfit_id, /datum/id_trim/bit_avatar)
+5 -2
View File
@@ -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)
+3
View File
@@ -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
+215 -81
View File
@@ -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)
+3 -1
View File
@@ -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
+30 -20
View File
@@ -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."
+14 -15
View File
@@ -279,30 +279,29 @@
* Returns the required item count, or required chemical units required to submit a bounty.
*/
/datum/bank_account/proc/bounty_num()
if(!civilian_bounty)
return FALSE
if(istype(civilian_bounty, /datum/bounty/item))
var/datum/bounty/item/item = civilian_bounty
return "[item.shipped_count]/[item.required_count]"
if(istype(civilian_bounty, /datum/bounty/reagent))
var/datum/bounty/reagent/chemical = civilian_bounty
return "[chemical.shipped_volume]/[chemical.required_volume] u"
if(istype(civilian_bounty, /datum/bounty/virus))
return "At least 1u"
return civilian_bounty?.print_required() || "N/A"
/**
* Produces the value of the account's civilian bounty reward, if able.
*/
/datum/bank_account/proc/bounty_value()
if(!civilian_bounty)
return FALSE
return civilian_bounty.reward
return civilian_bounty?.get_bounty_reward() || 0
/datum/bank_account/proc/set_bounty(datum/bounty/new_bounty, obj/item/id_card)
if(civilian_bounty)
reset_bounty(id_card)
civilian_bounty = new_bounty
civilian_bounty.on_selected(id_card)
/**
* Performs house-cleaning on variables when a civilian bounty is replaced, or, when a bounty is claimed.
*/
/datum/bank_account/proc/reset_bounty()
civilian_bounty = null
/datum/bank_account/proc/reset_bounty(obj/item/id_card)
if(civilian_bounty)
civilian_bounty.on_reset(id_card)
civilian_bounty = null
COOLDOWN_RESET(src, bounty_timer)
/datum/bank_account/department
+2 -3
View File
@@ -68,7 +68,7 @@
var/display_order = JOB_DISPLAY_ORDER_DEFAULT
///What types of bounty tasks can this job receive past the default?
///What types of bounty tasks can this job receive past the default? TODO, move to id trims.
var/bounty_types = CIV_JOB_BASIC
/// Goodies that can be received via the mail system.
@@ -405,8 +405,7 @@
var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[equipped.account_id]"]
if(account && account.account_id == equipped.account_id)
card.registered_account = account
LAZYADD(account.bank_cards, card)
card.set_account(account)
equipped.update_ID_card()
@@ -40,8 +40,7 @@
var/obj/item/card/id/advanced/cargo_gorilla/gorilla_id = new(spawned.loc)
gorilla_id.registered_name = spawned.name
gorilla_id.update_label()
gorilla_id.registered_account = bank_account
LAZYADD(bank_account.bank_cards, gorilla_id)
gorilla_id.set_account(bank_account)
spawned.put_in_hands(gorilla_id, del_on_fail = TRUE)
to_chat(spawned, span_boldnotice("You are Cargorilla, a pacifist friend of the station and carrier of freight."))
@@ -26,18 +26,17 @@
if(computer.stored_id.registered_account.civilian_bounty)
data["id_bounty_info"] = computer.stored_id.registered_account.civilian_bounty.description
data["id_bounty_num"] = computer.stored_id.registered_account.bounty_num()
data["id_bounty_value"] = (computer.stored_id.registered_account.civilian_bounty.reward) * (CIV_BOUNTY_SPLIT/100)
data["id_bounty_value"] = (computer.stored_id.registered_account.civilian_bounty.get_bounty_reward()) * (CIV_BOUNTY_SPLIT / 100)
if(computer.stored_id.registered_account.bounties)
data["picking"] = TRUE
data["id_bounty_names"] = list(computer.stored_id.registered_account.bounties[1].name,
computer.stored_id.registered_account.bounties[2].name,
computer.stored_id.registered_account.bounties[3].name)
data["id_bounty_infos"] = list(computer.stored_id.registered_account.bounties[1].description,
computer.stored_id.registered_account.bounties[2].description,
computer.stored_id.registered_account.bounties[3].description)
data["id_bounty_values"] = list(computer.stored_id.registered_account.bounties[1].reward * (CIV_BOUNTY_SPLIT/100),
computer.stored_id.registered_account.bounties[2].reward * (CIV_BOUNTY_SPLIT/100),
computer.stored_id.registered_account.bounties[3].reward * (CIV_BOUNTY_SPLIT/100))
data["id_bounty_names"] = list()
data["id_bounty_infos"] = list()
data["id_bounty_values"] = list()
for(var/datum/bounty/bounty as anything in computer.stored_id.registered_account.bounties)
data["id_bounty_names"] += bounty.name
data["id_bounty_infos"] += bounty.description
data["id_bounty_values"] += bounty.get_bounty_reward() * (CIV_BOUNTY_SPLIT / 100)
else
data["picking"] = FALSE
@@ -61,11 +60,11 @@
var/time_left = DisplayTimeText(COOLDOWN_TIMELEFT(id_account, bounty_timer), round_seconds_to = 1)
computer.balloon_alert(user, "try again in [time_left]!")
return FALSE
if(!id_account.account_job)
if(!computer.stored_id.trim)
computer.say("Requesting ID card has no job assignment registered!")
return FALSE
var/list/datum/bounty/crumbs = generate_bounty_list(id_account.account_job.bounty_types)
var/list/datum/bounty/crumbs = computer.stored_id.trim.generate_bounty_list()
COOLDOWN_START(id_account, bounty_timer, (5 MINUTES) - cooldown_reduction)
id_account.bounties = crumbs
@@ -80,31 +79,8 @@
if(!id_account?.bounties?[choice])
playsound(computer.loc, 'sound/machines/synth/synth_no.ogg', 40 , TRUE)
return
id_account.civilian_bounty = id_account.bounties[choice]
id_account.bounties = null
id_account.set_bounty(id_account.bounties[choice], computer.stored_id)
SSblackbox.record_feedback("tally", "bounties_assigned", 1, id_account.civilian_bounty.type)
return id_account.civilian_bounty
/**
* Generates a list of bounties for use with the civilian bounty pad, this is virtually identical to the stuff contained within: code/game/machinery/civilian_bounties.dm
* @param bounty_types the define taken from a job for selection of a random_bounty() proc.
* @param bounty_rolls the number of bounties to be selected from.
* @param assistant_failsafe Do we guarentee one assistant bounty per generated list? Used for non-assistant jobs to give an easier alternative to that job's default bounties.
*/
/datum/computer_file/program/civilianbounties/proc/generate_bounty_list(bounty_types, bounty_rolls = 3, assistant_failsafe = TRUE)
var/list/rolling_list = list()
if(assistant_failsafe)
rolling_list += random_bounty(CIV_JOB_BASIC)
while(bounty_rolls > 1)
var/datum/bounty/potential_bounty = random_bounty(bounty_types)
var/repeats_bool = FALSE
for(var/datum/iterator in rolling_list)
if(iterator.type == potential_bounty.type)
repeats_bool = TRUE
if(repeats_bool)
continue
rolling_list += potential_bounty
bounty_rolls -= 1
return rolling_list
#undef CIV_BOUNTY_SPLIT
+1 -7
View File
@@ -113,13 +113,7 @@
/obj/item/papercutter/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(istype(tool, /obj/item/paper))
if(is_type_in_list(tool, list(
/obj/item/paper/fake_report,
/obj/item/paper/holy_writ,
/obj/item/paper/pamphlet,
/obj/item/paper/paperslip,
/obj/item/paper/report,
)))
if(is_type_in_list(tool, list(/obj/item/paper/holy_writ, /obj/item/paper/pamphlet, /obj/item/paper/paperslip)))
balloon_alert(user, "won't fit!")
return ITEM_INTERACT_BLOCKING
if(stored_paper)
Binary file not shown.