diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm
index 7b619b274f5..32408e4f538 100644
--- a/code/__DEFINES/economy.dm
+++ b/code/__DEFINES/economy.dm
@@ -70,6 +70,7 @@
#define PAYMENT_CLINICAL "clinical"
#define PAYMENT_FRIENDLY "friendly"
#define PAYMENT_ANGRY "angry"
+#define PAYMENT_VENDING "vending"
#define MARKET_TREND_UPWARD 1
#define MARKET_TREND_DOWNWARD -1
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index 07fd4e2d560..651336fd12e 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -285,3 +285,10 @@ GLOBAL_LIST_INIT(WALLITEMS_EXTERIOR, typecacheof(list(
/obj/structure/camera_assembly,
/obj/structure/light_construct,
)))
+
+/// A static typecache of all the money-based items that can be actively used as currency.
+GLOBAL_LIST_INIT(allowed_money, typecacheof(list(
+ /obj/item/coin,
+ /obj/item/holochip,
+ /obj/item/stack/spacecash,
+)))
diff --git a/code/datums/components/payment.dm b/code/datums/components/payment.dm
index 1220614e9c3..5e79b28fd57 100644
--- a/code/datums/components/payment.dm
+++ b/code/datums/components/payment.dm
@@ -19,11 +19,6 @@
var/datum/bank_account/target_acc
///Does this payment component respect same-department-discount?
var/department_discount = FALSE
- ///A static typecache of all the money-based items that can be actively used as currency.
- var/static/list/allowed_money = typecacheof(list(
- /obj/item/stack/spacecash,
- /obj/item/holochip,
- /obj/item/coin))
/datum/component/payment/Initialize(_cost, _target, _style)
target_acc = _target
@@ -80,13 +75,13 @@
//Here is all the possible non-ID payment methods.
var/list/counted_money = list()
var/physical_cash_total = 0
- for(var/obj/item/credit in typecache_filter_list(user.get_all_contents(), allowed_money)) //Coins, cash, and credits.
+ for(var/obj/item/credit in typecache_filter_list(user.get_all_contents(), GLOB.allowed_money)) //Coins, cash, and credits.
if(physical_cash_total > total_cost)
break
physical_cash_total += credit.get_item_credit_value()
counted_money += credit
- if(is_type_in_typecache(user.pulling, allowed_money) && (physical_cash_total < total_cost)) //Coins(Pulled).
+ if(is_type_in_typecache(user.pulling, GLOB.allowed_money) && (physical_cash_total < total_cost)) //Coins(Pulled).
var/obj/item/counted_credit = user.pulling
physical_cash_total += counted_credit.get_item_credit_value()
counted_money += counted_credit
@@ -134,9 +129,11 @@
* Attempts to charge a mob, user, an integer number of credits, total_cost, directly from an ID card/bank account.
*/
/datum/component/payment/proc/handle_card(mob/living/user, obj/item/card/id/idcard, total_cost)
- var/atom/atom_parent = parent
+ var/atom/movable/atom_parent = parent
if(!idcard)
+ if(transaction_style == PAYMENT_VENDING)
+ to_chat(user, span_warning("No card found."))
return FALSE
if(!idcard?.registered_account)
switch(transaction_style)
@@ -146,6 +143,13 @@
to_chat(user, span_warning("ARE YOU JOKING. YOU DON'T HAVE A BANK ACCOUNT ON YOUR ID YOU IDIOT."))
if(PAYMENT_CLINICAL)
to_chat(user, span_warning("ID Card lacks a bank account. Advancing."))
+ if(PAYMENT_VENDING)
+ to_chat(user, span_warning("No account found."))
+
+ return FALSE
+
+ if(!idcard.registered_account.account_job)
+ atom_parent.say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.")
return FALSE
if(!(idcard.registered_account.has_money(total_cost)))
@@ -156,6 +160,8 @@
to_chat(user, span_warning("YOU MORON. YOU ABSOLUTE BAFOON. YOU INSUFFERABLE TOOL. YOU ARE POOR."))
if(PAYMENT_CLINICAL)
to_chat(user, span_warning("ID Card lacks funds. Aborting."))
+ if(PAYMENT_VENDING)
+ to_chat(user, span_warning("You do not possess the funds to purchase that."))
atom_parent.balloon_alert(user, "needs [total_cost] credit\s!")
return FALSE
target_acc.transfer_money(idcard.registered_account, total_cost, "Nanotrasen: Usage of Corporate Machinery")
diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm
index 0383767ce66..7084b313dff 100644
--- a/code/game/objects/items/vending_items.dm
+++ b/code/game/objects/items/vending_items.dm
@@ -19,8 +19,10 @@
w_class = WEIGHT_CLASS_BULKY
armor_type = /datum/armor/item_vending_refill
- // Built automatically from the corresponding vending machine.
- // If null, considered to be full. Otherwise, is list(/typepath = amount).
+ /**
+ * Built automatically from the corresponding vending machine.
+ * If null, considered to be full. Otherwise, is list(/typepath = amount).
+ */
var/list/products
var/list/product_categories
var/list/contraband
diff --git a/code/modules/cargo/exports/parts.dm b/code/modules/cargo/exports/parts.dm
index 840d40f1837..fc8c9656fea 100644
--- a/code/modules/cargo/exports/parts.dm
+++ b/code/modules/cargo/exports/parts.dm
@@ -33,3 +33,9 @@
unit_name = "data disk"
export_types = list(/obj/item/computer_disk)
include_subtypes = TRUE
+
+/datum/export/refill_canister
+ cost = CARGO_CRATE_VALUE * 0.5 //If someone want to make this worth more as it empties, go ahead
+ unit_name = "vending refill canister"
+ message = "Thank you for restocking the station!"
+ export_types = list(/obj/item/vending_refill)
diff --git a/code/modules/cargo/markets/market_uplink.dm b/code/modules/cargo/markets/market_uplink.dm
index 19c1a049a1b..a82218082e9 100644
--- a/code/modules/cargo/markets/market_uplink.dm
+++ b/code/modules/cargo/markets/market_uplink.dm
@@ -150,6 +150,7 @@
icon_state = "uplink"
//The original black market uplink
accessible_markets = list(/datum/market/blackmarket)
+ custom_premium_price = PAYCHECK_CREW * 2.5
/datum/crafting_recipe/blackmarket_uplink
diff --git a/code/modules/cargo/packs/vending_restock.dm b/code/modules/cargo/packs/vending_restock.dm
index cfe9961cc3a..10ae874d5d6 100644
--- a/code/modules/cargo/packs/vending_restock.dm
+++ b/code/modules/cargo/packs/vending_restock.dm
@@ -4,7 +4,7 @@
/datum/supply_pack/vending/bartending
name = "Booze-o-mat and Coffee Supply Crate"
desc = "Bring on the booze and coffee vending machine refills."
- cost = CARGO_CRATE_VALUE * 4
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/boozeomat,
/obj/item/vending_refill/coffee,
)
@@ -14,7 +14,7 @@
name = "Cigarette Supply Crate"
desc = "Don't believe the reports - smoke today! Contains a \
cigarette vending machine refill."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/cigarette)
crate_name = "cigarette supply crate"
crate_type = /obj/structure/closet/crate
@@ -62,7 +62,7 @@
/datum/supply_pack/vending/imported
name = "Imported Vending Machines"
desc = "Vending machines famous in other parts of the galaxy."
- cost = CARGO_CRATE_VALUE * 8
+ cost = CARGO_CRATE_VALUE * 5
contains = list(/obj/item/vending_refill/sustenance,
/obj/item/vending_refill/robotics,
/obj/item/vending_refill/sovietsoda,
@@ -74,7 +74,7 @@
name = "Medical Vending Crate"
desc = "Contains one NanoMed Plus refill, one NanoDrug Plus refill, \
and one wall-mounted NanoMed refill."
- cost = CARGO_CRATE_VALUE * 5
+ cost = CARGO_CRATE_VALUE * 3.5
contains = list(/obj/item/vending_refill/medical,
/obj/item/vending_refill/drugs,
/obj/item/vending_refill/wallmed,
@@ -85,7 +85,7 @@
name = "PTech Supply Crate"
desc = "Not enough cartridges after half the crew lost their PDA \
to explosions? This may fix it."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2.5
contains = list(/obj/item/vending_refill/cart)
crate_name = "\improper PTech supply crate"
@@ -103,7 +103,7 @@
name = "Snack Supply Crate"
desc = "One vending machine refill of cavity-bringin' goodness! \
The number one dentist recommended order!"
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/snack)
crate_name = "snacks supply crate"
@@ -111,14 +111,14 @@
name = "Softdrinks Supply Crate"
desc = "Got whacked by a toolbox, but you still have those pesky teeth? \
Get rid of those pearly whites with this soda machine refill, today!"
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/cola)
crate_name = "soft drinks supply crate"
/datum/supply_pack/vending/vendomat
name = "Part-Mart & YouTool Supply Crate"
desc = "More tools for your IED testing facility."
- cost = CARGO_CRATE_VALUE * 2
+ cost = CARGO_CRATE_VALUE * 3
contains = list(/obj/item/vending_refill/assist,
/obj/item/vending_refill/youtool,
)
@@ -138,7 +138,7 @@
name = "Autodrobe Supply Crate"
desc = "Autodrobe missing your favorite dress? Solve that issue today \
with this autodrobe refill."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/autodrobe)
crate_name = "autodrobe supply crate"
@@ -200,7 +200,7 @@
name = "Science Wardrobe Supply Crate"
desc = "This crate contains refills for the SciDrobe, \
GeneDrobe, and RoboDrobe."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 4.5
contains = list(/obj/item/vending_refill/wardrobe/robo_wardrobe,
/obj/item/vending_refill/wardrobe/gene_wardrobe,
/obj/item/vending_refill/wardrobe/science_wardrobe,
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 16a6eaf021e..551c900983e 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2722,3 +2722,20 @@ GLOBAL_LIST_EMPTY(fire_appearances)
end_look_down()
else
look_down()
+
+/**
+ * Totals the physical cash on the mob and returns the total.
+ */
+/mob/living/verb/tally_physical_credits()
+ //Here is all the possible non-ID payment methods.
+ var/list/counted_money = list()
+ var/physical_cash_total = 0
+ for(var/obj/item/credit as anything in typecache_filter_list(get_all_contents(), GLOB.allowed_money)) //Coins, cash, and credits.
+ physical_cash_total += credit.get_item_credit_value()
+ counted_money += credit
+
+ if(is_type_in_typecache(pulling, GLOB.allowed_money)) //Coins(Pulled).
+ var/obj/item/counted_credit = pulling
+ physical_cash_total += counted_credit.get_item_credit_value()
+ counted_money += counted_credit
+ return round(physical_cash_total)
diff --git a/code/modules/modular_computers/computers/item/disks/role_disks.dm b/code/modules/modular_computers/computers/item/disks/role_disks.dm
index da52ee76281..f7f20efb70b 100644
--- a/code/modules/modular_computers/computers/item/disks/role_disks.dm
+++ b/code/modules/modular_computers/computers/item/disks/role_disks.dm
@@ -98,6 +98,7 @@
starting_programs = list(
/datum/computer_file/program/shipping,
/datum/computer_file/program/budgetorders,
+ /datum/computer_file/program/restock_tracker,
)
/**
@@ -123,6 +124,6 @@
/datum/computer_file/program/alarm_monitor,
/datum/computer_file/program/atmosscan,
/datum/computer_file/program/supermatter_monitor,
-
+
)
diff --git a/code/modules/modular_computers/computers/item/role_tablet_presets.dm b/code/modules/modular_computers/computers/item/role_tablet_presets.dm
index 339def50add..9d42ec8c554 100644
--- a/code/modules/modular_computers/computers/item/role_tablet_presets.dm
+++ b/code/modules/modular_computers/computers/item/role_tablet_presets.dm
@@ -114,6 +114,7 @@
/datum/computer_file/program/robocontrol,
/datum/computer_file/program/budgetorders,
/datum/computer_file/program/shipping,
+ /datum/computer_file/program/restock_tracker,
)
/**
@@ -264,6 +265,7 @@
/datum/computer_file/program/shipping,
/datum/computer_file/program/budgetorders,
/datum/computer_file/program/robocontrol,
+ /datum/computer_file/program/restock_tracker,
)
/obj/item/modular_computer/pda/shaftminer
diff --git a/code/modules/modular_computers/file_system/programs/restock_tracker.dm b/code/modules/modular_computers/file_system/programs/restock_tracker.dm
new file mode 100644
index 00000000000..46462c0c6b5
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/restock_tracker.dm
@@ -0,0 +1,31 @@
+/datum/computer_file/program/restock_tracker
+ filename = "restockapp"
+ filedesc = "NT Restock Tracker"
+ downloader_category = PROGRAM_CATEGORY_SUPPLY
+ program_open_overlay = "restock"
+ extended_desc = "Nanotrasen IoT network listing all the vending machines found on station, and how well stocked they are each. Profitable!"
+ program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET
+ can_run_on_flags = PROGRAM_LAPTOP | PROGRAM_PDA
+ size = 4
+ program_icon = "cash-register"
+ tgui_id = "NtosRestock"
+
+/datum/computer_file/program/restock_tracker/ui_data()
+ var/list/data = list()
+ var/list/vending_list = list()
+ var/id_increment = 1
+ for(var/obj/machinery/vending/vendor as anything in GLOB.vending_machines_to_restock)
+ var/stock = vendor.total_loaded_stock()
+ var/max_stock = vendor.total_max_stock()
+ if((max_stock == 0 || (stock >= max_stock)) && vendor.credits_contained == 0)
+ continue
+ vending_list += list(list(
+ "name" = vendor.name,
+ "location" = get_area_name(vendor),
+ "credits" = vendor.credits_contained,
+ "percentage" = (stock / max_stock) * 100,
+ "id" = id_increment,
+ ))
+ id_increment++
+ data["vending_list"] = vending_list
+ return data
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index a344140d181..4ad46bb3b1a 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -14,8 +14,12 @@
premium = list()
*/
+/// List of vending machines that players can restock, so only vending machines that are on station or don't have a unique condition.
+GLOBAL_LIST_EMPTY(vending_machines_to_restock)
+
/// Maximum amount of items in a storage bag that we're transferring items to the vendor from.
#define MAX_VENDING_INPUT_AMOUNT 30
+#define CREDITS_DUMP_THRESHOLD 50
/**
* # vending record datum
*
@@ -178,6 +182,8 @@
var/displayed_currency_name = " cr"
///Whether our age check is currently functional
var/age_restrictions = TRUE
+ /// How many credits does this vending machine have? 20% of all sales go to this pool, and are given freely when the machine is restocked, or successfully tilted. Lost on deconstruction.
+ var/credits_contained = 0
/**
* Is this item on station or not
*
@@ -256,6 +262,7 @@
onstation = FALSE
if(circuit)
circuit.onstation = onstation //sync up the circuit so the pricing schema is carried over if it's reconstructed.
+
else if(HAS_TRAIT(SSstation, STATION_TRAIT_VENDING_SHORTAGE))
for (var/datum/data/vending_product/product_record as anything in product_records + coin_records + hidden_records)
/**
@@ -264,16 +271,22 @@
*/
var/max_amount = rand(CEILING(product_record.amount * 0.5, 1), product_record.amount)
product_record.amount = rand(0, max_amount)
+ credits_contained += rand(0, 1) //randomly add a few credits to the machine to make it look like it's been used, proportional to the amount missing.
if(tiltable && prob(6)) // 1 in 17 chance to start tilted (as an additional hint to the station trait behind it)
INVOKE_ASYNC(src, PROC_REF(tilt), loc)
+ credits_contained = 0 // If it's tilted, it's been looted, so no credits for you.
else if(circuit && (circuit.onstation != onstation)) //check if they're not the same to minimize the amount of edited values.
onstation = circuit.onstation //if it was constructed outside mapload, sync the vendor up with the circuit's var so you can't bypass price requirements by moving / reconstructing it off station.
+ if(onstation && !onstation_override)
+ AddComponent(/datum/component/payment, 0, SSeconomy.get_dep_account(payment_department), PAYMENT_VENDING)
+ GLOB.vending_machines_to_restock += src //We need to keep track of the final onstation vending machines so we can keep them restocked.
/obj/machinery/vending/Destroy()
QDEL_NULL(wires)
QDEL_NULL(coin)
QDEL_NULL(bill)
QDEL_NULL(sec_radio)
+ GLOB.vending_machines_to_restock -= src
return ..()
/obj/machinery/vending/can_speak()
@@ -611,6 +624,24 @@
else //no category found - dump it into standard stock
products[record.product_path] = record.amount
+/**
+ * Returns the total amount of items in the vending machine based on the product records and premium records, but not contraband
+ */
+/obj/machinery/vending/proc/total_loaded_stock()
+ var/total = 0
+ for(var/datum/data/vending_product/record as anything in product_records + coin_records)
+ total += record.amount
+ return total
+
+/**
+ * Returns the total amount of items in the vending machine based on the product records and premium records, but not contraband
+ */
+/obj/machinery/vending/proc/total_max_stock()
+ var/total_max = 0
+ for(var/datum/data/vending_product/record as anything in product_records + coin_records)
+ total_max += record.max_amount
+ return total_max
+
/obj/machinery/vending/crowbar_act(mob/living/user, obj/item/attack_item)
if(!component_parts)
return FALSE
@@ -655,7 +686,11 @@
// instantiate canister if needed
var/transferred = restock(canister)
if(transferred)
- to_chat(user, span_notice("You loaded [transferred] items in [src]."))
+ to_chat(user, span_notice("You loaded [transferred] items in [src][credits_contained > 0 ? ", and are rewarded [credits_contained] credits." : "."]"))
+ var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ cargo_account.adjust_money(round(credits_contained * 0.5), "Vending: Restock")
+ var/obj/item/holochip/payday = new(src, credits_contained)
+ try_put_in_hand(payday, user)
else
to_chat(user, span_warning("There's nothing to restock!"))
return
@@ -707,7 +742,7 @@
* freebies - number of free items to vend
*/
/obj/machinery/vending/proc/freebie(freebies)
- visible_message(span_notice("[src] yields [freebies > 1 ? "several free goodies" : "a free goody"]!"))
+ visible_message(span_notice("[src] yields [freebies > 1 ? "several free goodies" : "a free goody"][credits_contained > 0 ? " and some credits" : ""]!"))
for(var/i in 1 to freebies)
playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3)
@@ -726,6 +761,7 @@
returned_obj_to_dump.forceMove(get_turf(src))
record.amount--
break
+ deploy_credits()
/**
* Tilts ontop of the atom supplied, if crit is true some extra shit can happen. See [fall_and_crush] for return values.
@@ -1206,13 +1242,15 @@
/obj/machinery/vending/ui_data(mob/user)
. = list()
var/obj/item/card/id/card_used
+ var/held_cash = 0
if(isliving(user))
var/mob/living/living_user = user
card_used = living_user.get_idcard(TRUE)
+ held_cash = living_user.tally_physical_credits()
if(card_used?.registered_account)
.["user"] = list()
.["user"]["name"] = card_used.registered_account.account_holder
- .["user"]["cash"] = fetch_balance_to_use(card_used)
+ .["user"]["cash"] = fetch_balance_to_use(card_used) + held_cash
if(card_used.registered_account.account_job)
.["user"]["job"] = card_used.registered_account.account_job.title
.["user"]["department"] = card_used.registered_account.account_job.paycheck_department
@@ -1333,25 +1371,12 @@
vend_ready = TRUE
return
if(onstation)
+ // Here we do additional handing ahead of the payment component's logic, such as age restrictions and additional logging
var/obj/item/card/id/card_used
+ var/mob/living/living_user
if(isliving(usr))
- var/mob/living/living_user = usr
+ living_user = usr
card_used = living_user.get_idcard(TRUE)
- if(!card_used)
- speak("No card found.")
- flick(icon_deny,src)
- vend_ready = TRUE
- return
- else if (!card_used.registered_account)
- speak("No account found.")
- flick(icon_deny,src)
- vend_ready = TRUE
- return
- else if(!card_used.registered_account.account_job)
- speak("Departmental accounts have been blacklisted from personal expenses due to embezzlement.")
- flick(icon_deny, src)
- vend_ready = TRUE
- return
else if(age_restrictions && item_record.age_restricted && (!card_used.registered_age || card_used.registered_age < AGE_MINOR))
speak("You are not of legal age to purchase [item_record.name].")
if(!(usr in GLOB.narcd_underages))
@@ -1365,7 +1390,7 @@
vend_ready = TRUE
return
- if(!proceed_payment(card_used, item_record, price_to_use))
+ if(!proceed_payment(card_used, living_user, item_record, price_to_use))
return
if(last_shopper != REF(usr) || purchase_message_cooldown < world.time)
@@ -1407,11 +1432,12 @@
/**
* Handles payment processing: discounts, logging, balance change etc.
* arguments:
- * paying_id_card - the id card that will be billed for the product
- * product_to_vend - the product record of the item we're trying to vend
- * price_to_use - price of the item we're trying to vend
+ * paying_id_card - the id card that will be billed for the product.
+ * mob_paying - the mob that is trying to purchase the item.
+ * product_to_vend - the product record of the item we're trying to vend.
+ * price_to_use - price of the item we're trying to vend.
*/
-/obj/machinery/vending/proc/proceed_payment(obj/item/card/id/paying_id_card, datum/data/vending_product/product_to_vend, price_to_use)
+/obj/machinery/vending/proc/proceed_payment(obj/item/card/id/paying_id_card, mob/living/mob_paying, datum/data/vending_product/product_to_vend, price_to_use)
var/datum/bank_account/account = paying_id_card.registered_account
if(account.account_job && account.account_job.paycheck_department == payment_department)
price_to_use = max(round(price_to_use * DEPARTMENT_DISCOUNT), 1) //No longer free, but signifigantly cheaper.
@@ -1419,7 +1445,7 @@
price_to_use = product_to_vend.custom_premium_price ? product_to_vend.custom_premium_price : extra_price
if(LAZYLEN(product_to_vend.returned_products))
price_to_use = 0 //returned items are free
- if(price_to_use && !account.adjust_money(-price_to_use, "Vending: [product_to_vend.name]"))
+ if(price_to_use && (attempt_charge(src, mob_paying, price_to_use) & COMPONENT_OBJ_CANCEL_CHARGE))
speak("You do not possess the funds to purchase [product_to_vend.name].")
flick(icon_deny,src)
vend_ready = TRUE
@@ -1427,10 +1453,10 @@
//actual payment here
var/datum/bank_account/paying_id_account = SSeconomy.get_dep_account(payment_department)
if(paying_id_account)
- paying_id_account.adjust_money(price_to_use)
SSblackbox.record_feedback("amount", "vending_spent", price_to_use)
SSeconomy.track_purchase(account, price_to_use, name)
log_econ("[price_to_use] credits were inserted into [src] by [account.account_holder] to buy [product_to_vend].")
+ credits_contained += round(price_to_use * 0.2)
return TRUE
/obj/machinery/vending/process(seconds_per_tick)
@@ -1569,6 +1595,15 @@
tilt(fatty=hit_atom)
return ..()
+/** Drop credits when the vendor is attacked.*/
+/obj/machinery/vending/proc/deploy_credits()
+ if(credits_contained <= 0)
+ return
+ var/credits_to_remove = min(CREDITS_DUMP_THRESHOLD, round(credits_contained))
+ var/obj/item/holochip/holochip = new(loc, credits_to_remove)
+ credits_contained = max(0, credits_contained - credits_to_remove)
+ SSblackbox.record_feedback("amount", "vending machine looted", holochip.credits)
+
/obj/machinery/vending/custom
name = "Custom Vendor"
icon_state = "custom"
diff --git a/code/modules/vending/assist.dm b/code/modules/vending/assist.dm
index 229b19aeadb..a043a365046 100644
--- a/code/modules/vending/assist.dm
+++ b/code/modules/vending/assist.dm
@@ -21,6 +21,7 @@
/obj/item/assembly/timer = 2,
/obj/item/assembly/voice = 2,
/obj/item/stock_parts/cell/high = 1,
+ /obj/item/market_uplink/blackmarket = 1,
)
premium = list(
/obj/item/assembly/igniter/condenser = 2,
diff --git a/code/modules/vending/sustenance.dm b/code/modules/vending/sustenance.dm
index d8229121490..a1d11c30727 100644
--- a/code/modules/vending/sustenance.dm
+++ b/code/modules/vending/sustenance.dm
@@ -48,7 +48,7 @@
return
return ..()
-/obj/machinery/vending/sustenance/labor_camp/proceed_payment(obj/item/card/id/paying_id_card, datum/data/vending_product/product_to_vend, price_to_use)
+/obj/machinery/vending/sustenance/labor_camp/proceed_payment(obj/item/card/id/paying_id_card, mob/living/mob_paying, datum/data/vending_product/product_to_vend, price_to_use)
if(!istype(paying_id_card, /obj/item/card/id/advanced/prisoner))
speak("I don't take bribes! Pay with labor points!")
return FALSE
diff --git a/icons/obj/machines/modular_console.dmi b/icons/obj/machines/modular_console.dmi
index 2677dbb7122..7b370a76785 100644
Binary files a/icons/obj/machines/modular_console.dmi and b/icons/obj/machines/modular_console.dmi differ
diff --git a/icons/obj/modular_laptop.dmi b/icons/obj/modular_laptop.dmi
index c8ad438d1a3..1accc56f4c1 100644
Binary files a/icons/obj/modular_laptop.dmi and b/icons/obj/modular_laptop.dmi differ
diff --git a/icons/obj/modular_pda.dmi b/icons/obj/modular_pda.dmi
index 5a39c6cd726..afd75b78d4b 100644
Binary files a/icons/obj/modular_pda.dmi and b/icons/obj/modular_pda.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 5563d87506a..f9fdfc3f1b3 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -5187,6 +5187,7 @@
#include "code\modules\modular_computers\file_system\programs\powermonitor.dm"
#include "code\modules\modular_computers\file_system\programs\radar.dm"
#include "code\modules\modular_computers\file_system\programs\records.dm"
+#include "code\modules\modular_computers\file_system\programs\restock_tracker.dm"
#include "code\modules\modular_computers\file_system\programs\robocontrol.dm"
#include "code\modules\modular_computers\file_system\programs\robotact.dm"
#include "code\modules\modular_computers\file_system\programs\secureye.dm"
diff --git a/tgui/packages/tgui/interfaces/NtosRestock.tsx b/tgui/packages/tgui/interfaces/NtosRestock.tsx
new file mode 100644
index 00000000000..055dfda6e4e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NtosRestock.tsx
@@ -0,0 +1,12 @@
+import { NtosWindow } from '../layouts';
+import { RestockTracker } from './RestockTracker';
+
+export const NtosRestock = (props) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RestockTracker.jsx b/tgui/packages/tgui/interfaces/RestockTracker.jsx
new file mode 100644
index 00000000000..236f486069c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RestockTracker.jsx
@@ -0,0 +1,86 @@
+import { sortBy } from 'common/collections';
+import { round } from 'common/math';
+
+import { useBackend } from '../backend';
+import { ColorBox, ProgressBar, Section, Stack } from '../components';
+import { Window } from '../layouts';
+
+export const Restock = (props) => {
+ return (
+
+
+
+
+
+ );
+};
+
+export const RestockTracker = (props) => {
+ const { data } = useBackend();
+ const vending_list = sortBy((vend) => vend.percentage)(
+ data.vending_list ?? [],
+ );
+ return (
+
+
+
+
+ Vending Name
+
+
+ Location
+
+
+ Stock %
+
+
+ Credits stored
+
+
+
+ {vending_list?.map((vend) => (
+
+
+ {vend.name}
+
+
+ {vend.location}
+
+ 75
+ ? 'left'
+ : vend.percentage > 45
+ ? 'right'
+ : 'center'
+ }
+ >
+
+ {round(vend.percentage, 0.01)}
+
+
+ 50 ? 'good' : 'bad'}
+ >
+ 50 ? 'good' : 'bad'} mr={'5%'} />
+ {vend.credits}
+
+
+ ))}
+
+
+ );
+};