diff --git a/code/__DEFINES/devices.dm b/code/__DEFINES/devices.dm index 7368d8d0457..6b216c799f7 100644 --- a/code/__DEFINES/devices.dm +++ b/code/__DEFINES/devices.dm @@ -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 diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm index a44bc26079e..6c80e95d555 100644 --- a/code/__DEFINES/economy.dm +++ b/code/__DEFINES/economy.dm @@ -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" diff --git a/code/datums/components/crafting/equipment.dm b/code/datums/components/crafting/equipment.dm index 3a4744579e6..f5aa18b8f25 100644 --- a/code/datums/components/crafting/equipment.dm +++ b/code/datums/components/crafting/equipment.dm @@ -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 diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm index eb9f063d64f..9596dc9286e 100644 --- a/code/datums/id_trim/jobs.dm +++ b/code/datums/id_trim/jobs.dm @@ -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 diff --git a/code/game/area/areas/station/security.dm b/code/game/area/areas/station/security.dm index 8c15638c7f0..3166f7733d9 100644 --- a/code/game/area/areas/station/security.dm +++ b/code/game/area/areas/station/security.dm @@ -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" diff --git a/code/game/machinery/civilian_bounties.dm b/code/game/machinery/civilian_bounties.dm index 9f2254a28ce..f90b1b579b1 100644 --- a/code/game/machinery/civilian_bounties.dm +++ b/code/game/machinery/civilian_bounties.dm @@ -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 diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index ee48e793cc1..62cccd38aba 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -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,) diff --git a/code/game/objects/items/inspector.dm b/code/game/objects/items/inspector.dm index 56e63994c22..bc82fa85b94 100644 --- a/code/game/objects/items/inspector.dm +++ b/code/game/objects/items/inspector.dm @@ -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 screw 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 diff --git a/code/modules/bitrunning/antagonists/_parent.dm b/code/modules/bitrunning/antagonists/_parent.dm index 5a00026f996..e73d159bddb 100644 --- a/code/modules/bitrunning/antagonists/_parent.dm +++ b/code/modules/bitrunning/antagonists/_parent.dm @@ -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 diff --git a/code/modules/bitrunning/server/obj_generation.dm b/code/modules/bitrunning/server/obj_generation.dm index 23134dbe019..0e988b4d1a3 100644 --- a/code/modules/bitrunning/server/obj_generation.dm +++ b/code/modules/bitrunning/server/obj_generation.dm @@ -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) diff --git a/code/modules/cargo/bounties/item.dm b/code/modules/cargo/bounties/item.dm index 1fb1df9104d..0970cf1ad40 100644 --- a/code/modules/cargo/bounties/item.dm +++ b/code/modules/cargo/bounties/item.dm @@ -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) diff --git a/code/modules/cargo/bounties/reagent.dm b/code/modules/cargo/bounties/reagent.dm index 7d1d6106430..88d416621ec 100644 --- a/code/modules/cargo/bounties/reagent.dm +++ b/code/modules/cargo/bounties/reagent.dm @@ -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 diff --git a/code/modules/cargo/bounties/security.dm b/code/modules/cargo/bounties/security.dm index c5617ab0dcd..05ca109d80d 100644 --- a/code/modules/cargo/bounties/security.dm +++ b/code/modules/cargo/bounties/security.dm @@ -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) diff --git a/code/modules/cargo/bounties/virus.dm b/code/modules/cargo/bounties/virus.dm index 2721b2124d1..caab0e7d924 100644 --- a/code/modules/cargo/bounties/virus.dm +++ b/code/modules/cargo/bounties/virus.dm @@ -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 - diff --git a/code/modules/cargo/bounty.dm b/code/modules/cargo/bounty.dm index b90e8816a1f..8b3b0a38629 100644 --- a/code/modules/cargo/bounty.dm +++ b/code/modules/cargo/bounty.dm @@ -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 diff --git a/code/modules/cargo/markets/market_items/tools.dm b/code/modules/cargo/markets/market_items/tools.dm index 61ec8452e51..ff6771195ad 100644 --- a/code/modules/cargo/markets/market_items/tools.dm +++ b/code/modules/cargo/markets/market_items/tools.dm @@ -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." diff --git a/code/modules/economy/account.dm b/code/modules/economy/account.dm index 60cafe30a1b..797dd9d3856 100644 --- a/code/modules/economy/account.dm +++ b/code/modules/economy/account.dm @@ -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 diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 721c0671952..c6bcc968b2f 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -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() diff --git a/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm b/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm index bbc50389381..c7f0f8eb819 100644 --- a/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm +++ b/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm @@ -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.")) diff --git a/code/modules/modular_computers/file_system/programs/civilian_bounty.dm b/code/modules/modular_computers/file_system/programs/civilian_bounty.dm index 55bd6969af6..8fa6b2fc393 100644 --- a/code/modules/modular_computers/file_system/programs/civilian_bounty.dm +++ b/code/modules/modular_computers/file_system/programs/civilian_bounty.dm @@ -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 diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm index 86725aa18fc..705ab33837c 100644 --- a/code/modules/paperwork/paper_cutter.dm +++ b/code/modules/paperwork/paper_cutter.dm @@ -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) diff --git a/sound/effects/angryboat.ogg b/sound/effects/angryboat.ogg deleted file mode 100644 index ddd69b15f65..00000000000 Binary files a/sound/effects/angryboat.ogg and /dev/null differ