diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm
index 091544f4988..324e639db47 100644
--- a/code/__DEFINES/economy.dm
+++ b/code/__DEFINES/economy.dm
@@ -6,7 +6,13 @@
#define PAYCHECK_MEDIUM 40
#define PAYCHECK_HARD 70
#define PAYCHECK_COMMAND 100
-#define PAYCHECK_VIP 250
+#define PAYCHECK_VIP 2000
+/* Note: The current intention for crazy amount of money to VIP is that they can be a rich shitter
+ or be targeted by antags for their money - oh, my, being rich isn't always good.
+ The first buff to their money was to 2,000 credits. Nerf is fine if you think it's necessary,
+ but if you decrease their payment too much, they'll be no longer interested.
+ I recommend to nerf their gimmick spawn chance instead. */
+
#define PAYCHECK_WELFARE 20 //NEETbucks
@@ -24,5 +30,7 @@
#define ACCOUNT_CAR_NAME "Cargo Budget"
#define ACCOUNT_SEC "SEC"
#define ACCOUNT_SEC_NAME "Defense Budget"
+#define ACCOUNT_VIP "VIP"
+#define ACCOUNT_VIP_NAME "Nanotrasen VIP Expense Account Budget"
#define NO_FREEBIES "commies go home"
diff --git a/code/__HELPERS/jobs.dm b/code/__HELPERS/jobs.dm
new file mode 100644
index 00000000000..9bcc58d911c
--- /dev/null
+++ b/code/__HELPERS/jobs.dm
@@ -0,0 +1,316 @@
+// This proc is only used in `PDApainter.dm`, but for better readability, it's declared as global proc and stored here.
+// This returns a card icon style by given job name. Check `card.dmi` for the card list.
+/proc/get_cardstyle_by_jobname(jobname)
+ if(!jobname)
+ CRASH("The proc has taken a null value")
+
+ var/static/id_style = list(
+ // Command
+ "Command (Custom)" = "captain",
+ JOB_NAME_CAPTAIN = "captain",
+ "Acting Captain" = "captain",
+ // Service
+ "Service (Custom)" = "rawservice",
+ JOB_NAME_HEADOFPERSONNEL = "hop",
+ JOB_NAME_ASSISTANT = "id",
+ JOB_NAME_BOTANIST = "serv",
+ JOB_NAME_BARTENDER = "serv",
+ JOB_NAME_COOK = "serv",
+ JOB_NAME_JANITOR = "janitor",
+ JOB_NAME_CURATOR = "chap",
+ JOB_NAME_CHAPLAIN = "chap",
+ JOB_NAME_LAWYER = "lawyer",
+ JOB_NAME_CLOWN = "clown",
+ JOB_NAME_MIME = "mime",
+ JOB_NAME_STAGEMAGICIAN = "serv",
+ JOB_NAME_BARBER = "serv",
+ // Cargo
+ "Cargo (Custom)" = "rawcargo",
+ JOB_NAME_QUARTERMASTER = "qm",
+ JOB_NAME_CARGOTECHNICIAN = "cargo",
+ JOB_NAME_SHAFTMINER = "miner",
+ // Engineering
+ "Engineering (Custom)" = "rawengineering",
+ JOB_NAME_CHIEFENGINEER = "ce",
+ JOB_NAME_STATIONENGINEER = "engi",
+ JOB_NAME_ATMOSPHERICTECHNICIAN = "atmos",
+ // Medical
+ "Medical (Custom)" = "rawmedical",
+ JOB_NAME_CHIEFMEDICALOFFICER = "cmo",
+ JOB_NAME_MEDICALDOCTOR = "med",
+ JOB_NAME_PARAMEDIC = "paramed",
+ JOB_NAME_VIROLOGIST = "viro",
+ JOB_NAME_GENETICIST = "gene",
+ JOB_NAME_CHEMIST = "chemist",
+ JOB_NAME_PSYCHIATRIST = "med",
+ // R&D
+ "Science (Custom)" = "rawscience",
+ JOB_NAME_RESEARCHDIRECTOR = "rd",
+ JOB_NAME_SCIENTIST = "sci",
+ JOB_NAME_ROBOTICIST = "roboticist",
+ JOB_NAME_EXPLORATIONCREW = "exploration",
+ // Security
+ "Security (Custom)" = "rawsecurity",
+ JOB_NAME_HEADOFSECURITY = "hos",
+ JOB_NAME_WARDEN = "warden",
+ JOB_NAME_SECURITYOFFICER = "sec",
+ JOB_NAME_DETECTIVE = "detective",
+ JOB_NAME_BRIGPHYSICIAN = "brigphys",
+ JOB_NAME_DEPUTY = "deputy",
+ // ETC
+ "Unassigned" = "id",
+ JOB_NAME_PRISONER = "orange",
+ // EMAG
+ "CentCom (Custom)" = "centcom",
+ "CentCom" = "centcom",
+ "ERT" = "ert",
+ JOB_NAME_VIP = "gold",
+ JOB_NAME_KING = "gold",
+ "Syndicate" = "syndicate",
+ "Clown Operative" = "clown_op",
+ "Unknown" = "unknown",
+ // ETC2
+ "Ratvar" = "ratvar"
+ )
+ return id_style[jobname] || "noname" // default: a card with no shape
+
+// This returns a hud icon (from `hud.dmi`) by given job name.
+// Some custom title is from `PDApainter.dm`. You neec to check it if you're going to remove custom job.
+/proc/get_hud_by_jobname(jobname)
+ if(!jobname)
+ CRASH("The proc has taken a null value")
+
+ var/static/id_to_hud = list(
+ // Command
+ "Command (Custom)" = JOB_HUD_RAWCOMMAND,
+ JOB_NAME_CAPTAIN = JOB_HUD_CAPTAIN,
+ "Acting Captain" = JOB_HUD_ACTINGCAPTAIN ,
+
+ // Service
+ "Service (Custom)" = JOB_HUD_RAWSERVICE,
+ JOB_NAME_HEADOFPERSONNEL = JOB_HUD_HEADOFPERSONNEL,
+ JOB_NAME_ASSISTANT = JOB_HUD_ASSISTANT,
+ JOB_NAME_BARTENDER = JOB_HUD_BARTENDER,
+ JOB_NAME_COOK = JOB_HUD_COOK,
+ JOB_NAME_BOTANIST = JOB_HUD_BOTANIST,
+ JOB_NAME_CURATOR = JOB_HUD_CURATOR,
+ JOB_NAME_CHAPLAIN = JOB_HUD_CHAPLAIN,
+ JOB_NAME_JANITOR = JOB_HUD_JANITOR,
+ JOB_NAME_LAWYER = JOB_HUD_LAWYER,
+ JOB_NAME_MIME = JOB_HUD_MIME,
+ JOB_NAME_CLOWN = JOB_HUD_CLOWN,
+ JOB_NAME_STAGEMAGICIAN = JOB_HUD_STAGEMAGICIAN,
+ JOB_NAME_BARBER = JOB_HUD_BARBER,
+
+ // Cargo
+ "Cargo (Custom)" = JOB_HUD_RAWCARGO,
+ JOB_NAME_QUARTERMASTER = JOB_HUD_QUARTERMASTER,
+ JOB_NAME_CARGOTECHNICIAN = JOB_HUD_CARGOTECHNICIAN,
+ JOB_NAME_SHAFTMINER = JOB_HUD_SHAFTMINER,
+
+ // Engineering
+ "Engineering (Custom)" = JOB_HUD_RAWENGINEERING,
+ JOB_NAME_CHIEFENGINEER = JOB_HUD_CHIEFENGINEER,
+ JOB_NAME_STATIONENGINEER = JOB_HUD_STATIONENGINEER,
+ JOB_NAME_ATMOSPHERICTECHNICIAN = JOB_HUD_ATMOSPHERICTECHNICIAN,
+
+ // Medical
+ "Medical (Custom)" = JOB_HUD_RAWMEDICAL,
+ JOB_NAME_CHIEFMEDICALOFFICER = JOB_HUD_CHEIFMEDICALOFFICIER,
+ JOB_NAME_MEDICALDOCTOR = JOB_HUD_MEDICALDOCTOR,
+ JOB_NAME_PARAMEDIC = JOB_HUD_PARAMEDIC,
+ JOB_NAME_VIROLOGIST = JOB_HUD_VIROLOGIST,
+ JOB_NAME_CHEMIST = JOB_HUD_CHEMIST,
+ JOB_NAME_GENETICIST = JOB_HUD_GENETICIST,
+ JOB_NAME_PSYCHIATRIST = JOB_HUD_PSYCHIATRIST,
+
+ // R&D
+ "Science (Custom)" = JOB_HUD_RAWSCIENCE,
+ JOB_NAME_RESEARCHDIRECTOR = JOB_HUD_RESEARCHDIRECTOR,
+ JOB_NAME_SCIENTIST = JOB_HUD_SCIENTIST,
+ JOB_NAME_ROBOTICIST = JOB_HUD_ROBOTICIST,
+ JOB_NAME_EXPLORATIONCREW = JOB_HUD_EXPLORATIONCREW,
+
+ // Security
+ "Security (Custom)" = JOB_HUD_RAWSECURITY,
+ JOB_NAME_HEADOFSECURITY = JOB_HUD_HEADOFSECURITY,
+ JOB_NAME_SECURITYOFFICER = JOB_HUD_SECURITYOFFICER,
+ JOB_NAME_WARDEN = JOB_HUD_WARDEN,
+ JOB_NAME_DETECTIVE = JOB_HUD_DETECTIVE,
+ JOB_NAME_BRIGPHYSICIAN = JOB_HUD_BRIGPHYSICIAN,
+ JOB_NAME_DEPUTY = JOB_HUD_DEPUTY,
+
+ // CentCom
+ "CentCom (Custom)" = JOB_HUD_RAWCENTCOM,
+ "CentCom" = JOB_HUD_CENTCOM,
+ "ERT" = JOB_HUD_CENTCOM,
+
+ // ETC
+ JOB_NAME_VIP = JOB_HUD_VIP,
+ JOB_NAME_KING = JOB_HUD_KING,
+ "Syndicate" = JOB_HUD_SYNDICATE,
+ "Clown Operative" = JOB_HUD_SYNDICATE,
+ "Unassigned" = JOB_HUD_UNKNOWN,
+ JOB_NAME_PRISONER = JOB_HUD_PRISONER
+ )
+ return id_to_hud[jobname] || JOB_HUD_UNKNOWN // default: a grey unknown hud
+
+// This returns a department for banking system by given hud icon.
+// currently used in `card.dm` and `PDApainter.dm` to set a card's paycheck department
+/proc/get_department_by_hud(jobname)
+ if(!jobname)
+ CRASH("The proc has taken a null value")
+
+ var/static/hud_to_department_acc = list(
+ // Command
+ JOB_HUD_RAWCOMMAND = ACCOUNT_SEC,
+ JOB_HUD_CAPTAIN = ACCOUNT_SEC,
+ JOB_HUD_ACTINGCAPTAIN = ACCOUNT_SEC,
+
+ // Service + Civilian
+ JOB_HUD_RAWSERVICE = ACCOUNT_SRV,
+ JOB_HUD_HEADOFPERSONNEL = ACCOUNT_SRV,
+ JOB_HUD_ASSISTANT = ACCOUNT_CIV,
+ JOB_HUD_BARTENDER = ACCOUNT_SRV,
+ JOB_HUD_COOK = ACCOUNT_SRV,
+ JOB_HUD_BOTANIST = ACCOUNT_SRV,
+ JOB_HUD_CURATOR = ACCOUNT_CIV,
+ JOB_HUD_CHAPLAIN = ACCOUNT_CIV,
+ JOB_HUD_JANITOR = ACCOUNT_SRV,
+ JOB_HUD_LAWYER = ACCOUNT_CIV,
+ JOB_HUD_MIME = ACCOUNT_SRV,
+ JOB_HUD_CLOWN = ACCOUNT_SRV,
+ JOB_HUD_STAGEMAGICIAN = ACCOUNT_SRV,
+ JOB_HUD_BARBER = ACCOUNT_CIV,
+
+ // Cargo
+ JOB_HUD_RAWCARGO = ACCOUNT_CAR,
+ JOB_HUD_QUARTERMASTER = ACCOUNT_CAR,
+ JOB_HUD_CARGOTECHNICIAN = ACCOUNT_CAR,
+ JOB_HUD_SHAFTMINER = ACCOUNT_CAR,
+
+ // Engineering
+ JOB_HUD_RAWENGINEERING = ACCOUNT_ENG,
+ JOB_HUD_CHIEFENGINEER = ACCOUNT_ENG,
+ JOB_HUD_STATIONENGINEER = ACCOUNT_ENG,
+ JOB_HUD_ATMOSPHERICTECHNICIAN = ACCOUNT_ENG,
+
+ // Medical
+ JOB_HUD_RAWMEDICAL = ACCOUNT_MED,
+ JOB_HUD_CHEIFMEDICALOFFICIER = ACCOUNT_MED,
+ JOB_HUD_MEDICALDOCTOR = ACCOUNT_MED,
+ JOB_HUD_PARAMEDIC = ACCOUNT_MED,
+ JOB_HUD_VIROLOGIST = ACCOUNT_MED,
+ JOB_HUD_CHEMIST = ACCOUNT_MED,
+ JOB_HUD_GENETICIST = ACCOUNT_MED,
+ JOB_HUD_PSYCHIATRIST = ACCOUNT_MED,
+
+ // R&D
+ JOB_HUD_RAWSCIENCE = ACCOUNT_SCI,
+ JOB_HUD_RESEARCHDIRECTOR = ACCOUNT_SCI,
+ JOB_HUD_SCIENTIST = ACCOUNT_SCI,
+ JOB_HUD_ROBOTICIST = ACCOUNT_SCI,
+ JOB_HUD_EXPLORATIONCREW = ACCOUNT_SCI,
+
+ // Security
+ JOB_HUD_RAWSECURITY = ACCOUNT_SEC,
+ JOB_HUD_HEADOFSECURITY = ACCOUNT_SEC,
+ JOB_HUD_SECURITYOFFICER = ACCOUNT_SEC,
+ JOB_HUD_WARDEN = ACCOUNT_SEC,
+ JOB_HUD_DETECTIVE = ACCOUNT_SEC,
+ JOB_HUD_BRIGPHYSICIAN = ACCOUNT_SEC,
+ JOB_HUD_DEPUTY = ACCOUNT_SEC,
+
+ // CentCom
+ JOB_HUD_RAWCENTCOM = ACCOUNT_CIV,
+ JOB_HUD_CENTCOM = ACCOUNT_CIV,
+
+ // ETC
+ JOB_HUD_VIP = ACCOUNT_CIV, // Fake VIP gets a station department
+ JOB_HUD_KING = ACCOUNT_CIV,
+ JOB_HUD_SYNDICATE = ACCOUNT_CIV,
+ JOB_HUD_UNKNOWN = ACCOUNT_CIV,
+ JOB_HUD_PRISONER = ACCOUNT_CIV
+ )
+ return hud_to_department_acc[jobname] || ACCOUNT_CIV // default: Civ budget department
+
+// used to determine chat color by HUD in `chatmessage.dm`
+// Note: custom colors are what I really didn't put much attention into. feel free to change its color when you feel off.
+/datum/chatmessage/proc/get_chatcolor_by_hud(jobname)
+ if(!jobname)
+ CRASH("The proc has taken a null value")
+
+ var/static/hud_to_chatcolor = list(
+ // Command
+ JOB_HUD_RAWCOMMAND = JOB_CHATCOLOR_RAWCOMMAND,
+ JOB_HUD_CAPTAIN = JOB_CHATCOLOR_CAPTAIN,
+ JOB_HUD_ACTINGCAPTAIN = JOB_CHATCOLOR_ACTINGCAPTAIN,
+
+ // Service
+ JOB_HUD_RAWSERVICE = JOB_CHATCOLOR_RAWSERVICE,
+ JOB_HUD_HEADOFPERSONNEL = JOB_CHATCOLOR_HEADOFPERSONNEL,
+ JOB_HUD_ASSISTANT = JOB_CHATCOLOR_ASSISTANT,
+ JOB_HUD_BARTENDER = JOB_CHATCOLOR_BARTENDER,
+ JOB_HUD_COOK = JOB_CHATCOLOR_COOK,
+ JOB_HUD_BOTANIST = JOB_CHATCOLOR_BOTANIST,
+ JOB_HUD_CURATOR = JOB_CHATCOLOR_CURATOR,
+ JOB_HUD_CHAPLAIN = JOB_CHATCOLOR_CHAPLAIN,
+ JOB_HUD_JANITOR = JOB_CHATCOLOR_JANITOR,
+ JOB_HUD_LAWYER = JOB_CHATCOLOR_LAWYER,
+ JOB_HUD_MIME = JOB_CHATCOLOR_MIME,
+ JOB_HUD_CLOWN = JOB_CHATCOLOR_CLOWN,
+ JOB_HUD_STAGEMAGICIAN = JOB_CHATCOLOR_STAGEMAGICIAN,
+ JOB_HUD_BARBER = JOB_CHATCOLOR_BARBER,
+
+ // Cargo
+ JOB_HUD_RAWCARGO = JOB_CHATCOLOR_RAWCARGO,
+ JOB_HUD_QUARTERMASTER = JOB_CHATCOLOR_QUARTERMASTER,
+ JOB_HUD_CARGOTECHNICIAN = JOB_CHATCOLOR_CARGOTECHNICIAN,
+ JOB_HUD_SHAFTMINER = JOB_CHATCOLOR_SHAFTMINER,
+
+ // Engineering
+ JOB_HUD_RAWENGINEERING = JOB_CHATCOLOR_RAWENGINEERING,
+ JOB_HUD_CHIEFENGINEER = JOB_CHATCOLOR_CHIEFENGINEER,
+ JOB_HUD_STATIONENGINEER = JOB_CHATCOLOR_STATIONENGINEER,
+ JOB_HUD_ATMOSPHERICTECHNICIAN = JOB_CHATCOLOR_ATMOSPHERICTECHNICIAN,
+
+ // Medical
+ JOB_HUD_RAWMEDICAL = JOB_CHATCOLOR_RAWMEDICAL,
+ JOB_HUD_CHEIFMEDICALOFFICIER = JOB_CHATCOLOR_CHEIFMEDICALOFFICIER,
+ JOB_HUD_MEDICALDOCTOR = JOB_CHATCOLOR_MEDICALDOCTOR,
+ JOB_HUD_PARAMEDIC = JOB_CHATCOLOR_PARAMEDIC,
+ JOB_HUD_VIROLOGIST = JOB_CHATCOLOR_VIROLOGIST,
+ JOB_HUD_CHEMIST = JOB_CHATCOLOR_CHEMIST,
+ JOB_HUD_GENETICIST = JOB_CHATCOLOR_GENETICIST,
+ JOB_HUD_PSYCHIATRIST = JOB_CHATCOLOR_PSYCHIATRIST,
+
+ // R&D
+ JOB_HUD_RAWSCIENCE = JOB_CHATCOLOR_RAWSCIENCE,
+ JOB_HUD_RESEARCHDIRECTOR = JOB_CHATCOLOR_RESEARCHDIRECTOR,
+ JOB_HUD_SCIENTIST = JOB_CHATCOLOR_SCIENTIST,
+ JOB_HUD_ROBOTICIST = JOB_CHATCOLOR_ROBOTICIST,
+ JOB_HUD_EXPLORATIONCREW = JOB_CHATCOLOR_EXPLORATIONCREW,
+
+ // Security
+ JOB_HUD_RAWSECURITY = JOB_CHATCOLOR_RAWSECURITY,
+ JOB_HUD_HEADOFSECURITY = JOB_CHATCOLOR_HEADOFSECURITY,
+ JOB_HUD_WARDEN = JOB_CHATCOLOR_WARDEN,
+ JOB_HUD_SECURITYOFFICER = JOB_CHATCOLOR_SECURITYOFFICER,
+ JOB_HUD_DETECTIVE = JOB_CHATCOLOR_DETECTIVE,
+ JOB_HUD_BRIGPHYSICIAN = JOB_CHATCOLOR_BRIGPHYSICIAN,
+ JOB_HUD_DEPUTY = JOB_CHATCOLOR_DEPUTY,
+
+ // CentCom
+ JOB_HUD_RAWCENTCOM = JOB_CHATCOLOR_RAWCENTCOM,
+ JOB_HUD_CENTCOM = JOB_CHATCOLOR_CENTCOM,
+
+ // ETC
+ JOB_HUD_VIP = JOB_CHATCOLOR_VIP,
+ JOB_HUD_KING = JOB_CHATCOLOR_KING,
+ JOB_HUD_SYNDICATE = JOB_CHATCOLOR_SYNDICATE,
+ JOB_HUD_NOTCENTCOM = JOB_CHATCOLOR_NOTCENTCOM,
+ JOB_HUD_PRISONER = JOB_CHATCOLOR_PRISONER,
+ JOB_HUD_UNKNOWN = JOB_CHATCOLOR_UNKNOWN
+ )
+ return hud_to_chatcolor[jobname] || JOB_CHATCOLOR_UNKNOWN
+
diff --git a/code/controllers/subsystem/economy.dm b/code/controllers/subsystem/economy.dm
index cd3515790d0..ae362fb1acc 100644
--- a/code/controllers/subsystem/economy.dm
+++ b/code/controllers/subsystem/economy.dm
@@ -1,3 +1,5 @@
+#define VIP_BUDGET_BASE rand(8888888, 11111111)
+
SUBSYSTEM_DEF(economy)
name = "Economy"
wait = 5 MINUTES
@@ -12,6 +14,7 @@ SUBSYSTEM_DEF(economy)
ACCOUNT_SRV = ACCOUNT_SRV_NAME,
ACCOUNT_CAR = ACCOUNT_CAR_NAME,
ACCOUNT_SEC = ACCOUNT_SEC_NAME)
+ var/list/nonstation_accounts = list(ACCOUNT_VIP = ACCOUNT_VIP_NAME)
var/list/generated_accounts = list()
var/full_ancap = FALSE // Enables extra money charges for things that normally would be free, such as sleepers/cryo/cloning.
//Take care when enabling, as players will NOT respond well if the economy is set up for low cash flows.
@@ -25,6 +28,8 @@ SUBSYSTEM_DEF(economy)
var/budget_to_hand_out = round(budget_pool / department_accounts.len)
for(var/A in department_accounts)
new /datum/bank_account/department(A, budget_to_hand_out)
+ for(var/A in nonstation_accounts)
+ new /datum/bank_account/department(A, VIP_BUDGET_BASE)
return ..()
/datum/controller/subsystem/economy/fire(resumed = 0)
@@ -81,3 +86,9 @@ SUBSYSTEM_DEF(economy)
sci?.adjust_money(science_cash)
civ?.adjust_money(civilian_cash)
car?.adjust_money(cargo_cash)
+
+ // VIP budget will not dry
+ var/datum/bank_account/vip = get_dep_account(ACCOUNT_VIP)
+ vip?.adjust_money(cargo_cash)
+
+#undef VIP_BUDGET_BASE
diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm
index 403b04a5a26..15f83f8b130 100644
--- a/code/game/machinery/PDApainter.dm
+++ b/code/game/machinery/PDApainter.dm
@@ -220,7 +220,19 @@
return
if(!storedid)//is the ID still there?
return
+<<<<<<< HEAD
storedid.icon_state = id_icons[newidskin]
+=======
+ storedid.icon_state = get_cardstyle_by_jobname(newidskin)
+ storedid.hud_state = get_hud_by_jobname(newidskin)
+
+ // QoL to correct the system behavior
+ if(storedid.registered_account)
+ if(!storedid.registered_account.department_locked)
+ storedid.registered_account.account_department = get_department_by_hud(storedid.hud_state) // your true department by your hud icon color
+ GLOB.data_core.manifest_modify(storedid.registered_name, storedid.assignment, storedid.hud_state) // update crew manifest
+ // There are the same code lines in `card.dm`
+>>>>>>> 2ee586c7bc... VIP gets their own department budget than the civilian budget, and gets paid from it. (+department account lock feature) (#7330)
ejectid()
else
to_chat(user, "[src] is empty.")
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 17ac11c6aca..2344d30fc9d 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -298,7 +298,7 @@ Class Procs:
nap_violation(occupant)
return FALSE
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
- if(D)
+ if(D && !D.is_nonstation_account())
D.adjust_money(fair_market_price)
else
say("[market_verb] NAP Violation: No ID card found.")
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 1baf6947c20..f49b1609cd9 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -314,8 +314,8 @@
if(internal_radio)
SPEAK("The cloning of [mob_occupant.real_name] has been ended prematurely due to being unable to pay.")
else
- var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
- if(D)
+ var/datum/bank_account/department/D = SSeconomy.get_dep_account(payment_department)
+ if(D && !D.is_nonstation_account())
D.adjust_money(fair_market_price)
if(mob_occupant && (mob_occupant.stat == DEAD) || (mob_occupant.suiciding) || mob_occupant.hellbound) //Autoeject corpses and suiciding dudes.
connected_message("Clone Rejected: Deceased.")
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index b0fce8ec871..c1eadbcf1eb 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -721,6 +721,86 @@ update_label("John Doe", "Clowny")
department_ID = ACCOUNT_SEC
department_name = ACCOUNT_SEC_NAME
icon_state = "budget_sec"
+<<<<<<< HEAD
+=======
+ hud_state = JOB_HUD_RAWSECURITY
+
+// This will never be spawned, but should be trackable by admins anyway.
+/obj/item/card/id/departmental_budget/vip
+ department_ID = ACCOUNT_VIP
+ department_name = ACCOUNT_VIP_NAME
+ icon_state = "budget"
+ hud_state = JOB_HUD_VIP
+
+/// Job Specific ID Cards///
+// These should have default job name and hud state, etc, because chameleon card needs such information
+// ---- Command ----
+/obj/item/card/id/job/captain
+ name = "Job card (Com) - Captain"
+ icon_state = "captain"
+ assignment = JOB_NAME_CAPTAIN
+ hud_state = JOB_HUD_CAPTAIN
+
+/obj/item/card/id/job/rawcommand
+ name = "Job card (Com) - Custom"
+ icon_state = JOB_HUD_RAWCOMMAND
+ hud_state = JOB_HUD_RAWCOMMAND
+
+// ---- Service ----
+/obj/item/card/id/job/assistant
+ name = "Job card (Serv) - Assistant"
+ icon_state = "id"
+ assignment = JOB_NAME_ASSISTANT
+ hud_state = JOB_HUD_ASSISTANT
+
+/obj/item/card/id/job/head_of_personnel
+ name = "Job card (Serv) - HoP"
+ icon_state = "hop"
+ assignment = JOB_NAME_HEADOFPERSONNEL
+ hud_state = JOB_HUD_HEADOFPERSONNEL
+
+/obj/item/card/id/job/botanist
+ name = "Job card (Serv) - Botanist"
+ icon_state = "serv"
+ assignment = JOB_NAME_BOTANIST
+ hud_state = JOB_HUD_BOTANIST
+
+/obj/item/card/id/job/cook
+ name = "Job card (Serv) - Cook"
+ icon_state = "serv"
+ assignment = JOB_NAME_COOK
+ hud_state = JOB_HUD_COOK
+
+/obj/item/card/id/job/bartender
+ name = "Job card (Serv) - Bartender"
+ icon_state = "serv"
+ assignment = JOB_NAME_BARTENDER
+ hud_state = JOB_HUD_BARTENDER
+
+/obj/item/card/id/job/barber
+ name = "Job card (Serv) - Barber"
+ icon_state = "serv"
+ assignment = JOB_NAME_BARBER
+ hud_state = JOB_HUD_BARBER
+
+/obj/item/card/id/job/stage_magician
+ name = "Job card (Serv) - Magician"
+ icon_state = "serv"
+ assignment = JOB_NAME_STAGEMAGICIAN
+ hud_state = JOB_HUD_STAGEMAGICIAN
+
+/obj/item/card/id/job/curator
+ name = "Job card (Serv) - Curator"
+ icon_state = "chap"
+ assignment = JOB_NAME_CURATOR
+ hud_state = JOB_HUD_CURATOR
+
+/obj/item/card/id/job/chaplain
+ name = "Job card (Serv) - Chaplain"
+ icon_state = "chap"
+ assignment = JOB_NAME_CHAPLAIN
+ hud_state = JOB_HUD_CHAPLAIN
+>>>>>>> 2ee586c7bc... VIP gets their own department budget than the civilian budget, and gets paid from it. (+department account lock feature) (#7330)
///Job Specific ID Cards///
diff --git a/code/game/objects/items/mail.dm b/code/game/objects/items/mail.dm
new file mode 100644
index 00000000000..4e74d6cb3c5
--- /dev/null
+++ b/code/game/objects/items/mail.dm
@@ -0,0 +1,360 @@
+/// Mail is tamper-evident and unresealable, postmarked by CentCom for an individual recepient.
+/obj/item/mail
+ name = "mail"
+ gender = NEUTER
+ desc = "An officially postmarked, tamper-evident parcel powered by bluespace technology and regulated by CentCom, it's made of rather high-quality materials."
+ icon = 'icons/obj/bureaucracy.dmi'
+ icon_state = "mail_small"
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ item_flags = NOBLUDGEON
+ w_class = WEIGHT_CLASS_SMALL
+ throwforce = 0
+ throw_range = 1
+ throw_speed = 1
+ /// Destination tagging for the mail sorter.
+ mouse_drag_pointer = MOUSE_ACTIVE_POINTER
+ /// Weak reference to who this mail is for and who can open it.
+ var/sort_tag = 0
+ /// How many goodies this mail contains.
+ var/datum/weakref/recipient_ref
+ /// Goodies which can be given to anyone. The base weight for cash is 56. For there to be a 50/50 chance of getting a department item, they need 56 weight as well.
+ var/goodie_count = 1
+
+ var/static/list/generic_goodies = list(
+ /obj/item/stack/spacecash/c10 = 22, //the lamest chance to get item, what do you expect really?
+ /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game = 10,
+ /obj/item/reagent_containers/food/drinks/soda_cans/monkey_energy = 10,
+ /obj/item/reagent_containers/food/snacks/cheesiehonkers = 10,
+ /obj/item/reagent_containers/food/snacks/candy = 10,
+ /obj/item/reagent_containers/food/snacks/chips = 10,
+ /obj/item/stack/spacecash/c50 = 10,
+ /obj/item/stack/spacecash/c100 = 25,
+ /obj/item/stack/spacecash/c200 = 15,
+ /obj/item/stack/spacecash/c500 = 5,
+ /obj/item/stack/spacecash/c1000 = 1
+ )
+
+ //if the goodie is dangerous for the station, in this list it goes
+ var/static/list/hazard_goodies = list(
+ /obj/item/gun/ballistic/rifle/boltaction,
+ /obj/item/construction/rcd/arcd,
+ /obj/item/reagent_containers/spray/waterflower/superlube,
+ /mob/living/simple_animal/hostile/retaliate/clown,
+ /obj/item/clothing/accessory/holster/detective,
+ /obj/item/reagent_containers/hypospray/medipen/pumpup,
+ /obj/item/firing_pin,
+ /obj/item/storage/lockbox/loyalty,
+ /obj/item/grenade/clusterbuster/cleaner,
+ /obj/item/book/granter/spell/mimery_blockade,
+ /obj/item/gun/ballistic/rifle/boltaction/enchanted,
+ /obj/item/melee/classic_baton/police/telescopic,
+ /obj/item/reagent_containers/glass/bottle/random_virus/minor,
+ /obj/item/reagent_containers/glass/bottle/random_virus,
+ /obj/item/gun/ballistic/revolver/nagant
+ )
+
+ /// Overlays (pure fluff), Does the letter have the postmark overlay?
+ /// Does the letter have postmarks?
+ var/postmarked = TRUE
+ /// Does the letter have a stamp overlay?
+ var/stamped = TRUE
+ /// List of all stamp overlays on the letter.
+ var/list/stamps = list()
+ /// Maximum number of stamps on the letter.
+ var/stamp_max = 1
+ /// Physical offset of stamps on the object. X direction.
+ var/stamp_offset_x = 0
+ /// Physical offset of stamps on the object. Y direction.
+ var/stamp_offset_y = 2
+ /// Mail will have the color of the department the recipient is in.
+ var/static/list/department_colors
+
+/obj/item/mail/envelope
+ name = "envelope"
+ icon_state = "mail_large"
+ goodie_count = 2
+ stamp_max = 2
+ stamp_offset_y = 5
+
+/obj/item/mail/examine(mob/user)
+ . = ..()
+
+ var/datum/mind/recipient
+ if(recipient_ref)
+ recipient = recipient_ref.resolve()
+ var/msg = "You notice the postmarking on the front of the mail..."
+ if(recipient)
+ msg += "\nCertified NT mail for [recipient]."
+ else
+ msg += "\nCertified mail for [GLOB.station_name]."
+ . += "\n[msg]"
+
+
+/obj/item/mail/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_MOVABLE_DISPOSING, .proc/disposal_handling)
+ AddElement(/datum/element/item_scaling, 0.75, 1)
+ if(isnull(department_colors))
+ department_colors = list(
+ ACCOUNT_CIV = COLOR_WHITE,
+ ACCOUNT_ENG = COLOR_PALE_ORANGE,
+ ACCOUNT_SCI = COLOR_PALE_PURPLE_GRAY,
+ ACCOUNT_MED = COLOR_PALE_BLUE_GRAY,
+ ACCOUNT_SRV = COLOR_PALE_GREEN_GRAY,
+ ACCOUNT_CAR = COLOR_BEIGE,
+ ACCOUNT_SEC = COLOR_PALE_RED_GRAY,
+ ACCOUNT_VIP = COLOR_YELLOW,
+ )
+
+ // Icons
+ // Add some random stamps.
+ if(stamped)
+ var/stamp_count = rand(1, stamp_max)
+ for(var/i in 1 to stamp_count)
+ stamps += list("stamp_[rand(2, 10)]")
+ update_icon()
+
+/obj/item/mail/update_overlays()
+ . = ..()
+ var/bonus_stamp_offset = 0
+ for(var/stamp in stamps)
+ var/image/stamp_image = image(
+ icon = icon,
+ icon_state = stamp,
+ pixel_x = stamp_offset_x,
+ pixel_y = stamp_offset_y + bonus_stamp_offset
+ )
+ stamp_image.appearance_flags |= RESET_COLOR
+ add_overlay(stamp_image)
+ bonus_stamp_offset -= 5
+
+ if(postmarked)
+ var/image/postmark_image = image(
+ icon = icon,
+ icon_state = "postmark",
+ pixel_x = stamp_offset_x + rand(-3, 1),
+ pixel_y = stamp_offset_y + rand(bonus_stamp_offset + 3, 1)
+ )
+ postmark_image.appearance_flags |= RESET_COLOR
+ add_overlay(postmark_image)
+
+/obj/item/mail/attackby(obj/item/W, mob/user, params)
+ // Destination tagging
+ if(istype(W, /obj/item/dest_tagger))
+ var/obj/item/dest_tagger/destination_tag = W
+
+ if(sort_tag != destination_tag.currTag)
+ var/tag = uppertext(GLOB.TAGGERLOCATIONS[destination_tag.currTag])
+ to_chat(user, "*[tag]*")
+ sort_tag = destination_tag.currTag
+ playsound(loc, 'sound/machines/twobeep_high.ogg', 100, 1)
+
+/obj/item/mail/attack_self(mob/user)
+ if(recipient_ref)
+ var/datum/mind/recipient = recipient_ref.resolve()
+ // If the recipient's mind has gone, then anyone can open their mail
+ // whether a mind can actually be qdel'd is an exercise for the reader
+ if(recipient && recipient != user?.mind)
+ if(!is_changeling(user))
+ to_chat(user, "You can't open somebody else's mail! That's immoral!")
+ return
+ if(user.real_name != recipient.name)
+ to_chat(user, "We must keep our disguise intact.") // cuz your disguise cant open the mail so you shouldnt either
+ return
+
+ user.visible_message("[user] start to unwrap a package...", \
+ "You start to unwrap the package...", \
+ "You hear paper ripping.")
+ if(!do_after(user, 1.5 SECONDS, target = user))
+ return
+ user.temporarilyRemoveItemFromInventory(src, TRUE)
+ if(contents.len)
+ user.put_in_hands(contents[1])
+ playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ qdel(src)
+
+// Accepts a mind to initialize goodies for a piece of mail.
+/obj/item/mail/proc/initialize_for_recipient(datum/mind/recipient)
+ switch(rand(1,5))
+ if(5)
+ name = "[initial(name)] critical to [recipient.name] ([recipient.assigned_role])"
+ else
+ name = "[initial(name)] for [recipient.name] ([recipient.assigned_role])"
+ recipient_ref = WEAKREF(recipient)
+
+ //Recipients
+ var/mob/living/body = recipient.current
+ //Load the generic list of goodies
+ var/list/goodies = generic_goodies.Copy()
+ //Load the List of Dangerous goodies
+ var/list/danger_goodies = hazard_goodies
+
+ //Load the job the player have
+ var/datum/job/this_job = SSjob.name_occupations[recipient.assigned_role]
+ if(this_job)
+ goodies += this_job.mail_goodies
+ if(this_job.paycheck_department && department_colors[this_job.paycheck_department])
+ color = department_colors[this_job.paycheck_department]
+
+ for(var/i in 1 to goodie_count)
+ var/target_good = pickweight(goodies)
+ var/atom/movable/target_atom = new target_good(src)
+ body.log_message("[key_name(body)] received [target_atom.name] in the mail ([target_good])", LOG_GAME)
+ if(target_atom.type in danger_goodies)
+ message_admins("DANGEROUS ITEM RECIEVED:[ADMIN_LOOKUPFLW(body)] received [target_atom.name] in the mail ([target_good]) as a [recipient.assigned_role]")
+
+ return TRUE
+
+// Alternate setup, just complete garbage inside and anyone can open
+/obj/item/mail/proc/junk_mail()
+
+ var/obj/junk = /obj/item/paper/fluff/junkmail_generic
+ var/special_name = FALSE
+ add_overlay("[initial(icon_state)]-spam")
+
+ if(prob(25))
+ special_name = TRUE
+ junk = pick(list(/obj/item/paper/pamphlet/gateway,
+ /obj/item/paper/pamphlet/violent_video_games,
+ /obj/item/paper/fluff/junkmail_redpill,
+ /obj/item/paper/fluff/nice_argument
+ ))
+
+ var/static/list/junk_names = list(
+ /obj/item/paper/pamphlet/gateway = "[initial(name)] for [pick(GLOB.adjectives)] adventurers",
+ /obj/item/paper/pamphlet/violent_video_games = "[initial(name)] for the truth about the arcade centcom doesn't want to hear",
+ /obj/item/paper/fluff/junkmail_redpill = "[initial(name)] for those feeling [pick(GLOB.adjectives)] working at Nanotrasen",
+ /obj/item/paper/fluff/nice_argument = "[initial(name)] with INCREDIBLY IMPORTANT ARTIFACT- DELIVER TO SCIENCE DIVISION. HANDLE WITH CARE.",
+ )
+
+ //better spam mail names instead of being "IMPORTANT MAIL", courtesy of Monkestation
+ color = "#[pick(random_short_color())]"
+ switch(rand(1,10))
+
+ if(1,2)
+ name = special_name ? junk_names[junk] : "[initial(name)] for [pick(GLOB.alive_mob_list)]" //LETTER FOR IAN / BUBBLEGUM / MONKEY(420)
+ if(3,4)
+ name = special_name ? junk_names[junk] : "[initial(name)] for [pick(GLOB.player_list)]" //Letter for ANYONE, even that wizard rampaging through the station.
+ if(5)
+ name = special_name ? junk_names[junk] : "DO NOT OPEN"
+ else
+ name = special_name ? junk_names[junk] : "[pick("important","critical","crucial","serious","vital")] [initial(name)]"
+
+ junk = new junk(src)
+ return TRUE
+
+/obj/item/mail/proc/disposal_handling(disposal_source, obj/structure/disposalholder/disposal_holder, obj/machinery/disposal/disposal_machine, hasmob)
+ SIGNAL_HANDLER
+ if(!hasmob)
+ disposal_holder.destinationTag = sort_tag
+
+// Subtype that's always junkmail
+/obj/item/mail/junkmail/Initialize()
+ . = ..()
+ junk_mail()
+
+/obj/structure/closet/crate/mail
+ name = "mail crate"
+ desc = "A certified post crate from CentCom."
+ icon_state = "mail_crate"
+ door_anim_time = 0
+
+/* Fills this mail crate with N pieces of mail, where N is the lower of the amount var passed,
+** and the maximum capacity of this crate. If N is larger than the number of alive human players, the excess will be junkmail.*/
+/obj/structure/closet/crate/mail/proc/populate(amount)
+ var/mail_count = min(amount, storage_capacity)
+ //fills the crate for the recipients
+ var/list/mail_recipients = list()
+
+ for(var/mob/living/carbon/human/human in GLOB.player_list)
+ // Skip wizards, nuke ops, cyborgs and dead people; Centcom does not send them mail
+ if(human.stat == DEAD || !human.mind || !SSjob.GetJob(human.mind.assigned_role) || human.mind.special_role)
+ continue
+
+ mail_recipients += human.mind
+
+ if(mail_count < 15)
+ for(var/i in 1 to rand(3,8))
+ var/obj/item/mail/new_mail
+ if(prob(FULL_CRATE_LETTER_ODDS))
+ new_mail = new /obj/item/mail(src)
+ else
+ new_mail = new /obj/item/mail/envelope(src)
+ new_mail.junk_mail()
+
+ for(var/i in 1 to mail_count)
+ var/datum/mind/recipient = pick_n_take(mail_recipients)
+ var/obj/item/mail/new_mail
+ if(prob(FULL_CRATE_LETTER_ODDS))
+ new_mail = new /obj/item/mail(src)
+ else
+ new_mail = new /obj/item/mail/envelope(src)
+ if(recipient)
+ new_mail.initialize_for_recipient(recipient)
+ else
+ new_mail.junk_mail()
+
+ update_icon()
+
+/// Crate for mail that automatically depletes the economy subsystem's pending mail counter.
+/obj/structure/closet/crate/mail/economy/Initialize()
+ . = ..()
+ populate(SSeconomy.mail_waiting)
+ SSeconomy.mail_waiting = 0
+
+/// Crate for mail that automatically generates a lot of mail. Usually only normal mail, but on lowpop it may end up just being junk.
+/obj/structure/closet/crate/mail/full
+ name = "brimming mail crate"
+ desc = "A certified post crate from CentCom. Looks stuffed to the gills."
+
+/obj/structure/closet/crate/mail/full/Initialize()
+ . = ..()
+ populate(null)
+
+/obj/item/paper/fluff/junkmail_redpill
+ name = "smudged paper"
+ icon_state = "scrap"
+ var/nuclear_option_odds = 0.1
+
+/obj/item/paper/fluff/nice_argument
+ name = "RE: Nice Argument..."
+ icon_state = "paper"
+
+/obj/item/paper/fluff/nice_argument/Initialize()
+ . = ..()
+ var/station_name = station_name()
+ info = "Nice argument, however there's a small detail...
IP: '[rand(0,10)].[rand(0,255)].[rand(0,255)].[rand(0,255)]'
Station name: '[station_name]'
"
+
+/obj/item/paper/fluff/junkmail_redpill/Initialize()
+ . = ..()
+ // 1 in 1000 chance of getting 2 random nuke code characters.
+ if(!prob(nuclear_option_odds))
+ info = "You need to escape the simulation. Don't forget the numbers, they help you remember: '[random_code(4)]...'"
+ return
+ var/code = random_code(5)
+ for(var/obj/machinery/nuclearbomb/selfdestruct/nuke in GLOB.nuke_list)
+ if(nuke)
+ if(nuke.r_code == "ADMIN")
+ nuke.r_code = code
+ message_admins("Through junkmail, the self-destruct code was set to \"[code]\".")
+ else //Already set by admins/something else?
+ code = nuke.r_code
+ else
+ stack_trace("Station self-destruct not found during lone op team creation.")
+ code = null
+ info = "You need to escape the simulation. Don't forget the numbers, they help you remember: '[code[rand(1,5)]][code[rand(1,5)]][code[rand(1,5)]][code[rand(1,5)]]...'"
+
+//admin letter enabling players to brute force their way through the nuke code if they're so inclined.
+/obj/item/paper/fluff/junkmail_redpill/true
+ nuclear_option_odds = 100
+
+/obj/item/paper/fluff/junkmail_generic
+ name = "important document"
+ desc = "I wonder what's so important here..."
+ icon_state = "paper_spam"
+ color = "#FFCCFF"
+
+/obj/item/paper/fluff/junkmail_generic/Initialize()
+ . = ..()
+ info = pick(GLOB.junkmail_messages)
diff --git a/code/modules/economy/account.dm b/code/modules/economy/account.dm
index 4909704a681..13a0964a0da 100644
--- a/code/modules/economy/account.dm
+++ b/code/modules/economy/account.dm
@@ -1,3 +1,4 @@
+#define DEPARTMENT_LOCKED_JOBS list("VIP", "Captain", "Head of Security")
#define DUMPTIME 3000
/datum/bank_account
@@ -14,6 +15,7 @@
var/welfare = FALSE
var/being_dumped = FALSE //pink levels are rising
var/withdrawDelay = 0
+ var/department_locked = FALSE //TRUE locks from changing `account_department` into something else. used for VIP, Captain, and HoS. Those jobs don't need to change paycheck department.
/datum/bank_account/New(newname, job)
if(add_to_accounts)
@@ -22,6 +24,12 @@
account_job = job
account_id = rand(111111,999999)
paycheck_amount = account_job.paycheck
+<<<<<<< HEAD
+=======
+ account_department = account_job.paycheck_department
+ if(account_job.title in DEPARTMENT_LOCKED_JOBS)
+ department_locked = TRUE
+>>>>>>> 2ee586c7bc... VIP gets their own department budget than the civilian budget, and gets paid from it. (+department account lock feature) (#7330)
/datum/bank_account/Destroy()
if(add_to_accounts)
@@ -116,7 +124,17 @@
/datum/bank_account/department/New(dep_id, budget)
department_id = dep_id
account_balance = budget
- account_holder = SSeconomy.department_accounts[dep_id]
+ var/list/total_department_list = SSeconomy.department_accounts+SSeconomy.nonstation_accounts
+
+ account_holder = total_department_list[dep_id]
+
SSeconomy.generated_accounts += src
+/datum/bank_account/proc/is_nonstation_account() // returns TRUE if the budget account is not Station department. i.e.) medical budget, security budget: FALSE / `nonstation_accounts` like VIP one: TRUE
+ for(var/each in SSeconomy.nonstation_accounts)
+ if(account_holder == SSeconomy.nonstation_accounts[each])
+ return TRUE
+ return FALSE
+
#undef DUMPTIME
+#undef DEPARTMENT_LOCKED_JOBS
diff --git a/code/modules/jobs/job_types/gimmick.dm b/code/modules/jobs/job_types/gimmick.dm
index 82dfe3fe706..d1f9d1752be 100644
--- a/code/modules/jobs/job_types/gimmick.dm
+++ b/code/modules/jobs/job_types/gimmick.dm
@@ -148,9 +148,17 @@
outfit = /datum/outfit/job/gimmick/celebrity
access = list(ACCESS_MAINT_TUNNELS) //Assistants with shitloads of money, what could go wrong?
minimal_access = list(ACCESS_MAINT_TUNNELS)
+<<<<<<< HEAD
gimmick = TRUE
paycheck = PAYCHECK_VIP //our power is being fucking rich
chat_color = "#ebc96b"
+=======
+ paycheck = PAYCHECK_VIP //our power is being fucking rich
+ paycheck_department = ACCOUNT_VIP //budget will never dry
+
+ departments = DEPARTMENT_BITFLAG_SERVICE // might need to be changed
+ rpg_title = "Master of Patronage"
+>>>>>>> 2ee586c7bc... VIP gets their own department budget than the civilian budget, and gets paid from it. (+department account lock feature) (#7330)
species_outfits = list(
SPECIES_PLASMAMAN = /datum/outfit/plasmaman/vip
diff --git a/code/modules/modular_computers/file_system/programs/budgetordering.dm b/code/modules/modular_computers/file_system/programs/budgetordering.dm
new file mode 100644
index 00000000000..299280a6601
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/budgetordering.dm
@@ -0,0 +1,288 @@
+/datum/computer_file/program/budgetorders
+ filename = "orderapp"
+ filedesc = "Nanotrasen Internal Requisition Network (NIRN)"
+ category = PROGRAM_CATEGORY_SUPL
+ program_icon_state = "request"
+ extended_desc = "A request network that utilizes the Nanotrasen Ordering network to purchase supplies using a department budget account."
+ requires_ntnet = TRUE
+ usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET
+ size = 20
+ tgui_id = "NtosCargo"
+ program_icon = "credit-card"
+ //Are you actually placing orders with it?
+ var/requestonly = TRUE
+ //Can the tablet see or buy illegal stuff?
+ var/contraband = FALSE
+ //Is it being bought from a personal account, or is it being done via a budget/cargo?
+ var/self_paid = FALSE
+ //Can this console approve purchase requests?
+ var/can_approve_requests = FALSE
+ //What do we say when the shuttle moves with living beings on it.
+ var/safety_warning = "For safety and ethical reasons, the automated supply shuttle \
+ cannot transport live organisms, human remains, classified nuclear weaponry, \
+ homing beacons, mail, or machinery housing any form of artificial intelligence."
+ //If you're being raided by pirates, what do you tell the crew?
+ var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible."
+
+/datum/computer_file/program/budgetorders/proc/get_export_categories()
+ return EXPORT_CARGO
+
+/datum/computer_file/program/budgetorders/proc/get_buyer_id(mob/user) //gets access from id on person or inserted one
+ var/obj/item/card/id/id
+ if(ishuman(user))
+ var/mob/living/carbon/human/U = user
+ id = U.get_idcard(TRUE)
+ else if(computer)
+ var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD]
+ id = card_slot?.GetID()
+ return id ? id : FALSE
+
+/datum/computer_file/program/budgetorders/proc/is_visible_pack(mob/user, var/contraband)
+ if(issilicon(user)) //Borgs can't buy things.
+ return FALSE
+ if(computer.obj_flags & EMAGGED)
+ return TRUE
+ else if(contraband) //Hide contraband when non-emagged.
+ return FALSE
+
+ return TRUE
+
+/datum/computer_file/program/budgetorders/ui_data(mob/user)
+ . = ..()
+ var/list/data = get_header_data()
+ data["location"] = SSshuttle.supply.getStatusText()
+ var/datum/bank_account/buyer = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ var/obj/item/card/id/id_card = get_buyer_id(user)
+ if(get_buyer_id(user))
+ if((ACCESS_HEADS in id_card.access) || (ACCESS_QM in id_card.access))
+ requestonly = FALSE
+ buyer = SSeconomy.get_dep_account(id_card?.registered_account?.account_department)
+ can_approve_requests = TRUE
+ else
+ requestonly = TRUE
+ can_approve_requests = FALSE
+ else
+ requestonly = TRUE
+ if(isnull(buyer))
+ buyer = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ else if(buyer.is_nonstation_account())
+ buyer = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ if(buyer)
+ data["points"] = buyer.account_balance
+
+//Otherwise static data, that is being applied in ui_data as the crates visible and buyable are not static
+ data["requestonly"] = requestonly
+ data["supplies"] = list()
+ for(var/pack in SSshuttle.supply_packs)
+ var/datum/supply_pack/P = SSshuttle.supply_packs[pack]
+ if(!is_visible_pack(user, P.contraband) || P.hidden)
+ continue
+ if(!data["supplies"][P.group])
+ data["supplies"][P.group] = list(
+ "name" = P.group,
+ "packs" = list()
+ )
+ if((P.hidden && (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.DropPodOnly))
+ continue
+ data["supplies"][P.group]["packs"] += list(list(
+ "name" = P.name,
+ "cost" = P.cost,
+ "id" = pack,
+ "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name.
+ "access" = P.access
+ ))
+
+//Data regarding the User's capability to buy things.
+ data["has_id"] = id_card
+ data["away"] = SSshuttle.supply.getDockedId() == "supply_away"
+ data["self_paid"] = self_paid
+ data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE
+ data["loan"] = !!SSshuttle.shuttle_loan
+ data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched
+ data["can_send"] = FALSE //There is no situation where I want the app to be able to send the shuttle AWAY from the station, but conversely is fine.
+ data["can_approve_requests"] = can_approve_requests
+ data["app_cost"] = TRUE
+ var/message = "Remember to stamp and send back the supply manifests."
+ if(SSshuttle.centcom_message)
+ message = SSshuttle.centcom_message
+ if(SSshuttle.supplyBlocked)
+ message = blockade_warning
+ data["message"] = message
+ data["cart"] = list()
+ for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
+ data["cart"] += list(list(
+ "object" = SO.pack.name,
+ "cost" = SO.pack.cost,
+ "id" = SO.id,
+ "orderer" = SO.orderer,
+ "paid" = !isnull(SO.paying_account) //paid by requester
+ ))
+
+ data["requests"] = list()
+ for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ data["requests"] += list(list(
+ "object" = SO.pack.name,
+ "cost" = SO.pack.cost,
+ "orderer" = SO.orderer,
+ "reason" = SO.reason,
+ "id" = SO.id
+ ))
+
+ return data
+
+/datum/computer_file/program/budgetorders/ui_act(action, params, datum/tgui/ui)
+ if(..())
+ return
+ switch(action)
+ if("send")
+ if(!SSshuttle.supply.canMove())
+ computer.say(safety_warning)
+ return
+ if(SSshuttle.supplyBlocked)
+ computer.say(blockade_warning)
+ return
+ if(SSshuttle.supply.getDockedId() == "supply_home")
+ SSshuttle.supply.export_categories = get_export_categories()
+ SSshuttle.moveShuttle("supply", "supply_away", TRUE)
+ computer.say("The supply shuttle is departing.")
+ computer.investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO)
+ else
+ computer.investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO)
+ computer.say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.")
+ SSshuttle.moveShuttle("supply", "supply_home", TRUE)
+ . = TRUE
+ if("loan")
+ if(!SSshuttle.shuttle_loan)
+ return
+ if(SSshuttle.supplyBlocked)
+ computer.say(blockade_warning)
+ return
+ else if(SSshuttle.supply.mode != SHUTTLE_IDLE)
+ return
+ else if(SSshuttle.supply.getDockedId() != "supply_away")
+ return
+ else
+ SSshuttle.shuttle_loan.loan_shuttle()
+ computer.say("The supply shuttle has been loaned to CentCom.")
+ computer.investigate_log("[key_name(usr)] accepted a shuttle loan event.", INVESTIGATE_CARGO)
+ log_game("[key_name(usr)] accepted a shuttle loan event.")
+ . = TRUE
+ if("add")
+ var/id = text2path(params["id"])
+ var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
+ if(!istype(pack))
+ return
+ if((pack.hidden && (pack.contraband && !contraband) || pack.DropPodOnly))
+ return
+
+ var/name = "*None Provided*"
+ var/rank = "*None Provided*"
+ var/ckey = usr.ckey
+ if(ishuman(usr))
+ var/mob/living/carbon/human/H = usr
+ name = H.get_authentification_name()
+ rank = H.get_assignment(hand_first = TRUE)
+ else if(issilicon(usr))
+ name = usr.real_name
+ rank = "Silicon"
+
+ var/datum/bank_account/account
+ if(self_paid && ishuman(usr))
+ var/obj/item/card/id/id_card = get_buyer_id(usr)
+ if(!istype(id_card))
+ computer.say("No ID card detected.")
+ return
+ if(istype(id_card, /obj/item/card/id/departmental_budget))
+ computer.say("The application rejects [id_card].")
+ return
+ account = id_card.registered_account
+ if(!istype(account))
+ computer.say("Invalid bank account.")
+ return
+
+ var/reason = ""
+ if((requestonly && !self_paid) || !(get_buyer_id(usr)))
+ reason = stripped_input("Reason:", name, "")
+ if(isnull(reason) || ..())
+ return
+
+ if(!self_paid && ishuman(usr) && !account)
+ var/obj/item/card/id/id_card = get_buyer_id(usr)
+ if(!istype(id_card))
+ computer.say("No ID card detected.")
+ return
+ var/access = id_card.GetAccess()
+ if(!(computer.obj_flags & EMAGGED) && pack.access_budget && !(pack.access_budget in access))
+ computer.say("Insufficient access on [id_card].")
+ return
+ if(istype(id_card, /obj/item/card/id/departmental_budget))
+ computer.say("The application rejects [id_card].")
+ return
+ else
+ account = SSeconomy.get_dep_account(id_card?.registered_account?.account_department)
+ if(isnull(account))
+ computer.say("The application failed to identify [id_card].")
+ return
+ else if(account.is_nonstation_account())
+ computer.say("The application rejects [id_card].")
+ return
+
+
+ var/turf/T = get_turf(src)
+ var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account)
+ SO.generateRequisition(T)
+ if((requestonly && !self_paid) || !(get_buyer_id(usr)))
+ SSshuttle.requestlist += SO
+ else
+ SSshuttle.shoppinglist += SO
+ if(self_paid)
+ computer.say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.")
+ . = TRUE
+ if("remove")
+ var/id = text2num(params["id"])
+ for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
+ if(SO.id == id)
+ SSshuttle.shoppinglist -= SO
+ . = TRUE
+ break
+ if("clear")
+ SSshuttle.shoppinglist.Cut()
+ . = TRUE
+ if("approve")
+ var/id = text2num(params["id"])
+ for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ if(SO.id == id)
+ var/obj/item/card/id/id_card = get_buyer_id(usr)
+ if(id_card && id_card?.registered_account)
+ SO.paying_account = SSeconomy.get_dep_account(id_card?.registered_account?.account_department)
+ if(SO.paying_account.is_nonstation_account())
+ return
+ SSshuttle.requestlist -= SO
+ SSshuttle.shoppinglist += SO
+ . = TRUE
+ break
+ if("deny")
+ var/id = text2num(params["id"])
+ for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ if(SO.id == id)
+ SSshuttle.requestlist -= SO
+ . = TRUE
+ break
+ if("denyall")
+ SSshuttle.requestlist.Cut()
+ . = TRUE
+ if("toggleprivate")
+ self_paid = !self_paid
+ . = TRUE
+ if(.)
+ post_signal("supply")
+
+/datum/computer_file/program/budgetorders/proc/post_signal(command)
+
+ var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
+
+ if(!frequency)
+ return
+
+ var/datum/signal/status_signal = new(list("command" = command))
+ frequency.post_signal(src, status_signal)