From de8695acf8b6ca2a53c68c1843513027bfd8ef7a Mon Sep 17 00:00:00 2001
From: plsleavemealon <106043510+plsleavemealon@users.noreply.github.com>
Date: Wed, 13 Aug 2025 14:08:48 -0500
Subject: [PATCH] bounty app for PDA (#91802)
## About The Pull Request
This PR adds a brand new app to the PDA allowing you to view your
current bounties and even generate new ones.
You can even select a bounty remotely and it will seemlessly work.
The program is not without its own drawbacks. Choosing to reroll your
bounties from the app will always trigger a 5 minute cooldown, and this
cannot be reduced. Meaning that rerolling directly from the bounty
terminal (assuming it has been upgraded) is preferable as an upgraded
bounty pad will give the terminal a shorter cooldown. The cooldown is
shared between the app and the terminal, and depends on where you
initially rerolled it. So if you rerolled it from the app, it will
always be 5 minutes, but if you rerolled it from the terminal, it may be
as low as 2 minutes.
Otherwise it is identical to the way the bounty terminal functions,
minus the fact you cannot actually turn in the bounty using your PDA,
you still have to go to a terminal with connected bounty pad for that!
And in addition to that, in order for the app to work you must have your
ID inside your PDA, the app will not function without it (the top button
will be greyed out, and trying to push one of the options will just give
you a minor error sound)
Size is 5 GQ since that seemed like a good enough size for it.
## Why It's Good For The Game
It always annoyed me that I had to run all the way back to cargo to
check on what my bounties are, and this solves that. It also lets you
reroll your bounties remotely with a punishment of it always having the
default (5 min) waiting period between rerolls.
## Changelog
:cl:
add: Adds a new bounty app to the PDA allowing you to remotely check
your bounties
/:cl:




---------
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
---
.../file_system/programs/civilian_bounty.dm | 110 ++++++++++++++
tgstation.dme | 1 +
.../interfaces/NtosCivCargoHoldTerminal.jsx | 140 ++++++++++++++++++
3 files changed, 251 insertions(+)
create mode 100644 code/modules/modular_computers/file_system/programs/civilian_bounty.dm
create mode 100644 tgui/packages/tgui/interfaces/NtosCivCargoHoldTerminal.jsx
diff --git a/code/modules/modular_computers/file_system/programs/civilian_bounty.dm b/code/modules/modular_computers/file_system/programs/civilian_bounty.dm
new file mode 100644
index 00000000000..c9e65be9cec
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/civilian_bounty.dm
@@ -0,0 +1,110 @@
+///Everything within this file is an edited form from this file, stripping it of some various components because they are not needed for the PDA app: code/game/machinery/civilian_bounties.dm
+
+///Percentage of a civilian bounty the civilian will make.
+#define CIV_BOUNTY_SPLIT 30
+
+/datum/computer_file/program/civilianbounties
+ filename = "bountyapp"
+ filedesc = "Civilian Bounties"
+ downloader_category = PROGRAM_CATEGORY_SUPPLY
+ program_open_overlay = "request"
+ extended_desc = "Nanotrasen Civilian Bounty Requisition Network interface for displaying wanted items."
+ program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET
+ can_run_on_flags = PROGRAM_LAPTOP | PROGRAM_PDA
+ size = 5
+ tgui_id = "NtosCivCargoHoldTerminal"
+ program_icon = FA_ICON_BOXES_STACKED
+ var/status_report = "Ready for delivery."
+ var/points = 0
+
+/datum/computer_file/program/civilianbounties/ui_data(mob/user)
+ var/list/data = list()
+ data["points"] = points
+ data["status_report"] = status_report
+ data["id_inserted"] = computer.computer_id_slot
+ if(computer.computer_id_slot?.registered_account)
+ if(computer.computer_id_slot.registered_account.civilian_bounty)
+ data["id_bounty_info"] = computer.computer_id_slot.registered_account.civilian_bounty.description
+ data["id_bounty_num"] = computer.computer_id_slot.registered_account.bounty_num()
+ data["id_bounty_value"] = (computer.computer_id_slot.registered_account.civilian_bounty.reward) * (CIV_BOUNTY_SPLIT/100)
+ if(computer.computer_id_slot.registered_account.bounties)
+ data["picking"] = TRUE
+ data["id_bounty_names"] = list(computer.computer_id_slot.registered_account.bounties[1].name,
+ computer.computer_id_slot.registered_account.bounties[2].name,
+ computer.computer_id_slot.registered_account.bounties[3].name)
+ data["id_bounty_infos"] = list(computer.computer_id_slot.registered_account.bounties[1].description,
+ computer.computer_id_slot.registered_account.bounties[2].description,
+ computer.computer_id_slot.registered_account.bounties[3].description)
+ data["id_bounty_values"] = list(computer.computer_id_slot.registered_account.bounties[1].reward * (CIV_BOUNTY_SPLIT/100),
+ computer.computer_id_slot.registered_account.bounties[2].reward * (CIV_BOUNTY_SPLIT/100),
+ computer.computer_id_slot.registered_account.bounties[3].reward * (CIV_BOUNTY_SPLIT/100))
+ else
+ data["picking"] = FALSE
+
+ return data
+
+/datum/computer_file/program/civilianbounties/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ var/mob/user = ui.user
+ switch(action)
+ if("pick")
+ pick_bounty(params["value"])
+ if("bounty")
+ add_bounties(user, 0)
+
+///Here is where cargo bounties are added to the player's bank accounts, then adjusted and scaled into a civilian bounty.
+/datum/computer_file/program/civilianbounties/proc/add_bounties(mob/user, cooldown_reduction = 0)
+ var/datum/bank_account/id_account = computer.computer_id_slot?.registered_account
+ if(!id_account)
+ return
+ if((id_account.civilian_bounty || id_account.bounties) && !COOLDOWN_FINISHED(id_account, bounty_timer))
+ 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)
+ 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)
+ COOLDOWN_START(id_account, bounty_timer, (5 MINUTES) - cooldown_reduction)
+ id_account.bounties = crumbs
+
+/**
+ * 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.
+ *
+ * @param choice The index of the bounty in the list of bounties that the player can choose from.
+ */
+/datum/computer_file/program/civilianbounties/proc/pick_bounty(datum/bounty/choice)
+ var/datum/bank_account/id_account = computer.computer_id_slot?.registered_account
+ 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
+ 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/tgstation.dme b/tgstation.dme
index e220e38cb96..64ecdb7b493 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -5731,6 +5731,7 @@
#include "code\modules\modular_computers\file_system\programs\budgetordering.dm"
#include "code\modules\modular_computers\file_system\programs\card.dm"
#include "code\modules\modular_computers\file_system\programs\cargoship.dm"
+#include "code\modules\modular_computers\file_system\programs\civilian_bounty.dm"
#include "code\modules\modular_computers\file_system\programs\coupon.dm"
#include "code\modules\modular_computers\file_system\programs\crewmanifest.dm"
#include "code\modules\modular_computers\file_system\programs\dept_order.dm"
diff --git a/tgui/packages/tgui/interfaces/NtosCivCargoHoldTerminal.jsx b/tgui/packages/tgui/interfaces/NtosCivCargoHoldTerminal.jsx
new file mode 100644
index 00000000000..6b1f7dc6fe1
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NtosCivCargoHoldTerminal.jsx
@@ -0,0 +1,140 @@
+import {
+ Box,
+ Button,
+ Flex,
+ LabeledList,
+ NoticeBox,
+ Section,
+} from 'tgui-core/components';
+
+import { useBackend } from '../backend';
+import { NtosWindow } from '../layouts';
+
+export const NtosCivCargoHoldTerminal = (props) => {
+ const { act, data } = useBackend();
+ const { status_report, id_inserted, id_bounty_info, picking } = data;
+ const in_text = 'Welcome valued employee.';
+ const out_text = 'To begin, insert your ID into the console.';
+ return (
+
+
+
+
+
+ {id_inserted ? in_text : out_text}
+
+
+
+ {picking ? : }
+
+
+
+
+ );
+};
+
+const BountyTextBox = (props) => {
+ const { data } = useBackend();
+ const { id_bounty_info, id_bounty_value, id_bounty_num } = data;
+ const na_text = 'N/A, please add a new bounty.';
+ return (
+
+
+
+ {id_bounty_info ? id_bounty_info : na_text}
+
+
+ {id_bounty_info ? id_bounty_num : 'N/A'}
+
+
+ {id_bounty_info ? id_bounty_value : 'N/A'}
+
+
+
+ );
+};
+
+const BountyPickBox = (props) => {
+ const { act, data } = useBackend();
+ const { id_bounty_names, id_bounty_infos, id_bounty_values } = data;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const BountyPickButton = (props) => {
+ return (
+
+ );
+};