- Doing your job and not getting any recognition at work? Well, welcome to the
- merch shop! Here, you can buy cool things in exchange for money you earn when you've
- completed your Job Objectives.
-
-
-"}
- user << browse(dat, "window=merch;size=440x600;can_resize=0")
- onclose(user, "merch")
- return
-
-/obj/machinery/computer/merch/Topic(href, href_list)
- if(..())
- return TRUE
-
- add_fingerprint(usr)
-
- if(href_list["buy"])
- var/itemID = text2num(href_list["buy"])
- var/datum/storeitem/item = GLOB.centcomm_store.items[itemID]
- var/sure = alert(usr, "Are you sure you wish to purchase [item.name] for $[item.cost]?", "You sure?", "Yes", "No") in list("Yes", "No")
-
- if(!Adjacent(usr))
- to_chat(usr, "You are not close enough to do that.")
- return
-
- if(sure == "No")
- updateUsrDialog()
- return
-
- if(!GLOB.centcomm_store.PlaceOrder(usr, itemID))
- to_chat(usr, "Unable to charge your account.")
- else
- to_chat(usr, "You've successfully purchased the item. It should be in your hands or on the floor.")
-
- updateUsrDialog()
- return
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index 28654752385..950f91d57e1 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -235,53 +235,53 @@ to destroy them and players will be able to make replacements.
board_name = "Booze-O-Mat Vendor"
board_type = "machine"
origin_tech = "programming=1"
- build_path = /obj/machinery/vending/boozeomat
+ build_path = /obj/machinery/economy/vending/boozeomat
req_components = list(/obj/item/vending_refill/boozeomat = 1)
var/static/list/station_vendors = list(
- "Booze-O-Mat" = /obj/machinery/vending/boozeomat,
- "Solar's Best Hot Drinks" = /obj/machinery/vending/coffee,
- "Getmore Chocolate Corp" = /obj/machinery/vending/snack,
- "Mr. Chang" = /obj/machinery/vending/chinese,
- "Robust Softdrinks" = /obj/machinery/vending/cola,
- "ShadyCigs Deluxe" = /obj/machinery/vending/cigarette,
- "Hatlord 9000" = /obj/machinery/vending/hatdispenser,
- "Suitlord 9000" = /obj/machinery/vending/suitdispenser,
- "Shoelord 9000" = /obj/machinery/vending/shoedispenser,
- "AutoDrobe" = /obj/machinery/vending/autodrobe,
- "ClothesMate" = /obj/machinery/vending/clothing,
- "NanoMed Plus" = /obj/machinery/vending/medical,
- "NanoMed" = /obj/machinery/vending/wallmed,
- "Vendomat" = /obj/machinery/vending/assist,
- "YouTool" = /obj/machinery/vending/tool,
- "Engi-Vend" = /obj/machinery/vending/engivend,
- "NutriMax" = /obj/machinery/vending/hydronutrients,
- "MegaSeed Servitor" = /obj/machinery/vending/hydroseeds,
- "Sustenance Vendor" = /obj/machinery/vending/sustenance,
- "Plasteel Chef's Dinnerware Vendor" = /obj/machinery/vending/dinnerware,
- "PTech" = /obj/machinery/vending/cart,
- "Robotech Deluxe" = /obj/machinery/vending/robotics,
- "Robco Tool Maker" = /obj/machinery/vending/engineering,
- "BODA" = /obj/machinery/vending/sovietsoda,
- "SecTech" = /obj/machinery/vending/security,
- "CritterCare" = /obj/machinery/vending/crittercare,
- "SecDrobe" = /obj/machinery/vending/secdrobe,
- "DetDrobe" = /obj/machinery/vending/detdrobe,
- "MediDrobe" = /obj/machinery/vending/medidrobe,
- "ViroDrobe" = /obj/machinery/vending/virodrobe,
- "ChemDrobe" = /obj/machinery/vending/chemdrobe,
- "GeneDrobe" = /obj/machinery/vending/genedrobe,
- "SciDrobe" = /obj/machinery/vending/scidrobe,
- "RoboDrobe" = /obj/machinery/vending/robodrobe,
- "EngiDrobe" = /obj/machinery/vending/engidrobe,
- "AtmosDrobe" = /obj/machinery/vending/atmosdrobe,
- "CargoDrobe" = /obj/machinery/vending/cargodrobe,
- "ChefDrobe" = /obj/machinery/vending/chefdrobe,
- "BarDrobe" = /obj/machinery/vending/bardrobe,
- "HydroDrobe" = /obj/machinery/vending/hydrodrobe)
+ "Booze-O-Mat" = /obj/machinery/economy/vending/boozeomat,
+ "Solar's Best Hot Drinks" = /obj/machinery/economy/vending/coffee,
+ "Getmore Chocolate Corp" = /obj/machinery/economy/vending/snack,
+ "Mr. Chang" = /obj/machinery/economy/vending/chinese,
+ "Robust Softdrinks" = /obj/machinery/economy/vending/cola,
+ "ShadyCigs Deluxe" = /obj/machinery/economy/vending/cigarette,
+ "Hatlord 9000" = /obj/machinery/economy/vending/hatdispenser,
+ "Suitlord 9000" = /obj/machinery/economy/vending/suitdispenser,
+ "Shoelord 9000" = /obj/machinery/economy/vending/shoedispenser,
+ "AutoDrobe" = /obj/machinery/economy/vending/autodrobe,
+ "ClothesMate" = /obj/machinery/economy/vending/clothing,
+ "NanoMed Plus" = /obj/machinery/economy/vending/medical,
+ "NanoMed" = /obj/machinery/economy/vending/wallmed,
+ "Vendomat" = /obj/machinery/economy/vending/assist,
+ "YouTool" = /obj/machinery/economy/vending/tool,
+ "Engi-Vend" = /obj/machinery/economy/vending/engivend,
+ "NutriMax" = /obj/machinery/economy/vending/hydronutrients,
+ "MegaSeed Servitor" = /obj/machinery/economy/vending/hydroseeds,
+ "Sustenance Vendor" = /obj/machinery/economy/vending/sustenance,
+ "Plasteel Chef's Dinnerware Vendor" = /obj/machinery/economy/vending/dinnerware,
+ "PTech" = /obj/machinery/economy/vending/cart,
+ "Robotech Deluxe" = /obj/machinery/economy/vending/robotics,
+ "Robco Tool Maker" = /obj/machinery/economy/vending/engineering,
+ "BODA" = /obj/machinery/economy/vending/sovietsoda,
+ "SecTech" = /obj/machinery/economy/vending/security,
+ "CritterCare" = /obj/machinery/economy/vending/crittercare,
+ "SecDrobe" = /obj/machinery/economy/vending/secdrobe,
+ "DetDrobe" = /obj/machinery/economy/vending/detdrobe,
+ "MediDrobe" = /obj/machinery/economy/vending/medidrobe,
+ "ViroDrobe" = /obj/machinery/economy/vending/virodrobe,
+ "ChemDrobe" = /obj/machinery/economy/vending/chemdrobe,
+ "GeneDrobe" = /obj/machinery/economy/vending/genedrobe,
+ "SciDrobe" = /obj/machinery/economy/vending/scidrobe,
+ "RoboDrobe" = /obj/machinery/economy/vending/robodrobe,
+ "EngiDrobe" = /obj/machinery/economy/vending/engidrobe,
+ "AtmosDrobe" = /obj/machinery/economy/vending/atmosdrobe,
+ "CargoDrobe" = /obj/machinery/economy/vending/cargodrobe,
+ "ChefDrobe" = /obj/machinery/economy/vending/chefdrobe,
+ "BarDrobe" = /obj/machinery/economy/vending/bardrobe,
+ "HydroDrobe" = /obj/machinery/economy/vending/hydrodrobe)
var/static/list/unique_vendors = list(
- "ShadyCigs Ultra" = /obj/machinery/vending/cigarette/beach,
- "SyndiMed Plus" = /obj/machinery/vending/wallmed/syndicate)
+ "ShadyCigs Ultra" = /obj/machinery/economy/vending/cigarette/beach,
+ "SyndiMed Plus" = /obj/machinery/economy/vending/wallmed/syndicate)
/obj/item/circuitboard/vendor/screwdriver_act(mob/user, obj/item/I)
. = TRUE
@@ -294,7 +294,7 @@ to destroy them and players will be able to make replacements.
/obj/item/circuitboard/vendor/proc/set_type(type)
var/static/list/buildable_vendors = station_vendors + unique_vendors
- var/obj/machinery/vending/typepath = buildable_vendors[type]
+ var/obj/machinery/economy/vending/typepath = buildable_vendors[type]
build_path = typepath
board_name = "[type] Vendor"
format_board_name()
@@ -953,7 +953,7 @@ to destroy them and players will be able to make replacements.
/obj/item/circuitboard/clawgame
board_name = "Claw Game"
- build_path = /obj/machinery/arcade/claw
+ build_path = /obj/machinery/economy/arcade/claw
board_type = "machine"
origin_tech = "programming=1"
req_components = list(
@@ -982,3 +982,8 @@ to destroy them and players will be able to make replacements.
/obj/item/stock_parts/micro_laser = 1,
/obj/item/stack/cable_coil = 3,
/obj/item/stack/sheet/glass = 1)
+
+/obj/item/circuitboard/merch
+ name = "Merchandise Computer Circuitboard"
+ build_path = /obj/machinery/economy/merch
+ board_type = "machine"
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index a5e82fea38e..37ad8a53382 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -353,6 +353,8 @@
//Update any existing objectives involving this mob.
if(occupant.mind)
+ if(occupant.mind.initial_account)
+ GLOB.station_money_database.delete_user_account(occupant.mind.initial_account.account_number, "NAS Trurl Financial Services", FALSE)
for(var/datum/objective/O in GLOB.all_objectives)
if(O.target != occupant.mind)
continue
diff --git a/code/game/machinery/merch_vendor.dm b/code/game/machinery/merch_vendor.dm
new file mode 100644
index 00000000000..54bb1435151
--- /dev/null
+++ b/code/game/machinery/merch_vendor.dm
@@ -0,0 +1,133 @@
+/obj/machinery/economy/merch
+ name = "Nanotrasen Merchandise Vendor"
+ desc = "The one-stop-shop for all your Nanotrasen Swag"
+ icon = 'icons/obj/vending.dmi'
+ icon_state = "nt_merch"
+ light_color = LIGHT_COLOR_GREEN
+
+ var/static/list/merchandise = list()
+ var/static/list/imagelist = list()
+ var/list/merch_categories = list(
+ MERCH_CAT_APPAREL,
+ MERCH_CAT_TOY,
+ MERCH_CAT_DECORATION,
+ )
+
+/obj/machinery/economy/merch/attack_ai(mob/user as mob)
+ return attack_hand(user)
+
+/obj/machinery/economy/merch/attack_hand(mob/user)
+ ui_interact(user)
+
+/obj/machinery/economy/merch/Initialize(mapload)
+ . = ..()
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/merch(null)
+ if(!length(merchandise))
+ for(var/merch_path in subtypesof(/datum/merch_item))
+ var/datum/merch_item/merch = new merch_path()
+ merchandise[merch.category] += list(merch)
+ var/obj/item/I = merch.typepath
+ var/pp = replacetext(replacetext("[merch.typepath]", "/obj/item/", ""), "/", "-")
+ imagelist[pp] = "[icon2base64(icon(initial(I.icon), initial(I.icon_state), SOUTH, 1))]"
+
+/obj/machinery/economy/merch/attackby(obj/item/I, mob/user)
+ if(isspacecash(I))
+ insert_cash(I, user)
+ return TRUE
+
+ return ..()
+
+/obj/machinery/economy/merch/proc/do_purchase(datum/merch_item/merch, mob/user)
+ if(!merch)
+ return FALSE
+
+ if(!attempt_transaction(merch, user))
+ return FALSE
+
+ deliver(merch, user)
+ return TRUE
+
+/obj/machinery/economy/merch/proc/attempt_transaction(datum/merch_item/merch, mob/user)
+ if(cash_stored >= merch.cost)
+ if(pay_with_cash(merch.cost, "Purchase of [merch.name]", name, user, account_database.vendor_account))
+ give_change(user)
+ return TRUE
+ return FALSE
+
+ var/mob/living/carbon/human/H = user
+ if(istype(H))
+ var/obj/item/card/id/C = H.get_idcard(TRUE)
+ if(!C || !pay_with_card(C, merch.cost, "Purchase of [merch.name]", "NAS Trurl Merchandising", user, account_database.vendor_account))
+ return FALSE
+ else
+ to_chat(user, "Payment failure: you have no ID or other method of payment.")
+ return FALSE
+ return TRUE
+
+/obj/machinery/economy/merch/proc/deliver(datum/merch_item/item, mob/user)
+ var/obj/item/merch = new item.typepath(get_turf(src))
+ var/obj/item/smallDelivery/D = new(get_turf(src))
+ D.name = "small parcel - 'Your Nanotrasen Swag'"
+ D.wrapped = merch
+ merch.forceMove(D)
+ user.put_in_hands(D)
+ SSeconomy.total_vendor_transactions++
+
+/obj/machinery/economy/merch/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "MerchVendor", name, 450, 500, master_ui, state)
+ ui.open()
+
+/obj/machinery/economy/merch/ui_data(mob/user)
+ var/list/data = list()
+
+ data["inserted_cash"] = cash_transaction
+ var/datum/money_account/user_account = user.get_worn_id_account()
+ data["user_cash"] = user_account?.credit_balance
+
+ return data
+
+/obj/machinery/economy/merch/ui_static_data(mob/user)
+ var/list/static_data = list()
+
+ static_data["imagelist"] = imagelist
+ static_data["products"] = list()
+ for(var/category in merch_categories)
+ static_data["products"][category] = list()
+ for(var/datum/merch_item/merch in merchandise[category])
+ var/list/product_data = list(
+ "name" = merch.name,
+ "price" = merch.cost,
+ "desc" = merch.desc,
+ "category" = merch.category,
+ "path" = replacetext(replacetext("[merch.typepath]", "/obj/item/", ""), "/", "-"),
+ )
+ static_data["products"][category] += list(product_data)
+
+ return static_data
+
+/obj/machinery/economy/merch/ui_act(action, params, datum/tgui/ui)
+ if(..())
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
+
+ . = FALSE
+ var/mob/living/user = ui.user
+
+ switch(action)
+ if("purchase")
+ . = TRUE
+ for(var/datum/merch_item/merch in merchandise[params["category"]])
+ if(merch.name == params["name"])
+ if(do_purchase(merch, user)) //null checking done in proc
+ to_chat(user, "You've successfully purchased the item. It should be in your hands or on the floor.")
+ break
+ if("change")
+ . = TRUE
+ give_change(user)
+
+ if(.)
+ add_fingerprint(user)
diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm
index 75df85edab9..18749e05f63 100644
--- a/code/game/machinery/slotmachine.dm
+++ b/code/game/machinery/slotmachine.dm
@@ -1,45 +1,58 @@
-/obj/machinery/slot_machine
+/obj/machinery/economy/slot_machine
name = "slot machine"
desc = "Gambling for the antisocial."
icon = 'icons/obj/economy.dmi'
icon_state = "slots-off"
anchored = TRUE
density = TRUE
+
var/plays = 0
var/working = FALSE
- var/datum/money_account/account = null
- var/result = null
- var/resultlvl = null
+ var/result
+ var/resultlvl
-/obj/machinery/slot_machine/attack_hand(mob/user as mob)
+ var/datum/money_account/user_account
+
+/obj/machinery/economy/slot_machine/Initialize(mapload)
+ . = ..()
+ reconnect_database()
+
+/obj/machinery/economy/slot_machine/attack_hand(mob/user as mob)
ui_interact(user)
-/obj/machinery/slot_machine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/obj/machinery/economy/slot_machine/proc/set_user_account(datum/money_account/account)
+ if(user_account)
+ if(user_account == account)
+ return
+ clear_user_account()
+ user_account = account
+ if(user_account)
+ RegisterSignal(account, COMSIG_PARENT_QDELETING, PROC_REF(clear_user_account))
+
+/obj/machinery/economy/slot_machine/proc/clear_user_account()
+ UnregisterSignal(user_account, COMSIG_PARENT_QDELETING)
+ user_account = null
+
+/obj/machinery/economy/slot_machine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "SlotMachine", name, 350, 200, master_ui, state)
ui.open()
-/obj/machinery/slot_machine/ui_data(mob/user)
+/obj/machinery/economy/slot_machine/ui_data(mob/user)
var/list/data = list()
// Get account
- account = user.get_worn_id_account()
- if(!account)
- if(istype(user.get_active_hand(), /obj/item/card/id))
- account = get_card_account(user.get_active_hand())
- else
- account = null
-
+ set_user_account(user.get_worn_id_account())
// Send data
data["working"] = working
- data["money"] = account ? account.money : null
+ data["money"] = user_account?.credit_balance
data["plays"] = plays
data["result"] = result
data["resultlvl"] = resultlvl
return data
-/obj/machinery/slot_machine/ui_act(action, params)
+/obj/machinery/economy/slot_machine/ui_act(action, params)
if(..())
return
add_fingerprint(usr)
@@ -47,44 +60,45 @@
if(action == "spin")
if(working)
return
- if(!account || account.money < 50)
- return
- if(!account.charge(50, null, "Bet", "Slot Machine", "Slot Machine"))
+ if(!user_account || user_account.credit_balance < 10)
return
+ if(!account_database.charge_account(user_account, 10, "Slot Machine Charge", "DonkBook Betting", FALSE, TRUE))
+ return //we want to surpress transaction logs here in-case someone uses the slot machine 100+ times
+ account_database.credit_account(account_database.vendor_account, 10, "Slot Machine Charge", "DonkBook Betting", TRUE)
plays++
working = TRUE
icon_state = "slots-on"
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
addtimer(CALLBACK(src, PROC_REF(spin_slots), usr.name), 25)
-/obj/machinery/slot_machine/proc/spin_slots(userName)
- switch(rand(1,5000))
+/obj/machinery/economy/slot_machine/proc/spin_slots(userName)
+ switch(rand(1, 5000))
if(1)
- atom_say("JACKPOT! [userName] has won ten thousand credits!")
- GLOB.event_announcement.Announce("Congratulations to [userName] on winning the Jackpot of TEN THOUSAND CREDITS!", "Jackpot Winner")
- result = "JACKPOT! You win ten thousand credits!"
+ atom_say("JACKPOT! [userName] has won two thousand credits!")
+ GLOB.event_announcement.Announce("Congratulations to [userName] on winning the Jackpot of TWO THOUSAND CREDITS!", "Jackpot Winner")
+ result = "JACKPOT! You win two thousand credits!"
resultlvl = "teal"
- win_money(10000, 'sound/goonstation/misc/airraid_loop.ogg')
+ win_money(2000, 'sound/goonstation/misc/airraid_loop.ogg')
if(2 to 20)
- atom_say("Big Winner! [userName] has won a thousand credits!")
- result = "You win a thousand credits!"
+ atom_say("Big Winner! [userName] has won two hundred credits!")
+ result = "You win a two hundred credits!"
resultlvl = "green"
- win_money(1000, 'sound/goonstation/misc/klaxon.ogg')
+ win_money(200, 'sound/goonstation/misc/klaxon.ogg')
if(21 to 100)
- atom_say("Winner! [userName] has won five hundred credits!")
- result = "You win five hundred credits!"
+ atom_say("Winner! [userName] has won a hundred credits!")
+ result = "You win a hundred credits!"
resultlvl = "green"
- win_money(500, 'sound/goonstation/misc/bell.ogg')
+ win_money(100, 'sound/goonstation/misc/bell.ogg')
if(101 to 500)
- atom_say("Winner! [userName] has won two hundred credits!")
- result = "You win two hundred credits!"
+ atom_say("Winner! [userName] has won forty credits!")
+ result = "You win forty credits!"
resultlvl = "green"
- win_money(200)
+ win_money(40)
if(501 to 1000)
- atom_say("Winner! [userName] has won fifty credits!")
- result = "You win fifty credits!"
+ atom_say("Winner! [userName] has won ten credits!")
+ result = "You win ten credits!"
resultlvl = "green"
- win_money(50)
+ win_money(10)
else
result = "No luck!"
resultlvl = "orange"
@@ -92,14 +106,15 @@
icon_state = "slots-off"
SStgui.update_uis(src) // Push a UI update
-/obj/machinery/slot_machine/proc/win_money(amt, sound='sound/machines/ping.ogg')
+/obj/machinery/economy/slot_machine/proc/win_money(amount, sound = 'sound/machines/ping.ogg')
if(sound)
playsound(loc, sound, 55, 1)
- if(!account)
+ if(!user_account)
return
- account.credit(amt, "Slot Winnings", "Slot Machine", account.owner_name)
+ account_database.charge_account(account_database.vendor_account, amount, "Slot Machine Payout", "DonkBook Betting", TRUE, FALSE)
+ account_database.credit_account(user_account, amount, "Slot Machine Winnings Deposit", "DonkBook Betting", FALSE)
-/obj/machinery/slot_machine/wrench_act(mob/user, obj/item/I)
+/obj/machinery/economy/slot_machine/wrench_act(mob/user, obj/item/I)
. = TRUE
if(!I.tool_use_check(user, 0))
return
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
deleted file mode 100644
index 010a2ecc09d..00000000000
--- a/code/game/machinery/vending.dm
+++ /dev/null
@@ -1,2266 +0,0 @@
-/**
- * Datum used to hold information about a product in a vending machine
- */
-/datum/data/vending_product
- name = "generic"
- ///Typepath of the product that is created when this record "sells"
- var/product_path = null
- ///How many of this product we currently have
- var/amount = 0
- ///How many we can store at maximum
- var/max_amount = 0
- var/price = 0 // Price to buy one
-
-/obj/machinery/vending
- name = "\improper Vendomat"
- desc = "A generic vending machine."
- icon = 'icons/obj/vending.dmi'
- icon_state = "generic"
- layer = BELOW_OBJ_LAYER
- anchored = TRUE
- density = TRUE
- face_while_pulling = TRUE
- max_integrity = 300
- integrity_failure = 100
- armor = list(melee = 20, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70)
- /// Icon_state when vending
- var/icon_vend
- /// Icon_state when denying access
- var/icon_deny
- /// Icon to be an overlay over the base sprite
- var/icon_addon
- /// Icon for the broken overlay, defaults to icon_state + _broken
- var/icon_broken
- /// Icon for the off overlay, defaults to icon_state + _off
- var/icon_off
- /// Icon for the panel overlay, defaults to icon_state + _panel
- var/icon_panel
- /// Icon for the lightmask, defaults to icon_state + _off, _lightmask if one is defined.
- var/icon_lightmask
- // Power
- use_power = IDLE_POWER_USE
- idle_power_usage = 10
- var/vend_power_usage = 150
-
- var/light_range_on = 1
- var/light_power_on = 0.5
-
- // Vending-related
- /// No sales pitches if off
- var/active = TRUE
- /// If off, vendor is busy and unusable until current action finishes
- var/vend_ready = TRUE
- /// How long vendor takes to vend one item.
- var/vend_delay = 10
- /// Item currently being bought
- var/datum/data/vending_product/currently_vending = null
-
- // To be filled out at compile time
- var/list/products = list() // For each, use the following pattern:
- var/list/contraband = list() // list(/type/path = amount,/type/path2 = amount2)
- var/list/premium = list() // No specified amount = only one in stock
- var/list/prices = list() // Prices for each item, list(/type/path = price), items not in the list don't have a price.
-
- // List of vending_product items available.
- var/list/product_records = list()
- var/list/hidden_records = list()
- var/list/coin_records = list()
- var/list/imagelist = list()
-
- /// Unimplemented list of ads that are meant to show up somewhere, but don't.
- var/list/ads_list = list()
-
- // Stuff relating vocalizations
- /// List of slogans the vendor will say, optional
- var/list/slogan_list = list()
- var/vend_reply //Thank you for shopping!
- /// If true, prevent saying sales pitches
- var/shut_up = FALSE
- ///can we access the hidden inventory?
- var/extended_inventory = FALSE
- var/last_reply = 0
- var/last_slogan = 0 //When did we last pitch?
- var/slogan_delay = 6000 //How long until we can pitch again?
-
- //The type of refill canisters used by this machine.
- var/obj/item/vending_refill/refill_canister = null
-
- // Things that can go wrong
- /// Allows people to access a vendor that's normally access restricted.
- emagged = FALSE
- /// Shocks people like an airlock
- var/seconds_electrified = 0
- /// Fire items at customers! We're broken!
- var/shoot_inventory = FALSE
- /// How hard are we firing the items?
- var/shoot_speed = 3
- /// How often are we firing the items? (prob(...))
- var/shoot_chance = 2
-
- /// If true, enforce access checks on customers. Disabled by messing with wires.
- var/scan_id = TRUE
- /// Holder for a coin inserted into the vendor
- var/obj/item/coin/coin
- var/datum/wires/vending/wires = null
-
- /// boolean, whether this vending machine can accept people inserting items into it, used for coffee vendors
- var/item_slot = FALSE
- /// the actual item inserted
- var/obj/item/inserted_item = null
-
- /// blocks further flickering while true
- var/flickering = FALSE
- /// do I look unpowered, even when powered?
- var/force_no_power_icon_state = FALSE
-
-/obj/machinery/vending/Initialize(mapload)
- . = ..()
- var/build_inv = FALSE
- if(!refill_canister)
- build_inv = TRUE
- else
- component_parts = list()
- var/obj/item/circuitboard/vendor/V = new
- V.set_type(replacetext(initial(name), "\improper", ""))
- component_parts += V
- component_parts += new refill_canister
- RefreshParts()
-
- wires = new(src)
- if(build_inv) //non-constructable vending machine
- build_inventory(products, product_records)
- build_inventory(contraband, hidden_records)
- build_inventory(premium, coin_records)
- for(var/datum/data/vending_product/R in (product_records + coin_records + hidden_records))
- var/obj/item/I = R.product_path
- var/pp = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-")
- imagelist[pp] = "[icon2base64(icon(initial(I.icon), initial(I.icon_state), SOUTH, 1))]"
- if(LAZYLEN(slogan_list))
- // So not all machines speak at the exact same time.
- // The first time this machine says something will be at slogantime + this random value,
- // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is created.
- last_slogan = world.time + rand(0, slogan_delay)
-
- power_change()
-
-/obj/machinery/vending/Destroy()
- SStgui.close_uis(wires)
- QDEL_NULL(wires)
- QDEL_NULL(coin)
- QDEL_NULL(inserted_item)
- return ..()
-
-/obj/machinery/vending/RefreshParts() //Better would be to make constructable child
- if(!component_parts)
- return
-
- product_records = list()
- hidden_records = list()
- coin_records = list()
- if(refill_canister)
- build_inventory(products, product_records, start_empty = TRUE)
- build_inventory(contraband, hidden_records, start_empty = TRUE)
- build_inventory(premium, coin_records, start_empty = TRUE)
- for(var/obj/item/vending_refill/VR in component_parts)
- restock(VR)
-
-/obj/machinery/vending/update_overlays()
- . = ..()
- underlays.Cut()
- if(panel_open)
- . += "[icon_panel ? "[icon_panel]_panel" : "[icon_state]_panel"]"
- if(icon_addon)
- . += "[icon_addon]"
- if((stat & (BROKEN|NOPOWER)) || force_no_power_icon_state)
- . += "[icon_off ? "[icon_off]_off" : "[icon_state]_off"]"
- if(stat & BROKEN)
- . += "[icon_broken ? "[icon_broken]_broken" : "[icon_state]_broken"]"
- return
- if(light)
- underlays += emissive_appearance(icon, "[icon_lightmask ? "[icon_lightmask]_lightmask" : "[icon_state]_off"]")
-
-/*
- * Reimp, flash the screen on and off repeatedly.
- */
-/obj/machinery/vending/flicker()
- if(flickering)
- return FALSE
-
- if(stat & (BROKEN|NOPOWER))
- return FALSE
-
- flickering = TRUE
- INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/vending, flicker_event))
-
- return TRUE
-
-/*
- * Proc to be called by invoke_async in the above flicker() proc.
- */
-/obj/machinery/vending/proc/flicker_event()
- var/amount = rand(5, 15)
-
- for(var/i in 1 to amount)
- force_no_power_icon_state = TRUE
- set_light(0)
- update_icon(UPDATE_OVERLAYS)
- sleep(rand(1, 3))
-
- force_no_power_icon_state = FALSE
- set_light(light_range_on, light_power_on)
- update_icon(UPDATE_OVERLAYS)
- sleep(rand(1, 10))
- update_icon(UPDATE_OVERLAYS)
- flickering = FALSE
-
-/**
- * Build src.produdct_records from the products lists
- *
- * src.products, src.contraband, src.premium, and src.prices allow specifying
- * products that the vending machine is to carry without manually populating
- * src.product_records.
- */
-/obj/machinery/vending/proc/build_inventory(list/productlist, list/recordlist, start_empty = FALSE)
- for(var/typepath in productlist)
- var/amount = productlist[typepath]
- if(isnull(amount))
- amount = 0
-
- var/atom/temp = typepath
- var/datum/data/vending_product/R = new /datum/data/vending_product()
- R.name = initial(temp.name)
- R.product_path = typepath
- if(!start_empty)
- R.amount = amount
- R.max_amount = amount
- R.price = (typepath in prices) ? prices[typepath] : 0
- recordlist += R
-/**
- * Refill a vending machine from a refill canister
- *
- * This takes the products from the refill canister and then fills the products,contraband and premium product categories
- *
- * Arguments:
- * * canister - the vending canister we are refilling from
- */
-/obj/machinery/vending/proc/restock(obj/item/vending_refill/canister)
- if(!canister.products)
- canister.products = products.Copy()
- if(!canister.contraband)
- canister.contraband = contraband.Copy()
- if(!canister.premium)
- canister.premium = premium.Copy()
- . = 0
- . += refill_inventory(canister.products, product_records)
- . += refill_inventory(canister.contraband, hidden_records)
- . += refill_inventory(canister.premium, coin_records)
-/**
- * Refill our inventory from the passed in product list into the record list
- *
- * Arguments:
- * * productlist - list of types -> amount
- * * recordlist - existing record datums
- */
-/obj/machinery/vending/proc/refill_inventory(list/productlist, list/recordlist)
- . = 0
- for(var/R in recordlist)
- var/datum/data/vending_product/record = R
- var/diff = min(record.max_amount - record.amount, productlist[record.product_path])
- if (diff)
- productlist[record.product_path] -= diff
- record.amount += diff
- . += diff
-/**
- * Set up a refill canister that matches this machines products
- *
- * This is used when the machine is deconstructed, so the items aren't "lost"
- */
-/obj/machinery/vending/proc/update_canister()
- if(!component_parts)
- return
-
- var/obj/item/vending_refill/R = locate() in component_parts
- if(!R)
- CRASH("Constructible vending machine did not have a refill canister")
-
- R.products = unbuild_inventory(product_records)
- R.contraband = unbuild_inventory(hidden_records)
- R.premium = unbuild_inventory(coin_records)
-
-/**
- * Given a record list, go through and and return a list of type -> amount
- */
-/obj/machinery/vending/proc/unbuild_inventory(list/recordlist)
- . = list()
- for(var/R in recordlist)
- var/datum/data/vending_product/record = R
- .[record.product_path] += record.amount
-
-/obj/machinery/vending/deconstruct(disassembled = TRUE)
- eject_item()
- if(!refill_canister) //the non constructable vendors drop metal instead of a machine frame.
- new /obj/item/stack/sheet/metal(loc, 3)
- qdel(src)
- else
- ..()
-
-/obj/machinery/vending/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/coin))
- if(!premium.len)
- to_chat(user, "[src] does not accept coins.")
- return
- if(coin)
- to_chat(user, "There is already a coin in this machine!")
- return
- if(!user.drop_item())
- return
- I.forceMove(src)
- coin = I
- to_chat(user, "You insert [I] into [src].")
- SStgui.update_uis(src)
- return
- if(refill_canister && istype(I, refill_canister))
- if(!panel_open)
- to_chat(user, "You should probably unscrew the service panel first!")
- else if (stat & (BROKEN|NOPOWER))
- to_chat(user, "[src] does not respond.")
- else
- //if the panel is open we attempt to refill the machine
- var/obj/item/vending_refill/canister = I
- if(canister.get_part_rating() == 0)
- to_chat(user, "[canister] is empty!")
- else
- // instantiate canister if needed
- var/transferred = restock(canister)
- if(transferred)
- to_chat(user, "You loaded [transferred] items in [src].")
- else
- to_chat(user, "There's nothing to restock!")
- return
- if(item_slot_check(user, I))
- insert_item(user, I)
- return
- return ..()
-
-
-
-/obj/machinery/vending/crowbar_act(mob/user, obj/item/I)
- if(!component_parts)
- return
- . = TRUE
- default_deconstruction_crowbar(user, I)
-
-/obj/machinery/vending/multitool_act(mob/user, obj/item/I)
- . = TRUE
- if(!I.use_tool(src, user, 0, volume = I.tool_volume))
- return
- wires.Interact(user)
-
-/obj/machinery/vending/screwdriver_act(mob/user, obj/item/I)
- . = TRUE
- if(!I.use_tool(src, user, 0, volume = I.tool_volume))
- return
- if(anchored)
- panel_open = !panel_open
- panel_open ? SCREWDRIVER_OPEN_PANEL_MESSAGE : SCREWDRIVER_CLOSE_PANEL_MESSAGE
- update_icon(UPDATE_OVERLAYS)
- SStgui.update_uis(src)
-
-/obj/machinery/vending/wirecutter_act(mob/user, obj/item/I)
- . = TRUE
- if(I.use_tool(src, user, 0, volume = 0))
- wires.Interact(user)
-
-/obj/machinery/vending/wrench_act(mob/user, obj/item/I)
- . = TRUE
- if(!I.use_tool(src, user, 0, volume = 0))
- return
- default_unfasten_wrench(user, I, time = 60)
-
-//Override this proc to do per-machine checks on the inserted item, but remember to call the parent to handle these generic checks before your logic!
-/obj/machinery/vending/proc/item_slot_check(mob/user, obj/item/I)
- if(!item_slot)
- return FALSE
- if(inserted_item)
- to_chat(user, "There is something already inserted!")
- return FALSE
- return TRUE
-
-/* Example override for item_slot_check proc:
-/obj/machinery/vending/example/item_slot_check(mob/user, obj/item/I)
- if(!..())
- return FALSE
- if(!istype(I, /obj/item/toy))
- to_chat(user, "[I] isn't compatible with this machine's slot.")
- return FALSE
- return TRUE
-*/
-
-/obj/machinery/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/W)
- if(!istype(W))
- return FALSE
- if(!W.works_from_distance)
- return FALSE
- if(!component_parts || !refill_canister)
- return FALSE
-
- var/moved = 0
- if(panel_open || W.works_from_distance)
- if(W.works_from_distance)
- to_chat(user, display_parts(user))
- for(var/I in W)
- if(istype(I, refill_canister))
- moved += restock(I)
- else
- to_chat(user, display_parts(user))
- if(moved)
- to_chat(user, "[moved] items restocked.")
- W.play_rped_sound()
- return TRUE
-
-/obj/machinery/vending/on_deconstruction()
- update_canister()
- . = ..()
-
-/obj/machinery/vending/proc/insert_item(mob/user, obj/item/I)
- if(!item_slot || inserted_item)
- return
- if(!user.drop_item())
- to_chat(user, "[I] is stuck to your hand, you can't seem to put it down!")
- return
- inserted_item = I
- I.forceMove(src)
- to_chat(user, "You insert [I] into [src].")
- SStgui.update_uis(src)
-
-/obj/machinery/vending/proc/eject_item(mob/user)
- if(!item_slot || !inserted_item)
- return
- var/put_on_turf = TRUE
- if(user && iscarbon(user) && user.Adjacent(src))
- if(user.put_in_hands(inserted_item))
- put_on_turf = FALSE
- if(put_on_turf)
- var/turf/T = get_turf(src)
- inserted_item.forceMove(T)
- inserted_item = null
- SStgui.update_uis(src)
-
-/obj/machinery/vending/emag_act(user as mob)
- emagged = TRUE
- to_chat(user, "You short out the product lock on [src]")
-
-
-/obj/machinery/vending/proc/pay_with_cash(obj/item/stack/spacecash/cashmoney, mob/user)
- if(currently_vending.price > cashmoney.amount)
- // This is not a status display message, since it's something the character
- // themselves is meant to see BEFORE putting the money in
- to_chat(usr, "[bicon(cashmoney)] That is not enough money.")
- return FALSE
-
- // Bills (banknotes) cannot really have worth different than face value,
- // so we have to eat the bill and spit out change in a bundle
- // This is really dirty, but there's no superclass for all bills, so we
- // just assume that all spacecash that's not something else is a bill
-
- visible_message("[usr] inserts a credit chip into [src].")
- cashmoney.use(currently_vending.price)
-
- // Vending machines have no idea who paid with cash
- GLOB.vendor_account.credit(currently_vending.price, "Sale of [currently_vending.name]", name, "(cash)")
- return TRUE
-
-
-/obj/machinery/vending/proc/pay_with_card(obj/item/card/id/I, mob/M)
- visible_message("[M] swipes a card through [src].")
- return pay_with_account(get_card_account(I), M)
-
-/obj/machinery/vending/proc/pay_with_account(datum/money_account/customer_account, mob/M)
- if(!customer_account)
- to_chat(M, "Error: Unable to access account. Please contact technical support if problem persists.")
- return FALSE
- if(customer_account.suspended)
- to_chat(M, "Unable to access account: account suspended.")
- return FALSE
- // Have the customer punch in the PIN before checking if there's enough money.
- // Prevents people from figuring out acct is empty at high security levels
- if(customer_account.security_level != 0)
- // If card requires pin authentication (ie seclevel 1 or 2)
- var/attempt_pin = input("Enter pin code", "Vendor transaction") as num
- if(!attempt_account_access(customer_account.account_number, attempt_pin, 2))
- to_chat(M, "Unable to access account: incorrect credentials.")
- return FALSE
- if(currently_vending.price > customer_account.money)
- to_chat(M, "Your bank account has insufficient money to purchase this.")
- return FALSE
- // Okay to move the money at this point
- customer_account.charge(currently_vending.price, GLOB.vendor_account,
- "Purchase of [currently_vending.name]", name, GLOB.vendor_account.owner_name,
- "Sale of [currently_vending.name]", customer_account.owner_name)
- return TRUE
-
-
-/obj/machinery/vending/attack_ai(mob/user)
- return attack_hand(user)
-
-/obj/machinery/vending/attack_ghost(mob/user)
- return attack_hand(user)
-
-/obj/machinery/vending/attack_hand(mob/user)
- if(stat & (BROKEN|NOPOWER))
- return
-
- if(src.seconds_electrified != 0)
- if(src.shock(user, 100))
- return
-
- ui_interact(user)
- wires.Interact(user)
-
-/obj/machinery/vending/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- var/estimated_height = 100 + min(length(product_records) * 34, 500)
- if(length(prices) > 0)
- estimated_height += 100 // to account for the "current user" interface
- ui = new(user, src, ui_key, "Vending", name, 470, estimated_height, master_ui, state)
- ui.open()
-
-/obj/machinery/vending/ui_data(mob/user)
- var/list/data = list()
- var/mob/living/carbon/human/H
- var/obj/item/card/id/C
- data["guestNotice"] = "No valid ID card detected. Wear your ID, or present cash.";
- data["userMoney"] = 0
- data["user"] = null
- if(ishuman(user))
- H = user
- C = H.get_idcard(TRUE)
- if(!C && istype(H.wear_pda, /obj/item/pda))
- var/obj/item/pda/P = H.wear_pda
- if(istype(P.id, /obj/item/card/id))
- C = P.id
- var/obj/item/stack/spacecash/S = H.get_active_hand()
- if(istype(S))
- data["userMoney"] = S.amount
- data["guestNotice"] = "Accepting Cash. You have: [S.amount] credits."
- else if(istype(C))
- var/datum/money_account/A = get_card_account(C)
- if(istype(A))
- data["user"] = list()
- data["user"]["name"] = A.owner_name
- data["userMoney"] = A.money
- data["user"]["job"] = (istype(C) && C.rank) ? C.rank : "No Job"
- else
- data["guestNotice"] = "Unlinked ID detected. Present cash to pay.";
- data["stock"] = list()
- for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records)
- data["stock"][R.name] = R.amount
- data["extended_inventory"] = extended_inventory
- data["vend_ready"] = vend_ready
- data["coin_name"] = coin ? coin.name : FALSE
- data["panel_open"] = panel_open ? TRUE : FALSE
- data["speaker"] = shut_up ? FALSE : TRUE
- data["item_slot"] = item_slot // boolean
- data["inserted_item_name"] = inserted_item ? inserted_item.name : FALSE
- return data
-
-
-/obj/machinery/vending/ui_static_data(mob/user)
- var/list/data = list()
- data["chargesMoney"] = length(prices) > 0 ? TRUE : FALSE
- data["product_records"] = list()
- var/i = 1
- for (var/datum/data/vending_product/R in product_records)
- var/list/data_pr = list(
- path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
- name = R.name,
- price = (R.product_path in prices) ? prices[R.product_path] : 0,
- max_amount = R.max_amount,
- req_coin = FALSE,
- is_hidden = FALSE,
- inum = i
- )
- data["product_records"] += list(data_pr)
- i++
- data["coin_records"] = list()
- for (var/datum/data/vending_product/R in coin_records)
- var/list/data_cr = list(
- path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
- name = R.name,
- price = (R.product_path in prices) ? prices[R.product_path] : 0,
- max_amount = R.max_amount,
- req_coin = TRUE,
- is_hidden = FALSE,
- inum = i,
- premium = TRUE
- )
- data["coin_records"] += list(data_cr)
- i++
- data["hidden_records"] = list()
- for (var/datum/data/vending_product/R in hidden_records)
- var/list/data_hr = list(
- path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
- name = R.name,
- price = (R.product_path in prices) ? prices[R.product_path] : 0,
- max_amount = R.max_amount,
- req_coin = FALSE,
- is_hidden = TRUE,
- inum = i,
- premium = TRUE
- )
- data["hidden_records"] += list(data_hr)
- i++
- data["imagelist"] = imagelist
- return data
-
-/obj/machinery/vending/ui_act(action, params)
- . = ..()
- if(.)
- return
- if(issilicon(usr) && !isrobot(usr))
- to_chat(usr, "The vending machine refuses to interface with you, as you are not in its target demographic!")
- return
- switch(action)
- if("toggle_voice")
- if(panel_open)
- shut_up = !shut_up
- . = TRUE
- if("eject_item")
- eject_item(usr)
- . = TRUE
- if("remove_coin")
- if(!coin)
- to_chat(usr, "There is no coin in this machine.")
- return
- if(issilicon(usr))
- to_chat(usr, "You lack hands.")
- return
- to_chat(usr, "You remove [coin] from [src].")
- usr.put_in_hands(coin)
- coin = null
- . = TRUE
- if("vend")
- if(!vend_ready)
- to_chat(usr, "The vending machine is busy!")
- return
- if(panel_open)
- to_chat(usr, "The vending machine cannot dispense products while its service panel is open!")
- return
- var/key = text2num(params["inum"])
- var/list/display_records = product_records + coin_records
- if(extended_inventory)
- display_records = product_records + coin_records + hidden_records
- if(key < 1 || key > length(display_records))
- to_chat(usr, "ERROR: invalid inum passed to vendor. Report this bug.")
- return
- var/datum/data/vending_product/R = display_records[key]
- if(!istype(R))
- to_chat(usr, "ERROR: unknown vending_product record. Report this bug.")
- return
- var/list/record_to_check = product_records + coin_records
- if(extended_inventory)
- record_to_check = product_records + coin_records + hidden_records
- if(!R || !istype(R) || !R.product_path)
- to_chat(usr, "ERROR: unknown product record. Report this bug.")
- return
- if(R in hidden_records)
- if(!extended_inventory)
- // Exploit prevention, stop the user purchasing hidden stuff if they haven't hacked the machine.
- to_chat(usr, "ERROR: machine does not allow extended_inventory in current state. Report this bug.")
- return
- else if (!(R in record_to_check))
- // Exploit prevention, stop the user
- message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!")
- return
- if (R.amount <= 0)
- to_chat(usr, "Sold out of [R.name].")
- flick(icon_deny, src)
- return
-
- vend_ready = FALSE // From this point onwards, vendor is locked to performing this transaction only, until it is resolved.
-
- if(!ishuman(usr) || R.price <= 0)
- // Either the purchaser is not human, or the item is free.
- // Skip all payment logic.
- vend(R, usr)
- add_fingerprint(usr)
- vend_ready = TRUE
- . = TRUE
- return
-
- // --- THE REST OF THIS PROC IS JUST PAYMENT LOGIC ---
-
- var/mob/living/carbon/human/H = usr
- var/obj/item/card/id/C = H.get_idcard(TRUE)
-
- if(!GLOB.vendor_account || GLOB.vendor_account.suspended)
- to_chat(usr, "Vendor account offline. Unable to process transaction.")
- flick(icon_deny, src)
- vend_ready = TRUE
- return
-
- currently_vending = R
- var/paid = FALSE
-
- if(istype(usr.get_active_hand(), /obj/item/stack/spacecash))
- var/obj/item/stack/spacecash/S = usr.get_active_hand()
- paid = pay_with_cash(S)
- else if(istype(C, /obj/item/card))
- // Because this uses H.get_idcard(TRUE), it will attempt to use:
- // active hand, inactive hand, pda.id, and then wear_id ID in that order
- // this is important because it lets people buy stuff with someone else's ID by holding it while using the vendor
- paid = pay_with_card(C, usr)
- else if(usr.can_advanced_admin_interact())
- to_chat(usr, "Vending object due to admin interaction.")
- paid = TRUE
- else
- to_chat(usr, "Payment failure: you have no ID or other method of payment.")
- vend_ready = TRUE
- flick(icon_deny, src)
- . = TRUE // we set this because they shouldn't even be able to get this far, and we want the UI to update.
- return
- if(paid)
- vend(currently_vending, usr)
- . = TRUE
- else
- to_chat(usr, "Payment failure: unable to process payment.")
- vend_ready = TRUE
- if(.)
- add_fingerprint(usr)
-
-
-/obj/machinery/vending/proc/vend(datum/data/vending_product/R, mob/user)
- if(!allowed(user) && !user.can_admin_interact() && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
- to_chat(user, "Access denied.")//Unless emagged of course
- flick(icon_deny, src)
- vend_ready = TRUE
- return
-
- if(!R.amount)
- to_chat(user, "The vending machine has ran out of that product.")
- vend_ready = TRUE
- return
-
- vend_ready = FALSE //One thing at a time!!
-
- if(coin_records.Find(R))
- if(!coin)
- to_chat(user, "You need to insert a coin to get this item.")
- vend_ready = TRUE
- return
- if(coin.string_attached)
- if(prob(50))
- to_chat(user, "You successfully pull the coin out before [src] could swallow it.")
- else
- to_chat(user, "You weren't able to pull the coin out fast enough, the machine ate it, string and all.")
- QDEL_NULL(coin)
- else
- QDEL_NULL(coin)
-
- R.amount--
-
- if(((last_reply + (vend_delay + 200)) <= world.time) && vend_reply)
- speak(src.vend_reply)
- last_reply = world.time
-
- use_power(vend_power_usage) //actuators and stuff
- if(icon_vend) //Show the vending animation if needed
- flick(icon_vend, src)
- playsound(get_turf(src), 'sound/machines/machine_vend.ogg', 50, TRUE)
- addtimer(CALLBACK(src, PROC_REF(delayed_vend), R, user), vend_delay)
-
-/obj/machinery/vending/proc/delayed_vend(datum/data/vending_product/R, mob/user)
- do_vend(R, user)
- vend_ready = TRUE
- currently_vending = null
-
-//override this proc to add handling for what to do with the vended product when you have a inserted item and remember to include a parent call for this generic handling
-/obj/machinery/vending/proc/do_vend(datum/data/vending_product/R, mob/user)
- if(!item_slot || !inserted_item)
- var/put_on_turf = TRUE
- var/obj/vended = new R.product_path()
- if(user && iscarbon(user) && user.Adjacent(src))
- if(user.put_in_hands(vended))
- put_on_turf = FALSE
- if(put_on_turf)
- var/turf/T = get_turf(src)
- vended.forceMove(T)
- return TRUE
- return FALSE
-
-/* Example override for do_vend proc:
-/obj/machinery/vending/example/do_vend(datum/data/vending_product/R)
- if(..())
- return
- var/obj/item/vended = new R.product_path()
- if(inserted_item.force == initial(inserted_item.force)
- inserted_item.force += vended.force
- inserted_item.damtype = vended.damtype
- qdel(vended)
-*/
-
-/obj/machinery/vending/process()
- if(stat & (BROKEN|NOPOWER))
- return
-
- if(!active)
- return
-
- if(src.seconds_electrified > 0)
- src.seconds_electrified--
-
- //Pitch to the people! Really sell it!
- if(((last_slogan + src.slogan_delay) <= world.time) && (LAZYLEN(slogan_list)) && (!shut_up) && prob(5))
- var/slogan = pick(src.slogan_list)
- speak(slogan)
- last_slogan = world.time
-
- if(shoot_inventory && prob(shoot_chance))
- throw_item()
-
-/obj/machinery/vending/extinguish_light()
- set_light(0)
- underlays.Cut()
-
-/obj/machinery/vending/proc/speak(message)
- if(stat & NOPOWER)
- return
- if(!message)
- return
-
- atom_say(message)
-
-/obj/machinery/vending/power_change()
- if(powered())
- stat &= ~NOPOWER
- else
- stat |= NOPOWER
- if((stat & (BROKEN|NOPOWER)))
- set_light(0)
- else
- set_light(light_range_on, light_power_on)
- update_icon(UPDATE_OVERLAYS)
-
-/obj/machinery/vending/obj_break(damage_flag)
- if(!(stat & BROKEN))
- stat |= BROKEN
- set_light(0)
- update_icon(UPDATE_OVERLAYS)
-
- var/dump_amount = 0
- var/found_anything = TRUE
- while (found_anything)
- found_anything = FALSE
- for(var/record in shuffle(product_records))
- var/datum/data/vending_product/R = record
- if(R.amount <= 0) //Try to use a record that actually has something to dump.
- continue
- var/dump_path = R.product_path
- if(!dump_path)
- continue
- R.amount--
- // busting open a vendor will destroy some of the contents
- if(found_anything && prob(80))
- continue
-
- var/obj/O = new dump_path(loc)
- step(O, pick(GLOB.alldirs))
- found_anything = TRUE
- dump_amount++
- if(dump_amount >= 16)
- return
-
-//Somebody cut an important wire and now we're following a new definition of "pitch."
-/obj/machinery/vending/proc/throw_item()
- var/obj/throw_item = null
- var/mob/living/target = locate() in view(7, src)
- if(!target)
- return 0
-
- for(var/datum/data/vending_product/R in product_records)
- if(R.amount <= 0) //Try to use a record that actually has something to dump.
- continue
- var/dump_path = R.product_path
- if(!dump_path)
- continue
-
- R.amount--
- throw_item = new dump_path(loc)
- break
- if(!throw_item)
- return
- throw_item.throw_at(target, 16, 3)
- visible_message("[src] launches [throw_item.name] at [target.name]!")
-
-/obj/machinery/vending/onTransitZ()
- return
-/*
- * Vending machine types
- */
-
-/*
-
-/obj/machinery/vending/[vendors name here] // --vending machine template :)
- name = ""
- desc = ""
- icon = ''
- icon_state = ""
- vend_delay = 15
- products = list()
- contraband = list()
- premium = list()
-
-*/
-
-/obj/machinery/vending/assist
- products = list( /obj/item/assembly/prox_sensor = 5,/obj/item/assembly/igniter = 3,/obj/item/assembly/signaler = 4,
- /obj/item/wirecutters = 1, /obj/item/cartridge/signal = 4)
- contraband = list(/obj/item/flashlight = 5,/obj/item/assembly/timer = 2, /obj/item/assembly/voice = 2, /obj/item/assembly/health = 2)
- ads_list = list("Only the finest!","Have some tools.","The most robust equipment.","The finest gear in space!")
- refill_canister = /obj/item/vending_refill/assist
-
-/obj/machinery/vending/boozeomat
- name = "\improper Booze-O-Mat"
- desc = "A technological marvel, supposedly able to mix just the mixture you'd like to drink the moment you ask for one."
- icon_state = "boozeomat" //////////////18 drink entities below, plus the glasses, in case someone wants to edit the number of bottles
- icon_deny = "boozeomat_deny"
- icon_lightmask = "smartfridge"
- icon_panel = "smartfridge"
- icon_broken = "smartfridge"
- products = list(/obj/item/reagent_containers/food/drinks/bottle/gin = 5,
- /obj/item/reagent_containers/food/drinks/bottle/whiskey = 5,
- /obj/item/reagent_containers/food/drinks/bottle/tequila = 5,
- /obj/item/reagent_containers/food/drinks/bottle/vodka = 5,
- /obj/item/reagent_containers/food/drinks/bottle/vermouth = 5,
- /obj/item/reagent_containers/food/drinks/bottle/rum = 5,
- /obj/item/reagent_containers/food/drinks/bottle/wine = 5,
- /obj/item/reagent_containers/food/drinks/bag/goonbag = 3,
- /obj/item/reagent_containers/food/drinks/bottle/cognac = 5,
- /obj/item/reagent_containers/food/drinks/bottle/kahlua = 5,
- /obj/item/reagent_containers/food/drinks/cans/beer = 6,
- /obj/item/reagent_containers/food/drinks/cans/ale = 6,
- /obj/item/reagent_containers/food/drinks/cans/synthanol = 15,
- /obj/item/reagent_containers/food/drinks/bottle/orangejuice = 4,
- /obj/item/reagent_containers/food/drinks/bottle/tomatojuice = 4,
- /obj/item/reagent_containers/food/drinks/bottle/limejuice = 4,
- /obj/item/reagent_containers/food/drinks/bottle/cream = 4,
- /obj/item/reagent_containers/food/drinks/cans/tonic = 8,
- /obj/item/reagent_containers/food/drinks/cans/cola = 8,
- /obj/item/reagent_containers/food/drinks/cans/sodawater = 15,
- /obj/item/reagent_containers/food/drinks/drinkingglass = 30,
- /obj/item/reagent_containers/food/drinks/drinkingglass/shotglass = 30,
- /obj/item/reagent_containers/food/drinks/ice = 9)
- contraband = list(/obj/item/reagent_containers/food/drinks/tea = 10,
- /obj/item/reagent_containers/food/drinks/bottle/fernet = 5)
- vend_delay = 15
- slogan_list = list("I hope nobody asks me for a bloody cup o' tea...","Alcohol is humanity's friend. Would you abandon a friend?","Quite delighted to serve you!","Is nobody thirsty on this station?")
- ads_list = list("Drink up!","Booze is good for you!","Alcohol is humanity's best friend.","Quite delighted to serve you!","Care for a nice, cold beer?","Nothing cures you like booze!","Have a sip!","Have a drink!","Have a beer!","Beer is good for you!","Only the finest alcohol!","Best quality booze since 2053!","Award-winning wine!","Maximum alcohol!","Man loves beer.","A toast for progress!")
- refill_canister = /obj/item/vending_refill/boozeomat
-
-/obj/machinery/vending/boozeomat/syndicate_access
- req_access = list(ACCESS_SYNDICATE)
-
-
-/obj/machinery/vending/coffee
- name = "\improper Solar's Best Hot Drinks"
- desc = "A vending machine which dispenses hot drinks."
- ads_list = list("Have a drink!","Drink up!","It's good for you!","Would you like a hot joe?","I'd kill for some coffee!","The best beans in the galaxy.","Only the finest brew for you.","Mmmm. Nothing like a coffee.","I like coffee, don't you?","Coffee helps you work!","Try some tea.","We hope you like the best!","Try our new chocolate!","Admin conspiracies")
- icon_state = "coffee"
- icon_lightmask = "coffee"
- icon_vend = "coffee_vend"
- icon_panel = "screen_vendor"
- item_slot = TRUE
- vend_delay = 34
- products = list(/obj/item/reagent_containers/food/drinks/coffee = 25,/obj/item/reagent_containers/food/drinks/tea = 25,/obj/item/reagent_containers/food/drinks/h_chocolate = 25,
- /obj/item/reagent_containers/food/drinks/chocolate = 10, /obj/item/reagent_containers/food/drinks/chicken_soup = 10,/obj/item/reagent_containers/food/drinks/weightloss = 10,
- /obj/item/reagent_containers/food/drinks/mug = 15)
- contraband = list(/obj/item/reagent_containers/food/drinks/ice = 10)
- premium = list(/obj/item/reagent_containers/food/drinks/mug/novelty = 5)
- prices = list(/obj/item/reagent_containers/food/drinks/coffee = 25, /obj/item/reagent_containers/food/drinks/tea = 25, /obj/item/reagent_containers/food/drinks/h_chocolate = 25, /obj/item/reagent_containers/food/drinks/chocolate = 25,
- /obj/item/reagent_containers/food/drinks/chicken_soup = 30,/obj/item/reagent_containers/food/drinks/weightloss = 50, /obj/item/reagent_containers/food/drinks/mug = 50)
- refill_canister = /obj/item/vending_refill/coffee
-
-/obj/machinery/vending/coffee/free
- prices = list()
-
-/obj/machinery/vending/coffee/item_slot_check(mob/user, obj/item/I)
- if(!(istype(I, /obj/item/reagent_containers/glass) || istype(I, /obj/item/reagent_containers/food/drinks)))
- return FALSE
- if(!..())
- return FALSE
- if(!I.is_open_container())
- to_chat(user, "You need to open [I] before inserting it.")
- return FALSE
- return TRUE
-
-/obj/machinery/vending/coffee/do_vend(datum/data/vending_product/R, mob/user)
- if(..())
- return
- var/obj/item/reagent_containers/food/drinks/vended = new R.product_path()
-
- if(istype(vended, /obj/item/reagent_containers/food/drinks/mug))
- var/put_on_turf = TRUE
- if(user && iscarbon(user) && user.Adjacent(src))
- if(user.put_in_hands(vended))
- put_on_turf = FALSE
- if(put_on_turf)
- var/turf/T = get_turf(src)
- vended.forceMove(T)
- return
-
- vended.reagents.trans_to(inserted_item, vended.reagents.total_volume)
- if(vended.reagents.total_volume)
- var/put_on_turf = TRUE
- if(user && iscarbon(user) && user.Adjacent(src))
- if(user.put_in_hands(vended))
- put_on_turf = FALSE
- if(put_on_turf)
- var/turf/T = get_turf(src)
- vended.forceMove(T)
- else
- qdel(vended)
-
-
-/obj/machinery/vending/snack
- name = "\improper Getmore Chocolate Corp"
- desc = "A snack machine courtesy of the Getmore Chocolate Corporation, based out of Mars."
- slogan_list = list("Try our new nougat bar!","Twice the calories for half the price!")
- ads_list = list("The healthiest!","Award-winning chocolate bars!","Mmm! So good!","Oh my god it's so juicy!","Have a snack.","Snacks are good for you!","Have some more Getmore!","Best quality snacks straight from mars.","We love chocolate!","Try our new jerky!")
- icon_state = "snack"
- icon_lightmask = "nutri"
- icon_off = "nutri"
- icon_panel = "thin_vendor"
- products = list(/obj/item/reagent_containers/food/snacks/candy/candybar = 6,/obj/item/reagent_containers/food/drinks/dry_ramen = 6,/obj/item/reagent_containers/food/snacks/chips =6,
- /obj/item/reagent_containers/food/snacks/sosjerky = 6,/obj/item/reagent_containers/food/snacks/no_raisin = 6,/obj/item/reagent_containers/food/snacks/pistachios =6,
- /obj/item/reagent_containers/food/snacks/spacetwinkie = 6,/obj/item/reagent_containers/food/snacks/cheesiehonkers = 6,/obj/item/reagent_containers/food/snacks/tastybread = 6)
- premium = list(/obj/item/reagent_containers/food/snacks/stroopwafel = 2)
- contraband = list(/obj/item/reagent_containers/food/snacks/syndicake = 6)
- prices = list(/obj/item/reagent_containers/food/snacks/candy/candybar = 20,/obj/item/reagent_containers/food/drinks/dry_ramen = 30,
- /obj/item/reagent_containers/food/snacks/chips =25,/obj/item/reagent_containers/food/snacks/sosjerky = 30,/obj/item/reagent_containers/food/snacks/no_raisin = 20,
- /obj/item/reagent_containers/food/snacks/pistachios = 35, /obj/item/reagent_containers/food/snacks/spacetwinkie = 30,/obj/item/reagent_containers/food/snacks/cheesiehonkers = 25,/obj/item/reagent_containers/food/snacks/tastybread = 30)
- refill_canister = /obj/item/vending_refill/snack
-
-/obj/machinery/vending/snack/free
- prices = list()
-
-/obj/machinery/vending/chinese
- name = "\improper Mr. Chang"
- desc = "A self-serving Chinese food machine, for all your Chinese food needs."
- slogan_list = list("Taste 5000 years of culture!","Mr. Chang, approved for safe consumption in over 10 sectors!","Chinese food is great for a date night, or a lonely night!","You can't go wrong with Mr. Chang's authentic Chinese food!")
- icon_state = "chang"
- icon_lightmask = "chang"
- products = list(/obj/item/reagent_containers/food/snacks/chinese/chowmein = 6, /obj/item/reagent_containers/food/snacks/chinese/tao = 6, /obj/item/reagent_containers/food/snacks/chinese/sweetsourchickenball = 6, /obj/item/reagent_containers/food/snacks/chinese/newdles = 6,
- /obj/item/reagent_containers/food/snacks/chinese/rice = 6, /obj/item/reagent_containers/food/snacks/fortunecookie = 6)
- prices = list(/obj/item/reagent_containers/food/snacks/chinese/chowmein = 50, /obj/item/reagent_containers/food/snacks/chinese/tao = 50, /obj/item/reagent_containers/food/snacks/chinese/sweetsourchickenball = 50, /obj/item/reagent_containers/food/snacks/chinese/newdles = 50,
- /obj/item/reagent_containers/food/snacks/chinese/rice = 50, /obj/item/reagent_containers/food/snacks/fortunecookie = 50)
- refill_canister = /obj/item/vending_refill/chinese
-
-/obj/machinery/vending/chinese/free
- prices = list()
-
-/obj/machinery/vending/cola
- name = "\improper Robust Softdrinks"
- desc = "A soft drink vendor provided by Robust Industries, LLC."
- icon_state = "Cola_Machine"
- icon_lightmask = "Cola_Machine"
- icon_panel = "thin_vendor"
- slogan_list = list("Robust Softdrinks: More robust than a toolbox to the head!")
- ads_list = list("Refreshing!","Hope you're thirsty!","Over 1 million drinks sold!","Thirsty? Why not cola?","Please, have a drink!","Drink up!","The best drinks in space.")
- products = list(/obj/item/reagent_containers/food/drinks/cans/cola = 10,/obj/item/reagent_containers/food/drinks/cans/space_mountain_wind = 10,
- /obj/item/reagent_containers/food/drinks/cans/dr_gibb = 10,/obj/item/reagent_containers/food/drinks/cans/starkist = 10,
- /obj/item/reagent_containers/food/drinks/cans/space_up = 10,/obj/item/reagent_containers/food/drinks/cans/grape_juice = 10)
- contraband = list(/obj/item/reagent_containers/food/drinks/cans/thirteenloko = 5)
- prices = list(/obj/item/reagent_containers/food/drinks/cans/cola = 20,/obj/item/reagent_containers/food/drinks/cans/space_mountain_wind = 20,
- /obj/item/reagent_containers/food/drinks/cans/dr_gibb = 20,/obj/item/reagent_containers/food/drinks/cans/starkist = 20,
- /obj/item/reagent_containers/food/drinks/cans/space_up = 20,/obj/item/reagent_containers/food/drinks/cans/grape_juice = 20)
- refill_canister = /obj/item/vending_refill/cola
-
-/obj/machinery/vending/cola/free
- prices = list()
-
-/obj/machinery/vending/cart
- name = "\improper PTech"
- desc = "Cartridges for PDA's."
- slogan_list = list("Carts to go!")
- icon_state = "cart"
- icon_lightmask = "med"
- icon_deny = "cart_deny"
- icon_panel = "wide_vendor"
- products = list(/obj/item/pda =10,/obj/item/cartridge/mob_hunt_game = 25,/obj/item/cartridge/medical = 10,/obj/item/cartridge/chemistry = 10,
- /obj/item/cartridge/engineering = 10,/obj/item/cartridge/atmos = 10,/obj/item/cartridge/janitor = 10,
- /obj/item/cartridge/signal/toxins = 10,/obj/item/cartridge/signal = 10)
- contraband = list(/obj/item/cartridge/clown = 1,/obj/item/cartridge/mime = 1)
- prices = list(/obj/item/pda =300,/obj/item/cartridge/mob_hunt_game = 50,/obj/item/cartridge/medical = 200,/obj/item/cartridge/chemistry = 150,/obj/item/cartridge/engineering = 100,
- /obj/item/cartridge/atmos = 75,/obj/item/cartridge/janitor = 100,/obj/item/cartridge/signal/toxins = 150,
- /obj/item/cartridge/signal = 75)
- refill_canister = /obj/item/vending_refill/cart
-
-/obj/machinery/vending/cart/free
- prices = list()
-
-/obj/machinery/vending/liberationstation
- name = "\improper Liberation Station"
- desc = "An overwhelming amount of ancient patriotism washes over you just by looking at the machine."
- icon_state = "liberationstation"
- icon_lightmask = "liberationstation"
- req_access_txt = "1"
- slogan_list = list("Liberation Station: Your one-stop shop for all things second amendment!","Be a patriot today, pick up a gun!","Quality weapons for cheap prices!","Better dead than red!")
- ads_list = list("Float like an astronaut, sting like a bullet!","Express your second amendment today!","Guns don't kill people, but you can!","Who needs responsibilities when you have guns?")
- vend_reply = "Remember the name: Liberation Station!"
- products = list(/obj/item/gun/projectile/automatic/pistol/deagle/gold = 2,/obj/item/gun/projectile/automatic/pistol/deagle/camo = 2,
- /obj/item/gun/projectile/automatic/pistol/m1911 = 2,/obj/item/gun/projectile/automatic/proto = 2,
- /obj/item/gun/projectile/shotgun/automatic/combat = 2,/obj/item/gun/projectile/automatic/gyropistol = 1,
- /obj/item/gun/projectile/shotgun = 2,/obj/item/gun/projectile/automatic/ar = 2)
- premium = list(/obj/item/ammo_box/magazine/smgm9mm = 2,/obj/item/ammo_box/magazine/m50 = 4,/obj/item/ammo_box/magazine/m45 = 2,/obj/item/ammo_box/magazine/m75 = 2)
- contraband = list(/obj/item/clothing/under/costume/patriotsuit = 1,/obj/item/bedsheet/patriot = 3)
- armor = list(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 50)
- resistance_flags = FIRE_PROOF
-
-/obj/machinery/vending/toyliberationstation
- name = "\improper Syndicate Donksoft Toy Vendor"
- desc = "An ages 8 and up approved vendor that dispenses toys. If you were to find the right wires, you can unlock the adult mode setting!"
- icon_state = "syndi"
- icon_lightmask = "syndi"
- slogan_list = list("Get your cool toys today!","Trigger a valid hunter today!","Quality toy weapons for cheap prices!","Give them to HoPs for all access!","Give them to HoS to get permabrigged!")
- ads_list = list("Feel robust with your toys!","Express your inner child today!","Toy weapons don't kill people, but valid hunters do!","Who needs responsibilities when you have toy weapons?","Make your next murder FUN!")
- vend_reply = "Come back for more!"
- products = list(/obj/item/gun/projectile/automatic/toy = 10,
- /obj/item/gun/projectile/automatic/toy/pistol= 10,
- /obj/item/gun/projectile/shotgun/toy = 10,
- /obj/item/toy/sword = 10,
- /obj/item/ammo_box/foambox = 20,
- /obj/item/toy/foamblade = 10,
- /obj/item/toy/syndicateballoon = 10,
- /obj/item/clothing/suit/syndicatefake = 5,
- /obj/item/clothing/head/syndicatefake = 5) //OPS IN DORMS oh wait it's just an assistant
- contraband = list(/obj/item/gun/projectile/shotgun/toy/crossbow= 10, //Congrats, you unlocked the +18 setting!
- /obj/item/gun/projectile/automatic/c20r/toy/riot = 10,
- /obj/item/gun/projectile/automatic/l6_saw/toy/riot = 10,
- /obj/item/gun/projectile/automatic/sniper_rifle/toy = 10,
- /obj/item/ammo_box/foambox/riot = 20,
- /obj/item/toy/katana = 10,
- /obj/item/twohanded/dualsaber/toy = 5,
- /obj/item/deck/cards/syndicate = 10) //Gambling and it hurts, making it a +18 item
- armor = list(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 50)
- resistance_flags = FIRE_PROOF
-
-/obj/machinery/vending/cigarette
- name = "\improper ShadyCigs Deluxe"
- desc = "If you want to get cancer, might as well do it in style."
- slogan_list = list("Space cigs taste good like a cigarette should.","I'd rather toolbox than switch.","Smoke!","Don't believe the reports - smoke today!")
- ads_list = list("Probably not bad for you!","Don't believe the scientists!","It's good for you!","Don't quit, buy more!","Smoke!","Nicotine heaven.","Best cigarettes since 2150.","Award-winning cigs.")
- vend_delay = 34
- icon_state = "cigs"
- icon_lightmask = "cigs"
- products = list(
- /obj/item/storage/fancy/cigarettes/cigpack_robust = 12,
- /obj/item/storage/fancy/cigarettes/cigpack_uplift = 6,
- /obj/item/storage/fancy/cigarettes/cigpack_midori = 6,
- /obj/item/storage/fancy/cigarettes/cigpack_random = 6,
- /obj/item/reagent_containers/food/pill/patch/nicotine = 10,
- /obj/item/storage/box/matches = 10,
- /obj/item/lighter/random = 4,
- /obj/item/storage/fancy/rollingpapers = 5)
- contraband = list(/obj/item/lighter/zippo = 4)
- premium = list(/obj/item/clothing/mask/cigarette/cigar/havana = 2,
- /obj/item/storage/fancy/cigarettes/cigpack_robustgold = 1)
- prices = list(/obj/item/storage/fancy/cigarettes/cigpack_robust = 60,
- /obj/item/storage/fancy/cigarettes/cigpack_uplift = 80,
- /obj/item/storage/fancy/cigarettes/cigpack_midori = 100,
- /obj/item/storage/fancy/cigarettes/cigpack_random = 120,
- /obj/item/reagent_containers/food/pill/patch/nicotine = 70,
- /obj/item/storage/box/matches = 10,
- /obj/item/lighter/random = 60,
- /obj/item/storage/fancy/rollingpapers = 20)
- refill_canister = /obj/item/vending_refill/cigarette
-
-/obj/machinery/vending/cigarette/free
- prices = list()
-
-/obj/machinery/vending/cigarette/syndicate
- products = list(/obj/item/storage/fancy/cigarettes/cigpack_syndicate = 7,
- /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3,
- /obj/item/storage/fancy/cigarettes/cigpack_robust = 2,
- /obj/item/storage/fancy/cigarettes/cigpack_carp = 3,
- /obj/item/storage/fancy/cigarettes/cigpack_midori = 1,
- /obj/item/storage/box/matches = 10,
- /obj/item/lighter/zippo = 4,
- /obj/item/storage/fancy/rollingpapers = 5)
-
-/obj/machinery/vending/cigarette/syndicate/free
- prices = list()
-
-/obj/machinery/vending/cigarette/beach //Used in the lavaland_biodome_beach.dmm ruin
- name = "\improper ShadyCigs Ultra"
- desc = "Now with extra premium products!"
- ads_list = list("Probably not bad for you!","Dope will get you through times of no money better than money will get you through times of no dope!","It's good for you!")
- slogan_list = list("Turn on, tune in, drop out!","Better living through chemistry!","Toke!","Don't forget to keep a smile on your lips and a song in your heart!")
- products = list(/obj/item/storage/fancy/cigarettes = 5,
- /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3,
- /obj/item/storage/fancy/cigarettes/cigpack_robust = 3,
- /obj/item/storage/fancy/cigarettes/cigpack_carp = 3,
- /obj/item/storage/fancy/cigarettes/cigpack_midori = 3,
- /obj/item/storage/box/matches = 10,
- /obj/item/lighter/random = 4,
- /obj/item/storage/fancy/rollingpapers = 5)
- premium = list(/obj/item/clothing/mask/cigarette/cigar/havana = 2,
- /obj/item/storage/fancy/cigarettes/cigpack_robustgold = 1,
- /obj/item/lighter/zippo = 3)
- prices = list()
-
-/obj/machinery/vending/medical
- name = "\improper NanoMed Plus"
- desc = "Medical drug dispenser."
- icon_state = "med"
- icon_lightmask = "med"
- icon_deny = "med_deny"
- icon_panel = "wide_vendor"
- ads_list = list("Go save some lives!","The best stuff for your medbay.","Only the finest tools.","Natural chemicals!","This stuff saves lives.","Don't you want some?","Ping!")
- req_access_txt = "5"
- products = list(/obj/item/reagent_containers/syringe = 12,
- /obj/item/reagent_containers/food/pill/patch/styptic = 4,
- /obj/item/reagent_containers/food/pill/patch/silver_sulf = 4,
- /obj/item/reagent_containers/applicator/brute = 3,
- /obj/item/reagent_containers/applicator/burn = 3,
- /obj/item/reagent_containers/glass/bottle/charcoal = 4,
- /obj/item/reagent_containers/glass/bottle/epinephrine = 4,
- /obj/item/reagent_containers/glass/bottle/salicylic = 4,
- /obj/item/reagent_containers/glass/bottle/potassium_iodide = 3,
- /obj/item/reagent_containers/glass/bottle/saline = 5,
- /obj/item/reagent_containers/glass/bottle/morphine = 4,
- /obj/item/reagent_containers/glass/bottle/atropine = 3,
- /obj/item/reagent_containers/glass/bottle/oculine = 2,
- /obj/item/reagent_containers/syringe/antiviral = 6,
- /obj/item/reagent_containers/syringe/calomel = 10,
- /obj/item/reagent_containers/syringe/heparin = 4,
- /obj/item/reagent_containers/hypospray/autoinjector = 5,
- /obj/item/reagent_containers/food/pill/salbutamol = 10,
- /obj/item/reagent_containers/food/pill/mannitol = 10,
- /obj/item/reagent_containers/food/pill/mutadone = 5,
- /obj/item/stack/medical/bruise_pack/advanced = 4,
- /obj/item/stack/medical/ointment/advanced = 4,
- /obj/item/stack/medical/bruise_pack = 4,
- /obj/item/stack/medical/splint = 4,
- /obj/item/reagent_containers/glass/beaker = 4,
- /obj/item/reagent_containers/dropper = 4,
- /obj/item/healthanalyzer/advanced = 4,
- /obj/item/reagent_containers/hypospray/safety = 2,
- /obj/item/sensor_device = 2,
- /obj/item/pinpointer/crew = 2)
- contraband = list(/obj/item/reagent_containers/syringe/insulin = 6,
- /obj/item/reagent_containers/glass/bottle/sulfonal = 1,
- /obj/item/reagent_containers/glass/bottle/pancuronium = 1)
- armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 70)
- resistance_flags = FIRE_PROOF
- refill_canister = /obj/item/vending_refill/medical
-
-/obj/machinery/vending/medical/syndicate_access
- name = "\improper SyndiMed Plus"
- req_access = list(ACCESS_SYNDICATE)
-
-/obj/machinery/vending/plasmaresearch
- name = "\improper Toximate 3000"
- desc = "All the fine parts you need in one vending machine!"
- products = list(/obj/item/assembly/prox_sensor = 8, /obj/item/assembly/igniter = 8, /obj/item/assembly/signaler = 8,
- /obj/item/wirecutters = 1, /obj/item/assembly/timer = 8)
- contraband = list(/obj/item/flashlight = 5, /obj/item/assembly/voice = 3, /obj/item/assembly/health = 3, /obj/item/assembly/infra = 3)
-
-/obj/machinery/vending/wallmed
- name = "\improper NanoMed"
- desc = "Wall-mounted Medical Equipment dispenser."
- ads_list = list("Go save some lives!","The best stuff for your medbay.","Only the finest tools.","Natural chemicals!","This stuff saves lives.","Don't you want some?")
- icon_state = "wallmed"
- icon_deny = "wallmed_deny"
- icon_lightmask = "wallmed"
- icon_panel = "wallmed"
- icon_broken = "wallmed"
- density = FALSE //It is wall-mounted, and thus, not dense. --Superxpdude
- products = list(/obj/item/stack/medical/bruise_pack = 2, /obj/item/stack/medical/ointment = 2, /obj/item/reagent_containers/hypospray/autoinjector = 4, /obj/item/healthanalyzer = 1)
- contraband = list(/obj/item/reagent_containers/syringe/charcoal = 4, /obj/item/reagent_containers/syringe/antiviral = 4, /obj/item/reagent_containers/food/pill/tox = 1)
- armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 70)
- resistance_flags = FIRE_PROOF
- refill_canister = /obj/item/vending_refill/wallmed
-
-
-/obj/machinery/vending/wallmed/syndicate
- name = "\improper SyndiMed Plus"
- desc = "EVIL wall-mounted Medical Equipment dispenser."
- icon_state = "syndimed"
- icon_deny = "syndimed_deny"
- ads_list = list("Go end some lives!","The best stuff for your ship.","Only the finest tools.","Natural chemicals!","This stuff saves lives.","Don't you want some?","Ping!")
- req_access_txt = "150"
- products = list(/obj/item/stack/medical/bruise_pack = 2,/obj/item/stack/medical/ointment = 2,/obj/item/reagent_containers/hypospray/autoinjector = 4,/obj/item/healthanalyzer = 1)
- contraband = list(/obj/item/reagent_containers/syringe/charcoal = 4,/obj/item/reagent_containers/syringe/antiviral = 4,/obj/item/reagent_containers/food/pill/tox = 1)
-
-/obj/machinery/vending/security
- name = "\improper SecTech"
- desc = "A security equipment vendor."
- ads_list = list("Crack capitalist skulls!","Beat some heads in!","Don't forget - harm is good!","Your weapons are right here.","Handcuffs!","Freeze, scumbag!","Don't tase me bro!","Tase them, bro.","Why not have a donut?")
- icon_state = "sec"
- icon_lightmask = "sec"
- icon_deny = "sec_deny"
- icon_panel = "wide_vendor"
- req_access_txt = "1"
- products = list(/obj/item/restraints/handcuffs = 8,/obj/item/restraints/handcuffs/cable/zipties = 8,/obj/item/grenade/flashbang = 4,/obj/item/flash = 5,
- /obj/item/reagent_containers/food/snacks/donut = 12,/obj/item/storage/box/evidence = 6,/obj/item/flashlight/seclite = 4,/obj/item/restraints/legcuffs/bola/energy = 7,
- /obj/item/clothing/mask/muzzle/safety = 4)
- contraband = list(/obj/item/clothing/glasses/sunglasses = 2,/obj/item/storage/fancy/donut_box = 2,/obj/item/hailer = 5)
- refill_canister = /obj/item/vending_refill/security
-
-/obj/machinery/vending/hydronutrients
- name = "\improper NutriMax"
- desc = "A plant nutrients vendor"
- slogan_list = list("Aren't you glad you don't have to fertilize the natural way?","Now with 50% less stink!","Plants are people too!")
- ads_list = list("We like plants!","Don't you want some?","The greenest thumbs ever.","We like big plants.","Soft soil...")
- icon_state = "nutri"
- icon_deny = "nutri_deny"
- icon_lightmask = "nutri"
- icon_panel = "thin_vendor"
- products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 20,/obj/item/reagent_containers/glass/bottle/nutrient/l4z = 13,/obj/item/reagent_containers/glass/bottle/nutrient/rh = 6,/obj/item/reagent_containers/spray/pestspray = 20,
- /obj/item/reagent_containers/syringe = 5,/obj/item/storage/bag/plants = 5,/obj/item/cultivator = 3,/obj/item/shovel/spade = 3,/obj/item/plant_analyzer = 4)
- contraband = list(/obj/item/reagent_containers/glass/bottle/ammonia = 10,/obj/item/reagent_containers/glass/bottle/diethylamine = 5)
- refill_canister = /obj/item/vending_refill/hydronutrients
-
-/obj/machinery/vending/hydronutrients/syndicate_druglab
- products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 12,
- /obj/item/reagent_containers/glass/bottle/nutrient/l4z = 2,
- /obj/item/reagent_containers/glass/bottle/nutrient/rh = 3,
- /obj/item/reagent_containers/spray/pestspray = 7,
- /obj/item/reagent_containers/syringe = 11,
- /obj/item/storage/bag/plants = 2,
- /obj/item/cultivator = 3,
- /obj/item/shovel/spade = 2,
- /obj/item/plant_analyzer = 2,
- /obj/item/reagent_containers/glass/bottle/ammonia = 6,
- /obj/item/reagent_containers/glass/bottle/diethylamine = 8)
- contraband = list()
-
-/obj/machinery/vending/hydroseeds
- name = "\improper MegaSeed Servitor"
- desc = "When you need seeds fast!"
- slogan_list = list("THIS'S WHERE TH' SEEDS LIVE! GIT YOU SOME!","Hands down the best seed selection on the station!","Also certain mushroom varieties available, more for experts! Get certified today!")
- ads_list = list("We like plants!","Grow some crops!","Grow, baby, growww!","Aw h'yeah son!")
- icon_state = "seeds"
- icon_lightmask = "seeds"
- icon_panel = "thin_vendor"
- products = list(/obj/item/seeds/aloe =3,
- /obj/item/seeds/ambrosia = 3,
- /obj/item/seeds/apple = 3,
- /obj/item/seeds/banana = 3,
- /obj/item/seeds/berry = 3,
- /obj/item/seeds/cabbage = 3,
- /obj/item/seeds/carrot = 3,
- /obj/item/seeds/cherry = 3,
- /obj/item/seeds/chanter = 3,
- /obj/item/seeds/chili = 3,
- /obj/item/seeds/cocoapod = 3,
- /obj/item/seeds/coffee = 3,
- /obj/item/seeds/comfrey =3,
- /obj/item/seeds/corn = 3,
- /obj/item/seeds/cotton = 3,
- /obj/item/seeds/nymph =3,
- /obj/item/seeds/eggplant = 3,
- /obj/item/seeds/garlic = 3,
- /obj/item/seeds/grape = 3,
- /obj/item/seeds/grass = 3,
- /obj/item/seeds/lemon = 3,
- /obj/item/seeds/lime = 3,
- /obj/item/seeds/mint = 3,
- /obj/item/seeds/onion = 3,
- /obj/item/seeds/orange = 3,
- /obj/item/seeds/peanuts = 3,
- /obj/item/seeds/pineapple = 3,
- /obj/item/seeds/poppy = 3,
- /obj/item/seeds/potato = 3,
- /obj/item/seeds/pumpkin = 3,
- /obj/item/seeds/replicapod = 3,
- /obj/item/seeds/wheat/rice = 3,
- /obj/item/seeds/soya = 3,
- /obj/item/seeds/sugarcane = 3,
- /obj/item/seeds/sunflower = 3,
- /obj/item/seeds/tea = 3,
- /obj/item/seeds/tobacco = 3,
- /obj/item/seeds/tomato = 3,
- /obj/item/seeds/tower = 3,
- /obj/item/seeds/watermelon = 3,
- /obj/item/seeds/wheat = 3,
- /obj/item/seeds/whitebeet = 3)
- contraband = list(/obj/item/seeds/cannabis = 3,
- /obj/item/seeds/amanita = 2,
- /obj/item/seeds/fungus = 3,
- /obj/item/seeds/glowshroom = 2,
- /obj/item/seeds/liberty = 2,
- /obj/item/seeds/nettle = 2,
- /obj/item/seeds/plump = 2,
- /obj/item/seeds/reishi = 2,
- /obj/item/seeds/starthistle = 2,
- /obj/item/seeds/random = 2)
- premium = list(/obj/item/reagent_containers/spray/waterflower = 1)
- refill_canister = /obj/item/vending_refill/hydroseeds
-
-/obj/machinery/vending/hydroseeds/syndicate_druglab
- products = list(/obj/item/seeds/ambrosia/deus = 2,
- /obj/item/seeds/cannabis = 2,
- /obj/item/seeds/coffee = 3,
- /obj/item/seeds/liberty = 2,
- /obj/item/seeds/cannabis/rainbow = 1,
- /obj/item/seeds/reishi = 2,
- /obj/item/seeds/tobacco = 1)
- contraband = list()
- premium = list()
- refill_canister = null
-
-/obj/machinery/vending/magivend
- name = "\improper MagiVend"
- desc = "A magic vending machine."
- icon_state = "MagiVend"
- slogan_list = list("Sling spells the proper way with MagiVend!","Be your own Houdini! Use MagiVend!")
- vend_delay = 15
- vend_reply = "Have an enchanted evening!"
- ads_list = list("FJKLFJSD","AJKFLBJAKL","1234 LOONIES LOL!",">MFW","Kill them fuckers!","GET DAT FUKKEN DISK","HONK!","EI NATH","Destroy the station!","Admin conspiracies since forever!","Space-time bending hardware!")
- products = list(/obj/item/clothing/head/wizard = 1,
- /obj/item/clothing/suit/wizrobe = 1,
- /obj/item/clothing/head/wizard/red = 1,
- /obj/item/clothing/suit/wizrobe/red = 1,
- /obj/item/clothing/shoes/sandal = 1,
- /obj/item/clothing/suit/wizrobe/clown = 1,
- /obj/item/clothing/head/wizard/clown = 1,
- /obj/item/clothing/mask/gas/clownwiz = 1,
- /obj/item/clothing/shoes/clown_shoes/magical = 1,
- /obj/item/clothing/suit/wizrobe/mime = 1,
- /obj/item/clothing/head/wizard/mime = 1,
- /obj/item/clothing/mask/gas/mime/wizard = 1,
- /obj/item/clothing/shoes/sandal/marisa = 1,
- /obj/item/twohanded/staff = 2)
- contraband = list(/obj/item/reagent_containers/glass/bottle/wizarditis = 1)
- armor = list(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 50)
- resistance_flags = FIRE_PROOF
-
-/obj/machinery/vending/autodrobe
- name = "\improper AutoDrobe"
- desc = "A vending machine for costumes."
- icon_state = "theater"
- icon_lightmask = "theater"
- icon_deny = "theater_deny"
- slogan_list = list("Dress for success!","Suited and booted!","It's show time!","Why leave style up to fate? Use AutoDrobe!")
- vend_delay = 15
- vend_reply = "Thank you for using AutoDrobe!"
- products = list(/obj/item/clothing/suit/chickensuit = 1,
- /obj/item/clothing/head/chicken = 1,
- /obj/item/clothing/under/costume/gladiator = 1,
- /obj/item/clothing/head/helmet/gladiator = 1,
- /obj/item/clothing/under/misc/gimmick/rank/captain/suit = 1,
- /obj/item/clothing/head/flatcap = 1,
- /obj/item/clothing/suit/storage/labcoat/mad = 1,
- /obj/item/clothing/glasses/gglasses = 1,
- /obj/item/clothing/shoes/jackboots = 1,
- /obj/item/clothing/under/dress/schoolgirl = 1,
- /obj/item/clothing/under/dress/blackskirt = 1,
- /obj/item/clothing/suit/toggle/owlwings = 1,
- /obj/item/clothing/under/costume/owl = 1,
- /obj/item/clothing/mask/gas/owl_mask = 1,
- /obj/item/clothing/suit/toggle/owlwings/griffinwings = 1,
- /obj/item/clothing/under/costume/griffin = 1,
- /obj/item/clothing/shoes/griffin = 1,
- /obj/item/clothing/head/griffin = 1,
- /obj/item/clothing/accessory/waistcoat = 1,
- /obj/item/clothing/under/suit/black = 1,
- /obj/item/clothing/head/that =1,
- /obj/item/clothing/under/costume/kilt = 1,
- /obj/item/clothing/accessory/waistcoat = 1,
- /obj/item/clothing/glasses/monocle =1,
- /obj/item/clothing/head/bowlerhat = 1,
- /obj/item/cane = 1,
- /obj/item/clothing/under/misc/sl_suit = 1,
- /obj/item/clothing/mask/fakemoustache = 1,
- /obj/item/clothing/suit/bio_suit/plaguedoctorsuit = 1,
- /obj/item/clothing/head/plaguedoctorhat = 1,
- /obj/item/clothing/mask/gas/plaguedoctor = 1,
- /obj/item/clothing/suit/apron = 1,
- /obj/item/clothing/under/misc/waiter = 1,
- /obj/item/clothing/suit/jacket/miljacket = 1,
- /obj/item/clothing/suit/jacket/miljacket/white = 1,
- /obj/item/clothing/suit/jacket/miljacket/desert = 1,
- /obj/item/clothing/suit/jacket/miljacket/navy = 1,
- /obj/item/clothing/under/costume/pirate = 1,
- /obj/item/clothing/suit/pirate_brown = 1,
- /obj/item/clothing/suit/pirate_black =1,
- /obj/item/clothing/under/costume/pirate_rags =1,
- /obj/item/clothing/head/pirate = 1,
- /obj/item/clothing/head/bandana = 1,
- /obj/item/clothing/head/bandana = 1,
- /obj/item/clothing/under/costume/soviet = 1,
- /obj/item/clothing/head/ushanka = 1,
- /obj/item/clothing/suit/imperium_monk = 1,
- /obj/item/clothing/mask/gas/cyborg = 1,
- /obj/item/clothing/suit/holidaypriest = 1,
- /obj/item/clothing/head/wizard/marisa/fake = 1,
- /obj/item/clothing/suit/wizrobe/marisa/fake = 1,
- /obj/item/clothing/under/dress/sundress = 1,
- /obj/item/clothing/head/witchwig = 1,
- /obj/item/twohanded/staff/broom = 1,
- /obj/item/clothing/suit/wizrobe/fake = 1,
- /obj/item/clothing/head/wizard/fake = 1,
- /obj/item/twohanded/staff = 3,
- /obj/item/clothing/mask/gas/clown_hat/sexy = 1,
- /obj/item/clothing/under/rank/civilian/clown/sexy = 1,
- /obj/item/clothing/mask/gas/sexymime = 1,
- /obj/item/clothing/under/rank/civilian/mime/sexy = 1,
- /obj/item/clothing/mask/face/bat = 1,
- /obj/item/clothing/mask/face/bee = 1,
- /obj/item/clothing/mask/face/bear = 1,
- /obj/item/clothing/mask/face/raven = 1,
- /obj/item/clothing/mask/face/jackal = 1,
- /obj/item/clothing/mask/face/fox = 1,
- /obj/item/clothing/mask/face/tribal = 1,
- /obj/item/clothing/mask/face/rat = 1,
- /obj/item/clothing/suit/apron/overalls = 1,
- /obj/item/clothing/head/rabbitears =1,
- /obj/item/clothing/head/sombrero = 1,
- /obj/item/clothing/suit/poncho = 1,
- /obj/item/clothing/suit/poncho/green = 1,
- /obj/item/clothing/suit/poncho/red = 1,
- /obj/item/clothing/accessory/blue = 1,
- /obj/item/clothing/accessory/red = 1,
- /obj/item/clothing/accessory/black = 1,
- /obj/item/clothing/accessory/horrible = 1,
- /obj/item/clothing/under/costume/maid = 1,
- /obj/item/clothing/under/costume/janimaid = 1,
- /obj/item/clothing/under/costume/jester = 1,
- /obj/item/clothing/head/jester = 1,
- /obj/item/clothing/under/pants/camo = 1,
- /obj/item/clothing/mask/bandana = 1,
- /obj/item/clothing/mask/bandana/black = 1,
- /obj/item/clothing/shoes/singery = 1,
- /obj/item/clothing/under/costume/singery = 1,
- /obj/item/clothing/shoes/singerb = 1,
- /obj/item/clothing/under/costume/singerb = 1,
- /obj/item/clothing/suit/hooded/carp_costume = 1,
- /obj/item/clothing/suit/hooded/bee_costume = 1,
- /obj/item/clothing/suit/snowman = 1,
- /obj/item/clothing/head/snowman = 1,
- /obj/item/clothing/head/cueball = 1,
- /obj/item/clothing/under/misc/scratch = 1,
- /obj/item/clothing/under/dress/victdress = 1,
- /obj/item/clothing/under/dress/victdress/red = 1,
- /obj/item/clothing/suit/victcoat = 1,
- /obj/item/clothing/suit/victcoat/red = 1,
- /obj/item/clothing/under/suit/victsuit = 1,
- /obj/item/clothing/under/suit/victsuit/redblk = 1,
- /obj/item/clothing/under/suit/victsuit/red = 1,
- /obj/item/clothing/suit/tailcoat = 1,
- /obj/item/clothing/under/costume/tourist_suit = 1,
- /obj/item/clothing/suit/draculacoat = 1,
- /obj/item/clothing/head/zepelli = 1,
- /obj/item/clothing/under/misc/redhawaiianshirt = 1,
- /obj/item/clothing/under/misc/pinkhawaiianshirt = 1,
- /obj/item/clothing/under/misc/bluehawaiianshirt = 1,
- /obj/item/clothing/under/misc/orangehawaiianshirt = 1)
- contraband = list(/obj/item/clothing/suit/judgerobe = 1,
- /obj/item/clothing/head/powdered_wig = 1,
- /obj/item/gun/magic/wand = 1,
- /obj/item/clothing/mask/balaclava=1,
- /obj/item/clothing/mask/horsehead = 2)
- premium = list(/obj/item/clothing/suit/hgpirate = 1,
- /obj/item/clothing/head/hgpiratecap = 1,
- /obj/item/clothing/head/helmet/roman/fake = 1,
- /obj/item/clothing/head/helmet/roman/legionaire/fake = 1,
- /obj/item/clothing/under/costume/roman = 1,
- /obj/item/clothing/shoes/roman = 1,
- /obj/item/shield/riot/roman/fake = 1,
- /obj/item/clothing/under/costume/cuban_suit = 1,
- /obj/item/clothing/head/cuban_hat = 1)
- refill_canister = /obj/item/vending_refill/autodrobe
-
-/obj/machinery/vending/dinnerware
- name = "\improper Plasteel Chef's Dinnerware Vendor"
- desc = "A kitchen and restaurant equipment vendor."
- ads_list = list("Mm, food stuffs!","Food and food accessories.","Get your plates!","You like forks?","I like forks.","Woo, utensils.","You don't really need these...")
- icon_state = "dinnerware"
- icon_lightmask = "dinnerware"
- products = list(/obj/item/storage/bag/tray = 8,
- /obj/item/kitchen/utensil/fork = 6,
- /obj/item/trash/plate = 20,
- /obj/item/trash/bowl = 20,
- /obj/item/kitchen/knife = 3,
- /obj/item/kitchen/rollingpin = 2,
- /obj/item/kitchen/sushimat = 3,
- /obj/item/reagent_containers/food/drinks/drinkingglass = 8,
- /obj/item/clothing/suit/chef/classic = 2,
- /obj/item/storage/belt/chef = 2,
- /obj/item/reagent_containers/food/condiment/pack/ketchup = 5,
- /obj/item/reagent_containers/food/condiment/pack/hotsauce = 5,
- /obj/item/reagent_containers/food/condiment/saltshaker =5,
- /obj/item/reagent_containers/food/condiment/peppermill =5,
- /obj/item/whetstone = 2,
- /obj/item/mixing_bowl = 10,
- /obj/item/kitchen/mould/bear = 1, /obj/item/kitchen/mould/worm = 1,
- /obj/item/kitchen/mould/bean = 1, /obj/item/kitchen/mould/ball = 1,
- /obj/item/kitchen/mould/cane = 1, /obj/item/kitchen/mould/cash = 1,
- /obj/item/kitchen/mould/coin = 1, /obj/item/kitchen/mould/loli = 1,
- /obj/item/kitchen/cutter = 2)
- contraband = list(/obj/item/kitchen/rollingpin = 2, /obj/item/kitchen/knife/butcher = 2)
- refill_canister = /obj/item/vending_refill/dinnerware
-
-/obj/machinery/vending/sovietsoda
- name = "\improper BODA"
- desc = "Old sweet water vending machine."
- icon_state = "sovietsoda"
- icon_lightmask = "sovietsoda"
- ads_list = list("For Tsar and Country.","Have you fulfilled your nutrition quota today?","Very nice!","We are simple people, for this is all we eat.","If there is a person, there is a problem. If there is no person, then there is no problem.")
- products = list(/obj/item/reagent_containers/food/drinks/drinkingglass/soda = 30)
- contraband = list(/obj/item/reagent_containers/food/drinks/drinkingglass/cola = 20)
- resistance_flags = FIRE_PROOF
- refill_canister = /obj/item/vending_refill/sovietsoda
-
-/obj/machinery/vending/tool
- name = "\improper YouTool"
- desc = "Tools for tools."
- icon_state = "tool"
- icon_deny = "tool_deny"
- icon_lightmask = "tool"
- icon_panel = "generic"
- armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 70)
- resistance_flags = FIRE_PROOF
- products = list(/obj/item/stack/cable_coil/random = 10,/obj/item/crowbar = 5,/obj/item/weldingtool = 3,/obj/item/wirecutters = 5,
- /obj/item/wrench = 5,/obj/item/analyzer = 5,/obj/item/t_scanner = 5,/obj/item/screwdriver = 5)
- contraband = list(/obj/item/weldingtool/hugetank = 2,/obj/item/clothing/gloves/color/fyellow = 2)
- premium = list(/obj/item/clothing/gloves/color/yellow = 1)
- refill_canister = /obj/item/vending_refill/youtool
-
-/obj/machinery/vending/engivend
- name = "\improper Engi-Vend"
- desc = "Spare tool vending. What? Did you expect some witty description?"
- icon_state = "engivend"
- icon_deny = "engivend_deny"
- icon_panel = "generic"
- req_one_access_txt = "11;24" // Engineers and atmos techs can use this
- products = list(/obj/item/clothing/glasses/meson/engine = 2,/obj/item/multitool = 4, /obj/item/geiger_counter = 5, /obj/item/airlock_electronics = 10,/obj/item/firelock_electronics = 10,/obj/item/firealarm_electronics = 10,/obj/item/apc_electronics = 10,/obj/item/airalarm_electronics = 10,/obj/item/stock_parts/cell/high = 10,/obj/item/camera_assembly = 10)
- contraband = list(/obj/item/stock_parts/cell/potato = 3)
- premium = list(/obj/item/storage/belt/utility = 3)
- refill_canister = /obj/item/vending_refill/engivend
-
-/obj/machinery/vending/engineering
- name = "\improper Robco Tool Maker"
- desc = "Everything you need for do-it-yourself station repair."
- icon_state = "engi"
- icon_deny = "engi_deny"
- req_access_txt = "11"
- products = list(/obj/item/clothing/under/rank/engineering/chief_engineer = 4,/obj/item/clothing/under/rank/engineering/engineer = 4,/obj/item/clothing/shoes/workboots = 4,/obj/item/clothing/head/hardhat = 4,
- /obj/item/storage/belt/utility = 4,/obj/item/clothing/glasses/meson/engine = 4,/obj/item/clothing/gloves/color/yellow = 4, /obj/item/screwdriver = 12,
- /obj/item/crowbar = 12,/obj/item/wirecutters = 12,/obj/item/multitool = 12,/obj/item/wrench = 12,/obj/item/t_scanner = 12,
- /obj/item/stack/cable_coil = 8, /obj/item/stock_parts/cell = 8, /obj/item/weldingtool = 8,/obj/item/clothing/head/welding = 8,
- /obj/item/light/tube = 10,/obj/item/clothing/suit/fire = 4, /obj/item/stock_parts/scanning_module = 5,/obj/item/stock_parts/micro_laser = 5,
- /obj/item/stock_parts/matter_bin = 5,/obj/item/stock_parts/manipulator = 5)
- refill_canister = /obj/item/vending_refill/engineering
-
-/obj/machinery/vending/robotics
- name = "\improper Robotech Deluxe"
- desc = "All the tools you need to create your own robot army."
- icon_state = "robotics"
- icon_deny = "robotics_deny"
- icon_lightmask = "robotics"
- req_access_txt = "29"
- products = list(/obj/item/clothing/suit/storage/labcoat = 4,/obj/item/clothing/under/rank/rnd/roboticist = 4,/obj/item/stack/cable_coil = 4,/obj/item/flash = 4,
- /obj/item/stock_parts/cell/high = 12, /obj/item/assembly/prox_sensor = 3,/obj/item/assembly/signaler = 3,/obj/item/healthanalyzer = 3,
- /obj/item/scalpel = 2,/obj/item/circular_saw = 2,/obj/item/tank/internals/anesthetic = 2,/obj/item/clothing/mask/breath/medical = 5,
- /obj/item/screwdriver = 5,/obj/item/crowbar = 5)
- refill_canister = /obj/item/vending_refill/robotics
-
-/obj/machinery/vending/sustenance
- name = "\improper Sustenance Vendor"
- desc = "A vending machine which vends food, as required by section 47-C of the NT's Prisoner Ethical Treatment Agreement."
- slogan_list = list("Enjoy your meal.","Enough calories to support strenuous labor.")
- ads_list = list("The healthiest!","Award-winning chocolate bars!","Mmm! So good!","Oh my god it's so juicy!","Have a snack.","Snacks are good for you!","Have some more Getmore!","Best quality snacks straight from mars.","We love chocolate!","Try our new jerky!")
- icon_state = "sustenance"
- icon_lightmask = "nutri"
- icon_off = "nutri"
- icon_panel = "thin_vendor"
- products = list(/obj/item/reagent_containers/food/snacks/tofu = 24,
- /obj/item/reagent_containers/food/drinks/ice = 12,
- /obj/item/reagent_containers/food/snacks/candy/candy_corn = 6)
- contraband = list(/obj/item/kitchen/knife = 6,
- /obj/item/reagent_containers/food/drinks/coffee = 12,
- /obj/item/tank/internals/emergency_oxygen = 6,
- /obj/item/clothing/mask/breath = 6)
- refill_canister = /obj/item/vending_refill/sustenance
-
-/obj/machinery/vending/hatdispenser
- name = "\improper Hatlord 9000"
- desc = "It doesn't seem the slightest bit unusual. This frustrates you immensely."
- icon_state = "hats"
- icon_lightmask = "hats"
- icon_panel = "syndi"
- icon_broken = "wide_vendor"
- ads_list = list("Warning, not all hats are dog/monkey compatible. Apply forcefully with care.","Apply directly to the forehead.","Who doesn't love spending cash on hats?!","From the people that brought you collectable hat crates, Hatlord!")
- products = list(/obj/item/clothing/head/bowlerhat = 10,
- /obj/item/clothing/head/beaverhat = 10,
- /obj/item/clothing/head/boaterhat = 10,
- /obj/item/clothing/head/fedora = 10,
- /obj/item/clothing/head/fez = 10,
- /obj/item/clothing/head/beret = 10)
- contraband = list(/obj/item/clothing/head/bearpelt = 5)
- premium = list(/obj/item/clothing/head/soft/rainbow = 1)
- refill_canister = /obj/item/vending_refill/hatdispenser
-
-/obj/machinery/vending/suitdispenser
- name = "\improper Suitlord 9000"
- desc = "You wonder for a moment why all of your shirts and pants come conjoined. This hurts your head and you stop thinking about it."
- icon_state = "suits"
- icon_lightmask = "suits"
- icon_panel = "syndi"
- icon_broken = "wide_vendor"
- ads_list = list("Pre-Ironed, Pre-Washed, Pre-Wor-*BZZT*","Blood of your enemies washes right out!","Who are YOU wearing?","Look dapper! Look like an idiot!","Dont carry your size? How about you shave off some pounds you fat lazy- *BZZT*")
- products = list(/obj/item/clothing/under/color/black = 10, /obj/item/clothing/under/color/grey = 10, /obj/item/clothing/under/color/white = 10, /obj/item/clothing/under/color/darkred = 10, /obj/item/clothing/under/color/red = 10, /obj/item/clothing/under/color/lightred = 10,
- /obj/item/clothing/under/color/brown = 10, /obj/item/clothing/under/color/orange = 10, /obj/item/clothing/under/color/lightbrown = 10, /obj/item/clothing/under/color/yellow = 10, /obj/item/clothing/under/color/yellowgreen = 10, /obj/item/clothing/under/color/lightgreen = 10,
- /obj/item/clothing/under/color/green = 10, /obj/item/clothing/under/color/aqua = 10, /obj/item/clothing/under/color/darkblue = 10, /obj/item/clothing/under/color/blue = 10, /obj/item/clothing/under/color/lightblue = 10, /obj/item/clothing/under/color/purple = 10,
- /obj/item/clothing/under/color/lightpurple = 10, /obj/item/clothing/under/color/pink = 10)
- contraband = list(/obj/item/clothing/under/syndicate/tacticool = 5,/obj/item/clothing/under/color/orange/prison = 5)
- premium = list(/obj/item/clothing/under/color/rainbow = 1)
- refill_canister = /obj/item/vending_refill/suitdispenser
-
-/obj/machinery/vending/shoedispenser
- name = "\improper Shoelord 9000"
- desc = "Wow, hatlord looked fancy, suitlord looked streamlined, and this is just normal. The guy who designed these must be an idiot."
- icon_state = "shoes"
- icon_lightmask = "shoes"
- icon_panel = "syndi"
- icon_broken = "wide_vendor"
- ads_list = list("Put your foot down!","One size fits all!","IM WALKING ON SUNSHINE!","No hobbits allowed.","NO PLEASE WILLY, DONT HURT ME- *BZZT*")
- products = list(/obj/item/clothing/shoes/black = 10,/obj/item/clothing/shoes/brown = 10,/obj/item/clothing/shoes/blue = 10,/obj/item/clothing/shoes/green = 10,/obj/item/clothing/shoes/yellow = 10,/obj/item/clothing/shoes/purple = 10,/obj/item/clothing/shoes/red = 10,/obj/item/clothing/shoes/white = 10,/obj/item/clothing/shoes/sandal=10)
- contraband = list(/obj/item/clothing/shoes/orange = 5)
- premium = list(/obj/item/clothing/shoes/rainbow = 1)
- refill_canister = /obj/item/vending_refill/shoedispenser
-
-/obj/machinery/vending/syndicigs
- name = "\improper Suspicious Cigarette Machine"
- desc = "Smoke 'em if you've got 'em."
- slogan_list = list("Space cigs taste good like a cigarette should.","I'd rather toolbox than switch.","Smoke!","Don't believe the reports - smoke today!")
- ads_list = list("Probably not bad for you!","Don't believe the scientists!","It's good for you!","Don't quit, buy more!","Smoke!","Nicotine heaven.","Best cigarettes since 2150.","Award-winning cigs.")
- vend_delay = 34
- icon_state = "cigs"
- icon_lightmask = "cigs"
- products = list(/obj/item/storage/fancy/cigarettes/syndicate = 10,/obj/item/lighter/random = 5)
-
-/obj/machinery/vending/syndisnack
- name = "\improper Getmore Chocolate Corp"
- desc = "A modified snack machine courtesy of the Getmore Chocolate Corporation, based out of Mars"
- slogan_list = list("Try our new nougat bar!","Twice the calories for half the price!")
- ads_list = list("The healthiest!","Award-winning chocolate bars!","Mmm! So good!","Oh my god it's so juicy!","Have a snack.","Snacks are good for you!","Have some more Getmore!","Best quality snacks straight from mars.","We love chocolate!","Try our new jerky!")
- icon_state = "snack"
- icon_lightmask = "nutri"
- icon_off = "nutri"
- icon_panel = "thin_vendor"
- products = list(/obj/item/reagent_containers/food/snacks/chips =6,/obj/item/reagent_containers/food/snacks/sosjerky = 6,
- /obj/item/reagent_containers/food/snacks/syndicake = 6, /obj/item/reagent_containers/food/snacks/cheesiehonkers = 6)
-
-//don't forget to change the refill size if you change the machine's contents!
-/obj/machinery/vending/clothing
- name = "\improper ClothesMate" //renamed to make the slogan rhyme
- desc = "A vending machine for clothing."
- icon_state = "clothes"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- slogan_list = list("Dress for success!","Prepare to look swagalicious!","Look at all this free swag!","Why leave style up to fate? Use the ClothesMate!")
- vend_delay = 15
- vend_reply = "Thank you for using the ClothesMate!"
- products = list(/obj/item/clothing/head/that = 2,
- /obj/item/clothing/head/fedora = 1,
- /obj/item/clothing/glasses/monocle = 1,
- /obj/item/clothing/under/suit/navy = 2,
- /obj/item/clothing/under/costume/kilt = 1,
- /obj/item/clothing/under/misc/overalls = 1,
- /obj/item/clothing/under/suit/really_black = 2,
- /obj/item/clothing/suit/storage/lawyer/blackjacket = 2,
- /obj/item/clothing/under/pants/jeans = 3,
- /obj/item/clothing/under/pants/classicjeans = 2,
- /obj/item/clothing/under/pants/camo = 1,
- /obj/item/clothing/under/pants/blackjeans = 2,
- /obj/item/clothing/under/pants/khaki = 2,
- /obj/item/clothing/under/pants/white = 2,
- /obj/item/clothing/under/pants/red = 1,
- /obj/item/clothing/under/pants/black = 2,
- /obj/item/clothing/under/pants/tan = 2,
- /obj/item/clothing/under/pants/blue = 1,
- /obj/item/clothing/under/pants/track = 1,
- /obj/item/clothing/suit/jacket/miljacket = 1,
- /obj/item/clothing/head/beanie = 3,
- /obj/item/clothing/head/beanie/black = 3,
- /obj/item/clothing/head/beanie/red = 3,
- /obj/item/clothing/head/beanie/green = 3,
- /obj/item/clothing/head/beanie/darkblue = 3,
- /obj/item/clothing/head/beanie/purple = 3,
- /obj/item/clothing/head/beanie/yellow = 3,
- /obj/item/clothing/head/beanie/orange = 3,
- /obj/item/clothing/head/beanie/cyan = 3,
- /obj/item/clothing/head/beanie/christmas = 3,
- /obj/item/clothing/head/beanie/striped = 3,
- /obj/item/clothing/head/beanie/stripedred = 3,
- /obj/item/clothing/head/beanie/stripedblue = 3,
- /obj/item/clothing/head/beanie/stripedgreen = 3,
- /obj/item/clothing/head/beanie/rasta = 3,
- /obj/item/clothing/accessory/scarf/red = 1,
- /obj/item/clothing/accessory/scarf/green = 1,
- /obj/item/clothing/accessory/scarf/darkblue = 1,
- /obj/item/clothing/accessory/scarf/purple = 1,
- /obj/item/clothing/accessory/scarf/yellow = 1,
- /obj/item/clothing/accessory/scarf/orange = 1,
- /obj/item/clothing/accessory/scarf/lightblue = 1,
- /obj/item/clothing/accessory/scarf/white = 1,
- /obj/item/clothing/accessory/scarf/black = 1,
- /obj/item/clothing/accessory/scarf/zebra = 1,
- /obj/item/clothing/accessory/scarf/christmas = 1,
- /obj/item/clothing/accessory/stripedredscarf = 1,
- /obj/item/clothing/accessory/stripedbluescarf = 1,
- /obj/item/clothing/accessory/stripedgreenscarf = 1,
- /obj/item/clothing/accessory/waistcoat = 1,
- /obj/item/clothing/under/dress/sundress = 2,
- /obj/item/clothing/under/dress/stripeddress = 1,
- /obj/item/clothing/under/dress/sailordress = 1,
- /obj/item/clothing/under/dress/redeveninggown = 1,
- /obj/item/clothing/under/dress/blacktango = 1,
- /obj/item/clothing/suit/jacket = 3,
- /obj/item/clothing/suit/jacket/motojacket = 3,
- /obj/item/clothing/glasses/regular = 2,
- /obj/item/clothing/glasses/sunglasses_fake = 2,
- /obj/item/clothing/head/sombrero = 1,
- /obj/item/clothing/suit/poncho = 1,
- /obj/item/clothing/suit/ianshirt = 1,
- /obj/item/clothing/shoes/laceup = 2,
- /obj/item/clothing/shoes/black = 4,
- /obj/item/clothing/shoes/sandal = 1,
- /obj/item/clothing/gloves/fingerless = 2,
- /obj/item/storage/belt/fannypack = 1,
- /obj/item/storage/belt/fannypack/blue = 1,
- /obj/item/storage/belt/fannypack/red = 1,
- /obj/item/clothing/suit/mantle = 2,
- /obj/item/clothing/suit/mantle/old = 1,
- /obj/item/clothing/suit/mantle/regal = 2)
-
- contraband = list(/obj/item/clothing/under/syndicate/tacticool = 1,
- /obj/item/clothing/mask/balaclava = 1,
- /obj/item/clothing/head/ushanka = 1,
- /obj/item/clothing/under/costume/soviet = 1,
- /obj/item/storage/belt/fannypack/black = 1)
-
- premium = list(/obj/item/clothing/under/suit/checkered = 1,
- /obj/item/clothing/head/mailman = 1,
- /obj/item/clothing/under/misc/mailman = 1,
- /obj/item/clothing/suit/jacket/leather = 1,
- /obj/item/clothing/under/pants/mustangjeans = 1)
-
- refill_canister = /obj/item/vending_refill/clothing
-
-/obj/machinery/vending/artvend
- name = "\improper ArtVend"
- desc = "A vending machine for art supplies."
- slogan_list = list("Stop by for all your artistic needs!","Color the floors with crayons, not blood!","Don't be a starving artist, use ArtVend. ","Don't fart, do art!")
- ads_list = list("Just like Kindergarten!","Now with 1000% more vibrant colors!","Screwing with the janitor was never so easy!","Creativity is at the heart of every spessman.")
- vend_delay = 15
- icon_state = "artvend"
- icon_lightmask = "artvend"
- icon_panel = "screen_vendor"
- products = list(/obj/item/stack/cable_coil/random = 10,/obj/item/camera = 4,/obj/item/camera_film = 6,
- /obj/item/storage/photo_album = 2,/obj/item/stack/wrapping_paper = 4,/obj/item/stack/tape_roll = 5,/obj/item/stack/packageWrap = 4,
- /obj/item/storage/fancy/crayons = 4,/obj/item/hand_labeler = 4,/obj/item/paper = 10,
- /obj/item/c_tube = 10,/obj/item/pen = 5,/obj/item/pen/blue = 5,
- /obj/item/pen/red = 5)
- contraband = list(/obj/item/toy/crayon/mime = 1,/obj/item/toy/crayon/rainbow = 1)
- premium = list(/obj/item/poster/random_contraband = 5)
-
-/obj/machinery/vending/crittercare
- name = "\improper CritterCare"
- desc = "A vending machine for pet supplies."
- slogan_list = list("Stop by for all your animal's needs!","Cuddly pets deserve a stylish collar!","Pets in space, what could be more adorable?","Freshest fish eggs in the system!","Rocks are the perfect pet, buy one today!")
- ads_list = list("House-training costs extra!","Now with 1000% more cat hair!","Allergies are a sign of weakness!","Dogs are man's best friend. Remember that Vulpkanin!"," Heat lamps for Unathi!"," Vox-y want a cracker?")
- vend_delay = 15
- icon_state = "crittercare"
- icon_lightmask = "crittercare"
- icon_panel = "drobe"
- products = list(/obj/item/petcollar = 5, /obj/item/storage/firstaid/aquatic_kit/full = 5, /obj/item/fish_eggs/goldfish = 5,
- /obj/item/fish_eggs/clownfish = 5, /obj/item/fish_eggs/shark = 5, /obj/item/fish_eggs/feederfish = 10,
- /obj/item/fish_eggs/salmon = 5, /obj/item/fish_eggs/catfish = 5, /obj/item/fish_eggs/glofish = 5,
- /obj/item/fish_eggs/electric_eel = 5, /obj/item/fish_eggs/shrimp = 10, /obj/item/toy/pet_rock = 5,
- )
- prices = list(/obj/item/petcollar = 50, /obj/item/storage/firstaid/aquatic_kit/full = 60, /obj/item/fish_eggs/goldfish = 10,
- /obj/item/fish_eggs/clownfish = 10, /obj/item/fish_eggs/shark = 10, /obj/item/fish_eggs/feederfish = 5,
- /obj/item/fish_eggs/salmon = 10, /obj/item/fish_eggs/catfish = 10, /obj/item/fish_eggs/glofish = 10,
- /obj/item/fish_eggs/electric_eel = 10, /obj/item/fish_eggs/shrimp = 5, /obj/item/toy/pet_rock = 100,
- )
- contraband = list(/obj/item/fish_eggs/babycarp = 5)
- premium = list(/obj/item/toy/pet_rock/fred = 1, /obj/item/toy/pet_rock/roxie = 1)
- refill_canister = /obj/item/vending_refill/crittercare
-
-/obj/machinery/vending/crittercare/free
- prices = list()
-
-//Departmental clothing vendors
-
-/obj/machinery/vending/secdrobe
- name = "\improper SecDrobe"
- desc = "A vending machine for security and security-related clothing!"
- icon_state = "secdrobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- ads_list = list("Beat perps in style!", "It's red so you can't see the blood!", "You have the right to be fashionable!", "Now you can be the fashion police you always wanted to be!")
- vend_reply = "Thank you for using the SecDrobe!"
- products = list(/obj/item/clothing/under/rank/security/officer/corporate = 4,
- /obj/item/clothing/under/rank/security/officer/skirt/corporate = 4,
- /obj/item/clothing/under/rank/security/officer/dispatch = 4,
- /obj/item/clothing/under/rank/security/officer/skirt = 4,
- /obj/item/clothing/under/rank/security/officer = 4,
- /obj/item/clothing/under/rank/security/officer/uniform = 4,
- /obj/item/clothing/under/rank/security/formal = 4,
- /obj/item/clothing/under/rank/security/officer/fancy = 4,
- /obj/item/clothing/under/rank/security/officer/skirt/fancy = 4,
- /obj/item/clothing/head/soft/sec/corp = 4,
- /obj/item/clothing/head/officer = 4,
- /obj/item/clothing/head/beret/sec = 4,
- /obj/item/clothing/head/soft/sec = 4,
- /obj/item/clothing/head/drillsgt = 4,
- /obj/item/clothing/mask/bandana/red = 4,
- /obj/item/clothing/suit/jacket/secbomber = 2,
- /obj/item/clothing/suit/armor/secjacket = 4,
- /obj/item/clothing/suit/hooded/wintercoat/security = 4,
- /obj/item/clothing/shoes/jackboots = 4,
- /obj/item/clothing/shoes/jackboots/jacksandals = 4,
- /obj/item/clothing/shoes/laceup = 4,
- /obj/item/storage/backpack/duffel/security = 2,
- /obj/item/storage/backpack/security = 2,
- /obj/item/storage/backpack/satchel_sec = 2,
- /obj/item/clothing/gloves/color/black = 4,
- /obj/item/clothing/accessory/armband/sec = 6)
- premium = list(/obj/item/clothing/mask/balaclava = 1,
- /obj/item/clothing/mask/gas/sechailer/swat = 2)
- contraband = list(/obj/item/toy/figure/crew/hos = 1,
- /obj/item/toy/figure/crew/secofficer = 1)
- refill_canister = /obj/item/vending_refill/secdrobe
-
-/obj/machinery/vending/detdrobe
- name = "\improper DetDrobe"
- desc = "A machine for all your detective needs, as long as you only need clothes."
- icon_state = "detdrobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- ads_list = list("Apply your brilliant deductive methods in style!", "They already smell of cigarettes!")
- vend_reply = "Thank you for using the DetDrobe!"
- products = list(/obj/item/clothing/under/rank/security/detective = 2,
- /obj/item/clothing/suit/storage/det_suit = 2,
- /obj/item/clothing/suit/storage/det_suit/forensics/red = 1,
- /obj/item/clothing/suit/storage/det_suit/forensics/blue = 1,
- /obj/item/clothing/suit/armor/vest/det_suit = 1,
- /obj/item/clothing/head/det_hat = 2,
- /obj/item/clothing/glasses/sunglasses/noir = 2,
- /obj/item/clothing/accessory/waistcoat = 2,
- /obj/item/clothing/shoes/laceup = 2,
- /obj/item/clothing/shoes/brown = 2,
- /obj/item/clothing/shoes/jackboots = 2,
- /obj/item/clothing/head/fedora = 1,
- /obj/item/clothing/head/fedora/brownfedora = 1,
- /obj/item/clothing/head/fedora/whitefedora = 1,
- /obj/item/clothing/gloves/color/black = 2,
- /obj/item/clothing/gloves/color/latex = 2,
- /obj/item/reagent_containers/food/drinks/flask/detflask = 2,
- /obj/item/storage/fancy/cigarettes/dromedaryco = 5)
- contraband = list(/obj/item/toy/figure/crew/detective = 1)
- refill_canister = /obj/item/vending_refill/detdrobe
-
-/obj/machinery/vending/medidrobe
- name = "\improper MediDrobe"
- desc = "A vending machine rumoured to be capable of dispensing clothing for medical personnel."
- icon_state = "base_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "medidrobe"
- ads_list = list("Make those blood stains look fashionable!")
- vend_reply = "Thank you for using the MediDrobe!"
- products = list(/obj/item/clothing/under/rank/medical/doctor = 3,
- /obj/item/clothing/under/rank/medical/doctor/skirt = 3,
- /obj/item/clothing/under/rank/medical/scrubs = 3,
- /obj/item/clothing/under/rank/medical/scrubs/green = 3,
- /obj/item/clothing/under/rank/medical/scrubs/purple = 3,
- /obj/item/clothing/under/rank/medical/nurse = 3,
- /obj/item/clothing/under/rank/medical/gown = 3,
- /obj/item/clothing/head/beret/med = 3,
- /obj/item/clothing/head/surgery/blue = 3,
- /obj/item/clothing/head/surgery/green = 3,
- /obj/item/clothing/head/surgery/purple = 3,
- /obj/item/clothing/head/nursehat = 3,
- /obj/item/clothing/suit/hooded/wintercoat/medical = 3,
- /obj/item/clothing/suit/storage/fr_jacket = 3,
- /obj/item/clothing/suit/storage/labcoat = 3,
- /obj/item/clothing/accessory/armband/med = 3,
- /obj/item/clothing/accessory/armband/medgreen = 3,
- /obj/item/clothing/shoes/laceup = 3,
- /obj/item/clothing/shoes/white = 3,
- /obj/item/clothing/shoes/sandal/white = 3,
- /obj/item/storage/backpack/medic = 2,
- /obj/item/storage/backpack/satchel_med = 2,
- /obj/item/storage/backpack/duffel/medical = 2)
- premium = list(/obj/item/clothing/gloves/color/latex/nitrile = 3)
- contraband = list(/obj/item/toy/figure/crew/md = 1)
- refill_canister = /obj/item/vending_refill/medidrobe
-
-/obj/machinery/vending/virodrobe
- name = "\improper ViroDrobe"
- desc = "An unsterilized machine for dispending virology related clothing."
- icon_state = "base_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "virodrobe"
- ads_list = list("Viruses getting you down? Nothing a change of clothes can't fix!", "Upgrade to sterilized clothing today!")
- vend_reply = "Thank you for using the ViroDrobe!"
- products = list(/obj/item/clothing/under/rank/medical/virologist = 2,
- /obj/item/clothing/under/rank/medical/virologist/skirt = 2,
- /obj/item/clothing/head/beret/med = 2,
- /obj/item/clothing/suit/storage/labcoat/virologist = 2,
- /obj/item/clothing/accessory/armband/med = 2,
- /obj/item/clothing/mask/surgical = 2,
- /obj/item/clothing/shoes/laceup = 2,
- /obj/item/clothing/shoes/white = 2,
- /obj/item/clothing/shoes/sandal/white = 2,
- /obj/item/storage/backpack/virology = 2,
- /obj/item/storage/backpack/satchel_vir = 2,
- /obj/item/storage/backpack/duffel/virology = 2)
- contraband = list(/obj/item/toy/figure/crew/virologist = 1)
- refill_canister = /obj/item/vending_refill/virodrobe
-
-/obj/machinery/vending/chemdrobe
- name = "\improper ChemDrobe"
- desc = "A vending machine for dispensing chemistry related clothing."
- icon_state = "base_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "chemdrobe"
- ads_list = list("Our clothes are 0.5% more resistant to acid spills! Get yours now!")
- vend_reply = "Thank you for using the ChemDrobe!"
- products = list(/obj/item/clothing/under/rank/medical/chemist = 2,
- /obj/item/clothing/under/rank/medical/chemist/skirt = 2,
- /obj/item/clothing/head/beret/med = 2,
- /obj/item/clothing/suit/storage/labcoat/chemist = 2,
- /obj/item/clothing/accessory/armband/med = 2,
- /obj/item/clothing/mask/gas = 2,
- /obj/item/clothing/shoes/laceup = 2,
- /obj/item/clothing/shoes/white = 2,
- /obj/item/clothing/shoes/sandal/white = 2,
- /obj/item/storage/bag/chemistry = 2,
- /obj/item/storage/backpack/chemistry = 2,
- /obj/item/storage/backpack/satchel_chem = 2,
- /obj/item/storage/backpack/duffel/chemistry = 2)
- contraband = list(/obj/item/toy/figure/crew/chemist = 1)
- refill_canister = /obj/item/vending_refill/chemdrobe
-
-/obj/machinery/vending/genedrobe
- name = "\improper GeneDrobe"
- desc = "A machine for dispensing clothing related to genetics."
- icon_state = "base_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "genedrobe"
- ads_list = "Perfect for the mad scientist in you!"
- vend_reply = "Thank you for using the GeneDrobe!"
- products = list(/obj/item/clothing/under/rank/rnd/geneticist = 3,
- /obj/item/clothing/suit/storage/labcoat/genetics = 3,
- /obj/item/clothing/shoes/laceup = 3,
- /obj/item/clothing/shoes/white = 3,
- /obj/item/clothing/shoes/sandal/white = 3,
- /obj/item/storage/backpack/genetics = 2,
- /obj/item/storage/backpack/satchel_gen = 2,
- /obj/item/storage/backpack/duffel/genetics = 2,)
- contraband = list(/obj/item/toy/figure/crew/geneticist = 1)
- refill_canister = /obj/item/vending_refill/genedrobe
-
-/obj/machinery/vending/scidrobe
- name = "\improper SciDrobe"
- desc = "A simple vending machine suitable to dispense well tailored science clothing. Endorsed by Space Cubans."
- icon_state = "base_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "scidrobe"
- ads_list = list("Longing for the smell of plasma burnt flesh?", "Buy your science clothing now!", "Made with 10% Auxetics, so you don't have to worry about losing your arm!")
- vend_reply = "Thank you for using the SciDrobe!"
- products = list(/obj/item/clothing/under/rank/rnd/scientist = 6,
- /obj/item/clothing/under/rank/rnd/scientist/skirt = 3,
- /obj/item/clothing/suit/hooded/wintercoat/science = 3,
- /obj/item/clothing/suit/storage/labcoat/science = 3,
- /obj/item/clothing/head/beret/sci = 3,
- /obj/item/clothing/accessory/armband/science = 6,
- /obj/item/clothing/shoes/laceup = 3,
- /obj/item/clothing/shoes/white = 3,
- /obj/item/clothing/shoes/sandal/white = 3,
- /obj/item/storage/backpack/science = 2,
- /obj/item/storage/backpack/satchel_tox = 2,
- /obj/item/storage/backpack/duffel/science = 2,)
- contraband = list(/obj/item/toy/figure/crew/rd = 1,
- /obj/item/toy/figure/crew/scientist = 1)
- refill_canister = /obj/item/vending_refill/scidrobe
-
-/obj/machinery/vending/robodrobe
- name = "\improper RoboDrobe"
- desc = "A vending machine designed to dispense clothing known only to roboticists."
- icon_state = "base_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "robodrobe"
- ads_list = list("You turn me TRUE, use defines!","0110001101101100011011110111010001101000011001010111001101101000011001010111001001100101")
- vend_reply = "Thank you for using the RoboDrobe!"
- products = list(/obj/item/clothing/under/rank/rnd/roboticist = 3,
- /obj/item/clothing/under/rank/rnd/roboticist/skirt = 3,
- /obj/item/clothing/suit/storage/labcoat = 3,
- /obj/item/clothing/head/soft/black = 3,
- /obj/item/clothing/gloves/fingerless = 3,
- /obj/item/clothing/shoes/laceup = 3,
- /obj/item/clothing/shoes/white = 3,
- /obj/item/clothing/shoes/black = 3)
- contraband = list(/obj/item/toy/figure/crew/roboticist = 1)
- refill_canister = /obj/item/vending_refill/robodrobe
-
-/obj/machinery/vending/engidrobe
- name = "\improper EngiDrobe"
- desc = "A vending machine renowned for vending industrial grade clothing."
- icon_state = "yellow_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "engidrobe"
- ads_list = list("Guaranteed to protect your feet from industrial accidents!", "Afraid of radiation? Then wear yellow!")
- vend_reply = "Thank you for using the EngiDrobe!"
- products = list(/obj/item/clothing/under/rank/engineering/engineer = 6,
- /obj/item/clothing/under/rank/engineering/engineer/skirt = 3,
- /obj/item/clothing/suit/hooded/wintercoat/engineering = 3,
- /obj/item/clothing/suit/jacket/engibomber = 3,
- /obj/item/clothing/suit/storage/hazardvest = 3,
- /obj/item/clothing/head/beret/eng = 3,
- /obj/item/clothing/head/hardhat = 2,
- /obj/item/clothing/head/hardhat/orange = 2,
- /obj/item/clothing/head/hardhat/dblue = 2,
- /obj/item/clothing/accessory/armband/engine = 6,
- /obj/item/clothing/shoes/laceup = 3,
- /obj/item/clothing/shoes/workboots = 3,
- /obj/item/storage/backpack/industrial = 2,
- /obj/item/storage/backpack/satchel_eng = 2,
- /obj/item/storage/backpack/duffel/engineering = 2,
- /obj/item/storage/belt/utility = 2)
- premium = list(/obj/item/clothing/gloves/color/yellow = 2)
- contraband = list(/obj/item/toy/figure/crew/ce = 1,
- /obj/item/toy/figure/crew/engineer = 1)
- refill_canister = /obj/item/vending_refill/engidrobe
-
-/obj/machinery/vending/atmosdrobe
- name = "\improper AtmosDrobe"
- desc = "This relatively unknown vending machine delivers clothing for Atmospherics Technicians, an equally unknown job."
- icon_state = "yellow_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "atmosdrobe"
- ads_list = list("Guaranteed to protect your feet from atmospheric accidents!", "Get your inflammable clothing right here!")
- vend_reply = "Thank you for using the AtmosDrobe!"
- products = list(/obj/item/clothing/under/rank/engineering/atmospheric_technician = 6,
- /obj/item/clothing/under/rank/engineering/atmospheric_technician/skirt = 3,
- /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3,
- /obj/item/clothing/suit/jacket/atmosbomber = 3,
- /obj/item/clothing/suit/storage/hazardvest = 3,
- /obj/item/clothing/head/beret/atmos = 3,
- /obj/item/clothing/head/hardhat = 2,
- /obj/item/clothing/head/hardhat/red = 2,
- /obj/item/clothing/head/hardhat/orange = 2,
- /obj/item/clothing/head/hardhat/dblue = 2,
- /obj/item/clothing/gloves/color/black = 3,
- /obj/item/clothing/accessory/armband/engine = 3,
- /obj/item/clothing/shoes/laceup = 3,
- /obj/item/clothing/shoes/workboots = 3,
- /obj/item/storage/backpack/industrial/atmos = 2,
- /obj/item/storage/backpack/satchel_atmos = 2,
- /obj/item/storage/backpack/duffel/atmos = 2,
- /obj/item/storage/belt/utility = 2)
- contraband = list(/obj/item/toy/figure/crew/atmos = 1)
- refill_canister = /obj/item/vending_refill/atmosdrobe
-
-/obj/machinery/vending/cargodrobe
- name = "\improper CargoDrobe"
- desc = "A highly advanced vending machine for buying cargo related clothing for free... most of the time."
- icon_state = "yellow_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "cargodrobe"
- ads_list = list("Upgraded Assistant Style! Pick yours today!", "These shorts are comfy and easy to wear, get yours now!")
- vend_reply = "Thank you for using the CargoDrobe!"
- products = list(/obj/item/clothing/under/rank/cargo/tech = 6,
- /obj/item/clothing/under/rank/cargo/tech/skirt = 3,
- /obj/item/clothing/suit/hooded/wintercoat/cargo = 3,
- /obj/item/clothing/suit/jacket/cargobomber = 3,
- /obj/item/clothing/suit/storage/hazardvest = 3,
- /obj/item/clothing/head/soft = 3,
- /obj/item/clothing/head/hardhat/orange = 2,
- /obj/item/clothing/gloves/fingerless = 6,
- /obj/item/clothing/accessory/armband/cargo = 6,
- /obj/item/clothing/shoes/black = 3,
- /obj/item/clothing/shoes/workboots = 3)
- contraband = list(/obj/item/toy/figure/crew/qm = 1,
- /obj/item/toy/figure/crew/cargotech = 1)
- refill_canister = /obj/item/vending_refill/cargodrobe
-
-/obj/machinery/vending/chefdrobe
- name = "\improper ChefDrobe"
- desc = "This vending machine might not dispense meat, but it certainly dispenses chef related clothing."
- icon_state = "base_drobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- icon_addon = "chefdrobe"
- ads_list = list("Our clothes are guaranteed to protect you from food splatters!", "Comfortable enough for a CQC practice!")
- vend_reply = "Thank you for using the ChefDrobe!"
- products = list(/obj/item/clothing/under/rank/civilian/chef = 2,
- /obj/item/clothing/under/misc/waiter = 2,
- /obj/item/clothing/suit/chef = 2,
- /obj/item/clothing/suit/chef/classic = 2,
- /obj/item/clothing/head/chefhat = 2,
- /obj/item/clothing/head/soft/mime = 2,
- /obj/item/clothing/shoes/laceup = 2,
- /obj/item/clothing/shoes/white = 2,
- /obj/item/clothing/shoes/black = 2,
- /obj/item/clothing/accessory/waistcoat = 2,
- /obj/item/reagent_containers/glass/rag = 3)
- contraband = list(/obj/item/toy/figure/crew/chef = 1)
- premium = list(/obj/item/storage/box/dish_drive = 1)
- refill_canister = /obj/item/vending_refill/chefdrobe
-
-/obj/machinery/vending/bardrobe
- name = "\improper BarDrobe"
- desc = "A stylish vendor to dispense the most stylish bar clothing!"
- icon_state = "bardrobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- ads_list = list("Guaranteed to prevent stains from spilled drinks!")
- vend_reply = "Thank you for using the BarDrobe!"
- products = list(/obj/item/clothing/under/rank/civilian/bartender = 2,
- /obj/item/clothing/under/misc/sl_suit = 2,
- /obj/item/clothing/head/that = 2,
- /obj/item/clothing/head/soft/black = 2,
- /obj/item/clothing/suit/blacktrenchcoat = 2,
- /obj/item/clothing/shoes/laceup = 2,
- /obj/item/clothing/shoes/black = 2,
- /obj/item/clothing/accessory/waistcoat = 2,
- /obj/item/reagent_containers/glass/rag = 3)
- contraband = list(/obj/item/toy/figure/crew/bartender = 1)
- premium = list(/obj/item/storage/box/dish_drive = 1)
- refill_canister = /obj/item/vending_refill/bardrobe
-
-/obj/machinery/vending/hydrodrobe
- name = "\improper HydroDrobe"
- desc = "A machine with a catchy name. It dispenses botany related clothing and gear."
- icon_state = "hydrobe"
- icon_lightmask = "base_drobe"
- icon_panel = "drobe"
- ads_list = list("Do you love soil? Then buy our clothes!", "Get outfits to match your green thumb here!")
- vend_reply = "Thank you for using the HydroDrobe!"
- products = list(/obj/item/clothing/under/rank/civilian/hydroponics = 3,
- /obj/item/reagent_containers/glass/bucket = 3,
- /obj/item/clothing/suit/apron = 3,
- /obj/item/clothing/suit/apron/overalls = 3,
- /obj/item/clothing/mask/bandana/botany = 3,
- /obj/item/clothing/accessory/armband/hydro = 3,
- /obj/item/storage/backpack/botany = 2,
- /obj/item/storage/backpack/satchel_hyd = 2,
- /obj/item/storage/backpack/duffel/hydro = 2,)
- contraband = list(/obj/item/toy/figure/crew/botanist = 1)
- refill_canister = /obj/item/vending_refill/hydrodrobe
diff --git a/code/game/machinery/vendors/contraband_vendors.dm b/code/game/machinery/vendors/contraband_vendors.dm
new file mode 100644
index 00000000000..f27d18c35a9
--- /dev/null
+++ b/code/game/machinery/vendors/contraband_vendors.dm
@@ -0,0 +1,105 @@
+///Syndicate/Contraband Vendors
+
+/obj/machinery/economy/vending/wallmed/syndicate
+ name = "\improper SyndiMed Plus"
+ desc = "EVIL wall-mounted Medical Equipment dispenser."
+ icon_state = "syndimed"
+ icon_deny = "syndimed_deny"
+ ads_list = list("Go end some lives!", "The best stuff for your ship.", "Only the finest tools.", "Natural chemicals!", "This stuff saves lives.", "Don't you want some?", "Ping!")
+ req_access_txt = "150"
+ products = list(/obj/item/stack/medical/bruise_pack = 2,/obj/item/stack/medical/ointment = 2,/obj/item/reagent_containers/hypospray/autoinjector = 4,/obj/item/healthanalyzer = 1)
+ contraband = list(/obj/item/reagent_containers/syringe/charcoal = 4,/obj/item/reagent_containers/syringe/antiviral = 4,/obj/item/reagent_containers/food/pill/tox = 1)
+
+/obj/machinery/economy/vending/syndicigs
+ name = "\improper Suspicious Cigarette Machine"
+ desc = "Smoke 'em if you've got 'em."
+ slogan_list = list("Space cigs taste good like a cigarette should.", "I'd rather toolbox than switch.", "Smoke!", "Don't believe the reports - smoke today!")
+ ads_list = list("Probably not bad for you!", "Don't believe the scientists!", "It's good for you!", "Don't quit, buy more!", "Smoke!", "Nicotine heaven.", "Best cigarettes since 2150.", "Award-winning cigs.")
+ vend_delay = 34
+ icon_state = "cigs"
+ icon_lightmask = "cigs"
+ products = list(/obj/item/storage/fancy/cigarettes/syndicate = 10, /obj/item/lighter/random = 5)
+
+/obj/machinery/economy/vending/syndisnack
+ name = "\improper Getmore Chocolate Corp"
+ desc = "A modified snack machine courtesy of the Getmore Chocolate Corporation, based out of Mars"
+ slogan_list = list("Try our new nougat bar!", "Twice the calories for half the price!")
+ ads_list = list("The healthiest!", "Award-winning chocolate bars!", "Mmm! So good!", "Oh my god it's so juicy!", "Have a snack.", "Snacks are good for you!", "Have some more Getmore!", "Best quality snacks straight from mars.", "We love chocolate!", "Try our new jerky!")
+ icon_state = "snack"
+ icon_lightmask = "nutri"
+ icon_off = "nutri"
+ icon_panel = "thin_vendor"
+ products = list(/obj/item/reagent_containers/food/snacks/chips = 6,/obj/item/reagent_containers/food/snacks/sosjerky = 6,
+ /obj/item/reagent_containers/food/snacks/syndicake = 6, /obj/item/reagent_containers/food/snacks/cheesiehonkers = 6)
+
+/obj/machinery/economy/vending/hydroseeds/syndicate_druglab
+ products = list(/obj/item/seeds/ambrosia/deus = 2,
+ /obj/item/seeds/cannabis = 2,
+ /obj/item/seeds/coffee = 3,
+ /obj/item/seeds/liberty = 2,
+ /obj/item/seeds/cannabis/rainbow = 1,
+ /obj/item/seeds/reishi = 2,
+ /obj/item/seeds/tobacco = 1)
+ contraband = list()
+ premium = list()
+ refill_canister = null
+
+/obj/machinery/economy/vending/hydronutrients/syndicate_druglab
+ products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 12,
+ /obj/item/reagent_containers/glass/bottle/nutrient/l4z = 2,
+ /obj/item/reagent_containers/glass/bottle/nutrient/rh = 3,
+ /obj/item/reagent_containers/spray/pestspray = 7,
+ /obj/item/reagent_containers/syringe = 11,
+ /obj/item/storage/bag/plants = 2,
+ /obj/item/cultivator = 3,
+ /obj/item/shovel/spade = 2,
+ /obj/item/plant_analyzer = 2,
+ /obj/item/reagent_containers/glass/bottle/ammonia = 6,
+ /obj/item/reagent_containers/glass/bottle/diethylamine = 8)
+ contraband = list()
+
+/obj/machinery/economy/vending/liberationstation
+ name = "\improper Liberation Station"
+ desc = "An overwhelming amount of ancient patriotism washes over you just by looking at the machine."
+ icon_state = "liberationstation"
+ icon_lightmask = "liberationstation"
+ req_access_txt = "1"
+ slogan_list = list("Liberation Station: Your one-stop shop for all things second amendment!", "Be a patriot today, pick up a gun!", "Quality weapons for cheap prices!", "Better dead than red!")
+ ads_list = list("Float like an astronaut, sting like a bullet!", "Express your second amendment today!", "Guns don't kill people, but you can!", "Who needs responsibilities when you have guns?")
+ vend_reply = "Remember the name: Liberation Station!"
+ products = list(/obj/item/gun/projectile/automatic/pistol/deagle/gold = 2,/obj/item/gun/projectile/automatic/pistol/deagle/camo = 2,
+ /obj/item/gun/projectile/automatic/pistol/m1911 = 2,/obj/item/gun/projectile/automatic/proto = 2,
+ /obj/item/gun/projectile/shotgun/automatic/combat = 2,/obj/item/gun/projectile/automatic/gyropistol = 1,
+ /obj/item/gun/projectile/shotgun = 2,/obj/item/gun/projectile/automatic/ar = 2)
+ premium = list(/obj/item/ammo_box/magazine/smgm9mm = 2, /obj/item/ammo_box/magazine/m50 = 4, /obj/item/ammo_box/magazine/m45 = 2, /obj/item/ammo_box/magazine/m75 = 2)
+ contraband = list(/obj/item/clothing/under/costume/patriotsuit = 1, /obj/item/bedsheet/patriot = 3)
+ armor = list(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 50)
+ resistance_flags = FIRE_PROOF
+
+/obj/machinery/economy/vending/toyliberationstation
+ name = "\improper Syndicate Donksoft Toy Vendor"
+ desc = "An ages 8 and up approved vendor that dispenses toys. If you were to find the right wires, you can unlock the adult mode setting!"
+ icon_state = "syndi"
+ icon_lightmask = "syndi"
+ slogan_list = list("Get your cool toys today!", "Trigger a valid hunter today!", "Quality toy weapons for cheap prices!", "Give them to HoPs for all access!", "Give them to HoS to get permabrigged!")
+ ads_list = list("Feel robust with your toys!", "Express your inner child today!", "Toy weapons don't kill people, but valid hunters do!", "Who needs responsibilities when you have toy weapons?", "Make your next murder FUN!")
+ vend_reply = "Come back for more!"
+ products = list(/obj/item/gun/projectile/automatic/toy = 10,
+ /obj/item/gun/projectile/automatic/toy/pistol= 10,
+ /obj/item/gun/projectile/shotgun/toy = 10,
+ /obj/item/toy/sword = 10,
+ /obj/item/ammo_box/foambox = 20,
+ /obj/item/toy/foamblade = 10,
+ /obj/item/toy/syndicateballoon = 10,
+ /obj/item/clothing/suit/syndicatefake = 5,
+ /obj/item/clothing/head/syndicatefake = 5) //OPS IN DORMS oh wait it's just an assistant
+ contraband = list(/obj/item/gun/projectile/shotgun/toy/crossbow = 10, //Congrats, you unlocked the +18 setting!
+ /obj/item/gun/projectile/automatic/c20r/toy/riot = 10,
+ /obj/item/gun/projectile/automatic/l6_saw/toy/riot = 10,
+ /obj/item/gun/projectile/automatic/sniper_rifle/toy = 10,
+ /obj/item/ammo_box/foambox/riot = 20,
+ /obj/item/toy/katana = 10,
+ /obj/item/twohanded/dualsaber/toy = 5,
+ /obj/item/deck/cards/syndicate = 10) //Gambling and it hurts, making it a +18 item
+ armor = list(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 50)
+ resistance_flags = FIRE_PROOF
diff --git a/code/game/machinery/vendors/departmental_vendors.dm b/code/game/machinery/vendors/departmental_vendors.dm
new file mode 100644
index 00000000000..3417d117441
--- /dev/null
+++ b/code/game/machinery/vendors/departmental_vendors.dm
@@ -0,0 +1,220 @@
+/obj/machinery/economy/vending/engivend
+ name = "\improper Engi-Vend"
+ desc = "Spare tool vending. What? Did you expect some witty description?"
+ icon_state = "engivend"
+ icon_deny = "engivend_deny"
+ icon_panel = "generic"
+ req_one_access_txt = "11;24" // Engineers and atmos techs can use this
+ products = list(/obj/item/clothing/glasses/meson/engine = 2, /obj/item/multitool = 4, /obj/item/geiger_counter = 5, /obj/item/airlock_electronics = 10, /obj/item/firelock_electronics = 10, /obj/item/firealarm_electronics = 10, /obj/item/apc_electronics = 10, /obj/item/airalarm_electronics = 10, /obj/item/stock_parts/cell/high = 10, /obj/item/camera_assembly = 10)
+ contraband = list(/obj/item/stock_parts/cell/potato = 3)
+ premium = list(/obj/item/storage/belt/utility = 3)
+ refill_canister = /obj/item/vending_refill/engivend
+
+/obj/machinery/economy/vending/engineering
+ name = "\improper Robco Tool Maker"
+ desc = "Everything you need for do-it-yourself station repair."
+ icon_state = "engi"
+ icon_deny = "engi_deny"
+ req_access_txt = "11"
+ products = list(/obj/item/clothing/under/rank/engineering/chief_engineer = 4, /obj/item/clothing/under/rank/engineering/engineer = 40, /obj/item/clothing/shoes/workboots = 4, /obj/item/clothing/head/hardhat = 4,
+ /obj/item/storage/belt/utility = 4, /obj/item/clothing/glasses/meson/engine = 4,/obj/item/clothing/gloves/color/yellow = 4, /obj/item/screwdriver = 12,
+ /obj/item/crowbar = 12, /obj/item/wirecutters = 12, /obj/item/multitool = 12,/obj/item/wrench = 12, /obj/item/t_scanner = 12,
+ /obj/item/stack/cable_coil = 8, /obj/item/stock_parts/cell = 8, /obj/item/weldingtool = 8, /obj/item/clothing/head/welding = 8,
+ /obj/item/light/tube = 10, /obj/item/clothing/suit/fire = 4, /obj/item/stock_parts/scanning_module = 5, /obj/item/stock_parts/micro_laser = 5,
+ /obj/item/stock_parts/matter_bin = 5, /obj/item/stock_parts/manipulator = 5)
+ refill_canister = /obj/item/vending_refill/engineering
+
+/obj/machinery/economy/vending/robotics
+ name = "\improper Robotech Deluxe"
+ desc = "All the tools you need to create your own robot army."
+ icon_state = "robotics"
+ icon_deny = "robotics_deny"
+ icon_lightmask = "robotics"
+ req_access_txt = "29"
+ products = list(/obj/item/clothing/suit/storage/labcoat = 4, /obj/item/clothing/under/rank/rnd/roboticist = 4, /obj/item/stack/cable_coil = 4, /obj/item/flash = 4,
+ /obj/item/stock_parts/cell/high = 12, /obj/item/assembly/prox_sensor = 3, /obj/item/assembly/signaler = 3, /obj/item/healthanalyzer = 3,
+ /obj/item/scalpel = 2, /obj/item/circular_saw = 2, /obj/item/tank/internals/anesthetic = 2, /obj/item/clothing/mask/breath/medical = 5,
+ /obj/item/screwdriver = 5, /obj/item/crowbar = 5)
+ refill_canister = /obj/item/vending_refill/robotics
+
+/obj/machinery/economy/vending/dinnerware
+ name = "\improper Plasteel Chef's Dinnerware Vendor"
+ desc = "A kitchen and restaurant equipment vendor."
+ ads_list = list("Mm, food stuffs!","Food and food accessories.","Get your plates!","You like forks?","I like forks.","Woo, utensils.","You don't really need these...")
+ icon_state = "dinnerware"
+ icon_lightmask = "dinnerware"
+ products = list(/obj/item/storage/bag/tray = 8,
+ /obj/item/kitchen/utensil/fork = 6,
+ /obj/item/trash/plate = 20,
+ /obj/item/trash/bowl = 20,
+ /obj/item/kitchen/knife = 3,
+ /obj/item/kitchen/rollingpin = 2,
+ /obj/item/kitchen/sushimat = 3,
+ /obj/item/reagent_containers/food/drinks/drinkingglass = 8,
+ /obj/item/clothing/suit/chef/classic = 2,
+ /obj/item/storage/belt/chef = 2,
+ /obj/item/reagent_containers/food/condiment/pack/ketchup = 5,
+ /obj/item/reagent_containers/food/condiment/pack/hotsauce = 5,
+ /obj/item/reagent_containers/food/condiment/saltshaker =5,
+ /obj/item/reagent_containers/food/condiment/peppermill =5,
+ /obj/item/whetstone = 2,
+ /obj/item/mixing_bowl = 10,
+ /obj/item/kitchen/mould/bear = 1, /obj/item/kitchen/mould/worm = 1,
+ /obj/item/kitchen/mould/bean = 1, /obj/item/kitchen/mould/ball = 1,
+ /obj/item/kitchen/mould/cane = 1, /obj/item/kitchen/mould/cash = 1,
+ /obj/item/kitchen/mould/coin = 1, /obj/item/kitchen/mould/loli = 1,
+ /obj/item/kitchen/cutter = 2)
+ contraband = list(/obj/item/kitchen/rollingpin = 2, /obj/item/kitchen/knife/butcher = 2)
+ refill_canister = /obj/item/vending_refill/dinnerware
+
+/obj/machinery/economy/vending/hydronutrients
+ name = "\improper NutriMax"
+ desc = "A plant nutrients vendor"
+ slogan_list = list("Aren't you glad you don't have to fertilize the natural way?","Now with 50% less stink!","Plants are people too!")
+ ads_list = list("We like plants!","Don't you want some?","The greenest thumbs ever.","We like big plants.","Soft soil...")
+ icon_state = "nutri"
+ icon_deny = "nutri_deny"
+ icon_lightmask = "nutri"
+ icon_panel = "thin_vendor"
+ products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 20, /obj/item/reagent_containers/glass/bottle/nutrient/l4z = 13, /obj/item/reagent_containers/glass/bottle/nutrient/rh = 6, /obj/item/reagent_containers/spray/pestspray = 20,
+ /obj/item/reagent_containers/syringe = 5, /obj/item/storage/bag/plants = 5, /obj/item/cultivator = 3, /obj/item/shovel/spade = 3, /obj/item/plant_analyzer = 4)
+ contraband = list(/obj/item/reagent_containers/glass/bottle/ammonia = 10, /obj/item/reagent_containers/glass/bottle/diethylamine = 5)
+ refill_canister = /obj/item/vending_refill/hydronutrients
+
+/obj/machinery/economy/vending/hydroseeds
+ name = "\improper MegaSeed Servitor"
+ desc = "When you need seeds fast!"
+ slogan_list = list("THIS'S WHERE TH' SEEDS LIVE! GIT YOU SOME!","Hands down the best seed selection on the station!","Also certain mushroom varieties available, more for experts! Get certified today!")
+ ads_list = list("We like plants!","Grow some crops!","Grow, baby, growww!","Aw h'yeah son!")
+ icon_state = "seeds"
+ icon_lightmask = "seeds"
+ icon_panel = "thin_vendor"
+ products = list(/obj/item/seeds/aloe = 3,
+ /obj/item/seeds/ambrosia = 3,
+ /obj/item/seeds/apple = 3,
+ /obj/item/seeds/banana = 3,
+ /obj/item/seeds/berry = 3,
+ /obj/item/seeds/cabbage = 3,
+ /obj/item/seeds/carrot = 3,
+ /obj/item/seeds/cherry = 3,
+ /obj/item/seeds/chanter = 3,
+ /obj/item/seeds/chili = 3,
+ /obj/item/seeds/cocoapod = 3,
+ /obj/item/seeds/coffee = 3,
+ /obj/item/seeds/comfrey =3,
+ /obj/item/seeds/corn = 3,
+ /obj/item/seeds/cotton = 3,
+ /obj/item/seeds/nymph =3,
+ /obj/item/seeds/eggplant = 3,
+ /obj/item/seeds/garlic = 3,
+ /obj/item/seeds/grape = 3,
+ /obj/item/seeds/grass = 3,
+ /obj/item/seeds/lemon = 3,
+ /obj/item/seeds/lime = 3,
+ /obj/item/seeds/mint = 3,
+ /obj/item/seeds/onion = 3,
+ /obj/item/seeds/orange = 3,
+ /obj/item/seeds/peanuts = 3,
+ /obj/item/seeds/pineapple = 3,
+ /obj/item/seeds/poppy = 3,
+ /obj/item/seeds/potato = 3,
+ /obj/item/seeds/pumpkin = 3,
+ /obj/item/seeds/replicapod = 3,
+ /obj/item/seeds/wheat/rice = 3,
+ /obj/item/seeds/soya = 3,
+ /obj/item/seeds/sugarcane = 3,
+ /obj/item/seeds/sunflower = 3,
+ /obj/item/seeds/tea = 3,
+ /obj/item/seeds/tobacco = 3,
+ /obj/item/seeds/tomato = 3,
+ /obj/item/seeds/tower = 3,
+ /obj/item/seeds/watermelon = 3,
+ /obj/item/seeds/wheat = 3,
+ /obj/item/seeds/whitebeet = 3)
+ contraband = list(/obj/item/seeds/cannabis = 3,
+ /obj/item/seeds/amanita = 2,
+ /obj/item/seeds/fungus = 3,
+ /obj/item/seeds/glowshroom = 2,
+ /obj/item/seeds/liberty = 2,
+ /obj/item/seeds/nettle = 2,
+ /obj/item/seeds/plump = 2,
+ /obj/item/seeds/reishi = 2,
+ /obj/item/seeds/starthistle = 2,
+ /obj/item/seeds/random = 2)
+ premium = list(/obj/item/reagent_containers/spray/waterflower = 1)
+ refill_canister = /obj/item/vending_refill/hydroseeds
+
+/obj/machinery/economy/vending/medical
+ name = "\improper NanoMed Plus"
+ desc = "Medical drug dispenser."
+ icon_state = "med"
+ icon_lightmask = "med"
+ icon_deny = "med_deny"
+ icon_panel = "wide_vendor"
+ ads_list = list("Go save some lives!","The best stuff for your medbay.","Only the finest tools.","Natural chemicals!","This stuff saves lives.","Don't you want some?","Ping!")
+ req_access_txt = "5"
+ products = list(/obj/item/reagent_containers/syringe = 12,
+ /obj/item/reagent_containers/food/pill/patch/styptic = 4,
+ /obj/item/reagent_containers/food/pill/patch/silver_sulf = 4,
+ /obj/item/reagent_containers/applicator/brute = 3,
+ /obj/item/reagent_containers/applicator/burn = 3,
+ /obj/item/reagent_containers/glass/bottle/charcoal = 4,
+ /obj/item/reagent_containers/glass/bottle/epinephrine = 4,
+ /obj/item/reagent_containers/glass/bottle/salicylic = 4,
+ /obj/item/reagent_containers/glass/bottle/potassium_iodide = 3,
+ /obj/item/reagent_containers/glass/bottle/saline = 5,
+ /obj/item/reagent_containers/glass/bottle/morphine = 4,
+ /obj/item/reagent_containers/glass/bottle/atropine = 3,
+ /obj/item/reagent_containers/glass/bottle/oculine = 2,
+ /obj/item/reagent_containers/syringe/antiviral = 6,
+ /obj/item/reagent_containers/syringe/calomel = 10,
+ /obj/item/reagent_containers/syringe/heparin = 4,
+ /obj/item/reagent_containers/hypospray/autoinjector = 5,
+ /obj/item/reagent_containers/food/pill/salbutamol = 10,
+ /obj/item/reagent_containers/food/pill/mannitol = 10,
+ /obj/item/reagent_containers/food/pill/mutadone = 5,
+ /obj/item/stack/medical/bruise_pack/advanced = 4,
+ /obj/item/stack/medical/ointment/advanced = 4,
+ /obj/item/stack/medical/bruise_pack = 4,
+ /obj/item/stack/medical/splint = 4,
+ /obj/item/reagent_containers/glass/beaker = 4,
+ /obj/item/reagent_containers/dropper = 4,
+ /obj/item/healthanalyzer/advanced = 4,
+ /obj/item/reagent_containers/hypospray/safety = 2,
+ /obj/item/sensor_device = 2,
+ /obj/item/pinpointer/crew = 2)
+ contraband = list(/obj/item/reagent_containers/syringe/insulin = 6,
+ /obj/item/reagent_containers/glass/bottle/sulfonal = 1,
+ /obj/item/reagent_containers/glass/bottle/pancuronium = 1)
+ armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 70)
+ resistance_flags = FIRE_PROOF
+ refill_canister = /obj/item/vending_refill/medical
+
+/obj/machinery/economy/vending/medical/syndicate_access
+ name = "\improper SyndiMed Plus"
+ req_access = list(ACCESS_SYNDICATE)
+
+/obj/machinery/economy/vending/plasmaresearch
+ name = "\improper Toximate 3000"
+ desc = "All the fine parts you need in one vending machine!"
+ products = list(/obj/item/assembly/prox_sensor = 8, /obj/item/assembly/igniter = 8, /obj/item/assembly/signaler = 8,
+ /obj/item/wirecutters = 1, /obj/item/assembly/timer = 8)
+ contraband = list(/obj/item/flashlight = 5, /obj/item/assembly/voice = 3, /obj/item/assembly/health = 3, /obj/item/assembly/infra = 3)
+
+/obj/machinery/economy/vending/security
+ name = "\improper SecTech"
+ desc = "A security equipment vendor."
+ ads_list = list("Crack capitalist skulls!","Beat some heads in!","Don't forget - harm is good!","Your weapons are right here.","Handcuffs!","Freeze, scumbag!","Don't tase me bro!","Tase them, bro.","Why not have a donut?")
+ icon_state = "sec"
+ icon_lightmask = "sec"
+ icon_deny = "sec_deny"
+ icon_panel = "wide_vendor"
+ req_access_txt = "1"
+ products = list(/obj/item/restraints/handcuffs = 8, /obj/item/restraints/handcuffs/cable/zipties = 8, /obj/item/grenade/flashbang = 4, /obj/item/flash = 5,
+ /obj/item/reagent_containers/food/snacks/donut = 12, /obj/item/storage/box/evidence = 6, /obj/item/flashlight/seclite = 4, /obj/item/restraints/legcuffs/bola/energy = 7,
+ /obj/item/clothing/mask/muzzle/safety = 4)
+ contraband = list(/obj/item/clothing/glasses/sunglasses = 2, /obj/item/storage/fancy/donut_box = 2, /obj/item/hailer = 5)
+ refill_canister = /obj/item/vending_refill/security
+
+
+
diff --git a/code/game/machinery/vendors/generic_vendors.dm b/code/game/machinery/vendors/generic_vendors.dm
new file mode 100644
index 00000000000..6ed399558b0
--- /dev/null
+++ b/code/game/machinery/vendors/generic_vendors.dm
@@ -0,0 +1,872 @@
+//general-use clothing vendors, wardrobe vendors are in another file
+/obj/machinery/economy/vending/assist
+ products = list(/obj/item/assembly/prox_sensor = 5, /obj/item/assembly/igniter = 3, /obj/item/assembly/signaler = 4,
+ /obj/item/wirecutters = 1, /obj/item/cartridge/signal = 4)
+ contraband = list(/obj/item/flashlight = 5, /obj/item/assembly/timer = 2, /obj/item/assembly/voice = 2, /obj/item/assembly/health = 2)
+ ads_list = list("Only the finest!", "Have some tools.", "The most robust equipment.", "The finest gear in space!")
+ refill_canister = /obj/item/vending_refill/assist
+
+/obj/machinery/economy/vending/boozeomat
+ name = "\improper Booze-O-Mat"
+ desc = "A technological marvel, supposedly able to mix just the mixture you'd like to drink the moment you ask for one."
+ icon_state = "boozeomat" //////////////18 drink entities below, plus the glasses, in case someone wants to edit the number of bottles
+ icon_deny = "boozeomat_deny"
+ icon_lightmask = "smartfridge"
+ icon_panel = "smartfridge"
+ icon_broken = "smartfridge"
+ products = list(/obj/item/reagent_containers/food/drinks/bottle/gin = 5,
+ /obj/item/reagent_containers/food/drinks/bottle/whiskey = 5,
+ /obj/item/reagent_containers/food/drinks/bottle/tequila = 5,
+ /obj/item/reagent_containers/food/drinks/bottle/vodka = 5,
+ /obj/item/reagent_containers/food/drinks/bottle/vermouth = 5,
+ /obj/item/reagent_containers/food/drinks/bottle/rum = 5,
+ /obj/item/reagent_containers/food/drinks/bottle/wine = 5,
+ /obj/item/reagent_containers/food/drinks/bag/goonbag = 3,
+ /obj/item/reagent_containers/food/drinks/bottle/cognac = 5,
+ /obj/item/reagent_containers/food/drinks/bottle/kahlua = 5,
+ /obj/item/reagent_containers/food/drinks/cans/beer = 6,
+ /obj/item/reagent_containers/food/drinks/cans/ale = 6,
+ /obj/item/reagent_containers/food/drinks/cans/synthanol = 15,
+ /obj/item/reagent_containers/food/drinks/bottle/orangejuice = 4,
+ /obj/item/reagent_containers/food/drinks/bottle/tomatojuice = 4,
+ /obj/item/reagent_containers/food/drinks/bottle/limejuice = 4,
+ /obj/item/reagent_containers/food/drinks/bottle/cream = 4,
+ /obj/item/reagent_containers/food/drinks/cans/tonic = 8,
+ /obj/item/reagent_containers/food/drinks/cans/cola = 8,
+ /obj/item/reagent_containers/food/drinks/cans/sodawater = 15,
+ /obj/item/reagent_containers/food/drinks/drinkingglass = 30,
+ /obj/item/reagent_containers/food/drinks/drinkingglass/shotglass = 30,
+ /obj/item/reagent_containers/food/drinks/ice = 9)
+ contraband = list(/obj/item/reagent_containers/food/drinks/tea = 10,
+ /obj/item/reagent_containers/food/drinks/bottle/fernet = 5)
+ vend_delay = 15
+ slogan_list = list("I hope nobody asks me for a bloody cup o' tea...","Alcohol is humanity's friend. Would you abandon a friend?","Quite delighted to serve you!","Is nobody thirsty on this station?")
+ ads_list = list("Drink up!","Booze is good for you!","Alcohol is humanity's best friend.","Quite delighted to serve you!","Care for a nice, cold beer?","Nothing cures you like booze!","Have a sip!","Have a drink!","Have a beer!","Beer is good for you!","Only the finest alcohol!","Best quality booze since 2053!","Award-winning wine!","Maximum alcohol!","Man loves beer.","A toast for progress!")
+ refill_canister = /obj/item/vending_refill/boozeomat
+
+/obj/machinery/economy/vending/boozeomat/syndicate_access
+ req_access = list(ACCESS_SYNDICATE)
+
+
+/obj/machinery/economy/vending/coffee
+ name = "\improper Solar's Best Hot Drinks"
+ desc = "A vending machine which dispenses hot drinks."
+ ads_list = list("Have a drink!","Drink up!","It's good for you!","Would you like a hot joe?","I'd kill for some coffee!","The best beans in the galaxy.","Only the finest brew for you.","Mmmm. Nothing like a coffee.","I like coffee, don't you?","Coffee helps you work!","Try some tea.","We hope you like the best!","Try our new chocolate!","Admin conspiracies")
+ icon_state = "coffee"
+ icon_lightmask = "coffee"
+ icon_vend = "coffee_vend"
+ icon_panel = "screen_vendor"
+ item_slot = TRUE
+ vend_delay = 34
+ products = list(/obj/item/reagent_containers/food/drinks/coffee = 25, /obj/item/reagent_containers/food/drinks/tea = 25, /obj/item/reagent_containers/food/drinks/h_chocolate = 25,
+ /obj/item/reagent_containers/food/drinks/chocolate = 10, /obj/item/reagent_containers/food/drinks/chicken_soup = 10, /obj/item/reagent_containers/food/drinks/weightloss = 10,
+ /obj/item/reagent_containers/food/drinks/mug = 15)
+ contraband = list(/obj/item/reagent_containers/food/drinks/ice = 10)
+ premium = list(/obj/item/reagent_containers/food/drinks/mug/novelty = 5)
+ prices = list(/obj/item/reagent_containers/food/drinks/coffee = 80, /obj/item/reagent_containers/food/drinks/tea = 80, /obj/item/reagent_containers/food/drinks/h_chocolate = 64, /obj/item/reagent_containers/food/drinks/chocolate = 120,
+ /obj/item/reagent_containers/food/drinks/chicken_soup = 100, /obj/item/reagent_containers/food/drinks/weightloss = 50, /obj/item/reagent_containers/food/drinks/mug = 75)
+ refill_canister = /obj/item/vending_refill/coffee
+
+/obj/machinery/economy/vending/coffee/free
+ prices = list()
+
+/obj/machinery/economy/vending/coffee/item_slot_check(mob/user, obj/item/I)
+ if(!(istype(I, /obj/item/reagent_containers/glass) || istype(I, /obj/item/reagent_containers/food/drinks)))
+ return FALSE
+ if(!..())
+ return FALSE
+ if(!I.is_open_container())
+ to_chat(user, "You need to open [I] before inserting it.")
+ return FALSE
+ return TRUE
+
+/obj/machinery/economy/vending/coffee/do_vend(datum/data/vending_product/R, mob/user)
+ if(..())
+ return
+ var/obj/item/reagent_containers/food/drinks/vended = new R.product_path()
+
+ if(istype(vended, /obj/item/reagent_containers/food/drinks/mug))
+ var/put_on_turf = TRUE
+ if(user && iscarbon(user) && user.Adjacent(src))
+ if(user.put_in_hands(vended))
+ put_on_turf = FALSE
+ if(put_on_turf)
+ var/turf/T = get_turf(src)
+ vended.forceMove(T)
+ return
+
+ vended.reagents.trans_to(inserted_item, vended.reagents.total_volume)
+ if(vended.reagents.total_volume)
+ var/put_on_turf = TRUE
+ if(user && iscarbon(user) && user.Adjacent(src))
+ if(user.put_in_hands(vended))
+ put_on_turf = FALSE
+ if(put_on_turf)
+ var/turf/T = get_turf(src)
+ vended.forceMove(T)
+ else
+ qdel(vended)
+
+
+/obj/machinery/economy/vending/hatdispenser
+ name = "\improper Hatlord 9000"
+ desc = "It doesn't seem the slightest bit unusual. This frustrates you immensely."
+ icon_state = "hats"
+ icon_lightmask = "hats"
+ icon_panel = "syndi"
+ icon_broken = "wide_vendor"
+ ads_list = list("Warning, not all hats are dog/monkey compatible. Apply forcefully with care.","Apply directly to the forehead.","Who doesn't love spending cash on hats?!","From the people that brought you collectable hat crates, Hatlord!")
+ products = list(/obj/item/clothing/head/bowlerhat = 10,
+ /obj/item/clothing/head/beaverhat = 10,
+ /obj/item/clothing/head/boaterhat = 10,
+ /obj/item/clothing/head/fedora = 10,
+ /obj/item/clothing/head/fez = 10,
+ /obj/item/clothing/head/beret = 10)
+ contraband = list(/obj/item/clothing/head/bearpelt = 5)
+ premium = list(/obj/item/clothing/head/soft/rainbow = 1)
+ prices = list(/obj/item/clothing/head/bowlerhat = 20, /obj/item/clothing/head/beaverhat = 20, /obj/item/clothing/head/boaterhat = 20,
+ /obj/item/clothing/head/fedora = 20, /obj/item/clothing/head/beret = 20, /obj/item/clothing/head/fez = 20,
+ /obj/item/clothing/head/bearpelt = 30)
+ refill_canister = /obj/item/vending_refill/hatdispenser
+
+/obj/machinery/economy/vending/suitdispenser
+ name = "\improper Suitlord 9000"
+ desc = "You wonder for a moment why all of your shirts and pants come conjoined. This hurts your head and you stop thinking about it."
+ icon_state = "suits"
+ icon_lightmask = "suits"
+ icon_panel = "syndi"
+ icon_broken = "wide_vendor"
+ ads_list = list("Pre-Ironed, Pre-Washed, Pre-Wor-*BZZT*","Blood of your enemies washes right out!","Who are YOU wearing?","Look dapper! Look like an idiot!","Dont carry your size? How about you shave off some pounds you fat lazy- *BZZT*")
+ products = list(/obj/item/clothing/under/color/black = 10, /obj/item/clothing/under/color/grey = 10, /obj/item/clothing/under/color/white = 10, /obj/item/clothing/under/color/darkred = 10, /obj/item/clothing/under/color/red = 10, /obj/item/clothing/under/color/lightred = 10,
+ /obj/item/clothing/under/color/brown = 10, /obj/item/clothing/under/color/orange = 10, /obj/item/clothing/under/color/lightbrown = 10, /obj/item/clothing/under/color/yellow = 10, /obj/item/clothing/under/color/yellowgreen = 10, /obj/item/clothing/under/color/lightgreen = 10,
+ /obj/item/clothing/under/color/green = 10, /obj/item/clothing/under/color/aqua = 10, /obj/item/clothing/under/color/darkblue = 10, /obj/item/clothing/under/color/blue = 10, /obj/item/clothing/under/color/lightblue = 10, /obj/item/clothing/under/color/purple = 10,
+ /obj/item/clothing/under/color/lightpurple = 10, /obj/item/clothing/under/color/pink = 10)
+ contraband = list(/obj/item/clothing/under/syndicate/tacticool = 5,/obj/item/clothing/under/color/orange/prison = 5)
+ premium = list(/obj/item/clothing/under/color/rainbow = 1)
+ prices = list(/obj/item/clothing/under/color/black = 30, /obj/item/clothing/under/color/grey = 30, /obj/item/clothing/under/color/white = 50, /obj/item/clothing/under/color/darkred = 50, /obj/item/clothing/under/color/red = 50, /obj/item/clothing/under/color/lightred = 50,
+ /obj/item/clothing/under/color/brown = 30, /obj/item/clothing/under/color/orange = 50, /obj/item/clothing/under/color/lightbrown = 30, /obj/item/clothing/under/color/yellow = 50, /obj/item/clothing/under/color/yellowgreen = 50, /obj/item/clothing/under/color/lightgreen = 50,
+ /obj/item/clothing/under/color/green = 50, /obj/item/clothing/under/color/aqua = 50, /obj/item/clothing/under/color/darkblue = 50, /obj/item/clothing/under/color/blue = 50, /obj/item/clothing/under/color/lightblue = 30, /obj/item/clothing/under/color/purple = 50,
+ /obj/item/clothing/under/color/lightpurple = 50, /obj/item/clothing/under/color/pink = 50, /obj/item/clothing/under/syndicate/tacticool = 75,
+ /obj/item/clothing/under/color/orange/prison = 75)
+ refill_canister = /obj/item/vending_refill/suitdispenser
+
+/obj/machinery/economy/vending/shoedispenser
+ name = "\improper Shoelord 9000"
+ desc = "Wow, hatlord looked fancy, suitlord looked streamlined, and this is just normal. The guy who designed these must be an idiot."
+ icon_state = "shoes"
+ icon_lightmask = "shoes"
+ icon_panel = "syndi"
+ icon_broken = "wide_vendor"
+ ads_list = list("Put your foot down!","One size fits all!","IM WALKING ON SUNSHINE!","No hobbits allowed.","NO PLEASE WILLY, DONT HURT ME- *BZZT*")
+ products = list(/obj/item/clothing/shoes/black = 10, /obj/item/clothing/shoes/brown = 10, /obj/item/clothing/shoes/blue = 10,
+ /obj/item/clothing/shoes/green = 10, /obj/item/clothing/shoes/yellow = 10, /obj/item/clothing/shoes/purple = 10,
+ /obj/item/clothing/shoes/red = 10, /obj/item/clothing/shoes/white = 10, /obj/item/clothing/shoes/sandal = 10)
+ contraband = list(/obj/item/clothing/shoes/orange = 5)
+ premium = list(/obj/item/clothing/shoes/rainbow = 1)
+ prices= list(/obj/item/clothing/shoes/black = 20, /obj/item/clothing/shoes/brown = 20, /obj/item/clothing/shoes/blue = 20,
+ /obj/item/clothing/shoes/green = 20, /obj/item/clothing/shoes/yellow = 20, /obj/item/clothing/shoes/purple = 20,
+ /obj/item/clothing/shoes/red = 20, /obj/item/clothing/shoes/white = 20, /obj/item/clothing/shoes/sandal = 20,
+ /obj/item/clothing/shoes/orange = 40)
+ refill_canister = /obj/item/vending_refill/shoedispenser
+
+//don't forget to change the refill size if you change the machine's contents!
+/obj/machinery/economy/vending/clothing
+ name = "\improper ClothesMate" //renamed to make the slogan rhyme
+ desc = "A vending machine for clothing."
+ icon_state = "clothes"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ slogan_list = list("Dress for success!","Prepare to look swagalicious!","Look at all this free swag!","Why leave style up to fate? Use the ClothesMate!")
+ vend_delay = 15
+ vend_reply = "Thank you for using the ClothesMate!"
+ products = list(/obj/item/clothing/head/that = 2,
+ /obj/item/clothing/head/fedora = 1,
+ /obj/item/clothing/glasses/monocle = 1,
+ /obj/item/clothing/under/suit/navy = 2,
+ /obj/item/clothing/under/costume/kilt = 1,
+ /obj/item/clothing/under/misc/overalls = 1,
+ /obj/item/clothing/under/suit/really_black = 2,
+ /obj/item/clothing/suit/storage/lawyer/blackjacket = 2,
+ /obj/item/clothing/under/pants/jeans = 3,
+ /obj/item/clothing/under/pants/classicjeans = 2,
+ /obj/item/clothing/under/pants/camo = 1,
+ /obj/item/clothing/under/pants/blackjeans = 2,
+ /obj/item/clothing/under/pants/khaki = 2,
+ /obj/item/clothing/under/pants/white = 2,
+ /obj/item/clothing/under/pants/red = 1,
+ /obj/item/clothing/under/pants/black = 2,
+ /obj/item/clothing/under/pants/tan = 2,
+ /obj/item/clothing/under/pants/blue = 1,
+ /obj/item/clothing/under/pants/track = 1,
+ /obj/item/clothing/suit/jacket/miljacket = 1,
+ /obj/item/clothing/head/beanie = 3,
+ /obj/item/clothing/head/beanie/black = 3,
+ /obj/item/clothing/head/beanie/red = 3,
+ /obj/item/clothing/head/beanie/green = 3,
+ /obj/item/clothing/head/beanie/darkblue = 3,
+ /obj/item/clothing/head/beanie/purple = 3,
+ /obj/item/clothing/head/beanie/yellow = 3,
+ /obj/item/clothing/head/beanie/orange = 3,
+ /obj/item/clothing/head/beanie/cyan = 3,
+ /obj/item/clothing/head/beanie/christmas = 3,
+ /obj/item/clothing/head/beanie/striped = 3,
+ /obj/item/clothing/head/beanie/stripedred = 3,
+ /obj/item/clothing/head/beanie/stripedblue = 3,
+ /obj/item/clothing/head/beanie/stripedgreen = 3,
+ /obj/item/clothing/head/beanie/rasta = 3,
+ /obj/item/clothing/accessory/scarf/red = 1,
+ /obj/item/clothing/accessory/scarf/green = 1,
+ /obj/item/clothing/accessory/scarf/darkblue = 1,
+ /obj/item/clothing/accessory/scarf/purple = 1,
+ /obj/item/clothing/accessory/scarf/yellow = 1,
+ /obj/item/clothing/accessory/scarf/orange = 1,
+ /obj/item/clothing/accessory/scarf/lightblue = 1,
+ /obj/item/clothing/accessory/scarf/white = 1,
+ /obj/item/clothing/accessory/scarf/black = 1,
+ /obj/item/clothing/accessory/scarf/zebra = 1,
+ /obj/item/clothing/accessory/scarf/christmas = 1,
+ /obj/item/clothing/accessory/stripedredscarf = 1,
+ /obj/item/clothing/accessory/stripedbluescarf = 1,
+ /obj/item/clothing/accessory/stripedgreenscarf = 1,
+ /obj/item/clothing/accessory/waistcoat = 1,
+ /obj/item/clothing/under/dress/sundress = 2,
+ /obj/item/clothing/under/dress/stripeddress = 1,
+ /obj/item/clothing/under/dress/sailordress = 1,
+ /obj/item/clothing/under/dress/redeveninggown = 1,
+ /obj/item/clothing/under/dress/blacktango = 1,
+ /obj/item/clothing/suit/jacket = 3,
+ /obj/item/clothing/suit/jacket/motojacket = 3,
+ /obj/item/clothing/glasses/regular = 2,
+ /obj/item/clothing/glasses/sunglasses_fake = 2,
+ /obj/item/clothing/head/sombrero = 1,
+ /obj/item/clothing/suit/poncho = 1,
+ /obj/item/clothing/suit/ianshirt = 1,
+ /obj/item/clothing/shoes/laceup = 2,
+ /obj/item/clothing/shoes/black = 4,
+ /obj/item/clothing/shoes/sandal = 1,
+ /obj/item/clothing/gloves/fingerless = 2,
+ /obj/item/storage/belt/fannypack = 1,
+ /obj/item/storage/belt/fannypack/blue = 1,
+ /obj/item/storage/belt/fannypack/red = 1,
+ /obj/item/clothing/suit/mantle = 2,
+ /obj/item/clothing/suit/mantle/old = 1,
+ /obj/item/clothing/suit/mantle/regal = 2)
+
+ contraband = list(/obj/item/clothing/under/syndicate/tacticool = 1,
+ /obj/item/clothing/mask/balaclava = 1,
+ /obj/item/clothing/head/ushanka = 1,
+ /obj/item/clothing/under/costume/soviet = 1,
+ /obj/item/storage/belt/fannypack/black = 1)
+
+ premium = list(/obj/item/clothing/under/suit/checkered = 1,
+ /obj/item/clothing/head/mailman = 1,
+ /obj/item/clothing/under/misc/mailman = 1,
+ /obj/item/clothing/suit/jacket/leather = 1,
+ /obj/item/clothing/under/pants/mustangjeans = 1)
+
+ prices = list(/obj/item/clothing/head/that = 20,
+ /obj/item/clothing/head/fedora = 20,
+ /obj/item/clothing/glasses/monocle = 20,
+ /obj/item/clothing/under/suit/navy = 75,
+ /obj/item/clothing/under/costume/kilt = 75,
+ /obj/item/clothing/under/misc/overalls = 75,
+ /obj/item/clothing/under/suit/really_black = 75,
+ /obj/item/clothing/suit/storage/lawyer/blackjacket = 75,
+ /obj/item/clothing/under/pants/jeans = 75,
+ /obj/item/clothing/under/pants/classicjeans = 75,
+ /obj/item/clothing/under/pants/camo = 75,
+ /obj/item/clothing/under/pants/blackjeans = 75,
+ /obj/item/clothing/under/pants/khaki = 75,
+ /obj/item/clothing/under/pants/white = 75,
+ /obj/item/clothing/under/pants/red = 75,
+ /obj/item/clothing/under/pants/black = 75,
+ /obj/item/clothing/under/pants/tan = 75,
+ /obj/item/clothing/under/pants/blue = 75,
+ /obj/item/clothing/under/pants/track = 75,
+ /obj/item/clothing/suit/jacket/miljacket = 75,
+ /obj/item/clothing/head/beanie = 30,
+ /obj/item/clothing/head/beanie/black = 30,
+ /obj/item/clothing/head/beanie/red = 30,
+ /obj/item/clothing/head/beanie/green = 30,
+ /obj/item/clothing/head/beanie/darkblue = 30,
+ /obj/item/clothing/head/beanie/purple = 30,
+ /obj/item/clothing/head/beanie/yellow = 30,
+ /obj/item/clothing/head/beanie/orange = 30,
+ /obj/item/clothing/head/beanie/cyan = 30,
+ /obj/item/clothing/head/beanie/christmas = 40,
+ /obj/item/clothing/head/beanie/striped = 40,
+ /obj/item/clothing/head/beanie/stripedred = 40,
+ /obj/item/clothing/head/beanie/stripedblue = 40,
+ /obj/item/clothing/head/beanie/stripedgreen = 40,
+ /obj/item/clothing/head/beanie/rasta = 40,
+ /obj/item/clothing/accessory/scarf/red = 20,
+ /obj/item/clothing/accessory/scarf/green = 20,
+ /obj/item/clothing/accessory/scarf/darkblue = 20,
+ /obj/item/clothing/accessory/scarf/purple = 20,
+ /obj/item/clothing/accessory/scarf/yellow = 20,
+ /obj/item/clothing/accessory/scarf/orange = 20,
+ /obj/item/clothing/accessory/scarf/lightblue = 20,
+ /obj/item/clothing/accessory/scarf/white = 20,
+ /obj/item/clothing/accessory/scarf/black = 20,
+ /obj/item/clothing/accessory/scarf/zebra = 20,
+ /obj/item/clothing/accessory/scarf/christmas = 20,
+ /obj/item/clothing/accessory/stripedredscarf = 20,
+ /obj/item/clothing/accessory/stripedbluescarf = 20,
+ /obj/item/clothing/accessory/stripedgreenscarf = 20,
+ /obj/item/clothing/accessory/waistcoat = 20,
+ /obj/item/clothing/under/dress/sundress = 75,
+ /obj/item/clothing/under/dress/stripeddress = 75,
+ /obj/item/clothing/under/dress/sailordress = 75,
+ /obj/item/clothing/under/dress/redeveninggown = 100,
+ /obj/item/clothing/under/dress/blacktango = 100,
+ /obj/item/clothing/suit/jacket = 75,
+ /obj/item/clothing/suit/jacket/motojacket = 75,
+ /obj/item/clothing/glasses/regular = 20,
+ /obj/item/clothing/glasses/sunglasses_fake = 20,
+ /obj/item/clothing/head/sombrero = 40,
+ /obj/item/clothing/suit/poncho = 75,
+ /obj/item/clothing/suit/ianshirt = 75,
+ /obj/item/clothing/shoes/laceup = 40,
+ /obj/item/clothing/shoes/black = 20,
+ /obj/item/clothing/shoes/sandal = 20,
+ /obj/item/clothing/gloves/fingerless = 10,
+ /obj/item/storage/belt/fannypack = 50,
+ /obj/item/storage/belt/fannypack/blue = 50,
+ /obj/item/storage/belt/fannypack/red = 50,
+ /obj/item/clothing/suit/mantle = 20,
+ /obj/item/clothing/suit/mantle/old = 20,
+ /obj/item/clothing/suit/mantle/regal = 30)
+
+ refill_canister = /obj/item/vending_refill/clothing
+
+/obj/machinery/economy/vending/magivend
+ name = "\improper MagiVend"
+ desc = "A magic vending machine."
+ icon_state = "MagiVend"
+ slogan_list = list("Sling spells the proper way with MagiVend!","Be your own Houdini! Use MagiVend!")
+ vend_delay = 15
+ vend_reply = "Have an enchanted evening!"
+ ads_list = list("FJKLFJSD","AJKFLBJAKL","1234 LOONIES LOL!",">MFW","Kill them fuckers!","GET DAT FUKKEN DISK","HONK!","EI NATH","Destroy the station!","Admin conspiracies since forever!","Space-time bending hardware!")
+ products = list(/obj/item/clothing/head/wizard = 1,
+ /obj/item/clothing/suit/wizrobe = 1,
+ /obj/item/clothing/head/wizard/red = 1,
+ /obj/item/clothing/suit/wizrobe/red = 1,
+ /obj/item/clothing/shoes/sandal = 1,
+ /obj/item/clothing/suit/wizrobe/clown = 1,
+ /obj/item/clothing/head/wizard/clown = 1,
+ /obj/item/clothing/mask/gas/clownwiz = 1,
+ /obj/item/clothing/shoes/clown_shoes/magical = 1,
+ /obj/item/clothing/suit/wizrobe/mime = 1,
+ /obj/item/clothing/head/wizard/mime = 1,
+ /obj/item/clothing/mask/gas/mime/wizard = 1,
+ /obj/item/clothing/shoes/sandal/marisa = 1,
+ /obj/item/twohanded/staff = 2)
+ contraband = list(/obj/item/reagent_containers/glass/bottle/wizarditis = 1)
+ armor = list(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 50)
+ resistance_flags = FIRE_PROOF
+
+/obj/machinery/economy/vending/autodrobe
+ name = "\improper AutoDrobe"
+ desc = "A vending machine for costumes."
+ icon_state = "theater"
+ icon_lightmask = "theater"
+ icon_deny = "theater_deny"
+ slogan_list = list("Dress for success!","Suited and booted!","It's show time!","Why leave style up to fate? Use AutoDrobe!")
+ vend_delay = 15
+ vend_reply = "Thank you for using AutoDrobe!"
+ products = list(/obj/item/clothing/suit/chickensuit = 1,
+ /obj/item/clothing/head/chicken = 1,
+ /obj/item/clothing/under/costume/gladiator = 1,
+ /obj/item/clothing/head/helmet/gladiator = 1,
+ /obj/item/clothing/under/misc/gimmick/rank/captain/suit = 1,
+ /obj/item/clothing/head/flatcap = 1,
+ /obj/item/clothing/suit/storage/labcoat/mad = 1,
+ /obj/item/clothing/glasses/gglasses = 1,
+ /obj/item/clothing/shoes/jackboots = 1,
+ /obj/item/clothing/under/dress/schoolgirl = 1,
+ /obj/item/clothing/under/dress/blackskirt = 1,
+ /obj/item/clothing/suit/toggle/owlwings = 1,
+ /obj/item/clothing/under/costume/owl = 1,
+ /obj/item/clothing/mask/gas/owl_mask = 1,
+ /obj/item/clothing/suit/toggle/owlwings/griffinwings = 1,
+ /obj/item/clothing/under/costume/griffin = 1,
+ /obj/item/clothing/shoes/griffin = 1,
+ /obj/item/clothing/head/griffin = 1,
+ /obj/item/clothing/accessory/waistcoat = 1,
+ /obj/item/clothing/under/suit/black = 1,
+ /obj/item/clothing/head/that =1,
+ /obj/item/clothing/under/costume/kilt = 1,
+ /obj/item/clothing/accessory/waistcoat = 1,
+ /obj/item/clothing/glasses/monocle =1,
+ /obj/item/clothing/head/bowlerhat = 1,
+ /obj/item/cane = 1,
+ /obj/item/clothing/under/misc/sl_suit = 1,
+ /obj/item/clothing/mask/fakemoustache = 1,
+ /obj/item/clothing/suit/bio_suit/plaguedoctorsuit = 1,
+ /obj/item/clothing/head/plaguedoctorhat = 1,
+ /obj/item/clothing/mask/gas/plaguedoctor = 1,
+ /obj/item/clothing/suit/apron = 1,
+ /obj/item/clothing/under/misc/waiter = 1,
+ /obj/item/clothing/suit/jacket/miljacket = 1,
+ /obj/item/clothing/suit/jacket/miljacket/white = 1,
+ /obj/item/clothing/suit/jacket/miljacket/desert = 1,
+ /obj/item/clothing/suit/jacket/miljacket/navy = 1,
+ /obj/item/clothing/under/costume/pirate = 1,
+ /obj/item/clothing/suit/pirate_brown = 1,
+ /obj/item/clothing/suit/pirate_black =1,
+ /obj/item/clothing/under/costume/pirate_rags =1,
+ /obj/item/clothing/head/pirate = 1,
+ /obj/item/clothing/head/bandana = 1,
+ /obj/item/clothing/head/bandana = 1,
+ /obj/item/clothing/under/costume/soviet = 1,
+ /obj/item/clothing/head/ushanka = 1,
+ /obj/item/clothing/suit/imperium_monk = 1,
+ /obj/item/clothing/mask/gas/cyborg = 1,
+ /obj/item/clothing/suit/holidaypriest = 1,
+ /obj/item/clothing/head/wizard/marisa/fake = 1,
+ /obj/item/clothing/suit/wizrobe/marisa/fake = 1,
+ /obj/item/clothing/under/dress/sundress = 1,
+ /obj/item/clothing/head/witchwig = 1,
+ /obj/item/twohanded/staff/broom = 1,
+ /obj/item/clothing/suit/wizrobe/fake = 1,
+ /obj/item/clothing/head/wizard/fake = 1,
+ /obj/item/twohanded/staff = 3,
+ /obj/item/clothing/mask/gas/clown_hat/sexy = 1,
+ /obj/item/clothing/under/rank/civilian/clown/sexy = 1,
+ /obj/item/clothing/mask/gas/sexymime = 1,
+ /obj/item/clothing/under/rank/civilian/mime/sexy = 1,
+ /obj/item/clothing/mask/face/bat = 1,
+ /obj/item/clothing/mask/face/bee = 1,
+ /obj/item/clothing/mask/face/bear = 1,
+ /obj/item/clothing/mask/face/raven = 1,
+ /obj/item/clothing/mask/face/jackal = 1,
+ /obj/item/clothing/mask/face/fox = 1,
+ /obj/item/clothing/mask/face/tribal = 1,
+ /obj/item/clothing/mask/face/rat = 1,
+ /obj/item/clothing/suit/apron/overalls = 1,
+ /obj/item/clothing/head/rabbitears =1,
+ /obj/item/clothing/head/sombrero = 1,
+ /obj/item/clothing/suit/poncho = 1,
+ /obj/item/clothing/suit/poncho/green = 1,
+ /obj/item/clothing/suit/poncho/red = 1,
+ /obj/item/clothing/accessory/blue = 1,
+ /obj/item/clothing/accessory/red = 1,
+ /obj/item/clothing/accessory/black = 1,
+ /obj/item/clothing/accessory/horrible = 1,
+ /obj/item/clothing/under/costume/maid = 1,
+ /obj/item/clothing/under/costume/janimaid = 1,
+ /obj/item/clothing/under/costume/jester = 1,
+ /obj/item/clothing/head/jester = 1,
+ /obj/item/clothing/under/pants/camo = 1,
+ /obj/item/clothing/mask/bandana = 1,
+ /obj/item/clothing/mask/bandana/black = 1,
+ /obj/item/clothing/shoes/singery = 1,
+ /obj/item/clothing/under/costume/singery = 1,
+ /obj/item/clothing/shoes/singerb = 1,
+ /obj/item/clothing/under/costume/singerb = 1,
+ /obj/item/clothing/suit/hooded/carp_costume = 1,
+ /obj/item/clothing/suit/hooded/bee_costume = 1,
+ /obj/item/clothing/suit/snowman = 1,
+ /obj/item/clothing/head/snowman = 1,
+ /obj/item/clothing/head/cueball = 1,
+ /obj/item/clothing/under/misc/scratch = 1,
+ /obj/item/clothing/under/dress/victdress = 1,
+ /obj/item/clothing/under/dress/victdress/red = 1,
+ /obj/item/clothing/suit/victcoat = 1,
+ /obj/item/clothing/suit/victcoat/red = 1,
+ /obj/item/clothing/under/suit/victsuit = 1,
+ /obj/item/clothing/under/suit/victsuit/redblk = 1,
+ /obj/item/clothing/under/suit/victsuit/red = 1,
+ /obj/item/clothing/suit/tailcoat = 1,
+ /obj/item/clothing/under/costume/tourist_suit = 1,
+ /obj/item/clothing/suit/draculacoat = 1,
+ /obj/item/clothing/head/zepelli = 1,
+ /obj/item/clothing/under/misc/redhawaiianshirt = 1,
+ /obj/item/clothing/under/misc/pinkhawaiianshirt = 1,
+ /obj/item/clothing/under/misc/bluehawaiianshirt = 1,
+ /obj/item/clothing/under/misc/orangehawaiianshirt = 1)
+ contraband = list(/obj/item/clothing/suit/judgerobe = 1,
+ /obj/item/clothing/head/powdered_wig = 1,
+ /obj/item/gun/magic/wand = 1,
+ /obj/item/clothing/mask/balaclava=1,
+ /obj/item/clothing/mask/horsehead = 2)
+ premium = list(/obj/item/clothing/suit/hgpirate = 1,
+ /obj/item/clothing/head/hgpiratecap = 1,
+ /obj/item/clothing/head/helmet/roman/fake = 1,
+ /obj/item/clothing/head/helmet/roman/legionaire/fake = 1,
+ /obj/item/clothing/under/costume/roman = 1,
+ /obj/item/clothing/shoes/roman = 1,
+ /obj/item/shield/riot/roman/fake = 1,
+ /obj/item/clothing/under/costume/cuban_suit = 1,
+ /obj/item/clothing/head/cuban_hat = 1)
+
+ prices = list(/obj/item/clothing/suit/chickensuit = 100,
+ /obj/item/clothing/head/chicken = 50,
+ /obj/item/clothing/under/costume/gladiator = 100,
+ /obj/item/clothing/head/helmet/gladiator = 50,
+ /obj/item/clothing/under/misc/gimmick/rank/captain/suit = 100,
+ /obj/item/clothing/head/flatcap = 30,
+ /obj/item/clothing/suit/storage/labcoat/mad = 75,
+ /obj/item/clothing/glasses/gglasses = 20,
+ /obj/item/clothing/shoes/jackboots = 30,
+ /obj/item/clothing/under/dress/schoolgirl = 75,
+ /obj/item/clothing/under/dress/blackskirt = 75,
+ /obj/item/clothing/suit/toggle/owlwings = 120,
+ /obj/item/clothing/under/costume/owl = 100,
+ /obj/item/clothing/mask/gas/owl_mask = 50,
+ /obj/item/clothing/suit/toggle/owlwings/griffinwings = 120,
+ /obj/item/clothing/under/costume/griffin = 100,
+ /obj/item/clothing/shoes/griffin = 30,
+ /obj/item/clothing/head/griffin = 50,
+ /obj/item/clothing/under/suit/black = 75,
+ /obj/item/clothing/head/that = 30,
+ /obj/item/clothing/under/costume/kilt = 75,
+ /obj/item/clothing/accessory/waistcoat = 30,
+ /obj/item/clothing/glasses/monocle = 30,
+ /obj/item/clothing/head/bowlerhat = 20,
+ /obj/item/cane = 50,
+ /obj/item/clothing/under/misc/sl_suit = 75,
+ /obj/item/clothing/mask/fakemoustache = 20,
+ /obj/item/clothing/suit/bio_suit/plaguedoctorsuit = 100,
+ /obj/item/clothing/head/plaguedoctorhat = 40,
+ /obj/item/clothing/mask/gas/plaguedoctor = 50,
+ /obj/item/clothing/suit/apron = 75,
+ /obj/item/clothing/under/misc/waiter = 75,
+ /obj/item/clothing/suit/jacket/miljacket = 75,
+ /obj/item/clothing/suit/jacket/miljacket/white = 75,
+ /obj/item/clothing/suit/jacket/miljacket/desert = 75,
+ /obj/item/clothing/suit/jacket/miljacket/navy = 75,
+ /obj/item/clothing/under/costume/pirate = 100,
+ /obj/item/clothing/suit/pirate_brown = 100,
+ /obj/item/clothing/suit/pirate_black = 100,
+ /obj/item/clothing/under/costume/pirate_rags = 100,
+ /obj/item/clothing/head/pirate = 50,
+ /obj/item/clothing/head/bandana = 50,
+ /obj/item/clothing/head/bandana = 50,
+ /obj/item/clothing/under/costume/soviet = 100,
+ /obj/item/clothing/head/ushanka = 50,
+ /obj/item/clothing/suit/imperium_monk = 120,
+ /obj/item/clothing/mask/gas/cyborg = 50,
+ /obj/item/clothing/suit/holidaypriest = 100,
+ /obj/item/clothing/head/wizard/marisa/fake = 50,
+ /obj/item/clothing/suit/wizrobe/marisa/fake = 100,
+ /obj/item/clothing/under/dress/sundress = 75,
+ /obj/item/clothing/head/witchwig = 50,
+ /obj/item/twohanded/staff/broom = 50,
+ /obj/item/clothing/suit/wizrobe/fake = 75,
+ /obj/item/clothing/head/wizard/fake = 75,
+ /obj/item/twohanded/staff = 50,
+ /obj/item/clothing/mask/gas/clown_hat/sexy = 100,
+ /obj/item/clothing/under/rank/civilian/clown/sexy = 100,
+ /obj/item/clothing/mask/gas/sexymime = 100,
+ /obj/item/clothing/under/rank/civilian/mime/sexy = 100,
+ /obj/item/clothing/mask/face/bat = 100,
+ /obj/item/clothing/mask/face/bee = 100,
+ /obj/item/clothing/mask/face/bear = 100,
+ /obj/item/clothing/mask/face/raven = 100,
+ /obj/item/clothing/mask/face/jackal = 100,
+ /obj/item/clothing/mask/face/fox = 100,
+ /obj/item/clothing/mask/face/tribal = 100,
+ /obj/item/clothing/mask/face/rat = 100,
+ /obj/item/clothing/suit/apron/overalls = 75,
+ /obj/item/clothing/head/rabbitears = 75,
+ /obj/item/clothing/head/sombrero = 40,
+ /obj/item/clothing/suit/poncho = 75,
+ /obj/item/clothing/suit/poncho/green = 75,
+ /obj/item/clothing/suit/poncho/red = 75,
+ /obj/item/clothing/accessory/blue = 20,
+ /obj/item/clothing/accessory/red = 20,
+ /obj/item/clothing/accessory/black = 20,
+ /obj/item/clothing/accessory/horrible = 30,
+ /obj/item/clothing/under/costume/maid = 75,
+ /obj/item/clothing/under/costume/janimaid = 75,
+ /obj/item/clothing/under/costume/jester = 100,
+ /obj/item/clothing/head/jester = 50,
+ /obj/item/clothing/under/pants/camo = 75,
+ /obj/item/clothing/mask/bandana = 30,
+ /obj/item/clothing/mask/bandana/black = 30,
+ /obj/item/clothing/shoes/singery = 20,
+ /obj/item/clothing/under/costume/singery = 75,
+ /obj/item/clothing/shoes/singerb = 20,
+ /obj/item/clothing/under/costume/singerb = 75,
+ /obj/item/clothing/suit/hooded/carp_costume = 120,
+ /obj/item/clothing/suit/hooded/bee_costume = 120,
+ /obj/item/clothing/suit/snowman = 120,
+ /obj/item/clothing/head/snowman = 50,
+ /obj/item/clothing/head/cueball = 50,
+ /obj/item/clothing/under/misc/scratch = 75,
+ /obj/item/clothing/under/dress/victdress = 100,
+ /obj/item/clothing/under/dress/victdress/red = 100,
+ /obj/item/clothing/suit/victcoat = 100,
+ /obj/item/clothing/suit/victcoat/red = 100,
+ /obj/item/clothing/under/suit/victsuit = 100,
+ /obj/item/clothing/under/suit/victsuit/redblk = 100,
+ /obj/item/clothing/under/suit/victsuit/red = 100,
+ /obj/item/clothing/suit/tailcoat = 75,
+ /obj/item/clothing/under/costume/tourist_suit = 75,
+ /obj/item/clothing/suit/draculacoat = 100,
+ /obj/item/clothing/head/zepelli = 50,
+ /obj/item/clothing/under/misc/redhawaiianshirt = 75,
+ /obj/item/clothing/under/misc/pinkhawaiianshirt = 75,
+ /obj/item/clothing/under/misc/bluehawaiianshirt = 75,
+ /obj/item/clothing/under/misc/orangehawaiianshirt = 75)
+ refill_canister = /obj/item/vending_refill/autodrobe
+
+
+//Generic food vendors
+
+
+/obj/machinery/economy/vending/sustenance
+ name = "\improper Sustenance Vendor"
+ desc = "A vending machine which vends food, as required by section 47-C of the NT's Prisoner Ethical Treatment Agreement."
+ slogan_list = list("Enjoy your meal.","Enough calories to support strenuous labor.")
+ ads_list = list("The healthiest!","Award-winning chocolate bars!","Mmm! So good!","Oh my god it's so juicy!","Have a snack.","Snacks are good for you!","Have some more Getmore!","Best quality snacks straight from mars.","We love chocolate!","Try our new jerky!")
+ icon_state = "sustenance"
+ icon_lightmask = "nutri"
+ icon_off = "nutri"
+ icon_panel = "thin_vendor"
+ products = list(/obj/item/reagent_containers/food/snacks/tofu = 24,
+ /obj/item/reagent_containers/food/drinks/ice = 12,
+ /obj/item/reagent_containers/food/snacks/candy/candy_corn = 6)
+ contraband = list(/obj/item/kitchen/knife = 6,
+ /obj/item/reagent_containers/food/drinks/coffee = 12,
+ /obj/item/tank/internals/emergency_oxygen = 6,
+ /obj/item/clothing/mask/breath = 6)
+ refill_canister = /obj/item/vending_refill/sustenance
+
+/obj/machinery/economy/vending/sovietsoda
+ name = "\improper BODA"
+ desc = "Old sweet water vending machine."
+ icon_state = "sovietsoda"
+ icon_lightmask = "sovietsoda"
+ ads_list = list("For Tsar and Country.","Have you fulfilled your nutrition quota today?","Very nice!","We are simple people, for this is all we eat.","If there is a person, there is a problem. If there is no person, then there is no problem.")
+ products = list(/obj/item/reagent_containers/food/drinks/drinkingglass/soda = 30)
+ contraband = list(/obj/item/reagent_containers/food/drinks/drinkingglass/cola = 20)
+ resistance_flags = FIRE_PROOF
+ refill_canister = /obj/item/vending_refill/sovietsoda
+
+/obj/machinery/economy/vending/snack
+ name = "\improper Getmore Chocolate Corp"
+ desc = "A snack machine courtesy of the Getmore Chocolate Corporation, based out of Mars."
+ slogan_list = list("Try our new nougat bar!","Twice the calories for half the price!")
+ ads_list = list("The healthiest!","Award-winning chocolate bars!","Mmm! So good!","Oh my god it's so juicy!","Have a snack.","Snacks are good for you!","Have some more Getmore!","Best quality snacks straight from mars.","We love chocolate!","Try our new jerky!")
+ icon_state = "snack"
+ icon_lightmask = "nutri"
+ icon_off = "nutri"
+ icon_panel = "thin_vendor"
+ products = list(/obj/item/reagent_containers/food/snacks/candy/candybar = 6, /obj/item/reagent_containers/food/drinks/dry_ramen = 6, /obj/item/reagent_containers/food/snacks/chips = 6,
+ /obj/item/reagent_containers/food/snacks/sosjerky = 6,/obj/item/reagent_containers/food/snacks/no_raisin = 6, /obj/item/reagent_containers/food/snacks/pistachios = 6,
+ /obj/item/reagent_containers/food/snacks/spacetwinkie = 6, /obj/item/reagent_containers/food/snacks/cheesiehonkers = 6, /obj/item/reagent_containers/food/snacks/tastybread = 6)
+ premium = list(/obj/item/reagent_containers/food/snacks/stroopwafel = 2)
+ contraband = list(/obj/item/reagent_containers/food/snacks/syndicake = 6)
+ prices = list(/obj/item/reagent_containers/food/snacks/candy/candybar = 64, /obj/item/reagent_containers/food/drinks/dry_ramen = 32, /obj/item/reagent_containers/food/snacks/chips = 64,
+ /obj/item/reagent_containers/food/snacks/sosjerky = 64, /obj/item/reagent_containers/food/snacks/no_raisin = 80, /obj/item/reagent_containers/food/snacks/pistachios = 80,
+ /obj/item/reagent_containers/food/snacks/spacetwinkie = 64, /obj/item/reagent_containers/food/snacks/cheesiehonkers = 64,/obj/item/reagent_containers/food/snacks/tastybread = 80,
+ /obj/item/reagent_containers/food/snacks/syndicake = 175) //syndicakes are genuinely kind of powerful
+ refill_canister = /obj/item/vending_refill/snack
+
+/obj/machinery/economy/vending/snack/free
+ prices = list()
+
+/obj/machinery/economy/vending/chinese
+ name = "\improper Mr. Chang"
+ desc = "A self-serving Chinese food machine, for all your Chinese food needs."
+ slogan_list = list("Taste 5000 years of culture!","Mr. Chang, approved for safe consumption in over 10 sectors!","Chinese food is great for a date night, or a lonely night!","You can't go wrong with Mr. Chang's authentic Chinese food!")
+ icon_state = "chang"
+ icon_lightmask = "chang"
+ products = list(/obj/item/reagent_containers/food/snacks/chinese/chowmein = 6, /obj/item/reagent_containers/food/snacks/chinese/tao = 6, /obj/item/reagent_containers/food/snacks/chinese/sweetsourchickenball = 6, /obj/item/reagent_containers/food/snacks/chinese/newdles = 6,
+ /obj/item/reagent_containers/food/snacks/chinese/rice = 6, /obj/item/reagent_containers/food/snacks/fortunecookie = 6)
+ prices = list(/obj/item/reagent_containers/food/snacks/chinese/chowmein = 125, /obj/item/reagent_containers/food/snacks/chinese/tao = 125, /obj/item/reagent_containers/food/snacks/chinese/sweetsourchickenball = 125, /obj/item/reagent_containers/food/snacks/chinese/newdles = 100,
+ /obj/item/reagent_containers/food/snacks/chinese/rice = 100, /obj/item/reagent_containers/food/snacks/fortunecookie = 50)
+ refill_canister = /obj/item/vending_refill/chinese
+
+/obj/machinery/economy/vending/chinese/free
+ prices = list()
+
+/obj/machinery/economy/vending/cola
+ name = "\improper Robust Softdrinks"
+ desc = "A soft drink vendor provided by Robust Industries, LLC."
+ icon_state = "Cola_Machine"
+ icon_lightmask = "Cola_Machine"
+ icon_panel = "thin_vendor"
+ slogan_list = list("Robust Softdrinks: More robust than a toolbox to the head!")
+ ads_list = list("Refreshing!","Hope you're thirsty!","Over 1 million drinks sold!","Thirsty? Why not cola?","Please, have a drink!","Drink up!","The best drinks in space.")
+ products = list(/obj/item/reagent_containers/food/drinks/cans/cola = 10, /obj/item/reagent_containers/food/drinks/cans/space_mountain_wind = 10,
+ /obj/item/reagent_containers/food/drinks/cans/dr_gibb = 10, /obj/item/reagent_containers/food/drinks/cans/starkist = 10,
+ /obj/item/reagent_containers/food/drinks/cans/space_up = 10, /obj/item/reagent_containers/food/drinks/cans/grape_juice = 10)
+ contraband = list(/obj/item/reagent_containers/food/drinks/cans/thirteenloko = 5)
+ prices = list(/obj/item/reagent_containers/food/drinks/cans/cola = 45, /obj/item/reagent_containers/food/drinks/cans/space_mountain_wind = 50,
+ /obj/item/reagent_containers/food/drinks/cans/dr_gibb = 50, /obj/item/reagent_containers/food/drinks/cans/starkist = 50,
+ /obj/item/reagent_containers/food/drinks/cans/space_up = 50, /obj/item/reagent_containers/food/drinks/cans/grape_juice = 50)
+ refill_canister = /obj/item/vending_refill/cola
+
+/obj/machinery/economy/vending/cola/free
+ prices = list()
+
+///General miscellanious vendors
+/obj/machinery/economy/vending/artvend
+ name = "\improper ArtVend"
+ desc = "A vending machine for art supplies."
+ slogan_list = list("Stop by for all your artistic needs!","Color the floors with crayons, not blood!","Don't be a starving artist, use ArtVend. ","Don't fart, do art!")
+ ads_list = list("Just like Kindergarten!","Now with 1000% more vibrant colors!","Screwing with the janitor was never so easy!","Creativity is at the heart of every spessman.")
+ vend_delay = 15
+ icon_state = "artvend"
+ icon_lightmask = "artvend"
+ icon_panel = "screen_vendor"
+ products = list(/obj/item/stack/cable_coil/random = 10, /obj/item/camera = 4, /obj/item/camera_film = 6,
+ /obj/item/storage/photo_album = 2, /obj/item/stack/wrapping_paper = 4, /obj/item/stack/tape_roll = 5, /obj/item/stack/packageWrap = 4,
+ /obj/item/storage/fancy/crayons = 4, /obj/item/hand_labeler = 4, /obj/item/paper = 10,
+ /obj/item/c_tube = 10, /obj/item/pen = 5, /obj/item/pen/blue = 5,
+ /obj/item/pen/red = 5)
+ contraband = list(/obj/item/toy/crayon/mime = 1,/obj/item/toy/crayon/rainbow = 1)
+ premium = list(/obj/item/poster/random_contraband = 5)
+
+/obj/machinery/economy/vending/tool
+ name = "\improper YouTool"
+ desc = "Tools for tools."
+ icon_state = "tool"
+ icon_deny = "tool_deny"
+ icon_lightmask = "tool"
+ icon_panel = "generic"
+ armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 70)
+ resistance_flags = FIRE_PROOF
+ products = list(/obj/item/stack/cable_coil/random = 10, /obj/item/crowbar = 5, /obj/item/weldingtool = 3, /obj/item/wirecutters = 5,
+ /obj/item/wrench = 5, /obj/item/analyzer = 5, /obj/item/t_scanner = 5, /obj/item/screwdriver = 5)
+ contraband = list(/obj/item/weldingtool/hugetank = 2, /obj/item/clothing/gloves/color/fyellow = 2)
+ premium = list(/obj/item/clothing/gloves/color/yellow = 1)
+ prices = list(/obj/item/stack/cable_coil/random = 20, /obj/item/crowbar = 75, /obj/item/weldingtool = 100,
+ /obj/item/wirecutters = 50, /obj/item/wrench = 75,/obj/item/analyzer = 25, /obj/item/t_scanner = 25,
+ /obj/item/screwdriver = 50, /obj/item/weldingtool/hugetank = 120)
+ refill_canister = /obj/item/vending_refill/youtool
+
+/// we want a free version for engineering to use
+/obj/machinery/economy/vending/tool/free
+ prices = list()
+ desc = "Free Tools for tools."
+
+/obj/machinery/economy/vending/crittercare
+ name = "\improper CritterCare"
+ desc = "A vending machine for pet supplies."
+ slogan_list = list("Stop by for all your animal's needs!","Cuddly pets deserve a stylish collar!","Pets in space, what could be more adorable?","Freshest fish eggs in the system!","Rocks are the perfect pet, buy one today!")
+ ads_list = list("House-training costs extra!","Now with 1000% more cat hair!","Allergies are a sign of weakness!","Dogs are man's best friend. Remember that Vulpkanin!"," Heat lamps for Unathi!"," Vox-y want a cracker?")
+ vend_delay = 15
+ icon_state = "crittercare"
+ icon_lightmask = "crittercare"
+ icon_panel = "drobe"
+ products = list(/obj/item/petcollar = 5, /obj/item/storage/firstaid/aquatic_kit/full =5, /obj/item/fish_eggs/goldfish = 5,
+ /obj/item/fish_eggs/clownfish = 5, /obj/item/fish_eggs/shark = 5, /obj/item/fish_eggs/feederfish = 10,
+ /obj/item/fish_eggs/salmon = 5, /obj/item/fish_eggs/catfish = 5, /obj/item/fish_eggs/glofish = 5,
+ /obj/item/fish_eggs/electric_eel = 5, /obj/item/fish_eggs/shrimp = 10, /obj/item/toy/pet_rock = 5,
+ )
+ prices = list(/obj/item/petcollar = 75, /obj/item/storage/firstaid/aquatic_kit/full = 50, /obj/item/fish_eggs/goldfish = 10,
+ /obj/item/fish_eggs/clownfish = 30, /obj/item/fish_eggs/shark = 30, /obj/item/fish_eggs/feederfish = 20,
+ /obj/item/fish_eggs/salmon = 30, /obj/item/fish_eggs/catfish = 30, /obj/item/fish_eggs/glofish = 10,
+ /obj/item/fish_eggs/electric_eel = 30, /obj/item/fish_eggs/shrimp = 10, /obj/item/toy/pet_rock = 50,
+ )
+ contraband = list(/obj/item/fish_eggs/babycarp = 5)
+ premium = list(/obj/item/toy/pet_rock/fred = 1, /obj/item/toy/pet_rock/roxie = 1)
+ refill_canister = /obj/item/vending_refill/crittercare
+
+/obj/machinery/economy/vending/crittercare/free
+ prices = list()
+
+/obj/machinery/economy/vending/cigarette
+ name = "\improper ShadyCigs Deluxe"
+ desc = "If you want to get cancer, might as well do it in style."
+ slogan_list = list("Space cigs taste good like a cigarette should.","I'd rather toolbox than switch.","Smoke!","Don't believe the reports - smoke today!")
+ ads_list = list("Probably not bad for you!","Don't believe the scientists!","It's good for you!","Don't quit, buy more!","Smoke!","Nicotine heaven.","Best cigarettes since 2150.","Award-winning cigs.")
+ vend_delay = 34
+ icon_state = "cigs"
+ icon_lightmask = "cigs"
+ products = list(
+ /obj/item/storage/fancy/cigarettes/cigpack_robust = 12,
+ /obj/item/storage/fancy/cigarettes/cigpack_uplift = 6,
+ /obj/item/storage/fancy/cigarettes/cigpack_midori = 6,
+ /obj/item/storage/fancy/cigarettes/cigpack_random = 6,
+ /obj/item/reagent_containers/food/pill/patch/nicotine = 10,
+ /obj/item/storage/box/matches = 10,
+ /obj/item/lighter/random = 4,
+ /obj/item/storage/fancy/rollingpapers = 5)
+ contraband = list(/obj/item/lighter/zippo = 4)
+ premium = list(/obj/item/clothing/mask/cigarette/cigar/havana = 2,
+ /obj/item/storage/fancy/cigarettes/cigpack_robustgold = 1)
+ prices = list(/obj/item/storage/fancy/cigarettes/cigpack_robust = 25,
+ /obj/item/storage/fancy/cigarettes/cigpack_uplift = 35,
+ /obj/item/storage/fancy/cigarettes/cigpack_midori = 60,
+ /obj/item/storage/fancy/cigarettes/cigpack_random = 80,
+ /obj/item/reagent_containers/food/pill/patch/nicotine = 70,
+ /obj/item/storage/box/matches = 20,
+ /obj/item/lighter/random = 40,
+ /obj/item/storage/fancy/rollingpapers = 30)
+ refill_canister = /obj/item/vending_refill/cigarette
+
+/obj/machinery/economy/vending/cigarette/free
+ prices = list()
+
+/obj/machinery/economy/vending/cigarette/syndicate
+ products = list(/obj/item/storage/fancy/cigarettes/cigpack_syndicate = 7,
+ /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3,
+ /obj/item/storage/fancy/cigarettes/cigpack_robust = 2,
+ /obj/item/storage/fancy/cigarettes/cigpack_carp = 3,
+ /obj/item/storage/fancy/cigarettes/cigpack_midori = 1,
+ /obj/item/storage/box/matches = 10,
+ /obj/item/lighter/zippo = 4,
+ /obj/item/storage/fancy/rollingpapers = 5)
+
+/obj/machinery/economy/vending/cigarette/syndicate/free
+ prices = list()
+
+/obj/machinery/economy/vending/cigarette/beach //Used in the lavaland_biodome_beach.dmm ruin
+ name = "\improper ShadyCigs Ultra"
+ desc = "Now with extra premium products!"
+ ads_list = list("Probably not bad for you!","Dope will get you through times of no money better than money will get you through times of no dope!","It's good for you!")
+ slogan_list = list("Turn on, tune in, drop out!","Better living through chemistry!","Toke!","Don't forget to keep a smile on your lips and a song in your heart!")
+ products = list(/obj/item/storage/fancy/cigarettes = 5,
+ /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3,
+ /obj/item/storage/fancy/cigarettes/cigpack_robust = 3,
+ /obj/item/storage/fancy/cigarettes/cigpack_carp = 3,
+ /obj/item/storage/fancy/cigarettes/cigpack_midori = 3,
+ /obj/item/storage/box/matches = 10,
+ /obj/item/lighter/random = 4,
+ /obj/item/storage/fancy/rollingpapers = 5)
+ premium = list(/obj/item/clothing/mask/cigarette/cigar/havana = 2,
+ /obj/item/storage/fancy/cigarettes/cigpack_robustgold = 1,
+ /obj/item/lighter/zippo = 3)
+ prices = list()
+
+/obj/machinery/economy/vending/wallmed
+ name = "\improper NanoMed"
+ desc = "Wall-mounted Medical Equipment dispenser."
+ ads_list = list("Go save some lives!","The best stuff for your medbay.","Only the finest tools.","Natural chemicals!","This stuff saves lives.","Don't you want some?")
+ icon_state = "wallmed"
+ icon_deny = "wallmed_deny"
+ icon_lightmask = "wallmed"
+ icon_panel = "wallmed"
+ icon_broken = "wallmed"
+ density = FALSE //It is wall-mounted, and thus, not dense. --Superxpdude
+ products = list(/obj/item/stack/medical/bruise_pack = 2, /obj/item/stack/medical/ointment = 2, /obj/item/reagent_containers/hypospray/autoinjector = 4, /obj/item/healthanalyzer = 1)
+ contraband = list(/obj/item/reagent_containers/syringe/charcoal = 4, /obj/item/reagent_containers/syringe/antiviral = 4, /obj/item/reagent_containers/food/pill/tox = 1)
+ armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 70)
+ //this shouldn't be priced
+ resistance_flags = FIRE_PROOF
+ refill_canister = /obj/item/vending_refill/wallmed
+
+/obj/machinery/economy/vending/cart
+ name = "\improper PTech"
+ desc = "Cartridges for PDA's."
+ slogan_list = list("Carts to go!")
+ icon_state = "cart"
+ icon_lightmask = "med"
+ icon_deny = "cart_deny"
+ icon_panel = "wide_vendor"
+ products = list(/obj/item/pda =10,/obj/item/cartridge/mob_hunt_game = 25, /obj/item/cartridge/medical = 10, /obj/item/cartridge/chemistry = 10,
+ /obj/item/cartridge/engineering = 10, /obj/item/cartridge/atmos = 10, /obj/item/cartridge/janitor = 10,
+ /obj/item/cartridge/signal/toxins = 10, /obj/item/cartridge/signal = 10)
+ contraband = list(/obj/item/cartridge/clown = 1,/obj/item/cartridge/mime = 1)
+ prices = list(/obj/item/pda = 300, /obj/item/cartridge/mob_hunt_game = 50, /obj/item/cartridge/medical = 200,
+ /obj/item/cartridge/chemistry = 150,/obj/item/cartridge/engineering = 100, /obj/item/cartridge/atmos = 75,
+ /obj/item/cartridge/janitor = 100,/obj/item/cartridge/signal/toxins = 150, /obj/item/cartridge/signal = 75)
+ refill_canister = /obj/item/vending_refill/cart
+
+/obj/machinery/economy/vending/cart/free
+ prices = list()
diff --git a/code/game/machinery/vendors/vending.dm b/code/game/machinery/vendors/vending.dm
new file mode 100644
index 00000000000..4ca8cd4dbaf
--- /dev/null
+++ b/code/game/machinery/vendors/vending.dm
@@ -0,0 +1,885 @@
+/**
+ * Datum used to hold information about a product in a vending machine
+ */
+/datum/data/vending_product
+ name = "generic"
+ ///Typepath of the product that is created when this record "sells"
+ var/product_path = null
+ ///How many of this product we currently have
+ var/amount = 0
+ ///How many we can store at maximum
+ var/max_amount = 0
+ /// Price to buy one
+ var/price = 0
+
+/obj/machinery/economy/vending
+ name = "\improper Vendomat"
+ desc = "A generic vending machine."
+ icon = 'icons/obj/vending.dmi'
+ icon_state = "generic"
+ layer = BELOW_OBJ_LAYER
+ anchored = TRUE
+ density = TRUE
+ face_while_pulling = TRUE
+ max_integrity = 300
+ integrity_failure = 100
+ armor = list(melee = 20, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70)
+
+ /// Icon_state when vending
+ var/icon_vend
+ /// Icon_state when denying access
+ var/icon_deny
+ /// Icon to be an overlay over the base sprite
+ var/icon_addon
+ /// Icon for the broken overlay, defaults to icon_state + _broken
+ var/icon_broken
+ /// Icon for the off overlay, defaults to icon_state + _off
+ var/icon_off
+ /// Icon for the panel overlay, defaults to icon_state + _panel
+ var/icon_panel
+ /// Icon for the lightmask, defaults to icon_state + _off, _lightmask if one is defined.
+ var/icon_lightmask
+ // Power
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 10
+ var/vend_power_usage = 150
+
+ var/light_range_on = 1
+ var/light_power_on = 0.5
+
+ // Vending-related
+ /// No sales pitches if off
+ var/active = TRUE
+ /// If off, vendor is busy and unusable until current action finishes
+ var/vend_ready = TRUE
+ /// How long vendor takes to vend one item.
+ var/vend_delay = 10
+ /// Item currently being bought
+ var/datum/data/vending_product/currently_vending
+
+ // To be filled out at compile time
+ var/list/products = list() // For each, use the following pattern:
+ var/list/contraband = list() // list(/type/path = amount,/type/path2 = amount2)
+ var/list/premium = list() // No specified amount = only one in stock
+ var/list/prices = list() // Prices for each item, list(/type/path = price), items not in the list don't have a price.
+
+ // List of vending_product items available.
+ var/list/product_records = list()
+ var/list/hidden_records = list()
+ var/list/coin_records = list()
+ var/list/imagelist = list()
+
+ /// Unimplemented list of ads that are meant to show up somewhere, but don't.
+ var/list/ads_list = list()
+
+ // Stuff relating vocalizations
+ /// List of slogans the vendor will say, optional
+ var/list/slogan_list = list()
+ var/vend_reply //Thank you for shopping!
+ /// If true, prevent saying sales pitches
+ var/shut_up = FALSE
+ ///can we access the hidden inventory?
+ var/extended_inventory = FALSE
+ var/last_reply = 0
+ var/last_slogan = 0 //When did we last pitch?
+ var/slogan_delay = 10 MINUTES //How long until we can pitch again?
+
+ //The type of refill canisters used by this machine.
+ var/obj/item/vending_refill/refill_canister
+
+ // Things that can go wrong
+ /// Allows people to access a vendor that's normally access restricted.
+ emagged = FALSE
+ /// Shocks people like an airlock
+ var/seconds_electrified = 0
+ /// Fire items at customers! We're broken!
+ var/shoot_inventory = FALSE
+ /// How hard are we firing the items?
+ var/shoot_speed = 3
+ /// How often are we firing the items? (prob(...))
+ var/shoot_chance = 2
+
+ /// If true, enforce access checks on customers. Disabled by messing with wires.
+ var/scan_id
+ /// Holder for a coin inserted into the vendor
+ var/obj/item/coin/coin
+ var/datum/wires/vending/wires
+
+ /// boolean, whether this vending machine can accept people inserting items into it, used for coffee vendors
+ var/item_slot = FALSE
+ /// the actual item inserted
+ var/obj/item/inserted_item
+
+ /// blocks further flickering while true
+ var/flickering = FALSE
+ /// do I look unpowered, even when powered?
+ var/force_no_power_icon_state = FALSE
+
+ ///the money account that is tethered to this vendor
+ var/datum/money_account/vendor_account
+
+/obj/machinery/economy/vending/Initialize(mapload)
+ . = ..()
+ var/build_inv = FALSE
+ if(!refill_canister)
+ build_inv = TRUE
+ else
+ component_parts = list()
+ var/obj/item/circuitboard/vendor/V = new
+ V.set_type(replacetext(initial(name), "\improper", ""))
+ component_parts += V
+ component_parts += new refill_canister
+ RefreshParts()
+
+ wires = new(src)
+ if(build_inv) //non-constructable vending machine
+ build_inventory(products, product_records)
+ build_inventory(contraband, hidden_records)
+ build_inventory(premium, coin_records)
+ for(var/datum/data/vending_product/R in (product_records + coin_records + hidden_records))
+ var/obj/item/I = R.product_path
+ var/pp = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-")
+ imagelist[pp] = "[icon2base64(icon(initial(I.icon), initial(I.icon_state), SOUTH, 1))]"
+
+ if(LAZYLEN(slogan_list))
+ // So not all machines speak at the exact same time.
+ // The first time this machine says something will be at slogantime + this random value,
+ // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is created.
+ last_slogan = world.time + rand(0, slogan_delay)
+
+ if(account_database)
+ vendor_account = account_database.vendor_account
+
+ reconnect_database()
+ power_change()
+
+/obj/machinery/economy/vending/Destroy()
+ SStgui.close_uis(wires)
+ QDEL_NULL(wires)
+ QDEL_NULL(coin)
+ QDEL_NULL(inserted_item)
+ return ..()
+
+/obj/machinery/economy/vending/RefreshParts() //Better would be to make constructable child
+ if(!component_parts)
+ return
+
+ product_records = list()
+ hidden_records = list()
+ coin_records = list()
+ if(refill_canister)
+ build_inventory(products, product_records, start_empty = TRUE)
+ build_inventory(contraband, hidden_records, start_empty = TRUE)
+ build_inventory(premium, coin_records, start_empty = TRUE)
+ for(var/obj/item/vending_refill/VR in component_parts)
+ restock(VR)
+
+/obj/machinery/economy/vending/update_overlays()
+ . = ..()
+ underlays.Cut()
+ if(panel_open)
+ . += "[icon_panel ? "[icon_panel]_panel" : "[icon_state]_panel"]"
+ if(icon_addon)
+ . += "[icon_addon]"
+ if((stat & (BROKEN|NOPOWER)) || force_no_power_icon_state)
+ . += "[icon_off ? "[icon_off]_off" : "[icon_state]_off"]"
+ if(stat & BROKEN)
+ . += "[icon_broken ? "[icon_broken]_broken" : "[icon_state]_broken"]"
+ return
+ if(light)
+ underlays += emissive_appearance(icon, "[icon_lightmask ? "[icon_lightmask]_lightmask" : "[icon_state]_off"]")
+
+/*
+ * Reimp, flash the screen on and off repeatedly.
+ */
+/obj/machinery/economy/vending/flicker()
+ if(flickering)
+ return FALSE
+
+ if(stat & (BROKEN|NOPOWER))
+ return FALSE
+
+ flickering = TRUE
+ INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/economy/vending, flicker_event))
+
+ return TRUE
+
+/*
+ * Proc to be called by invoke_async in the above flicker() proc.
+ */
+/obj/machinery/economy/vending/proc/flicker_event()
+ var/amount = rand(5, 15)
+
+ for(var/i in 1 to amount)
+ force_no_power_icon_state = TRUE
+ set_light(0)
+ update_icon(UPDATE_OVERLAYS)
+ sleep(rand(1, 3))
+
+ force_no_power_icon_state = FALSE
+ set_light(light_range_on, light_power_on)
+ update_icon(UPDATE_OVERLAYS)
+ sleep(rand(1, 10))
+ update_icon(UPDATE_OVERLAYS)
+ flickering = FALSE
+
+/**
+ * Build src.produdct_records from the products lists
+ *
+ * src.products, src.contraband, src.premium, and src.prices allow specifying
+ * products that the vending machine is to carry without manually populating
+ * src.product_records.
+ */
+/obj/machinery/economy/vending/proc/build_inventory(list/productlist, list/recordlist, start_empty = FALSE)
+ for(var/typepath in productlist)
+ var/amount = productlist[typepath]
+ if(isnull(amount))
+ amount = 0
+
+ var/atom/temp = typepath
+ var/datum/data/vending_product/R = new /datum/data/vending_product()
+ R.name = initial(temp.name)
+ R.product_path = typepath
+ if(!start_empty)
+ R.amount = amount
+ R.max_amount = amount
+ R.price = (typepath in prices) ? prices[typepath] : 0
+ recordlist += R
+/**
+ * Refill a vending machine from a refill canister
+ *
+ * This takes the products from the refill canister and then fills the products,contraband and premium product categories
+ *
+ * Arguments:
+ * * canister - the vending canister we are refilling from
+ */
+/obj/machinery/economy/vending/proc/restock(obj/item/vending_refill/canister)
+ if(!canister.products)
+ canister.products = products.Copy()
+ if(!canister.contraband)
+ canister.contraband = contraband.Copy()
+ if(!canister.premium)
+ canister.premium = premium.Copy()
+ . = 0
+ . += refill_inventory(canister.products, product_records)
+ . += refill_inventory(canister.contraband, hidden_records)
+ . += refill_inventory(canister.premium, coin_records)
+/**
+ * Refill our inventory from the passed in product list into the record list
+ *
+ * Arguments:
+ * * productlist - list of types -> amount
+ * * recordlist - existing record datums
+ */
+/obj/machinery/economy/vending/proc/refill_inventory(list/productlist, list/recordlist)
+ . = 0
+ for(var/datum/data/vending_product/record as anything in recordlist)
+ var/diff = min(record.max_amount - record.amount, productlist[record.product_path])
+ if(diff)
+ productlist[record.product_path] -= diff
+ record.amount += diff
+ . += diff
+/**
+ * Set up a refill canister that matches this machines products
+ *
+ * This is used when the machine is deconstructed, so the items aren't "lost"
+ */
+/obj/machinery/economy/vending/proc/update_canister()
+ if(!component_parts)
+ return
+
+ var/obj/item/vending_refill/R = locate() in component_parts
+ if(!R)
+ CRASH("Constructible vending machine did not have a refill canister")
+
+ R.products = unbuild_inventory(product_records)
+ R.contraband = unbuild_inventory(hidden_records)
+ R.premium = unbuild_inventory(coin_records)
+
+/**
+ * Given a record list, go through and and return a list of type -> amount
+ */
+/obj/machinery/economy/vending/proc/unbuild_inventory(list/recordlist)
+ . = list()
+ for(var/datum/data/vending_product/record as anything in recordlist)
+ .[record.product_path] += record.amount
+
+/obj/machinery/economy/vending/deconstruct(disassembled = TRUE)
+ eject_item()
+ if(!refill_canister) //the non constructable vendors drop metal instead of a machine frame.
+ new /obj/item/stack/sheet/metal(loc, 3)
+ qdel(src)
+ else
+ ..()
+
+/obj/machinery/economy/vending/attackby(obj/item/I, mob/user, params)
+ if(isspacecash(I))
+ insert_cash(I, user)
+ return
+ if(istype(I, /obj/item/coin))
+ if(!length(premium))
+ to_chat(user, "[src] does not accept coins.")
+ return
+ if(coin)
+ to_chat(user, "There is already a coin in this machine!")
+ return
+ if(!user.drop_item())
+ return
+ I.forceMove(src)
+ coin = I
+ to_chat(user, "You insert [I] into [src].")
+ SStgui.update_uis(src)
+ return
+ if(refill_canister && istype(I, refill_canister))
+ if(!panel_open)
+ to_chat(user, "You should probably unscrew the service panel first!")
+ else if (stat & (BROKEN|NOPOWER))
+ to_chat(user, "[src] does not respond.")
+ else
+ //if the panel is open we attempt to refill the machine
+ var/obj/item/vending_refill/canister = I
+ if(canister.get_part_rating() == 0)
+ to_chat(user, "[canister] is empty!")
+ else
+ // instantiate canister if needed
+ var/transferred = restock(canister)
+ if(transferred)
+ to_chat(user, "You loaded [transferred] items in [src].")
+ else
+ to_chat(user, "There's nothing to restock!")
+ return
+ if(item_slot_check(user, I))
+ insert_item(user, I)
+ return
+ return ..()
+
+
+
+/obj/machinery/economy/vending/crowbar_act(mob/user, obj/item/I)
+ if(!component_parts)
+ return
+ . = TRUE
+ default_deconstruction_crowbar(user, I)
+
+/obj/machinery/economy/vending/multitool_act(mob/user, obj/item/I)
+ . = TRUE
+ if(!I.use_tool(src, user, 0, volume = I.tool_volume))
+ return
+ wires.Interact(user)
+
+/obj/machinery/economy/vending/screwdriver_act(mob/user, obj/item/I)
+ . = TRUE
+ if(!I.use_tool(src, user, 0, volume = I.tool_volume))
+ return
+ if(!anchored)
+ return
+ panel_open = !panel_open
+
+ if(panel_open)
+ SCREWDRIVER_OPEN_PANEL_MESSAGE
+ else
+ SCREWDRIVER_CLOSE_PANEL_MESSAGE
+
+ update_icon(UPDATE_OVERLAYS)
+ SStgui.update_uis(src)
+
+/obj/machinery/economy/vending/wirecutter_act(mob/user, obj/item/I)
+ . = TRUE
+ if(I.use_tool(src, user, 0, volume = 0))
+ wires.Interact(user)
+
+/obj/machinery/economy/vending/wrench_act(mob/user, obj/item/I)
+ . = TRUE
+ if(!I.use_tool(src, user, 0, volume = 0))
+ return
+ default_unfasten_wrench(user, I, time = 6 SECONDS)
+
+//Override this proc to do per-machine checks on the inserted item, but remember to call the parent to handle these generic checks before your logic!
+/obj/machinery/economy/vending/proc/item_slot_check(mob/user, obj/item/I)
+ if(!item_slot)
+ return FALSE
+ if(inserted_item)
+ to_chat(user, "There is something already inserted!")
+ return FALSE
+ return TRUE
+
+/* Example override for item_slot_check proc:
+/obj/machinery/economy/vending/example/item_slot_check(mob/user, obj/item/I)
+ if(!..())
+ return FALSE
+ if(!istype(I, /obj/item/toy))
+ to_chat(user, "[I] isn't compatible with this machine's slot.")
+ return FALSE
+ return TRUE
+*/
+
+/obj/machinery/economy/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/W)
+ if(!istype(W))
+ return FALSE
+ if(!W.works_from_distance)
+ return FALSE
+ if(!component_parts || !refill_canister)
+ return FALSE
+
+ var/moved = 0
+ if(panel_open || W.works_from_distance)
+ if(W.works_from_distance)
+ to_chat(user, display_parts(user))
+ for(var/I in W)
+ if(istype(I, refill_canister))
+ moved += restock(I)
+ else
+ to_chat(user, display_parts(user))
+ if(moved)
+ to_chat(user, "[moved] items restocked.")
+ W.play_rped_sound()
+ return TRUE
+
+/obj/machinery/economy/vending/on_deconstruction()
+ update_canister()
+ . = ..()
+
+/obj/machinery/economy/vending/proc/insert_item(mob/user, obj/item/I)
+ if(!item_slot || inserted_item)
+ return
+ if(!user.drop_item())
+ to_chat(user, "[I] is stuck to your hand, you can't seem to put it down!")
+ return
+ inserted_item = I
+ I.forceMove(src)
+ to_chat(user, "You insert [I] into [src].")
+ SStgui.update_uis(src)
+
+/obj/machinery/economy/vending/proc/eject_item(mob/user)
+ if(!item_slot || !inserted_item)
+ return
+ var/put_on_turf = TRUE
+ if(user && iscarbon(user) && user.Adjacent(src))
+ if(user.put_in_hands(inserted_item))
+ put_on_turf = FALSE
+ if(put_on_turf)
+ var/turf/T = get_turf(src)
+ inserted_item.forceMove(T)
+ inserted_item = null
+ SStgui.update_uis(src)
+
+/obj/machinery/economy/vending/emag_act(mob/user)
+ emagged = TRUE
+ to_chat(user, "You short out the product lock on [src]")
+
+/obj/machinery/economy/vending/attack_ai(mob/user)
+ return attack_hand(user)
+
+/obj/machinery/economy/vending/attack_ghost(mob/user)
+ return attack_hand(user)
+
+/obj/machinery/economy/vending/attack_hand(mob/user)
+ if(stat & (BROKEN|NOPOWER))
+ return
+
+ if(seconds_electrified != 0 && shock(user, 100))
+ return
+
+ ui_interact(user)
+ wires.Interact(user)
+
+/obj/machinery/economy/vending/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ var/estimated_height = 100 + min(length(product_records) * 34, 500)
+ if(length(prices) > 0)
+ estimated_height += 100 // to account for the "current user" interface
+ ui = new(user, src, ui_key, "Vending", name, 470, estimated_height, master_ui, state)
+ ui.open()
+
+/obj/machinery/economy/vending/ui_data(mob/user)
+ var/list/data = list()
+
+ data["usermoney"] = 0
+ data["inserted_cash"] = cash_transaction
+ data["user"] = null
+ if(ishuman(user))
+ var/obj/item/card/id/C
+ var/mob/living/carbon/human/H = user
+ C = H.get_idcard(TRUE)
+ if(!C && istype(H.wear_pda, /obj/item/pda))
+ var/obj/item/pda/P = H.wear_pda
+ if(istype(P.id, /obj/item/card/id))
+ C = P.id
+ if(istype(C))
+ var/datum/money_account/A = C.get_card_account()
+ if(A)
+ data["user"] = list()
+ data["user"]["name"] = A.account_name
+ data["usermoney"] = A.credit_balance
+ data["user"]["job"] = C.rank ? C.rank : "No Job"
+
+ data["stock"] = list()
+ for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records)
+ data["stock"][R.name] = R.amount
+ data["extended_inventory"] = extended_inventory
+ data["vend_ready"] = vend_ready
+ data["coin_name"] = coin ? coin.name : FALSE
+ data["panel_open"] = panel_open ? TRUE : FALSE
+ data["speaker"] = shut_up ? FALSE : TRUE
+ data["item_slot"] = item_slot // boolean
+ data["inserted_item_name"] = inserted_item ? inserted_item.name : FALSE
+ return data
+
+
+/obj/machinery/economy/vending/ui_static_data(mob/user)
+ var/list/data = list()
+ data["chargesMoney"] = length(prices) > 0 ? TRUE : FALSE
+ data["product_records"] = list()
+ var/i = 1
+ for(var/datum/data/vending_product/R in product_records)
+ var/list/data_pr = list(
+ path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
+ name = R.name,
+ price = (R.product_path in prices) ? prices[R.product_path] : 0,
+ max_amount = R.max_amount,
+ req_coin = FALSE,
+ is_hidden = FALSE,
+ inum = i++
+ )
+ data["product_records"] += list(data_pr)
+ data["coin_records"] = list()
+ for (var/datum/data/vending_product/R in coin_records)
+ var/list/data_cr = list(
+ path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
+ name = R.name,
+ price = (R.product_path in prices) ? prices[R.product_path] : 0,
+ max_amount = R.max_amount,
+ req_coin = TRUE,
+ is_hidden = FALSE,
+ inum = i++,
+ premium = TRUE
+ )
+ data["coin_records"] += list(data_cr)
+ data["hidden_records"] = list()
+ for (var/datum/data/vending_product/R in hidden_records)
+ var/list/data_hr = list(
+ path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
+ name = R.name,
+ price = (R.product_path in prices) ? prices[R.product_path] : 0,
+ max_amount = R.max_amount,
+ req_coin = FALSE,
+ is_hidden = TRUE,
+ inum = i++,
+ premium = TRUE
+ )
+ data["hidden_records"] += list(data_hr)
+ data["imagelist"] = imagelist
+ return data
+
+/obj/machinery/economy/vending/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/living/user = ui.user
+
+ if(issilicon(user) && !isrobot(user))
+ to_chat(user, "The vending machine refuses to interface with you, as you are not in its target demographic!")
+ return
+ switch(action)
+ if("toggle_voice")
+ if(panel_open)
+ shut_up = !shut_up
+ . = TRUE
+ if("eject_item")
+ eject_item(user)
+ . = TRUE
+ if("remove_coin")
+ if(!coin)
+ to_chat(user, "There is no coin in this machine.")
+ return
+ if(istype(user, /mob/living/silicon))
+ to_chat(user, "You lack hands.")
+ return
+ to_chat(user, "You remove [coin] from [src].")
+ user.put_in_hands(coin)
+ coin = null
+ . = TRUE
+ if("change")
+ . = TRUE
+ give_change(user)
+ if("vend")
+ var/key = text2num(params["inum"])
+ try_vend(key, user)
+ if(.)
+ add_fingerprint(user)
+
+
+/obj/machinery/economy/vending/proc/try_vend(key, mob/user)
+ if(!vend_ready)
+ to_chat(user, "The vending machine is busy!")
+ return
+ if(panel_open)
+ to_chat(user, "The vending machine cannot dispense products while its service panel is open!")
+ return
+
+ var/list/display_records = product_records + coin_records
+ if(extended_inventory)
+ display_records = product_records + coin_records + hidden_records
+ if(key < 1 || key > length(display_records))
+ log_debug("invalid inum passed to a [name] vendor.")
+ return
+ var/datum/data/vending_product/R = display_records[key]
+ if(!istype(R))
+ log_debug("player attempted to access an unknown vending_product at a [name] vendor.")
+ return
+ var/list/record_to_check = product_records + coin_records
+ if(extended_inventory)
+ record_to_check = product_records + coin_records + hidden_records
+ if(!R.product_path)
+ log_debug("player attempted to access an unknown product record at a [name] vendor.")
+ return
+ if(R in hidden_records)
+ if(!extended_inventory)
+ // Exploit prevention, stop the user purchasing hidden stuff if they haven't hacked the machine.
+ log_debug("player attempted to access a [name] vendor extended inventory when it was not allowed.")
+ return
+ else if(!(R in record_to_check))
+ // Exploit prevention, stop the user
+ message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(user)]!")
+ return
+ if(R.amount <= 0)
+ to_chat(user, "Sold out of [R.name].")
+ flick(icon_deny, src)
+ return
+
+ vend_ready = FALSE // From this point onwards, vendor is locked to performing this transaction only, until it is resolved.
+
+ if(!ishuman(user) || R.price <= 0)
+ // Either the purchaser is not human, or the item is free.
+ // Skip all payment logic.
+ vend(R, user)
+ add_fingerprint(user)
+ vend_ready = TRUE
+ . = TRUE
+ return
+
+ // --- THE REST OF THIS PROC IS JUST PAYMENT LOGIC ---
+
+ var/mob/living/carbon/human/H = user
+ var/obj/item/card/id/C = H.get_idcard(TRUE)
+
+ currently_vending = R
+ var/paid = FALSE
+
+ if(cash_transaction < currently_vending.price && GLOB.station_money_database.vendor_account?.suspended)
+ to_chat(user, "Vendor account offline. Unable to process transaction.")
+ flick(icon_deny, src)
+ vend_ready = TRUE
+ return
+
+ if(cash_transaction >= currently_vending.price)
+ paid = pay_with_cash(currently_vending.price, "Vendor Transaction", name, user, GLOB.station_money_database.vendor_account)
+ else if(istype(C, /obj/item/card))
+ // Because this uses H.get_idcard(TRUE), it will attempt to use:
+ // active hand, inactive hand, pda.id, and then wear_id ID in that order
+ // this is important because it lets people buy stuff with someone else's ID by holding it while using the vendor
+ paid = pay_with_card(C, currently_vending.price, "Vendor transaction", name, user, GLOB.station_money_database.vendor_account)
+ else if(user.can_advanced_admin_interact())
+ to_chat(user, "Vending object due to admin interaction.")
+ paid = TRUE
+ else
+ to_chat(user, "Payment failure: you have no ID or other method of payment.")
+ vend_ready = TRUE
+ flick(icon_deny, src)
+ . = TRUE // we set this because they shouldn't even be able to get this far, and we want the UI to update.
+ return
+ if(paid)
+ SSeconomy.total_vendor_transactions++
+ vend(currently_vending, user)
+ . = TRUE
+ else
+ to_chat(user, "Payment failure: unable to process payment.")
+ vend_ready = TRUE
+
+/obj/machinery/economy/vending/proc/vend(datum/data/vending_product/R, mob/user)
+ if(!allowed(user) && !user.can_admin_interact() && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
+ to_chat(user, "Access denied.")//Unless emagged of course
+ flick(icon_deny, src)
+ vend_ready = TRUE
+ return
+
+ if(!R.amount)
+ to_chat(user, "The vending machine has ran out of that product.")
+ vend_ready = TRUE
+ return
+
+ vend_ready = FALSE //One thing at a time!!
+
+ if(coin_records.Find(R))
+ if(!coin)
+ to_chat(user, "You need to insert a coin to get this item.")
+ vend_ready = TRUE
+ return
+ if(coin.string_attached)
+ if(prob(50))
+ to_chat(user, "You successfully pull the coin out before [src] could swallow it.")
+ else
+ to_chat(user, "You weren't able to pull the coin out fast enough, the machine ate it, string and all.")
+ QDEL_NULL(coin)
+ else
+ QDEL_NULL(coin)
+
+ R.amount--
+
+ if(last_reply + vend_delay + 200 <= world.time && vend_reply)
+ speak(vend_reply)
+ last_reply = world.time
+
+ use_power(vend_power_usage) //actuators and stuff
+ if(icon_vend) //Show the vending animation if needed
+ flick(icon_vend, src)
+ playsound(get_turf(src), 'sound/machines/machine_vend.ogg', 50, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(delayed_vend), R, user), vend_delay)
+
+/obj/machinery/economy/vending/proc/delayed_vend(datum/data/vending_product/R, mob/user)
+ do_vend(R, user)
+ vend_ready = TRUE
+ currently_vending = null
+
+//override this proc to add handling for what to do with the vended product when you have a inserted item and remember to include a parent call for this generic handling
+/obj/machinery/economy/vending/proc/do_vend(datum/data/vending_product/R, mob/user)
+ if(!item_slot || !inserted_item)
+ var/put_on_turf = TRUE
+ var/obj/vended = new R.product_path()
+ if(user && iscarbon(user) && user.Adjacent(src))
+ if(user.put_in_hands(vended))
+ put_on_turf = FALSE
+ if(put_on_turf)
+ var/turf/T = get_turf(src)
+ vended.forceMove(T)
+ return TRUE
+ return FALSE
+
+/* Example override for do_vend proc:
+/obj/machinery/economy/vending/example/do_vend(datum/data/vending_product/R)
+ if(..())
+ return
+ var/obj/item/vended = new R.product_path()
+ if(inserted_item.force == initial(inserted_item.force)
+ inserted_item.force += vended.force
+ inserted_item.damtype = vended.damtype
+ qdel(vended)
+*/
+
+/obj/machinery/economy/vending/process()
+ if(stat & (BROKEN|NOPOWER))
+ return
+
+ if(!active)
+ return
+
+ if(seconds_electrified > 0)
+ seconds_electrified--
+
+ //Pitch to the people! Really sell it!
+ if(last_slogan + slogan_delay <= world.time && LAZYLEN(slogan_list) && !shut_up && prob(5))
+ var/slogan = pick(slogan_list)
+ speak(slogan)
+ last_slogan = world.time
+
+ if(shoot_inventory && prob(shoot_chance))
+ throw_item()
+
+/obj/machinery/economy/vending/extinguish_light()
+ set_light(0)
+ underlays.Cut()
+
+/obj/machinery/economy/vending/proc/speak(message)
+ if(stat & NOPOWER)
+ return
+ if(!message)
+ return
+
+ atom_say(message)
+
+/obj/machinery/economy/vending/power_change()
+ if(powered())
+ stat &= ~NOPOWER
+ else
+ stat |= NOPOWER
+ if(stat & (BROKEN|NOPOWER))
+ set_light(0)
+ else
+ set_light(light_range_on, light_power_on)
+ update_icon(UPDATE_OVERLAYS)
+
+/obj/machinery/economy/vending/obj_break(damage_flag)
+ if(stat & BROKEN)
+ return
+ stat |= BROKEN
+ set_light(0)
+ update_icon(UPDATE_OVERLAYS)
+
+ var/dump_amount = 0
+ var/found_anything = TRUE
+ while(found_anything)
+ found_anything = FALSE
+ for(var/record in shuffle(product_records))
+ var/datum/data/vending_product/R = record
+ if(R.amount <= 0) //Try to use a record that actually has something to dump.
+ continue
+ var/dump_path = R.product_path
+ if(!dump_path)
+ continue
+ R.amount--
+ // busting open a vendor will destroy some of the contents
+ if(found_anything && prob(80))
+ continue
+
+ var/obj/O = new dump_path(loc)
+ step(O, pick(GLOB.alldirs))
+ found_anything = TRUE
+ dump_amount++
+ if(dump_amount >= 16)
+ return
+
+//Somebody cut an important wire and now we're following a new definition of "pitch."
+/obj/machinery/economy/vending/proc/throw_item()
+ var/obj/throw_item = null
+ var/mob/living/target = locate() in view(7, src)
+ if(!target)
+ return 0
+
+ for(var/datum/data/vending_product/R in product_records)
+ if(R.amount <= 0) //Try to use a record that actually has something to dump.
+ continue
+ var/dump_path = R.product_path
+ if(!dump_path)
+ continue
+
+ R.amount--
+ throw_item = new dump_path(loc)
+ break
+ if(!throw_item)
+ return
+ throw_item.throw_at(target, 16, 3)
+ visible_message("[src] launches [throw_item.name] at [target.name]!")
+
+/obj/machinery/economy/vending/onTransitZ()
+ return
+/*
+ * Vending machine types
+ */
+
+/*
+
+/obj/machinery/economy/vending/[vendors name here] // --vending machine template :)
+ name = ""
+ desc = ""
+ icon = ''
+ icon_state = ""
+ vend_delay = 15
+ products = list()
+ contraband = list()
+ premium = list()
+
+*/
+
+
diff --git a/code/game/machinery/vendors/wardrobe_vendors.dm b/code/game/machinery/vendors/wardrobe_vendors.dm
new file mode 100644
index 00000000000..b6e8e014238
--- /dev/null
+++ b/code/game/machinery/vendors/wardrobe_vendors.dm
@@ -0,0 +1,563 @@
+//Departmental clothing vendors
+
+/obj/machinery/economy/vending/secdrobe
+ name = "\improper SecDrobe"
+ desc = "A vending machine for security and security-related clothing!"
+ icon_state = "secdrobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ ads_list = list("Beat perps in style!", "It's red so you can't see the blood!", "You have the right to be fashionable!", "Now you can be the fashion police you always wanted to be!")
+ vend_reply = "Thank you for using the SecDrobe!"
+ products = list(/obj/item/clothing/under/rank/security/officer/corporate = 4,
+ /obj/item/clothing/under/rank/security/officer/skirt/corporate = 4,
+ /obj/item/clothing/under/rank/security/officer/dispatch = 4,
+ /obj/item/clothing/under/rank/security/officer/skirt = 4,
+ /obj/item/clothing/under/rank/security/officer = 4,
+ /obj/item/clothing/under/rank/security/officer/uniform = 4,
+ /obj/item/clothing/under/rank/security/formal = 4,
+ /obj/item/clothing/under/rank/security/officer/fancy = 4,
+ /obj/item/clothing/under/rank/security/officer/skirt/fancy = 4,
+ /obj/item/clothing/head/soft/sec/corp = 4,
+ /obj/item/clothing/head/officer = 4,
+ /obj/item/clothing/head/beret/sec = 4,
+ /obj/item/clothing/head/soft/sec = 4,
+ /obj/item/clothing/head/drillsgt = 4,
+ /obj/item/clothing/mask/bandana/red = 4,
+ /obj/item/clothing/suit/jacket/secbomber = 2,
+ /obj/item/clothing/suit/armor/secjacket = 4,
+ /obj/item/clothing/suit/hooded/wintercoat/security = 4,
+ /obj/item/clothing/shoes/jackboots = 4,
+ /obj/item/clothing/shoes/jackboots/jacksandals = 4,
+ /obj/item/clothing/shoes/laceup = 4,
+ /obj/item/storage/backpack/duffel/security = 2,
+ /obj/item/storage/backpack/security = 2,
+ /obj/item/storage/backpack/satchel_sec = 2,
+ /obj/item/clothing/gloves/color/black = 4,
+ /obj/item/clothing/accessory/armband/sec = 6)
+ premium = list(/obj/item/clothing/mask/balaclava = 1,
+ /obj/item/clothing/mask/gas/sechailer/swat = 2)
+ contraband = list(/obj/item/toy/figure/crew/hos = 1,
+ /obj/item/toy/figure/crew/secofficer = 1)
+
+ prices = list(/obj/item/clothing/under/rank/security/officer/corporate = 50,
+ /obj/item/clothing/under/rank/security/officer/skirt/corporate = 50,
+ /obj/item/clothing/under/rank/security/officer/dispatch = 50,
+ /obj/item/clothing/under/rank/security/officer/skirt = 50,
+ /obj/item/clothing/under/rank/security/officer = 50,
+ /obj/item/clothing/under/rank/security/officer/uniform = 50,
+ /obj/item/clothing/under/rank/security/formal = 50,
+ /obj/item/clothing/under/rank/security/officer/fancy = 50,
+ /obj/item/clothing/under/rank/security/officer/skirt/fancy = 50,
+ /obj/item/clothing/head/soft/sec/corp = 40,
+ /obj/item/clothing/head/officer = 40,
+ /obj/item/clothing/head/beret/sec = 40,
+ /obj/item/clothing/head/soft/sec = 40,
+ /obj/item/clothing/head/drillsgt = 40,
+ /obj/item/clothing/mask/bandana/red = 40,
+ /obj/item/clothing/suit/jacket/secbomber = 75,
+ /obj/item/clothing/suit/armor/secjacket = 75,
+ /obj/item/clothing/suit/hooded/wintercoat/security = 75,
+ /obj/item/clothing/shoes/jackboots = 20,
+ /obj/item/clothing/shoes/jackboots/jacksandals = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/storage/backpack/duffel/security = 50,
+ /obj/item/storage/backpack/security = 50,
+ /obj/item/storage/backpack/satchel_sec = 50,
+ /obj/item/clothing/gloves/color/black = 20,
+ /obj/item/clothing/accessory/armband/sec = 20)
+ refill_canister = /obj/item/vending_refill/secdrobe
+
+/obj/machinery/economy/vending/detdrobe
+ name = "\improper DetDrobe"
+ desc = "A machine for all your detective needs, as long as you only need clothes."
+ icon_state = "detdrobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ ads_list = list("Apply your brilliant deductive methods in style!", "They already smell of cigarettes!")
+ vend_reply = "Thank you for using the DetDrobe!"
+ products = list(/obj/item/clothing/under/rank/security/detective = 2,
+ /obj/item/clothing/suit/storage/det_suit = 2,
+ /obj/item/clothing/suit/storage/det_suit/forensics/red = 1,
+ /obj/item/clothing/suit/storage/det_suit/forensics/blue = 1,
+ /obj/item/clothing/suit/armor/vest/det_suit = 1,
+ /obj/item/clothing/glasses/sunglasses/noir = 2,
+ /obj/item/clothing/head/det_hat = 2,
+ /obj/item/clothing/accessory/waistcoat = 2,
+ /obj/item/clothing/shoes/laceup = 2,
+ /obj/item/clothing/shoes/brown = 2,
+ /obj/item/clothing/shoes/jackboots = 2,
+ /obj/item/clothing/head/fedora = 1,
+ /obj/item/clothing/head/fedora/brownfedora = 1,
+ /obj/item/clothing/head/fedora/whitefedora = 1,
+ /obj/item/clothing/gloves/color/black = 2,
+ /obj/item/clothing/gloves/color/latex = 2,
+ /obj/item/reagent_containers/food/drinks/flask/detflask = 2,
+ /obj/item/storage/fancy/cigarettes/dromedaryco = 5)
+ prices = list(/obj/item/clothing/under/rank/security/detective = 50,
+ /obj/item/clothing/suit/storage/det_suit = 75,
+ /obj/item/clothing/suit/storage/det_suit/forensics/red = 75,
+ /obj/item/clothing/suit/storage/det_suit/forensics/blue = 75,
+ /obj/item/clothing/suit/armor/vest/det_suit = 75,
+ /obj/item/clothing/head/det_hat = 40,
+ /obj/item/clothing/glasses/sunglasses/noir = 30,
+ /obj/item/clothing/accessory/waistcoat = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/brown = 20,
+ /obj/item/clothing/shoes/jackboots = 20,
+ /obj/item/clothing/head/fedora = 20,
+ /obj/item/clothing/head/fedora/brownfedora = 20,
+ /obj/item/clothing/head/fedora/whitefedora = 20,
+ /obj/item/clothing/gloves/color/black = 20,
+ /obj/item/clothing/gloves/color/latex = 20,
+ /obj/item/reagent_containers/food/drinks/flask/detflask = 50,
+ /obj/item/storage/fancy/cigarettes/dromedaryco = 5)
+ contraband = list(/obj/item/toy/figure/crew/detective = 1)
+ refill_canister = /obj/item/vending_refill/detdrobe
+
+/obj/machinery/economy/vending/medidrobe
+ name = "\improper MediDrobe"
+ desc = "A vending machine rumoured to be capable of dispensing clothing for medical personnel."
+ icon_state = "base_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "medidrobe"
+ ads_list = list("Make those blood stains look fashionable!")
+ vend_reply = "Thank you for using the MediDrobe!"
+ products = list(/obj/item/clothing/under/rank/medical/doctor = 3,
+ /obj/item/clothing/under/rank/medical/doctor/skirt = 3,
+ /obj/item/clothing/under/rank/medical/scrubs = 3,
+ /obj/item/clothing/under/rank/medical/scrubs/green = 3,
+ /obj/item/clothing/under/rank/medical/scrubs/purple = 3,
+ /obj/item/clothing/under/rank/medical/nurse = 3,
+ /obj/item/clothing/under/rank/medical/gown = 3,
+ /obj/item/clothing/head/beret/med = 3,
+ /obj/item/clothing/head/surgery/blue = 3,
+ /obj/item/clothing/head/surgery/green = 3,
+ /obj/item/clothing/head/surgery/purple = 3,
+ /obj/item/clothing/head/nursehat = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/medical = 3,
+ /obj/item/clothing/suit/storage/fr_jacket = 3,
+ /obj/item/clothing/suit/storage/labcoat = 3,
+ /obj/item/clothing/accessory/armband/med = 3,
+ /obj/item/clothing/accessory/armband/medgreen = 3,
+ /obj/item/clothing/shoes/laceup = 3,
+ /obj/item/clothing/shoes/white = 3,
+ /obj/item/clothing/shoes/sandal/white = 3,
+ /obj/item/storage/backpack/medic = 2,
+ /obj/item/storage/backpack/satchel_med = 2,
+ /obj/item/storage/backpack/duffel/medical = 2)
+ premium = list(/obj/item/clothing/gloves/color/latex/nitrile = 3)
+ contraband = list(/obj/item/toy/figure/crew/md = 1)
+ prices = list(/obj/item/clothing/under/rank/medical/doctor = 50,
+ /obj/item/clothing/under/rank/medical/doctor/skirt = 50,
+ /obj/item/clothing/under/rank/medical/scrubs = 50,
+ /obj/item/clothing/under/rank/medical/scrubs/green = 50,
+ /obj/item/clothing/under/rank/medical/scrubs/purple = 50,
+ /obj/item/clothing/under/rank/medical/nurse = 50,
+ /obj/item/clothing/under/rank/medical/gown = 50,
+ /obj/item/clothing/head/beret/med = 20,
+ /obj/item/clothing/head/surgery/blue = 20,
+ /obj/item/clothing/head/surgery/green = 20,
+ /obj/item/clothing/head/surgery/purple = 20,
+ /obj/item/clothing/head/nursehat = 20,
+ /obj/item/clothing/suit/hooded/wintercoat/medical = 75,
+ /obj/item/clothing/suit/storage/fr_jacket = 75,
+ /obj/item/clothing/suit/storage/labcoat = 75,
+ /obj/item/clothing/accessory/armband/med = 20,
+ /obj/item/clothing/accessory/armband/medgreen = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/white = 20,
+ /obj/item/clothing/shoes/sandal/white = 20,
+ /obj/item/storage/backpack/medic = 50,
+ /obj/item/storage/backpack/satchel_med = 50,
+ /obj/item/storage/backpack/duffel/medical = 50)
+ refill_canister = /obj/item/vending_refill/medidrobe
+
+/obj/machinery/economy/vending/virodrobe
+ name = "\improper ViroDrobe"
+ desc = "An unsterilized machine for dispending virology related clothing."
+ icon_state = "base_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "virodrobe"
+ ads_list = list("Viruses getting you down? Nothing a change of clothes can't fix!", "Upgrade to sterilized clothing today!")
+ vend_reply = "Thank you for using the ViroDrobe!"
+ products = list(/obj/item/clothing/under/rank/medical/virologist = 2,
+ /obj/item/clothing/under/rank/medical/virologist/skirt = 2,
+ /obj/item/clothing/head/beret/med = 2,
+ /obj/item/clothing/suit/storage/labcoat/virologist = 2,
+ /obj/item/clothing/accessory/armband/med = 2,
+ /obj/item/clothing/mask/surgical = 2,
+ /obj/item/clothing/shoes/laceup = 2,
+ /obj/item/clothing/shoes/white = 2,
+ /obj/item/clothing/shoes/sandal/white = 2,
+ /obj/item/storage/backpack/virology = 2,
+ /obj/item/storage/backpack/satchel_vir = 2,
+ /obj/item/storage/backpack/duffel/virology = 2)
+ contraband = list(/obj/item/toy/figure/crew/virologist = 1)
+ prices = list(/obj/item/clothing/under/rank/medical/virologist = 50,
+ /obj/item/clothing/under/rank/medical/virologist/skirt = 50,
+ /obj/item/clothing/head/beret/med = 20,
+ /obj/item/clothing/suit/storage/labcoat/virologist = 75,
+ /obj/item/clothing/accessory/armband/med = 20,
+ /obj/item/clothing/mask/surgical = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/white = 20,
+ /obj/item/clothing/shoes/sandal/white = 20,
+ /obj/item/storage/backpack/virology = 50,
+ /obj/item/storage/backpack/satchel_vir = 50,
+ /obj/item/storage/backpack/duffel/virology = 50)
+ refill_canister = /obj/item/vending_refill/virodrobe
+
+/obj/machinery/economy/vending/chemdrobe
+ name = "\improper ChemDrobe"
+ desc = "A vending machine for dispensing chemistry related clothing."
+ icon_state = "base_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "chemdrobe"
+ ads_list = list("Our clothes are 0.5% more resistant to acid spills! Get yours now!")
+ vend_reply = "Thank you for using the ChemDrobe!"
+ products = list(/obj/item/clothing/under/rank/medical/chemist = 2,
+ /obj/item/clothing/under/rank/medical/chemist/skirt = 2,
+ /obj/item/clothing/head/beret/med = 2,
+ /obj/item/clothing/suit/storage/labcoat/chemist = 2,
+ /obj/item/clothing/accessory/armband/med = 2,
+ /obj/item/clothing/mask/gas = 2,
+ /obj/item/clothing/shoes/laceup = 2,
+ /obj/item/clothing/shoes/white = 2,
+ /obj/item/clothing/shoes/sandal/white = 2,
+ /obj/item/storage/bag/chemistry = 2,
+ /obj/item/storage/backpack/chemistry = 2,
+ /obj/item/storage/backpack/satchel_chem = 2,
+ /obj/item/storage/backpack/duffel/chemistry = 2)
+ prices = list(/obj/item/clothing/under/rank/medical/chemist = 50,
+ /obj/item/clothing/under/rank/medical/chemist/skirt = 50,
+ /obj/item/clothing/head/beret/med = 20,
+ /obj/item/clothing/suit/storage/labcoat/chemist = 75,
+ /obj/item/clothing/accessory/armband/med = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/white = 20,
+ /obj/item/clothing/shoes/sandal/white = 20,
+ /obj/item/storage/backpack/chemistry = 50,
+ /obj/item/storage/backpack/satchel_chem = 50,
+ /obj/item/storage/backpack/duffel/chemistry = 50)
+ contraband = list(/obj/item/toy/figure/crew/chemist = 1)
+ refill_canister = /obj/item/vending_refill/chemdrobe
+
+/obj/machinery/economy/vending/genedrobe
+ name = "\improper GeneDrobe"
+ desc = "A machine for dispensing clothing related to genetics."
+ icon_state = "base_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "genedrobe"
+ ads_list = "Perfect for the mad scientist in you!"
+ vend_reply = "Thank you for using the GeneDrobe!"
+ products = list(/obj/item/clothing/under/rank/rnd/geneticist = 3,
+ /obj/item/clothing/suit/storage/labcoat/genetics = 3,
+ /obj/item/clothing/shoes/laceup = 3,
+ /obj/item/clothing/shoes/white = 3,
+ /obj/item/clothing/shoes/sandal/white = 3,
+ /obj/item/storage/backpack/genetics = 2,
+ /obj/item/storage/backpack/satchel_gen = 2,
+ /obj/item/storage/backpack/duffel/genetics = 2,)
+ contraband = list(/obj/item/toy/figure/crew/geneticist = 1)
+ prices = list(/obj/item/clothing/under/rank/rnd/geneticist = 50,
+ /obj/item/clothing/suit/storage/labcoat/genetics = 75,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/white = 20,
+ /obj/item/clothing/shoes/sandal/white = 20,
+ /obj/item/storage/backpack/genetics = 50,
+ /obj/item/storage/backpack/satchel_gen = 50,
+ /obj/item/storage/backpack/duffel/genetics = 50,)
+ refill_canister = /obj/item/vending_refill/genedrobe
+
+
+/obj/machinery/economy/vending/scidrobe
+ name = "\improper SciDrobe"
+ desc = "A simple vending machine suitable to dispense well tailored science clothing. Endorsed by Space Cubans."
+ icon_state = "base_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "scidrobe"
+ ads_list = list("Longing for the smell of plasma burnt flesh?", "Buy your science clothing now!", "Made with 10% Auxetics, so you don't have to worry about losing your arm!")
+ vend_reply = "Thank you for using the SciDrobe!"
+ products = list(/obj/item/clothing/under/rank/rnd/scientist = 6,
+ /obj/item/clothing/under/rank/rnd/scientist/skirt = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/science = 3,
+ /obj/item/clothing/suit/storage/labcoat/science = 3,
+ /obj/item/clothing/head/beret/sci = 3,
+ /obj/item/clothing/accessory/armband/science = 6,
+ /obj/item/clothing/shoes/laceup = 3,
+ /obj/item/clothing/shoes/white = 3,
+ /obj/item/clothing/shoes/sandal/white = 3,
+ /obj/item/storage/backpack/science = 2,
+ /obj/item/storage/backpack/satchel_tox = 2,
+ /obj/item/storage/backpack/duffel/science = 2,)
+ contraband = list(/obj/item/toy/figure/crew/rd = 1,
+ /obj/item/toy/figure/crew/scientist = 1)
+ prices = list(/obj/item/clothing/under/rank/rnd/scientist = 50,
+ /obj/item/clothing/under/rank/rnd/scientist/skirt = 50,
+ /obj/item/clothing/suit/hooded/wintercoat/science = 75,
+ /obj/item/clothing/suit/storage/labcoat/science = 75,
+ /obj/item/clothing/head/beret/sci = 20,
+ /obj/item/clothing/accessory/armband/science = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/white = 20,
+ /obj/item/clothing/shoes/sandal/white = 20,
+ /obj/item/storage/backpack/science = 50,
+ /obj/item/storage/backpack/satchel_tox = 50,
+ /obj/item/storage/backpack/duffel/science = 50,)
+ refill_canister = /obj/item/vending_refill/scidrobe
+
+/obj/machinery/economy/vending/robodrobe
+ name = "\improper RoboDrobe"
+ desc = "A vending machine designed to dispense clothing known only to roboticists."
+ icon_state = "base_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "robodrobe"
+ ads_list = list("You turn me TRUE, use defines!","0110001101101100011011110111010001101000011001010111001101101000011001010111001001100101")
+ vend_reply = "Thank you for using the RoboDrobe!"
+ products = list(/obj/item/clothing/under/rank/rnd/roboticist = 3,
+ /obj/item/clothing/under/rank/rnd/roboticist/skirt = 3,
+ /obj/item/clothing/suit/storage/labcoat = 3,
+ /obj/item/clothing/head/soft/black = 3,
+ /obj/item/clothing/gloves/fingerless = 3,
+ /obj/item/clothing/shoes/laceup = 3,
+ /obj/item/clothing/shoes/white = 3,
+ /obj/item/clothing/shoes/black = 3)
+ contraband = list(/obj/item/toy/figure/crew/roboticist = 1)
+ prices = list(/obj/item/clothing/under/rank/rnd/roboticist = 50,
+ /obj/item/clothing/under/rank/rnd/roboticist/skirt = 50,
+ /obj/item/clothing/suit/storage/labcoat = 75,
+ /obj/item/clothing/head/soft/black = 20,
+ /obj/item/clothing/gloves/fingerless = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/white = 20,
+ /obj/item/clothing/shoes/black = 20)
+ refill_canister = /obj/item/vending_refill/robodrobe
+
+/obj/machinery/economy/vending/engidrobe
+ name = "\improper EngiDrobe"
+ desc = "A vending machine renowned for vending industrial grade clothing."
+ icon_state = "yellow_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "engidrobe"
+ ads_list = list("Guaranteed to protect your feet from industrial accidents!", "Afraid of radiation? Then wear yellow!")
+ vend_reply = "Thank you for using the EngiDrobe!"
+ products = list(/obj/item/clothing/under/rank/engineering/engineer = 6,
+ /obj/item/clothing/under/rank/engineering/engineer/skirt = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/engineering = 3,
+ /obj/item/clothing/suit/jacket/engibomber = 3,
+ /obj/item/clothing/suit/storage/hazardvest = 3,
+ /obj/item/clothing/head/beret/eng = 3,
+ /obj/item/clothing/head/hardhat = 2,
+ /obj/item/clothing/head/hardhat/orange = 2,
+ /obj/item/clothing/head/hardhat/dblue = 2,
+ /obj/item/clothing/accessory/armband/engine = 6,
+ /obj/item/clothing/shoes/laceup = 3,
+ /obj/item/clothing/shoes/workboots = 3,
+ /obj/item/storage/backpack/industrial = 2,
+ /obj/item/storage/backpack/satchel_eng = 2,
+ /obj/item/storage/backpack/duffel/engineering = 2,
+ /obj/item/storage/belt/utility = 2)
+ premium = list(/obj/item/clothing/gloves/color/yellow = 2)
+ contraband = list(/obj/item/toy/figure/crew/ce = 1,
+ /obj/item/toy/figure/crew/engineer = 1)
+ prices = list(/obj/item/clothing/under/rank/engineering/engineer = 50,
+ /obj/item/clothing/under/rank/engineering/engineer/skirt = 50,
+ /obj/item/clothing/suit/hooded/wintercoat/engineering = 75,
+ /obj/item/clothing/suit/jacket/engibomber = 75,
+ /obj/item/clothing/suit/storage/hazardvest = 75,
+ /obj/item/clothing/head/beret/eng = 20,
+ /obj/item/clothing/head/hardhat = 20,
+ /obj/item/clothing/head/hardhat/orange = 30,
+ /obj/item/clothing/head/hardhat/dblue = 30,
+ /obj/item/clothing/accessory/armband/engine = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/workboots = 20,
+ /obj/item/storage/backpack/industrial = 50,
+ /obj/item/storage/backpack/satchel_eng = 50,
+ /obj/item/storage/backpack/duffel/engineering = 50,
+ /obj/item/storage/belt/utility = 75)
+ refill_canister = /obj/item/vending_refill/engidrobe
+
+/obj/machinery/economy/vending/atmosdrobe
+ name = "\improper AtmosDrobe"
+ desc = "This relatively unknown vending machine delivers clothing for Atmospherics Technicians, an equally unknown job."
+ icon_state = "yellow_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "atmosdrobe"
+ ads_list = list("Guaranteed to protect your feet from atmospheric accidents!", "Get your inflammable clothing right here!")
+ vend_reply = "Thank you for using the AtmosDrobe!"
+ products = list(/obj/item/clothing/under/rank/engineering/atmospheric_technician = 6,
+ /obj/item/clothing/under/rank/engineering/atmospheric_technician/skirt = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3,
+ /obj/item/clothing/suit/jacket/atmosbomber = 3,
+ /obj/item/clothing/suit/storage/hazardvest = 3,
+ /obj/item/clothing/head/beret/atmos = 3,
+ /obj/item/clothing/head/hardhat = 2,
+ /obj/item/clothing/head/hardhat/red = 2,
+ /obj/item/clothing/head/hardhat/orange = 2,
+ /obj/item/clothing/head/hardhat/dblue = 2,
+ /obj/item/clothing/gloves/color/black = 3,
+ /obj/item/clothing/accessory/armband/engine = 3,
+ /obj/item/clothing/shoes/laceup = 3,
+ /obj/item/clothing/shoes/workboots = 3,
+ /obj/item/storage/backpack/industrial/atmos = 2,
+ /obj/item/storage/backpack/satchel_atmos = 2,
+ /obj/item/storage/backpack/duffel/atmos = 2,
+ /obj/item/storage/belt/utility = 2)
+ contraband = list(/obj/item/toy/figure/crew/atmos = 1)
+ prices = list(/obj/item/clothing/under/rank/engineering/atmospheric_technician = 50,
+ /obj/item/clothing/under/rank/engineering/atmospheric_technician/skirt = 50,
+ /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 75,
+ /obj/item/clothing/suit/jacket/atmosbomber = 75,
+ /obj/item/clothing/suit/storage/hazardvest = 50,
+ /obj/item/clothing/head/beret/atmos = 20,
+ /obj/item/clothing/head/hardhat = 20,
+ /obj/item/clothing/head/hardhat/red = 30,
+ /obj/item/clothing/head/hardhat/orange = 30,
+ /obj/item/clothing/head/hardhat/dblue = 30,
+ /obj/item/clothing/gloves/color/black = 20,
+ /obj/item/clothing/accessory/armband/engine = 20,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/workboots = 30,
+ /obj/item/storage/backpack/industrial/atmos = 50,
+ /obj/item/storage/backpack/satchel_atmos = 50,
+ /obj/item/storage/backpack/duffel/atmos = 50,
+ /obj/item/storage/belt/utility = 75)
+ refill_canister = /obj/item/vending_refill/atmosdrobe
+
+/obj/machinery/economy/vending/cargodrobe
+ name = "\improper CargoDrobe"
+ desc = "A highly advanced vending machine for buying cargo related clothing for free... most of the time."
+ icon_state = "yellow_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "cargodrobe"
+ ads_list = list("Upgraded Assistant Style! Pick yours today!", "These shorts are comfy and easy to wear, get yours now!")
+ vend_reply = "Thank you for using the CargoDrobe!"
+ products = list(/obj/item/clothing/under/rank/cargo/tech = 6,
+ /obj/item/clothing/under/rank/cargo/tech/skirt = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/cargo = 3,
+ /obj/item/clothing/suit/jacket/cargobomber = 3,
+ /obj/item/clothing/suit/storage/hazardvest = 3,
+ /obj/item/clothing/head/soft = 3,
+ /obj/item/clothing/head/hardhat/orange = 2,
+ /obj/item/clothing/gloves/fingerless = 6,
+ /obj/item/clothing/accessory/armband/cargo = 6,
+ /obj/item/clothing/shoes/black = 3,
+ /obj/item/clothing/shoes/workboots = 3)
+ contraband = list(/obj/item/toy/figure/crew/qm = 1,
+ /obj/item/toy/figure/crew/cargotech = 1)
+ prices = list(/obj/item/clothing/under/rank/cargo/tech = 50,
+ /obj/item/clothing/under/rank/cargo/tech/skirt = 50,
+ /obj/item/clothing/suit/hooded/wintercoat/cargo = 75,
+ /obj/item/clothing/suit/jacket/cargobomber = 75,
+ /obj/item/clothing/suit/storage/hazardvest = 50,
+ /obj/item/clothing/head/soft = 20,
+ /obj/item/clothing/head/hardhat/orange = 30,
+ /obj/item/clothing/gloves/fingerless = 20,
+ /obj/item/clothing/accessory/armband/cargo = 20,
+ /obj/item/clothing/shoes/black = 20,
+ /obj/item/clothing/shoes/workboots = 20)
+ refill_canister = /obj/item/vending_refill/cargodrobe
+
+/obj/machinery/economy/vending/chefdrobe
+ name = "\improper ChefDrobe"
+ desc = "This vending machine might not dispense meat, but it certainly dispenses chef related clothing."
+ icon_state = "base_drobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ icon_addon = "chefdrobe"
+ ads_list = list("Our clothes are guaranteed to protect you from food splatters!", "Comfortable enough for a CQC practice!")
+ vend_reply = "Thank you for using the ChefDrobe!"
+ products = list(/obj/item/clothing/under/rank/civilian/chef = 2,
+ /obj/item/clothing/under/misc/waiter = 2,
+ /obj/item/clothing/suit/chef = 2,
+ /obj/item/clothing/suit/chef/classic = 2,
+ /obj/item/clothing/head/chefhat = 2,
+ /obj/item/clothing/head/soft/mime = 2,
+ /obj/item/clothing/shoes/laceup = 2,
+ /obj/item/clothing/shoes/white = 2,
+ /obj/item/clothing/shoes/black = 2,
+ /obj/item/clothing/accessory/waistcoat = 2,
+ /obj/item/reagent_containers/glass/rag = 3)
+ contraband = list(/obj/item/toy/figure/crew/chef = 1)
+ premium = list(/obj/item/storage/box/dish_drive = 1)
+ prices = list(/obj/item/clothing/under/rank/civilian/chef = 50,
+ /obj/item/clothing/under/misc/waiter = 50,
+ /obj/item/clothing/suit/chef = 50,
+ /obj/item/clothing/suit/chef/classic = 50,
+ /obj/item/clothing/head/chefhat = 50,
+ /obj/item/clothing/head/soft/mime = 30,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/white = 20,
+ /obj/item/clothing/shoes/black = 20,
+ /obj/item/clothing/accessory/waistcoat = 20,
+ /obj/item/reagent_containers/glass/rag = 5)
+ refill_canister = /obj/item/vending_refill/chefdrobe
+
+/obj/machinery/economy/vending/bardrobe
+ name = "\improper BarDrobe"
+ desc = "A stylish vendor to dispense the most stylish bar clothing!"
+ icon_state = "bardrobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ ads_list = list("Guaranteed to prevent stains from spilled drinks!")
+ vend_reply = "Thank you for using the BarDrobe!"
+ products = list(/obj/item/clothing/under/rank/civilian/bartender = 2,
+ /obj/item/clothing/under/misc/sl_suit = 2,
+ /obj/item/clothing/head/that = 2,
+ /obj/item/clothing/head/soft/black = 2,
+ /obj/item/clothing/suit/blacktrenchcoat = 2,
+ /obj/item/clothing/shoes/laceup = 2,
+ /obj/item/clothing/shoes/black = 2,
+ /obj/item/clothing/accessory/waistcoat = 2,
+ /obj/item/reagent_containers/glass/rag = 3)
+ contraband = list(/obj/item/toy/figure/crew/bartender = 1)
+ premium = list(/obj/item/storage/box/dish_drive = 1)
+ prices = list(/obj/item/clothing/under/rank/civilian/bartender = 50,
+ /obj/item/clothing/under/misc/sl_suit = 50,
+ /obj/item/clothing/head/that = 20,
+ /obj/item/clothing/head/soft/black = 20,
+ /obj/item/clothing/suit/blacktrenchcoat = 75,
+ /obj/item/clothing/shoes/laceup = 30,
+ /obj/item/clothing/shoes/black = 20,
+ /obj/item/clothing/accessory/waistcoat = 20,
+ /obj/item/reagent_containers/glass/rag = 5)
+ refill_canister = /obj/item/vending_refill/bardrobe
+
+/obj/machinery/economy/vending/hydrodrobe
+ name = "\improper HydroDrobe"
+ desc = "A machine with a catchy name. It dispenses botany related clothing and gear."
+ icon_state = "hydrobe"
+ icon_lightmask = "base_drobe"
+ icon_panel = "drobe"
+ ads_list = list("Do you love soil? Then buy our clothes!", "Get outfits to match your green thumb here!")
+ vend_reply = "Thank you for using the HydroDrobe!"
+ products = list(/obj/item/clothing/under/rank/civilian/hydroponics = 3,
+ /obj/item/reagent_containers/glass/bucket = 3,
+ /obj/item/clothing/suit/apron = 3,
+ /obj/item/clothing/suit/apron/overalls = 3,
+ /obj/item/clothing/mask/bandana/botany = 3,
+ /obj/item/clothing/accessory/armband/hydro = 3,
+ /obj/item/storage/backpack/botany = 2,
+ /obj/item/storage/backpack/satchel_hyd = 2,
+ /obj/item/storage/backpack/duffel/hydro = 2,)
+ contraband = list(/obj/item/toy/figure/crew/botanist = 1)
+ prices = list(/obj/item/clothing/under/rank/civilian/hydroponics = 50,
+ /obj/item/reagent_containers/glass/bucket = 15,
+ /obj/item/clothing/suit/apron = 50,
+ /obj/item/clothing/suit/apron/overalls = 50,
+ /obj/item/clothing/mask/bandana/botany = 20,
+ /obj/item/clothing/accessory/armband/hydro = 20,
+ /obj/item/storage/backpack/botany = 50,
+ /obj/item/storage/backpack/satchel_hyd = 50,
+ /obj/item/storage/backpack/duffel/hydro = 50)
+ refill_canister = /obj/item/vending_refill/hydrodrobe
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index 62382cba0dd..f1a2d882262 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -268,9 +268,9 @@
user.drop_item()
- var/datum/job_objective/make_cyborg/task = user.mind.findJobTask(/datum/job_objective/make_cyborg)
+ var/datum/job_objective/make_cyborg/task = user.mind.find_job_task(/datum/job_objective/make_cyborg)
if(istype(task))
- task.unit_completed()
+ task.completed = TRUE
O.invisibility = 0
//Transfer debug settings to new mob
diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm
index 01d57d884dc..b1d39649dca 100644
--- a/code/game/objects/items/weapons/cards_ids.dm
+++ b/code/game/objects/items/weapons/cards_ids.dm
@@ -20,6 +20,9 @@
var/list/files = list( )
+/obj/item/card/proc/get_card_account()
+ return GLOB.station_money_database.find_user_account(associated_account_number)
+
/obj/item/card/data
name = "data card"
desc = "A disk containing data."
@@ -249,6 +252,9 @@
name = "[(!registered_name) ? "identification card" : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]"
+/obj/item/card/id/proc/get_departments()
+ return get_departments_from_job(assignment)
+
/obj/item/card/id/attackby(obj/item/W as obj, mob/user as mob, params)
..()
diff --git a/code/game/objects/items/weapons/cash.dm b/code/game/objects/items/weapons/cash.dm
index 8e6e34d8622..937e9d15209 100644
--- a/code/game/objects/items/weapons/cash.dm
+++ b/code/game/objects/items/weapons/cash.dm
@@ -11,15 +11,31 @@
w_class = WEIGHT_CLASS_TINY
resistance_flags = FLAMMABLE
singular_name = "credit"
- max_amount = 1000000
+ max_amount = MAX_STACKABLE_CASH
merge_type = /obj/item/stack/spacecash
-/obj/item/stack/spacecash/New(loc, amt = null)
- ..()
- update_icon(UPDATE_ICON_STATE)
+/obj/item/stack/spacecash/Initialize(mapload, uncompensated = TRUE)
+ . = ..()
+ if(uncompensated)
+ SSeconomy.space_credits_created += amount
+ SSeconomy.total_space_cash += amount
+ update_icon()
+
+/obj/item/stack/spacecash/Destroy()
+ SSeconomy.total_space_cash -= amount
+ SSeconomy.space_credits_destroyed += amount
+ return ..()
+
+/obj/item/stack/spacecash/merge(obj/item/stack/S)
+ . = ..()
+ update_icon()
+
+/obj/item/stack/spacecash/change_stack(mob/living/user)
+ . = ..()
+ update_icon()
/obj/item/stack/spacecash/update_icon_state()
- name = "[amount == max_amount ? "1000000" : amount] Credit[amount > 1 ? "s" : ""]"
+ name = "[amount == max_amount ? "10000" : amount] Credit[amount > 1 ? "s" : ""]"
if(amount >= 1 && amount < 10)
icon_state = "cashgreen"
else if(amount >= 10 && amount < 50)
@@ -28,11 +44,14 @@
icon_state = "cashblue"
else if(amount >= 500 && amount < 1000)
icon_state = "cashindi"
- else if(amount >= 1000 && amount < 1000000)
+ else if(amount >= 1000 && amount <= 10000)
icon_state = "cashpurp"
else
icon_state = "cashrbow"
+/obj/item/stack/spacecash/c5
+ amount = 5
+
/obj/item/stack/spacecash/c10
amount = 10
@@ -54,5 +73,8 @@
/obj/item/stack/spacecash/c1000
amount = 1000
-/obj/item/stack/spacecash/c1000000
- amount = 1000000
+/obj/item/stack/spacecash/c10000
+ amount = 10000
+
+/obj/item/twohanded/required/cash_pile
+ name = "Pile of Cash"
diff --git a/code/game/objects/items/weapons/dice.dm b/code/game/objects/items/weapons/dice.dm
index 323e4e58079..676da2dd72f 100644
--- a/code/game/objects/items/weapons/dice.dm
+++ b/code/game/objects/items/weapons/dice.dm
@@ -235,7 +235,7 @@
var/turf/Start = get_turf(src)
for(var/direction in GLOB.alldirs)
var/turf/dirturf = get_step(Start,direction)
- if(rand(0,1))
+ if(prob(50))
new /obj/item/stack/spacecash/c1000(dirturf)
else
var/obj/item/storage/bag/money/M = new(dirturf)
diff --git a/code/game/objects/items/weapons/storage/secure.dm b/code/game/objects/items/weapons/storage/secure.dm
index 93ee5d76cc0..bacc41dba69 100644
--- a/code/game/objects/items/weapons/storage/secure.dm
+++ b/code/game/objects/items/weapons/storage/secure.dm
@@ -219,8 +219,8 @@
/obj/item/storage/secure/briefcase/syndie/populate_contents()
..()
- for(var/I in 1 to 5)
- new /obj/item/stack/spacecash/c1000(src)
+ for(var/I in 1 to 3)
+ new /obj/item/stack/spacecash/c200(src)
// -----------------------------
// Secure Safe
diff --git a/code/game/objects/items/weapons/storage/wallets.dm b/code/game/objects/items/weapons/storage/wallets.dm
index 6e9f1173b76..42aaca3a3bd 100644
--- a/code/game/objects/items/weapons/storage/wallets.dm
+++ b/code/game/objects/items/weapons/storage/wallets.dm
@@ -75,10 +75,10 @@
/obj/item/storage/wallet/random/populate_contents()
var/cash = pick(/obj/item/stack/spacecash,
+ /obj/item/stack/spacecash/c5,
/obj/item/stack/spacecash/c10,
- /obj/item/stack/spacecash/c100,
- /obj/item/stack/spacecash/c500,
- /obj/item/stack/spacecash/c1000)
+ /obj/item/stack/spacecash/c50,
+ /obj/item/stack/spacecash/c100)
var/coin = pickweight(list(/obj/item/coin/iron = 3,
/obj/item/coin/silver = 2,
/obj/item/coin/gold = 1))
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
index 50112fbd11b..a4a2f4a7708 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
@@ -73,8 +73,8 @@
/obj/structure/closet/secure_closet/freezer/money/populate_contents()
for(var/i in 1 to 3)
- new /obj/item/stack/spacecash/c1000(src)
- for(var/i in 1 to 5)
- new /obj/item/stack/spacecash/c500(src)
- for(var/i in 1 to 6)
new /obj/item/stack/spacecash/c200(src)
+ for(var/i in 1 to 5)
+ new /obj/item/stack/spacecash/c100(src)
+ for(var/i in 1 to 6)
+ new /obj/item/stack/spacecash/c50(src)
diff --git a/code/modules/antagonists/traitor/contractor/datums/contractor_hub.dm b/code/modules/antagonists/traitor/contractor/datums/contractor_hub.dm
index 913225a6f1e..52cb04443da 100644
--- a/code/modules/antagonists/traitor/contractor/datums/contractor_hub.dm
+++ b/code/modules/antagonists/traitor/contractor/datums/contractor_hub.dm
@@ -136,7 +136,7 @@
completed_contracts++
reward_tc_available += tc
rep += rep_per_completion
- owner?.initial_account?.credit(creds, pick(list(
+ var/notify_text = pick(list(
"CONGRATULATIONS. You are the 10,000th visitor of SquishySlimes.squish. Please find attached your [creds] credits.",
"Congratulations on winning your bet in the latest Clown vs. Mime match! Your account was credited with [creds] credits.",
"Deer fund beneficiary, We have please to imform you that overdue fund payments has finally is approved and yuor account credited with [creds] creadits.",
@@ -144,8 +144,17 @@
"Thank you for your initial investment of 500 credits! We have credited your account with [creds] as a token of appreciation.",
"Your refund request for 100 Dr. Maxman pills with the reason \"I need way more than 100 pills!\" has been received. We have credited your account with [creds] credits.",
"Your refund request for your WetSkrell.nt subscription has been received. We have credited your account with [creds] credits.",
- )))
- // Clean up
+ ))
+ var/transaction_person
+ if(prob(50))
+ transaction_person = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
+ else
+ transaction_person = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
+ //we want to make this transfer look real as possible, only if someone is really really closely looking at it will they notic the transaction person isn't real
+ if(GLOB.station_money_database.credit_account(owner?.initial_account, creds, "Transfer From [transaction_person]", "NanoBank Transfer Services", FALSE))
+ if(LAZYLEN(owner.initial_account.associated_nanobank_programs))
+ for(var/datum/data/pda/app/nanobank/program as anything in owner.initial_account.associated_nanobank_programs)
+ program.notify(notify_text, TRUE)
current_contract = null
/**
diff --git a/code/modules/antagonists/traitor/contractor/datums/syndicate_contract.dm b/code/modules/antagonists/traitor/contractor/datums/syndicate_contract.dm
index 07ef221aa48..ffb76066558 100644
--- a/code/modules/antagonists/traitor/contractor/datums/syndicate_contract.dm
+++ b/code/modules/antagonists/traitor/contractor/datums/syndicate_contract.dm
@@ -36,7 +36,7 @@
/obj/item/toy/syndicateballoon,
)
/// The base credits reward upon completion. Multiplied by the two lower bounds below.
- var/credits_base = 100
+ var/credits_base = 10
// The lower bound of the credits reward multiplier.
var/credits_lower_mult = 25
// The upper bound of the credits reward multiplier.
diff --git a/code/modules/arcade/arcade_base.dm b/code/modules/arcade/arcade_base.dm
index fb7b891f3be..79d20c30e85 100644
--- a/code/modules/arcade/arcade_base.dm
+++ b/code/modules/arcade/arcade_base.dm
@@ -1,54 +1,52 @@
-/obj/machinery/arcade
+/obj/machinery/economy/arcade
name = "Arcade Game"
desc = "One of the most generic arcade games ever."
icon = 'icons/obj/arcade.dmi'
icon_state = "clawmachine_on"
- density = TRUE
- anchored = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 40
+
var/tokens = 0
var/freeplay = FALSE //for debugging and admin kindness
var/token_price = 0
- var/last_winner = null //for letting people who to hunt down and steal prizes from
var/window_name = "arcade" //in case you want to change the window name for certain machines
-/obj/machinery/arcade/Initialize(mapload)
+/obj/machinery/economy/arcade/Initialize(mapload)
. = ..()
- if(type == /obj/machinery/arcade) //if you spawn the base-type, it will replace itself with a random subtype for randomness
- var/choice = pick(subtypesof(/obj/machinery/arcade))
+ if(type == /obj/machinery/economy/arcade) //if you spawn the base-type, it will replace itself with a random subtype for randomness
+ var/choice = pick(subtypesof(/obj/machinery/economy/arcade))
new choice(loc)
return INITIALIZE_HINT_QDEL
-/obj/machinery/arcade/examine(mob/user)
+/obj/machinery/economy/arcade/examine(mob/user)
. = ..()
if(freeplay)
. += "Someone enabled freeplay on this machine!"
else
if(token_price)
- . += "\The [src.name] costs [token_price] credits per play."
+ . += "[src] costs [token_price] credits per play."
if(!tokens)
- . += "\The [src.name] has no available play credits. Better feed the machine!"
+ . += "[src] has no available play credits. Better feed the machine!"
else if(tokens == 1)
- . += "\The [src.name] has only 1 play credit left!"
+ . += "[src] has only 1 play credit left!"
else
- . += "\The [src.name] has [tokens] play credits!"
+ . += "[src] has [tokens] play credits!"
-/obj/machinery/arcade/attack_hand(mob/user as mob)
+/obj/machinery/economy/arcade/attack_hand(mob/user)
if(..())
if(in_use && src == user.machine) //this one checks if they fell down/died and closes the game
- src.close_game()
+ close_game()
return
if(in_use && src == user.machine) //this one just checks if they are playing so it doesn't eat tokens
return
interact(user)
-/obj/machinery/arcade/interact(mob/user as mob)
+/obj/machinery/economy/arcade/interact(mob/user)
if(stat & BROKEN || panel_open)
return
if(!tokens && !freeplay)
- to_chat(user, "\The [src.name] doesn't have enough credits to play! Pay first!")
+ to_chat(user, "\The [src] doesn't have enough credits to play! Pay first!")
return
if(!in_use && (tokens || freeplay))
in_use = 1
@@ -59,88 +57,56 @@
to_chat(user, "Someone else is already playing this machine, please wait your turn!")
return
-/obj/machinery/arcade/attackby(obj/item/O, mob/user, params)
+/obj/machinery/economy/arcade/attackby(obj/item/O, mob/user, params)
if(exchange_parts(user, O))
return
if(!freeplay)
- if(istype(O, /obj/item/card/id))
- var/obj/item/card/id/C = O
- if(pay_with_card(C))
+ if(isspacecash(O))
+ insert_cash(O, user, token_price)
+ if(pay_with_cash(token_price, "Arcade Token Purchase", "DonkBook Gaming", user, account_database.vendor_account))
tokens += 1
return
- else if(istype(O, /obj/item/stack/spacecash))
- var/obj/item/stack/spacecash/C = O
- if(pay_with_cash(C, user))
+ if(istype(O, /obj/item/card/id))
+ if(pay_with_card(O, token_price, "Arcade Token Purchase", "DonkBook Gaming", user, account_database.vendor_account))
tokens += 1
return
return ..()
-/obj/machinery/arcade/screwdriver_act(mob/living/user, obj/item/I)
+/obj/machinery/economy/arcade/screwdriver_act(mob/living/user, obj/item/I)
if(!anchored)
return FALSE
default_deconstruction_screwdriver(user, icon_state, icon_state, I)
update_icon()
return TRUE
-/obj/machinery/arcade/crowbar_act(mob/living/user, obj/item/I)
+/obj/machinery/economy/arcade/crowbar_act(mob/living/user, obj/item/I)
if(!component_parts || !panel_open)
return FALSE
default_deconstruction_crowbar(user, I)
return TRUE
-/obj/machinery/arcade/proc/pay_with_cash(obj/item/stack/spacecash/cashmoney, mob/user)
- if(cashmoney.amount < token_price)
- to_chat(user, "[bicon(cashmoney)] That is not enough money.")
- return 0
- visible_message("[usr] inserts a credit chip into [src].")
- cashmoney.use(token_price)
- return 1
-
-/obj/machinery/arcade/proc/pay_with_card(obj/item/card/id/I, mob/user)
- visible_message("[usr] swipes a card through [src].")
- var/datum/money_account/customer_account = attempt_account_access_nosec(I.associated_account_number)
- if(!customer_account)
- to_chat(user, "Error: Unable to access account. Please contact technical support if problem persists.")
- return 0
-
- if(customer_account.suspended)
- to_chat(user, "Unable to access account: account suspended.")
- return 0
-
- // Have the customer punch in the PIN before checking if there's enough money. Prevents people from figuring out acct is
- // empty at high security levels
- if(customer_account.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
- var/attempt_pin = input("Enter pin code", "Vendor transaction") as num
- customer_account = attempt_account_access(I.associated_account_number, attempt_pin, 2)
-
- if(!customer_account)
- to_chat(user, "Unable to access account: incorrect credentials.")
- return 0
-
- return customer_account.charge(token_price, null, "Purchase of [name] credit", name, name)
-
-/obj/machinery/arcade/proc/start_play(mob/user as mob)
+/obj/machinery/economy/arcade/proc/start_play(mob/user as mob)
user.set_machine(src)
if(!freeplay)
tokens -= 1
-/obj/machinery/arcade/proc/close_game()
- in_use = 0
+/obj/machinery/economy/arcade/proc/close_game()
+ in_use = FALSE
for(var/mob/user in viewers(world.view, src)) // I don't know who you are.
if(user.client && user.machine == src) // I will look for you,
user.unset_machine() // I will find you,
user << browse(null, "window=[window_name]") // And I will kill you.
return
-/obj/machinery/arcade/proc/win()
+/obj/machinery/economy/arcade/proc/win()
return
-/obj/machinery/arcade/process()
+/obj/machinery/economy/arcade/process()
if(in_use)
- src.updateUsrDialog()
+ updateUsrDialog()
if(!in_use)
- src.close_game()
+ close_game()
-/obj/machinery/arcade/Destroy()
- src.close_game()
+/obj/machinery/economy/arcade/Destroy()
+ close_game()
return ..()
diff --git a/code/modules/arcade/claw_game.dm b/code/modules/arcade/claw_game.dm
index ba1818be523..d1c6bd55581 100644
--- a/code/modules/arcade/claw_game.dm
+++ b/code/modules/arcade/claw_game.dm
@@ -1,6 +1,6 @@
GLOBAL_VAR(claw_game_html)
-/obj/machinery/arcade/claw
+/obj/machinery/economy/arcade/claw
name = "Claw Game"
desc = "One of the most infuriating ways to win a toy."
icon = 'icons/obj/arcade.dmi'
@@ -17,7 +17,7 @@ GLOBAL_VAR(claw_game_html)
'icons/obj/arcade_images/prize_inside.png',
'icons/obj/arcade_images/prizeorbs.png')
-/obj/machinery/arcade/claw/Initialize(mapload)
+/obj/machinery/economy/arcade/claw/Initialize(mapload)
. = ..()
machine_image = pick("_1", "_2")
update_icon(UPDATE_ICON_STATE)
@@ -33,13 +33,13 @@ GLOBAL_VAR(claw_game_html)
if(!GLOB.claw_game_html)
GLOB.claw_game_html = file2text('code/modules/arcade/crane.html')
-/obj/machinery/arcade/claw/RefreshParts()
+/obj/machinery/economy/arcade/claw/RefreshParts()
var/bin_upgrades = 0
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
bin_upgrades = B.rating
bonus_prize_chance = bin_upgrades * 5 //equals +5% chance per matter bin rating level (+20% with rating 4)
-/obj/machinery/arcade/claw/update_icon_state()
+/obj/machinery/economy/arcade/claw/update_icon_state()
if(stat & BROKEN)
icon_state = "clawmachine[machine_image]_broken"
else if(panel_open)
@@ -49,7 +49,7 @@ GLOBAL_VAR(claw_game_html)
else
icon_state = "clawmachine[machine_image]_on"
-/obj/machinery/arcade/claw/win()
+/obj/machinery/economy/arcade/claw/win()
icon_state = "clawmachine[machine_image]_win"
if(prob(bonus_prize_chance)) //double prize mania!
atom_say("DOUBLE PRIZE!")
@@ -60,7 +60,7 @@ GLOBAL_VAR(claw_game_html)
playsound(loc, 'sound/arcade/win.ogg', 50, TRUE)
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_icon), UPDATE_ICON_STATE), 10)
-/obj/machinery/arcade/claw/start_play(mob/user as mob)
+/obj/machinery/economy/arcade/claw/start_play(mob/user as mob)
..()
user << browse_rsc('page.css')
for(var/i in 1 to img_resources.len)
@@ -75,7 +75,7 @@ GLOBAL_VAR(claw_game_html)
popup.open()
user.set_machine(src)
-/obj/machinery/arcade/claw/Topic(href, list/href_list)
+/obj/machinery/economy/arcade/claw/Topic(href, list/href_list)
if(..())
return
var/prize_won = null
diff --git a/code/modules/economy/ATM.dm b/code/modules/economy/ATM.dm
deleted file mode 100644
index 17c9bfb77bd..00000000000
--- a/code/modules/economy/ATM.dm
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
-
-TODO:
-give money an actual use (QM stuff, vending machines)
-send money to people (might be worth attaching money to custom database thing for this, instead of being in the ID)
-log transactions
-
-*/
-
-#define DEFAULT_SCREEN 0
-#define CHANGE_SECURITY_LEVEL 1
-#define TRANSFER_FUNDS 2
-#define VIEW_TRANSACTION_LOGS 3
-#define PRINT_DELAY 100
-#define LOCKOUT_TIME 120
-
-/obj/machinery/atm
- name = "Nanotrasen automatic teller machine"
- desc = "For all your monetary needs! Just insert your ID card to make a withdrawal or deposit!"
- icon = 'icons/obj/terminals.dmi'
- icon_state = "atm"
- anchored = TRUE
- use_power = IDLE_POWER_USE
- idle_power_usage = 10
- var/obj/machinery/computer/account_database/linked_db
- var/datum/money_account/authenticated_account
- var/number_incorrect_tries = 0
- var/previous_account_number = 0
- var/max_pin_attempts = 3
- var/ticks_left_locked_down = 0
- var/ticks_left_timeout = 0
- var/machine_id = ""
- var/obj/item/card/held_card
- var/editing_security_level = 0
- var/view_screen = DEFAULT_SCREEN
- var/lastprint = 0 // Printer needs time to cooldown
-
-
-/obj/machinery/atm/Initialize(mapload)
- . = ..()
- machine_id = "[station_name()] RT #[GLOB.num_financial_terminals++]"
- reconnect_database()
- update_icon()
-
-/obj/machinery/atm/update_icon_state()
- . = ..()
-
- if(stat & NOPOWER)
- icon_state = "atm_off"
- else
- icon_state = "atm"
-
-/obj/machinery/atm/update_overlays()
- . = ..()
- underlays.Cut()
-
- if(stat & NOPOWER)
- return
-
- underlays += emissive_appearance(icon, "atm_lightmask")
-
-/obj/machinery/atm/power_change()
- ..()
- if(stat & NOPOWER)
- set_light(0)
- else
- set_light(1, LIGHTING_MINIMUM_POWER)
- update_icon()
-
-/obj/machinery/atm/process()
- if(stat & NOPOWER)
- return
-
- if(linked_db && ((linked_db.stat & NOPOWER) || !linked_db.activated))
- linked_db = null
- authenticated_account = null
- visible_message("[bicon(src)][src] buzzes rudely, \"Connection to remote database lost.\"")
- SStgui.update_uis(src)
-
- if(ticks_left_timeout > 0)
- ticks_left_timeout--
- if(ticks_left_timeout <= 0)
- authenticated_account = null
- if(ticks_left_locked_down > 0)
- ticks_left_locked_down--
- if(ticks_left_locked_down <= 0)
- number_incorrect_tries = 0
-
- if(authenticated_account)
- var/turf/T = get_turf(src)
- if(istype(T) && locate(/obj/item/stack/spacecash) in T)
- var/cash_amount = 0
- for(var/obj/item/stack/spacecash/S in T)
- cash_amount += S.amount
- if(cash_amount)
- playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE)
- for(var/obj/item/stack/spacecash/S in T)
- S.use(S.amount)
- authenticated_account.charge(-cash_amount, null, "Credit deposit", machine_id, "Terminal")
-
-/obj/machinery/atm/proc/reconnect_database()
- for(var/obj/machinery/computer/account_database/DB in GLOB.machines)
- if(DB.z == z && !(DB.stat & NOPOWER) && DB.activated)
- linked_db = DB
- break
-
-/obj/machinery/atm/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/card))
- if(!powered())
- return
-
- if(!held_card)
- user.drop_item()
- I.forceMove(src)
- held_card = I
- if(authenticated_account && held_card.associated_account_number != authenticated_account.account_number)
- authenticated_account = null
- SStgui.update_uis(src)
- else if(authenticated_account)
- if(istype(I, /obj/item/stack/spacecash))
- //consume the money
- if(!powered())
- return
- var/obj/item/stack/spacecash/C = I
- playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE)
-
- authenticated_account.credit(C.amount, "Credit deposit", machine_id, authenticated_account.owner_name)
-
- to_chat(user, "You insert [C] into [src].")
- SStgui.update_uis(src)
- C.use(C.amount)
- else
- return ..()
-
-/obj/machinery/atm/attack_hand(mob/user)
- if(..())
- return TRUE
- if(issilicon(user))
- to_chat(user, "Artificial unit recognized. Artificial units do not currently receive monetary compensation, as per Nanotrasen regulation #1005.")
- return
- if(!linked_db)
- reconnect_database()
- ui_interact(user)
-
-/obj/machinery/atm/attack_ghost(mob/user)
- ui_interact(user)
-
-/obj/machinery/atm/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if (!ui)
- ui = new(user, src, ui_key, "ATM", name, 550, 650)
- ui.open()
-
-/obj/machinery/atm/ui_data(mob/user)
- var/list/data = list()
- data["view_screen"] = view_screen
- data["machine_id"] = machine_id
- data["held_card_name"] = held_card ? held_card.name : "------"
- data["ticks_left_locked_down"] = ticks_left_locked_down
- data["linked_db"] = linked_db
-
- data["authenticated_account"] = authenticated_account
- if(authenticated_account)
- data["owner_name"] = authenticated_account.owner_name
- data["money"] = authenticated_account.money
- data["security_level"] = authenticated_account.security_level
-
- var/list/trx = list()
- for(var/datum/transaction/T in authenticated_account.transaction_log)
- trx[++trx.len] = list(
- "date" = T.date,
- "time" = T.time,
- "target_name" = T.target_name,
- "purpose" = T.purpose,
- "amount" = T.amount,
- "source_terminal" = T.source_terminal)
- data["transaction_log"] = trx
-
- return data
-
-/obj/machinery/atm/ui_act(action, params)
- if(..())
- return
-
- switch(action)
- if("transfer")
- if(!authenticated_account || !linked_db)
- return
- var/transfer_amount = text2num(params["funds_amount"])
- if(transfer_amount <= 0)
- to_chat(usr, "[bicon(src)]That is not a valid amount.")
- else if(transfer_amount <= authenticated_account.money)
- var/target_account_number = text2num(params["target_acc_number"])
- var/transfer_purpose = params["purpose"]
- if(linked_db.charge_to_account(target_account_number, authenticated_account, transfer_purpose, machine_id, transfer_amount))
- to_chat(usr, "[bicon(src)]Funds transfer successful.")
- else
- to_chat(usr, "[bicon(src)]Funds transfer failed.")
- else
- to_chat(usr, "[bicon(src)]You don't have enough funds to do that!")
-
- if("view_screen")
- var/list/valid_screen = list(DEFAULT_SCREEN, CHANGE_SECURITY_LEVEL, TRANSFER_FUNDS, VIEW_TRANSACTION_LOGS)
- var/screen_proper = text2num(params["view_screen"])
- if(screen_proper in valid_screen)
- view_screen = screen_proper
- else
- message_admins("Warning: possible href exploit by [key_name(usr)] - Invalid screen number passed into an ATM")
- log_debug("Warning: possible href exploit by [key_name(usr)] - Invalid screen number passed into an ATM")
-
- if("change_security_level")
- if(authenticated_account)
- var/new_sec_level = max(min(text2num(params["new_security_level"]), 2), 0)
- authenticated_account.security_level = new_sec_level
-
- if("attempt_auth")
- if(linked_db)
- if(!ticks_left_locked_down)
- var/tried_account_num = text2num(params["account_num"])
- if(!tried_account_num && held_card)
- tried_account_num = held_card.associated_account_number
- var/tried_pin = text2num(params["account_pin"])
-
- authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1)
- if(!authenticated_account)
- number_incorrect_tries++
- if(previous_account_number == tried_account_num)
- if(number_incorrect_tries > max_pin_attempts)
- //lock down the atm
- ticks_left_locked_down = 30
- playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
-
- //create an entry in the account transaction log
- var/datum/money_account/failed_account = linked_db.get_account(tried_account_num)
- if(failed_account)
- var/datum/transaction/T = new()
- T.target_name = failed_account.owner_name
- T.purpose = "Unauthorised login attempt"
- T.source_terminal = machine_id
- T.date = GLOB.current_date_string
- T.time = station_time_timestamp()
- failed_account.transaction_log.Add(T)
- else
- to_chat(usr, "[bicon(src)]Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempt\s remaining.")
- previous_account_number = tried_account_num
- playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
- else
- to_chat(usr, "[bicon(src)]Incorrect pin/account combination entered.")
- number_incorrect_tries = 0
- else
- playsound(src, 'sound/machines/twobeep.ogg', 50, TRUE)
- ticks_left_timeout = LOCKOUT_TIME
- view_screen = DEFAULT_SCREEN
-
- //create a transaction log entry
- var/datum/transaction/T = new()
- T.target_name = authenticated_account.owner_name
- T.purpose = "Remote terminal access"
- T.source_terminal = machine_id
- T.date = GLOB.current_date_string
- T.time = station_time_timestamp()
- authenticated_account.transaction_log.Add(T)
- to_chat(usr, "[bicon(src)]Access granted. Welcome user '[authenticated_account.owner_name].'")
- previous_account_number = tried_account_num
-
- if("withdrawal")
- var/amount = max(text2num(params["funds_amount"]), 0)
- if(amount <= 0)
- to_chat(usr, "[bicon(src)]That is not a valid amount.")
- else if(authenticated_account && amount > 0)
- if(amount > 100000) // Prevent crashes
- to_chat(usr, "[bicon(src)]The ATM's screen flashes, 'Maximum single withdrawal limit reached, defaulting to 100,000.'")
- amount = 100000
- if(authenticated_account.charge(amount, null, "Credit withdrawal", machine_id, authenticated_account.owner_name))
- playsound(src, 'sound/machines/chime.ogg', 50, TRUE)
- withdraw_arbitrary_sum(amount)
-
- if("balance_statement")
- if(authenticated_account)
- if(world.timeofday < lastprint + PRINT_DELAY)
- to_chat(usr, "[src] flashes an error on its display.")
- return
- lastprint = world.timeofday
- playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, TRUE)
- var/obj/item/paper/R = new(loc)
- R.name = "Account balance: [authenticated_account.owner_name]"
- R.info = {"NT Automated Teller Account Statement
- Account holder: [authenticated_account.owner_name]
- Account number: [authenticated_account.account_number]
- Balance: $[authenticated_account.money]
- Date and time: [station_time_timestamp()], [GLOB.current_date_string]
- Service terminal ID: [machine_id] "}
-
- //stamp the paper
- var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
- stampoverlay.icon_state = "paper_stamp-cent"
- if(!R.stamped)
- R.stamped = new()
- R.stamped += /obj/item/stamp
- R.overlays += stampoverlay
- R.stamps += "This paper has been stamped by the Automatic Teller Machine."
-
- playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE)
-
- if("insert_card")
- if(held_card)
- held_card.forceMove(loc)
- authenticated_account = null
- if(ishuman(usr))
- usr.put_in_hands(held_card)
- held_card = null
- else
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/card/id))
- usr.drop_item()
- I.forceMove(src)
- held_card = I
-
- if("logout")
- authenticated_account = null
-
- . = TRUE
-
-//create the most effective combination of notes to make up the requested amount
-/obj/machinery/atm/proc/withdraw_arbitrary_sum(arbitrary_sum)
- var/obj/item/stack/spacecash/C = new(amt = arbitrary_sum)
- if(!usr?.put_in_hands(C))
- C.forceMove(get_step(get_turf(src), turn(dir, 180)))
diff --git a/code/modules/economy/Accounts.dm b/code/modules/economy/Accounts.dm
deleted file mode 100644
index c51500bedc7..00000000000
--- a/code/modules/economy/Accounts.dm
+++ /dev/null
@@ -1,171 +0,0 @@
-#define STATION_CREATION_DATE "2 April, 2555"
-#define STATION_CREATION_TIME "11:24:30"
-#define STATION_START_CASH 75000
-#define STATION_SOURCE_TERMINAL "Biesel GalaxyNet Terminal #227"
-#define DEPARTMENT_START_CASH 5000
-
-GLOBAL_VAR_INIT(num_financial_terminals, 1)
-GLOBAL_DATUM(station_account, /datum/money_account)
-GLOBAL_LIST_EMPTY(department_accounts)
-GLOBAL_VAR_INIT(next_account_number, 0)
-GLOBAL_DATUM(centcomm_account_db, /obj/machinery/computer/account_database) // this being an object hurts me deeply on the inside
-GLOBAL_DATUM(vendor_account, /datum/money_account)
-GLOBAL_LIST_EMPTY(all_money_accounts)
-
-/proc/create_station_account()
- if(!GLOB.station_account)
- GLOB.next_account_number = rand(111111, 999999)
-
- GLOB.station_account = new()
- GLOB.station_account.owner_name = "[station_name()] Station Account"
- GLOB.station_account.account_number = rand(111111, 999999)
- GLOB.station_account.remote_access_pin = rand(111111, 999999)
- GLOB.station_account.money = STATION_START_CASH
-
- //create an entry in the account transaction log for when it was created
- GLOB.station_account.makeTransactionLog(STATION_START_CASH, "Account Creation", STATION_SOURCE_TERMINAL, GLOB.station_account.owner_name, FALSE,
- STATION_CREATION_DATE, STATION_CREATION_TIME)
-
- //add the account
- GLOB.all_money_accounts.Add(GLOB.station_account)
-
-/proc/create_department_account(department)
- GLOB.next_account_number = rand(111111, 999999)
-
- var/datum/money_account/department_account = new()
- department_account.owner_name = "[department] Account"
- department_account.account_number = rand(111111, 999999)
- department_account.remote_access_pin = rand(111111, 999999)
- department_account.money = DEPARTMENT_START_CASH
-
- //create an entry in the account transaction log for when it was created
- department_account.makeTransactionLog(DEPARTMENT_START_CASH, "Account Creation", STATION_SOURCE_TERMINAL, department_account.owner_name, FALSE,
- STATION_CREATION_DATE, STATION_CREATION_TIME)
-
- //add the account
- GLOB.all_money_accounts.Add(department_account)
-
- GLOB.department_accounts[department] = department_account
-
-//the current ingame time (hh:mm:ss) can be obtained by calling:
-//station_time_timestamp("hh:mm:ss")
-
-/proc/create_account(new_owner_name = "Default user", starting_funds = 0, obj/machinery/computer/account_database/source_db)
-
- //create a new account
- var/datum/money_account/M = new()
- M.owner_name = new_owner_name
- M.remote_access_pin = rand(111111, 999999)
- M.money = starting_funds
-
- //create an entry in the account transaction log for when it was created
- var/datum/transaction/T = new()
- T.target_name = new_owner_name
- T.purpose = "Account creation"
- T.amount = starting_funds
- if(!source_db)
- //set a random date, time and location some time over the past few decades
- T.date = "[num2text(rand(1,31))] [pick(GLOB.month_names)], [rand(GLOB.game_year - 20,GLOB.game_year - 1)]"
- T.time = "[rand(0,23)]:[rand(0,59)]:[rand(0,59)]"
- T.source_terminal = "NTGalaxyNet Terminal #[rand(111,1111)]"
-
- M.account_number = rand(111111, 999999)
- else
- T.date = GLOB.current_date_string
- T.time = station_time_timestamp()
- T.source_terminal = source_db.machine_id
-
- M.account_number = GLOB.next_account_number
- GLOB.next_account_number += rand(1,25)
-
- //create a sealed package containing the account details
- var/obj/item/smallDelivery/P = new /obj/item/smallDelivery(source_db.loc)
-
- var/obj/item/paper/R = new /obj/item/paper(P)
- playsound(source_db.loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1)
- P.wrapped = R
- R.name = "Account information: [M.owner_name]"
-
- var/overseer = "Unknown"
- var/datum/ui_login/L = source_db.ui_login_get()
- if(L.id)
- overseer = L.id.registered_name
- R.info = {"Account details (confidential)
- Account holder: [M.owner_name]
- Account number: [M.account_number]
- Account pin: [M.remote_access_pin]
- Starting balance: $[M.money]
- Date and time: [station_time_timestamp()], [GLOB.current_date_string]
- Creation terminal ID: [source_db.machine_id]
- Authorised NT officer overseeing creation: [overseer] "}
-
- //stamp the paper
- var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
- stampoverlay.icon_state = "paper_stamp-cent"
- if(!R.stamped)
- R.stamped = new
- R.stamped += /obj/item/stamp
- R.overlays += stampoverlay
- R.stamps += "This paper has been stamped by the Accounts Database."
-
- //add the account
- M.transaction_log.Add(T)
- GLOB.all_money_accounts.Add(M)
-
- return M
-
-/datum/money_account
- var/owner_name = ""
- var/account_number = 0
- var/remote_access_pin = 0
- var/money = 0
- var/suspended = 0
- var/list/transaction_log = list()
- var/security_level = 0 //0 - auto-identify from worn ID, require only account number
- //1 - require manual login / account number and pin
- //2 - require card and manual login
-
-/datum/money_account/New()
- ..()
-
-/datum/transaction
- var/target_name = ""
- var/purpose = ""
- var/amount = 0
- var/date = ""
- var/time = ""
- var/source_terminal = ""
-
-/obj/machinery/computer/account_database/proc/charge_to_account(attempt_account_number, datum/money_account/source, purpose, terminal_id, amount)
- if(!activated)
- return 0
- for(var/datum/money_account/D in GLOB.all_money_accounts)
- if(D.account_number == attempt_account_number && !D.suspended)
- source.charge(amount, D, purpose, terminal_id, "Account #[D.account_number]", "Transfer from [source.owner_name]",
- "[D.owner_name]")
- return 1
-
- return 0
-
-//this returns the first account datum that matches the supplied accnum/pin combination, it returns null if the combination did not match any account
-/proc/attempt_account_access(attempt_account_number, attempt_pin_number, security_level_passed = 0, pin_needed=1)
- for(var/datum/money_account/D in GLOB.all_money_accounts)
- if(D.account_number == attempt_account_number)
- if( D.security_level <= security_level_passed && (!D.security_level || D.remote_access_pin == attempt_pin_number || !pin_needed) )
- return D
-
-/obj/machinery/computer/account_database/proc/get_account(account_number)
- for(var/datum/money_account/D in GLOB.all_money_accounts)
- if(D.account_number == account_number)
- return D
-
-/proc/attempt_account_access_nosec(attempt_account_number)
- for(var/datum/money_account/D in GLOB.all_money_accounts)
- if(D.account_number == attempt_account_number)
- return D
-
-#undef STATION_CREATION_DATE
-#undef STATION_CREATION_TIME
-#undef STATION_START_CASH
-#undef STATION_SOURCE_TERMINAL
-#undef DEPARTMENT_START_CASH
diff --git a/code/modules/economy/Accounts_DB.dm b/code/modules/economy/Accounts_DB.dm
index 874c64c5ead..30f3eaa8eec 100644
--- a/code/modules/economy/Accounts_DB.dm
+++ b/code/modules/economy/Accounts_DB.dm
@@ -1,247 +1,180 @@
-GLOBAL_VAR(current_date_string)
+/*
+ * # Money Account Database
+ *
+ * Datum for tracking a group of related money account, facilitates account creation, deletion, and interaction
+ *
+*/
+/datum/money_account_database
+ ///list of money account datums for individual users
+ var/list/user_accounts = list()
+ ///list of actions taken on the money account database, such as creating/deleting accounts
+ var/list/database_logs = list()
+ ///Tracking log of all actions on the account databse, used for admin logging and debugging
+ var/list/hidden_database_logs = list()
-#define AUT_ACCLST 1
-#define AUT_ACCINF 2
-#define AUT_ACCNEW 3
+ var/datum/money_account/vendor_account
+ ///Will the database permit actions on it? Useful for random events
+ var/online = TRUE
-/obj/machinery/computer/account_database
- name = "Accounts Uplink Terminal"
- desc = "Access transaction logs, account data and all kinds of other financial records."
- icon_screen = "accounts"
- req_one_access = list(ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_CENT_COMMANDER)
- light_color = LIGHT_COLOR_GREEN
- var/receipt_num
- var/machine_id = ""
- var/datum/money_account/detailed_account_view
- var/activated = TRUE
- var/const/fund_cap = 1000000
- /// Current UI page
- var/current_page = AUT_ACCLST
- /// Next time a print can be made
- var/next_print = 0
-
-/obj/machinery/computer/account_database/New()
- // Why the fuck are these not in a subsystem? They are global variables for fucks sake
- // If someone ever makes a map without one of these consoles, the entire eco AND date system breaks
- // This upsets me a lot
- // AA Todo: SSeconomy
- if(!GLOB.station_account)
- create_station_account()
-
- if(GLOB.department_accounts.len == 0)
- for(var/department in GLOB.station_departments)
- create_department_account(department)
- if(!GLOB.vendor_account)
- create_department_account("Vendor")
- GLOB.vendor_account = GLOB.department_accounts["Vendor"]
-
- if(!GLOB.current_date_string)
- GLOB.current_date_string = "[time2text(world.timeofday, "DD Month")], [GLOB.game_year]"
-
- machine_id = "[station_name()] Acc. DB #[GLOB.num_financial_terminals++]"
- ..()
-
-/obj/machinery/computer/account_database/proc/accounting_letterhead(report_name)
- var/datum/ui_login/L = ui_login_get()
- return {"
-
[report_name]
-
[station_name()] Accounting Report
- Generated By: [L?.id?.registered_name ? L.id.registered_name : "Unknown"], [L?.id?.assignment ? L.id.assignment : "Unknown"]
-
- "}
-
-/obj/machinery/computer/account_database/attackby(obj/O, mob/user, params)
- if(ui_login_attackby(O, user))
- return
+/datum/money_account_database/vv_edit_var(var_name, var_value)
+ if(var_name == "vendor_account")
+ if(!istype(var_value, /datum/money_account))
+ return FALSE //this is how you break the economy
return ..()
-/obj/machinery/computer/account_database/attack_hand(mob/user)
- ui_interact(user)
+/datum/money_account_database/can_vv_delete()
+ message_admins("An admin attempted to VV delete a money account database, this will break the economy system for the round, if you know what you are doing please use advanced proccall")
+ return FALSE
-/obj/machinery/computer/account_database/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "AccountsUplinkTerminal", name, 800, 600, master_ui, state)
- ui.open()
+/datum/money_account_database/proc/create_account(account_name = "Unnamed", starting_funds = 0, _security_level = ACCOUNT_SECURITY_ID, terminal, supress_log = FALSE)
+ var/datum/money_account/new_account = new(account_name, starting_funds, ACCOUNT_SECURITY_ID)
+ user_accounts += new_account
+ new_account.database_holder = src
+ if(!supress_log)
+ log_account_action(new_account, starting_funds, "Account Creation", terminal, log_on_database = TRUE)
+ return new_account
-/obj/machinery/computer/account_database/ui_data(mob/user)
- var/list/data = list()
- data["currentPage"] = current_page
- data["is_printing"] = (next_print > world.time)
- ui_login_data(data, user)
- if(data["loginState"]["logged_in"])
- switch(current_page)
- if(AUT_ACCLST)
- var/list/accounts = list()
- for(var/i in 1 to length(GLOB.all_money_accounts))
- var/datum/money_account/D = GLOB.all_money_accounts[i]
- accounts.Add(list(list(
- "account_number" = D.account_number,
- "owner_name" = D.owner_name,
- "suspended" = D.suspended ? "SUSPENDED" : "Active",
- "account_index" = i)))
+/datum/money_account_database/proc/create_vendor_account()
+ if(vendor_account)
+ log_debug("create_vendor_account() was called but a vendor account already exists")
+ return
+ var/datum/money_account/new_vendor_account = new("[station_name()] Vendor Account", DEPARTMENT_BASE_PAY_MEDIUM, ACCOUNT_SECURITY_RESTRICTED)
+ vendor_account = new_vendor_account
+ vendor_account.database_holder = src
- data["accounts"] = accounts
+///takes in an account_numb and returns either an account if it locates one or null if it finds none
+/datum/money_account_database/proc/find_user_account(account_number, include_departments = FALSE)
+ for(var/datum/money_account/account in user_accounts)
+ if(account.account_number == account_number)
+ return account
- if(AUT_ACCINF)
- data["account_number"] = detailed_account_view.account_number
- data["owner_name"] = detailed_account_view.owner_name
- data["money"] = detailed_account_view.money
- data["suspended"] = detailed_account_view.suspended
+/datum/money_account_database/proc/delete_user_account(account_number, terminal, supress_log = FALSE)
+ for(var/datum/money_account/account as anything in user_accounts) // <--- this does not include department account for A GOOD REASON
+ if(account.account_number == account_number)
+ if(!supress_log)
+ log_account_action(account, null, "Delete Money Account", terminal, log_on_database = supress_log)
+ user_accounts -= account
+ qdel(account)
+ return TRUE
+ return FALSE
- var/list/transactions = list()
- for(var/datum/transaction/T in detailed_account_view.transaction_log)
- transactions.Add(list(list(
- "date" = T.date,
- "time" = T.time,
- "target_name" = T.target_name,
- "purpose" = T.purpose,
- "amount" = T.amount,
- "source_terminal" = T.source_terminal)))
+/datum/money_account_database/proc/charge_account(datum/money_account/account, amount, purpose, transactor, allow_overdraft = FALSE, supress_log = FALSE)
+ if(!online)
+ return
+ . = account.try_withdraw_credits(amount, allow_overdraft)
+ if(. && !supress_log)
+ var/database_log = amount >= DATABASE_LOG_THRESHHOLD ? TRUE : FALSE
+ log_account_action(account, amount, purpose, transactor, is_deposit = FALSE, log_on_database = database_log)
- data["transactions"] = transactions
- return data
-
-
-/obj/machinery/computer/account_database/ui_act(action, list/params)
- if(..())
+/datum/money_account_database/proc/credit_account(datum/money_account/account, amount, purpose, transactor, supress_log = FALSE)
+ if(!online)
+ return
+ . = account.deposit_credits(amount)
+ if(!.)
return
- . = TRUE
+ if(!supress_log)
+ var/database_log = amount >= DATABASE_LOG_THRESHHOLD ? TRUE : FALSE
+ log_account_action(account, amount, purpose, transactor, is_deposit = TRUE, log_on_database = database_log) //no if check here for now, since deposit credits currently will always return true
+ if(account == vendor_account)
+ SSeconomy.current_10_minute_spending += amount
+ SSeconomy.total_space_credits -= amount //space credits go to die in the vendor_account for now
+ SSeconomy.space_credits_destroyed += amount
- if(ui_login_act(action, params))
+/datum/money_account_database/proc/try_authenticate_login(datum/money_account/account, pin, restricted_bypass = FALSE, is_vendor = FALSE, is_admin = FALSE)
+ if(!online && !is_admin)
return
+ return account.authenticate_login(pin, restricted_bypass, is_vendor, is_admin)
- if(!ui_login_get().logged_in)
+/datum/money_account_database/proc/log_account_action(datum/money_account/account, amount, purpose, transactor, is_deposit, log_on_database = FALSE)
+ var/datum/transaction/T = account.make_transaction_log(amount, purpose, transactor, is_deposit)
+ if(T && log_on_database)
+ database_logs += T
+ hidden_database_logs += T
+
+/datum/money_account_database/proc/create_transfer_request(datum/money_account/user_account, amount, purpose, datum/money_account/target_account)
+ if(!user_account)
+ CRASH("Money Account DB attempted to create transfer request from a null money account")
+ var/datum/transfer_request/request = new /datum/transfer_request()
+ request.requesting_account = user_account
+ request.amount = amount
+ request.purpose = purpose
+ request.time = world.time
+ target_account.create_transfer_request(request)
+
+/*
+ * # resolve_transfer_request
+ *
+ * handles resolving the transfer request on a money account, if succesful, credits requesting account, if no
+ * issue occur with processing transfer_request, will return TRUE, else it will return FALSE
+ *
+ * Arguments:
+ * * request - transfer request that is being resolved
+ * * user_account - money account where the transfer request is (and account that will be charged)
+ * * accepted - Bool, indicates whether or not the user has "accepted" the transfer request
+*/
+/datum/money_account_database/proc/resolve_transfer_request(datum/transfer_request/request, datum/money_account/user_account, accepted = FALSE)
+ if(!accepted) //proc on money account needs to be called first in order to clear the request from the account
+ user_account.resolve_transfer_request(request)
+ return TRUE
+ if(charge_account(user_account, request.amount, "Transfer to [request.requesting_account.account_name]", "NanoBank Transfer Services", FALSE, FALSE))
+ credit_account(request.requesting_account, request.amount, "Transfer from [user_account.account_name]", "NanoBank Transfer Services", FALSE)
+ if(request.amount >= 5) //we don't care about miniscule transfers
+ SSeconomy.total_credit_transfers++
+ user_account.resolve_transfer_request(request) //this must be called after we're done referencing of it, cause the request will get deleted
+ return TRUE
+ return FALSE
+
+/*
+ * # Main Station Money Account Database
+ *
+ * Datum for tracking crew member, department, and station accounts
+*/
+/datum/money_account_database/main_station
+ ///list of money accounts for each department on station
+ var/list/department_accounts = list()
+
+/datum/money_account_database/main_station/create_account(account_name = "Unnamed", starting_funds = CREW_MEMBER_STARTING_BALANCE, _security_level = ACCOUNT_SECURITY_ID, terminal, supress_log = FALSE)
+ var/datum/money_account/new_account = ..(account_name, starting_funds, _security_level, terminal, supress_log)
+ new_account.set_credits(starting_funds)
+ return new_account
+
+/datum/money_account_database/main_station/proc/create_department_account(department, base_pay, starting_balance)
+ if(department_accounts[department])
+ log_debug("create_department_account() was called to create a [department] account but account for that department already exists")
return
+ var/datum/money_account/department_account = new("[department] Account", base_pay, ACCOUNT_SECURITY_RESTRICTED, ACCOUNT_TYPE_DEPARTMENT, starting_balance)
+ department_account.database_holder = src
+ department_accounts[department] = department_account
- switch(action)
- if("view_account_detail")
- var/index = text2num(params["index"])
- if(index && index > 0 && index <= length(GLOB.all_money_accounts))
- detailed_account_view = GLOB.all_money_accounts[index]
- current_page = AUT_ACCINF
+/datum/money_account_database/main_station/create_vendor_account()
+ var/datum/money_account/new_vendor_account = new("[station_name()] Vendor Account", DEPARTMENT_BASE_PAY_MEDIUM, ACCOUNT_SECURITY_RESTRICTED)
+ vendor_account = new_vendor_account
- if("back")
- detailed_account_view = null
- current_page = AUT_ACCLST
+/datum/money_account_database/main_station/proc/get_account_by_department(department)
+ return department_accounts[department]
- if("toggle_suspension")
- if(detailed_account_view)
- detailed_account_view.suspended = !detailed_account_view.suspended
+/datum/money_account_database/main_station/proc/get_all_department_accounts()
+ var/list/account_list = list()
+ for(var/department in department_accounts)
+ account_list += department_accounts[department]
+ return account_list
- if("create_new_account")
- current_page = AUT_ACCNEW
+/datum/money_account_database/main_station/find_user_account(account_number, include_departments = FALSE)
+ var/list/accounts_to_search = include_departments ? (user_accounts + get_all_department_accounts()) : user_accounts
+ for(var/datum/money_account/account in accounts_to_search)
+ if(account.account_number == account_number)
+ return account
- if("finalise_create_account")
- var/account_name = params["holder_name"]
- var/starting_funds = max(text2num(params["starting_funds"]), 0)
- if(!account_name || !starting_funds)
- return
+/*
+ * # NAS Trurl Money Account Database
+ *
+ * Datum for tracking CC money account, used for admin CC characters.
+*/
- starting_funds = clamp(starting_funds, 0, GLOB.station_account.money) // Not authorized to put the station in debt.
- starting_funds = min(starting_funds, fund_cap) // Not authorized to give more than the fund cap.
+/datum/money_account_database/central_command
- var/datum/money_account/M = create_account(account_name, starting_funds, src)
- if(starting_funds > 0)
- GLOB.station_account.charge(starting_funds, null, "New account activation", "", "New account activation", M.owner_name)
-
- current_page = AUT_ACCLST
-
-
- if("print_records")
- // Anti spam measures
- if(next_print > world.time)
- to_chat(usr, "The printer is busy spooling. It will be ready in [(next_print - world.time) / 10] seconds.")
- return
- var/text
- playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1)
- var/obj/item/paper/P = new(loc)
- P.name = "financial account list"
- text = {"
- [accounting_letterhead("Financial Account List")]
-
-
-
-
-
Account Number
-
Holder
-
Balance
-
Status
-
-
-
- "}
-
- for(var/i in 1 to length(GLOB.all_money_accounts))
- var/datum/money_account/D = GLOB.all_money_accounts[i]
- text += {"
-
-
#[D.account_number]
-
[D.owner_name]
-
$[D.money]
-
[D.suspended ? "Suspended" : "Active"]
-
- "}
-
- text += {"
-
-
- "}
-
- P.info = text
- visible_message("[src] prints out a report.")
- next_print = world.time + 30 SECONDS
-
- if("print_account_details")
- // Anti spam measures
- if(next_print > world.time)
- to_chat(usr, "The printer is busy spooling. It will be ready in [(next_print - world.time) / 10] seconds.")
- return
- var/text
- playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1)
- var/obj/item/paper/P = new(loc)
- P.name = "account #[detailed_account_view.account_number] details"
- var/title = "Account #[detailed_account_view.account_number] Details"
- text = {"
- [accounting_letterhead(title)]
- Holder: [detailed_account_view.owner_name]
- Balance: $[detailed_account_view.money]
- Status: [detailed_account_view.suspended ? "Suspended" : "Active"]
- Transactions: ([detailed_account_view.transaction_log.len])
-
-
-
-
Timestamp
-
Target
-
Reason
-
Value
-
Terminal
-
-
-
- "}
-
- for(var/datum/transaction/T in detailed_account_view.transaction_log)
- text += {"
-
-
[T.date] [T.time]
-
[T.target_name]
-
[T.purpose]
-
[T.amount]
-
[T.source_terminal]
-
- "}
-
- text += {"
-
-
- "}
-
- P.info = text
- visible_message("[src] prints out a report.")
- next_print = world.time + 30 SECONDS
-
-#undef AUT_ACCLST
-#undef AUT_ACCINF
-#undef AUT_ACCNEW
+/datum/money_account_database/central_command/create_account(account_name = "NAS Trurl Account", starting_funds = CC_OFFICER_STARTING_BALANCE, _security_level = ACCOUNT_SECURITY_CC, supress_log = FALSE)
+ var/datum/money_account/new_account = ..()
+ new_account.set_credits(starting_funds)
+ return new_account
diff --git a/code/modules/economy/EFTPOS.dm b/code/modules/economy/EFTPOS.dm
deleted file mode 100644
index b18573bd31c..00000000000
--- a/code/modules/economy/EFTPOS.dm
+++ /dev/null
@@ -1,196 +0,0 @@
-/obj/item/eftpos
- name = "EFTPOS scanner"
- desc = "Swipe your ID card to make purchases electronically."
- icon = 'icons/obj/device.dmi'
- icon_state = "eftpos"
- w_class = WEIGHT_CLASS_SMALL
- var/machine_name = ""
- var/transaction_locked = 0
- var/transaction_paid = 0
- var/transaction_amount = 0
- var/transaction_purpose = "Default charge"
- var/access_code = 0
- var/obj/machinery/computer/account_database/linked_db
- var/datum/money_account/linked_account
-
-/obj/item/eftpos/Initialize(mapload)
- machine_name = "[station_name()] EFTPOS #[GLOB.num_financial_terminals++]"
- access_code = rand(1111,111111)
- reconnect_database()
- print_reference()
-
- //by default, connect to the station account
- //the user of the EFTPOS device can change the target account though, and no-one will be the wiser (except whoever's being charged)
- linked_account = GLOB.station_account
- return ..()
-
-/obj/item/eftpos/proc/print_reference()
- playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1)
- var/obj/item/paper/R = new(loc)
- R.name = "Reference: [machine_name]"
- R.info = {"[machine_name] reference
- Access code: [access_code]
- Do not lose or misplace this code. "}
- //stamp the paper
- var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
- stampoverlay.icon_state = "paper_stamp-cent"
- if(!R.stamped)
- R.stamped = new
- R.stamped += /obj/item/stamp
- R.overlays += stampoverlay
- R.stamps += "This paper has been stamped by the EFTPOS device."
- var/obj/item/smallDelivery/D = new(get_turf(loc))
- if(ishuman(loc))
- var/mob/living/carbon/human/H = loc
- if(H.back)
- D.forceMove(H.back)
- R.forceMove(D)
- D.wrapped = R
- D.name = "small parcel - 'EFTPOS access code'"
-
-/obj/item/eftpos/proc/reconnect_database()
- var/turf/location = get_turf(src)
- if(!location)
- return
-
- for(var/obj/machinery/computer/account_database/DB in GLOB.machines)
- if(DB.z == location.z)
- linked_db = DB
- break
-
-/obj/item/eftpos/attack_self(mob/user)
- ui_interact(user)
-
-/obj/item/eftpos/attackby(obj/O, mob/user, params)
- if(istype(O, /obj/item/card))
- //attempt to connect to a new db, and if that doesn't work then fail
- if(!linked_db)
- reconnect_database()
- if(linked_db)
- if(linked_account)
- scan_card(O, user)
- SStgui.update_uis(src)
- else
- to_chat(user, "[bicon(src)]Unable to connect to linked account.")
- else
- to_chat(user, "[bicon(src)]Unable to connect to accounts database.")
- else
- return ..()
-
-/obj/item/eftpos/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "EFTPOS", name, 800, 300, master_ui, state)
- ui.open()
-
-/obj/item/eftpos/ui_data(mob/user)
- var/list/data = list()
- data["machine_name"] = machine_name
- data["transaction_locked"] = transaction_locked
- data["transaction_paid"] = transaction_paid
- data["transaction_purpose"] = transaction_purpose
- data["transaction_amount"] = transaction_amount
- data["linked_account"] = linked_account ? linked_account.owner_name : null
- return data
-
-/obj/item/eftpos/ui_act(action, list/params)
- if(..())
- return
-
- . = TRUE
-
- switch(action)
- if("change_code")
- var/attempt_code = input("Re-enter the current EFTPOS access code", "Confirm old EFTPOS code") as num
- if(attempt_code == access_code)
- var/trycode = input("Enter a new access code for this device (4-6 digits, numbers only)", "Enter new EFTPOS code") as num
- if(trycode >= 1000 && trycode <= 999999)
- access_code = trycode
- else
- alert("That is not a valid code!")
- print_reference()
- else
- to_chat(usr, "[bicon(src)]Incorrect code entered.")
- if("link_account")
- if(!linked_db)
- reconnect_database()
- if(linked_db)
- var/attempt_account_num = input("Enter account number to pay EFTPOS charges into", "New account number") as num
- var/attempt_pin = input("Enter pin code", "Account pin") as num
- linked_account = attempt_account_access(attempt_account_num, attempt_pin, 1)
- else
- to_chat(usr, "[bicon(src)]Unable to connect to accounts database.")
- if("trans_purpose")
- var/purpose = clean_input("Enter reason for EFTPOS transaction", "Transaction purpose", transaction_purpose)
- if(purpose)
- transaction_purpose = purpose
- if("trans_value")
- var/try_num = input("Enter amount for EFTPOS transaction", "Transaction amount", transaction_amount) as num
- if(try_num < 0)
- alert("That is not a valid amount!")
- else
- transaction_amount = try_num
- if("toggle_lock")
- if(transaction_locked)
- var/attempt_code = input("Enter EFTPOS access code", "Reset Transaction") as num
- if(attempt_code == access_code)
- transaction_locked = 0
- transaction_paid = 0
- else if(linked_account)
- transaction_locked = 1
- else
- to_chat(usr, "[bicon(src)]No account connected to send transactions to.")
- if("scan_card")
- //attempt to connect to a new db, and if that doesn't work then fail
- if(!linked_db)
- reconnect_database()
- if(linked_db && linked_account)
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/card))
- scan_card(I, usr)
- else
- to_chat(usr, "[bicon(src)]Unable to link accounts.")
- if("reset")
- //reset the access code - requires HoP/captain access
- var/obj/item/I = usr.get_active_hand()
- if(istype(I, /obj/item/card))
- var/obj/item/card/id/C = I
- if((ACCESS_CENT_COMMANDER in C.access) || (ACCESS_HOP in C.access) || (ACCESS_CAPTAIN in C.access))
- access_code = 0
- to_chat(usr, "[bicon(src)]Access code reset to 0.")
- else if(istype(I, /obj/item/card/emag))
- access_code = 0
- to_chat(usr, "[bicon(src)]Access code reset to 0.")
-
-
-/obj/item/eftpos/proc/scan_card(obj/item/card/I, mob/user)
- if(istype(I, /obj/item/card/id))
- var/obj/item/card/id/C = I
- visible_message("[user] swipes a card through [src].")
-
- if(!transaction_locked || transaction_paid)
- return
-
- if(!linked_account)
- to_chat(user, "[bicon(src)]EFTPOS is not connected to an account.")
- return
-
- var/confirm = alert("Are you sure you want to pay $[transaction_amount] to Account: [linked_account.owner_name] ", "Confirm transaction", "Yes", "No")
- if(confirm == "No")
- return
- var/attempt_pin = input("Enter pin code", "EFTPOS transaction") as num
- var/datum/money_account/D = attempt_account_access(C.associated_account_number, attempt_pin, 2)
-
- if(!D)
- to_chat(user, "[bicon(src)]Unable to access account. Check security settings and try again.")
-
- if(transaction_amount > D.money)
- to_chat(user, "[bicon(src)]You don't have that much money!")
- return
-
- var/transSuccess = D.charge(transaction_amount, linked_account, transaction_purpose, machine_name, D.owner_name)
- if(transSuccess == TRUE)
- playsound(src, 'sound/machines/chime.ogg', 50, 1)
- visible_message("[src] chimes!")
- transaction_paid = 1
- //emag?
diff --git a/code/modules/economy/Job_Departments.dm b/code/modules/economy/Job_Departments.dm
deleted file mode 100644
index 44a12dae02e..00000000000
--- a/code/modules/economy/Job_Departments.dm
+++ /dev/null
@@ -1,70 +0,0 @@
-GLOBAL_LIST_INIT(station_departments, list("Command", "Medical", "Engineering", "Science", "Security", "Cargo", "Support", "Assistant"))
-
-// The department the job belongs to.
-/datum/job/var/department = null
-
-// Whether this is a head position
-/datum/job/var/head_position = 0
-
-/datum/job/captain/department = "Command"
-/datum/job/captain/head_position = 1
-
-/datum/job/hop/department = "Support"
-/datum/job/hop/head_position = 1
-
-/datum/job/assistant/department = "Assistant"
-
-/datum/job/bartender/department = "Support"
-
-/datum/job/chef/department = "Support"
-
-/datum/job/hydro/department = "Support"
-
-/datum/job/mining/department = "Support"
-
-/datum/job/janitor/department = "Support"
-
-/datum/job/librarian/department = "Support"
-
-/datum/job/lawyer/department = "Support"
-
-/datum/job/chaplain/department = "Support"
-
-/datum/job/qm/department = "Cargo"
-/datum/job/qm/head_position = 1
-
-/datum/job/cargo_tech/department = "Cargo"
-
-/datum/job/chief_engineer/department = "Engineering"
-/datum/job/chief_engineer/head_position = 1
-
-/datum/job/engineer/department = "Engineering"
-
-/datum/job/atmos/department = "Engineering"
-
-/datum/job/cmo/department = "Medical"
-/datum/job/cmo/head_position = 1
-
-/datum/job/doctor/department = "Medical"
-
-/datum/job/chemist/department = "Medical"
-
-/datum/job/geneticist/department = "Medical"
-
-/datum/job/psychiatrist/department = "Medical"
-
-/datum/job/rd/department = "Science"
-/datum/job/rd/head_position = 1
-
-/datum/job/scientist/department = "Science"
-
-/datum/job/roboticist/department = "Science"
-
-/datum/job/hos/department = "Security"
-/datum/job/hos/head_position = 1
-
-/datum/job/warden/department = "Security"
-
-/datum/job/detective/department = "Security"
-
-/datum/job/officer/department = "Security"
diff --git a/code/modules/economy/README.md b/code/modules/economy/README.md
new file mode 100644
index 00000000000..0a2946b1b28
--- /dev/null
+++ b/code/modules/economy/README.md
@@ -0,0 +1,121 @@
+# Paradise Economy
+
+## Introduction
+This README was last updated on October 2nd, 2022
+
+## Economy SubSystem
+The Economy SubSystem performs a few important tasks that turns the gears of the economy
+
+* `payday()` - This is called every 30 minutes. the payday proc goes through every single money account and credits it the amount it has its paycheck set to. In addition, it will add/subtract credits based on what bonuses/deductions the money account has on it. In addition, SSeconomy tracks global deductions and bonuses which will be applied to EVERY paycheck and then zero'd out after the payday is over. If a money account is open on a NanoBank app, they will be alerted to the paycheck through the app.
+
+* `process_job_tasks()`- This is called every 30 seconds to check if players who have job objectives have completed the requirements of those job objectives, if so, it will add a "bonus" to the players next check and notify the player of the payroll modification. This proc handles calling procs and changing variables that will check completion on the job task and disable it from being checked in the future once payout is given.
+
+## The Space Credit Financial System
+The Space Credit system is split up into two important datums: money accounts (`/datum/money_account`) and money account databases (`datum/money_account_database`). Money accounts represent individual accounts and money account databases represent a collection of money accounts.
+
+### Money Accounts
+A `/datum/money_acount` or "money account" is where most economy data is stored. In money accounts information is contained about the owner as well as all relevant credentials and setting for the account. Money accounts are rather barebones and only contain helper functions to simplify direct interaction.
+
+**Credit Balance**
+Accounts are primarily a store of space credits for crew
+`/datum/money_account/proc/try_withdraw_credits` - used to subtract credits from account balance with some safety checks
+`/datum/money_account/proc/deposit_credits` - used to deposit credits into the account balance
+`/datum/money_account/proc/set_credits` - used to set credit balance to a specified value
+
+**Account Security**
+Accounts are not perfectly secure and safe, in fact, they're built to be broken into when crew members are not careful with their credentials, or an antagonist chooses to try and access another's account
+
+`/datum/money_account/proc/authenticate_login`
+This authenticate_login() proc is used to check the provided credentials against the accounts security level, it returns fails (FALSE) or success (TRUE) if the provided values pass the threshold/security reqs for the specificed security level
+There are a few important accounts level
+
+* `ACCOUNT_SECURITY_ID` - Account can be accessed with only the account number
+* `ACCOUNT_SECURITY_PIN` - Account can be accessed only with the account number AND correct pin
+* `ACCOUNT_SECURITY_RESTRICTED` - Account has same pin/acc-number restrictions but can only be accessed through special machines
+* `ACCOUNT_SECURITY_CC` - Account requires user to be an admin, this is for CC character safety
+* `ACCOUNT_SECURITY_VENDOR` - Account requires forced programmatic access, players in-game cannot access this account
+
+`make_transaction_log` - Creates a `/datum/transaction` object to be tracked on money account
+Transactions are just logs of money going in and out of the account so that players can see where there money comes from and how it is being spent, it provides a paper trail as well for security and legal affairs
+
+**Money Transfers**
+Money transfers are the transfer of space credits between money accounts, while the movement of space credits out of one account into another is handled by an account database, the actual requests that have not been resolved are stored and handled on money accounts
+
+`/datum/money_account/proc/create_transfer_request`
+Simple helper proc that handles creating a transfer request on the money account, needed to interact with LAZYLISTs
+`/datum/money_account/proc/resolve_transfer_request`
+another helper proc that clears the transfer request
+
+Neither of these procs actually transfer money account and rather just deal with how the information is being stored and processed on the money account, the actual money transfer is done through the account database.
+
+**Why You Shouldn't use these Procs**
+You shouldn't be using these procs unless you intend to change how the account database interacts with money accounts. Like mentioned previously, money accounts are only for storing data and don't actual perform the functions and power the moving parts of the economy. This is because there can be upwards of 100 money accounts in one round, in order to make this memory performative, money accounts are focused on being as efficient as possible by utilizing LAZY LISTS and facilitating good Garbage Collection practices
+
+Instead, if you want to perform calculations, purchases, transfers, etc with multiple money accounts, ALL of that should be done through a account database or some form of financial machinery but not the money account.
+
+### Account Databases
+If Money accounts are just records of data, account databases are a store of multiple money data records. A `datum/money_account_database` holds lists of money accounts and serves as the primary means in which interactions are carried out
+
+most instances of any financial/economy machinery/programs should have a reference to the main station
+database. You'll see a lot of machinery already use a var ref to the main station db, through this, most all economy actions can be performed. (Please see each procs documentation for usage). This is how you will interact with money account, through the account database.
+
+**Referencing Money Databases**
+A good way to make sure your machine can access the station money database
+`var/datum/money_account_database/account_db = GLOB.station_money_database`
+Additionally, if you need to access a different money database you can go through SSeconomy
+`SSeconomy.money_account_databases`
+
+**Using a Money DB to find an account**
+The only way to find personal money accounts in a money DB is with the account associated account number
+you can use `find_user_account()` for this.
+`/datum/money_account_database/proc/find_user_account(account_number, include_departments = FALSE)`
+for this proc specifically, you can specify to include departments in the search
+
+For the main station type account db, you can also find departments, you can either get all the departments in one list an iterate through it yourself, or just provide the name of the department
+`/datum/money_account_database/main_station/proc/get_all_department_accounts()`
+`/datum/money_account_database/main_station/proc/get_account_by_department(department)`
+
+**Moving Money In and Out of Money Accounts**
+You can charge a money account, when doing this through the account_db, you need to provide information about the purchase so that if the charge goes through, proper transaction logs can be created
+`/datum/money_account_database/proc/charge_account`
+
+You can add credits to a money account with the credit account proc, works similairly to charge account
+`/datum/money_account_database/proc/credit_account`
+
+Create a money transfer request datum on specified money account, notifies the money account to start storing this data
+`/datum/money_account_database/proc/create_transfer_request`
+Resolves a money transfer request, deletes transfer request and charges/credits involved accounts
+`/datum/money_account_database/proc/resolve_transfer_request`
+
+**Money Account Security through the DB**
+Check user permission to access a money account based on given parameters
+`/datum/money_account_database/proc/try_authenticate_login`
+Create a transaction log on a money account (and DB in some cases), basically a constructor
+`/datum/money_account_database/proc/log_account_action`
+
+
+for example, in order to interact with money account A you will need to go through your account database
+`account_db.credit_account(A, 50, "Account Credit", ...)`
+as opposed to doing it the wrong way which doesn't create a transaction log or check to see if the DB is online
+`A.deposit_credits(50)`
+
+As with the previousl example, you may notice that a lot of these procs exist on the money account level. Those are internal procs that exist for the account database to use. The reason for this is to ensure proper logging for players and admins, handle interactions where two money accounts are involved, and to prevent runtimes that may occur from value mismatches. Many procs such as a `charge_account()` and `resolve_transfer_request()` also ensure perfect transfer of credits, this is important (And explained in the next section)
+
+### Perfect Credit Transfer
+The power of an in-game economy exists because of how much control we have over it. Every credit spent needs to go somewhere, and every credit given needs to come from somewhere. One should avoid just crediting accounts on a whim or removing money for no reason. Since we don't have current implementations of supply/demand or rolling prices (increasing or decreasing), prices of items in-game are fixed. That means that the money supply is the only thing controlling how much or little of items that the crew can buy.
+
+Space Credits Taps (In-Flow of Space Credits into the economy)
+* SSeconomy Payday every 30 minutes
+* Job Objectives Completion
+* Cargo selling crates, plasma, seeds, and research
+* Round-start Money Account Balances
+* Slot Machine Winnings (although this really is a space credit drain in disguise)
+* Contract Completions (contractors)
+
+Space Credit Drains/Exchanges (Out-Flow of space credits out of the economy)
+* Vending Machine Purchases
+* Cargo Supply Console Purchases
+* Physical Destruction of Space cash or credits stored in economy machinery
+* RARE: Sol Traders
+
+## Other Important Economy Machinery/Programs
diff --git a/code/modules/economy/Economy_Events.dm b/code/modules/economy/economy_events/economy_events.dm
similarity index 100%
rename from code/modules/economy/Economy_Events.dm
rename to code/modules/economy/economy_events/economy_events.dm
diff --git a/code/modules/economy/Economy_Events_Mundane.dm b/code/modules/economy/economy_events/economy_events_mundane.dm
similarity index 100%
rename from code/modules/economy/Economy_Events_Mundane.dm
rename to code/modules/economy/economy_events/economy_events_mundane.dm
diff --git a/code/modules/economy/Economy_TradeDestinations.dm b/code/modules/economy/economy_events/economy_trade_destinations.dm
similarity index 100%
rename from code/modules/economy/Economy_TradeDestinations.dm
rename to code/modules/economy/economy_events/economy_trade_destinations.dm
diff --git a/code/modules/economy/economy_helpers.dm b/code/modules/economy/economy_helpers.dm
new file mode 100644
index 00000000000..7eae9abf0b9
--- /dev/null
+++ b/code/modules/economy/economy_helpers.dm
@@ -0,0 +1,34 @@
+/mob/proc/get_worn_id_account()
+ if(!ishuman(src))
+ return
+ var/mob/living/carbon/human/H = src
+ var/obj/item/card/id/I = H.get_idcard()
+ if(!istype(I))
+ return null
+ var/datum/money_account/D = GLOB.station_money_database.find_user_account(I.associated_account_number)
+ return D
+
+/datum/mind/proc/set_initial_account(datum/money_account/account) //needed for GC'ing
+ initial_account = account
+ RegisterSignal(initial_account, COMSIG_PARENT_QDELETING, PROC_REF(clear_initial_account))
+
+/datum/mind/proc/clear_initial_account() //needed for GC'ing
+ UnregisterSignal(initial_account, COMSIG_PARENT_QDELETING)
+ initial_account = null
+
+
+///matches a string job name to their department(s) and returns it as a list
+/proc/get_departments_from_job(job_name)
+ if(!job_name)
+ return
+ var/list/found_departments = list()
+ for(var/datum/station_department/department as anything in SSjobs.station_departments)
+ for(var/job in department.department_roles)
+ if(job == job_name)
+ found_departments += department
+ return found_departments
+
+/proc/get_department_from_name(name)
+ for(var/datum/station_department/department in SSjobs.station_departments)
+ if(department.department_name == name)
+ return department
diff --git a/code/modules/economy/economy_machinery/account_terminal.dm b/code/modules/economy/economy_machinery/account_terminal.dm
new file mode 100644
index 00000000000..a3d60655f12
--- /dev/null
+++ b/code/modules/economy/economy_machinery/account_terminal.dm
@@ -0,0 +1,117 @@
+#define AUT_ACCLST 1
+#define AUT_ACCINF 2
+#define AUT_ACCNEW 3
+
+/obj/machinery/computer/account_database
+ name = "Accounts Uplink Terminal"
+ desc = "Access transaction logs, account data and all kinds of other financial records."
+ icon_screen = "accounts"
+ req_one_access = list(ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_CENT_COMMANDER)
+ light_color = LIGHT_COLOR_GREEN
+
+ var/activated = TRUE
+ /// Current UI page
+ var/current_page = AUT_ACCLST
+ ///account currently being viewed
+ var/datum/money_account/detailed_account_view
+ ///station account database
+ var/datum/money_account_database/account_db
+
+/obj/machinery/computer/account_database/attackby(obj/O, mob/user, params)
+ if(ui_login_attackby(O, user))
+ return
+ return ..()
+
+/obj/machinery/computer/account_database/attack_hand(mob/user)
+ ui_interact(user)
+
+/obj/machinery/computer/account_database/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "AccountsUplinkTerminal", name, 800, 600, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/account_database/ui_data(mob/user)
+ var/list/data = list()
+ data["currentPage"] = current_page
+ ui_login_data(data, user)
+
+ if(data["loginState"]["logged_in"])
+ switch(current_page)
+ if(AUT_ACCLST)
+ data["accounts"] = list()
+ for(var/datum/money_account/account as anything in GLOB.station_money_database.user_accounts)
+ var/list/account_data = list(
+ "account_number" = account.account_number,
+ "owner_name" = account.account_name,
+ "suspended" = account.suspended ? "SUSPENDED" : "Active")
+ data["accounts"] += list(account_data)
+ data["department_accounts"] = list()
+ for(var/datum/money_account/account as anything in GLOB.station_money_database.get_all_department_accounts())
+ var/list/account_data = list(
+ "account_number" = account.account_number,
+ "name" = account.account_name,
+ "suspended" = account.suspended ? "SUSPENDED" : "Active")
+ data["department_accounts"] += list(account_data)
+ if(AUT_ACCINF)
+ data["account_number"] = detailed_account_view.account_number
+ data["account_pin"] = detailed_account_view.account_pin
+ data["owner_name"] = detailed_account_view.account_name
+ data["money"] = detailed_account_view.credit_balance
+ data["suspended"] = detailed_account_view.suspended
+
+ data["is_department_account"] = (detailed_account_view.account_type == ACCOUNT_TYPE_DEPARTMENT)
+ data["transactions"] = list()
+ for(var/datum/transaction/T in detailed_account_view.account_log)
+ var/list/transaction_info = list(
+ "target_name" = T.transactor,
+ "time" = T.time,
+ "purpose" = T.purpose,
+ "amount" = T.amount,
+ "is_deposit" = T.is_deposit
+ )
+ data["transactions"] += list(transaction_info)
+ return data
+
+
+/obj/machinery/computer/account_database/ui_act(action, list/params)
+ if(..())
+ return
+
+ . = TRUE
+
+ if(ui_login_act(action, params))
+ return
+
+ if(!ui_login_get().logged_in)
+ return
+
+ switch(action)
+ if("view_account_detail")
+ var/account_num = text2num(params["account_num"])
+ if(account_num)
+ detailed_account_view = GLOB.station_money_database.find_user_account(account_num, include_departments = TRUE)
+ RegisterSignal(detailed_account_view, COMSIG_PARENT_QDELETING, PROC_REF(clear_viewed_account))
+ current_page = AUT_ACCINF
+ if("back")
+ clear_viewed_account()
+ if("toggle_suspension")
+ if(detailed_account_view)
+ detailed_account_view.suspended = !detailed_account_view.suspended
+ if("create_new_account")
+ current_page = AUT_ACCNEW
+ if("finalise_create_account")
+ var/account_name = params["holder_name"]
+ if(!account_name)
+ return
+ GLOB.station_money_database.create_account(account_name, 0, ACCOUNT_SECURITY_ID, name, supress_log = FALSE)
+ current_page = AUT_ACCLST
+
+/obj/machinery/computer/account_database/proc/clear_viewed_account()
+ UnregisterSignal(detailed_account_view, COMSIG_PARENT_QDELETING)
+ detailed_account_view = null
+ current_page = AUT_ACCLST
+
+#undef AUT_ACCLST
+#undef AUT_ACCINF
+#undef AUT_ACCNEW
diff --git a/code/modules/economy/economy_machinery/atm.dm b/code/modules/economy/economy_machinery/atm.dm
new file mode 100644
index 00000000000..0ba52bdf614
--- /dev/null
+++ b/code/modules/economy/economy_machinery/atm.dm
@@ -0,0 +1,311 @@
+#define ATM_SCREEN_DEFAULT 0
+#define ATM_SCREEN_SECURITY 1
+#define ATM_SCREEN_TRANSFER 2
+#define ATM_SCREEN_LOGS 3
+
+#define PRINT_DELAY (30 SECONDS)
+#define LOCKOUT_TIME (10 SECONDS)
+
+/obj/machinery/economy/atm
+ name = "Nanotrasen automatic teller machine"
+ desc = "For all your monetary needs! Just insert your ID card to make a withdrawal or deposit!"
+ icon = 'icons/obj/terminals.dmi'
+ icon_state = "atm"
+ anchored = TRUE
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 10
+ density = FALSE
+ restricted_bypass = TRUE
+
+ ///Current money account the ATM is accessing
+ var/datum/money_account/authenticated_account
+ ///ID Card that is currently inserted into the ATM
+ var/obj/item/card/held_card
+
+ ///UI screen the ATM is on currently
+ var/view_screen = ATM_SCREEN_DEFAULT
+ ///cooldown inbetween printing balance statements n stuff
+ var/print_cooldown = 0
+ ///the time when the lockout on the ATM is lifted
+ var/lockout_time = 0
+ ///failed login attempts counter, used for locking out the atm
+ var/login_attempts = 0
+
+/obj/machinery/economy/atm/Initialize(mapload)
+ . = ..()
+ update_icon()
+
+/obj/machinery/economy/atm/update_icon_state()
+ . = ..()
+ if(stat & NOPOWER)
+ icon_state = "atm_off"
+ else
+ icon_state = "atm"
+
+/obj/machinery/economy/atm/update_overlays()
+ . = ..()
+ underlays.Cut()
+
+ if(stat & NOPOWER)
+ return
+
+ underlays += emissive_appearance(icon, "atm_lightmask")
+
+/obj/machinery/economy/atm/power_change()
+ ..()
+ if(stat & NOPOWER)
+ set_light(0)
+ else
+ set_light(1, LIGHTING_MINIMUM_POWER)
+ update_icon()
+
+/obj/machinery/economy/atm/process()
+ if(stat & NOPOWER)
+ return
+
+/obj/machinery/economy/atm/attack_hand(mob/user)
+ if(..())
+ return TRUE
+ if(issilicon(user))
+ to_chat(user, "Artificial unit recognized. Artificial units do not currently receive monetary compensation, as per Nanotrasen regulation #1005.")
+ return
+ if(!account_database)
+ reconnect_database()
+
+ ui_interact(user)
+
+/obj/machinery/economy/atm/attack_ghost(mob/user)
+ ui_interact(user)
+
+/obj/machinery/economy/atm/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/card))
+ if(powered())
+ handle_id_insert(I, user)
+ return TRUE
+ else if(authenticated_account)
+ if(istype(I, /obj/item/stack/spacecash))
+ if(!powered())
+ return
+ insert_cash(I, user)
+ return TRUE
+
+ return ..()
+
+/obj/machinery/economy/atm/insert_cash(obj/item/stack/spacecash/cash_money, mob/user)
+ visible_message("[user] inserts [cash_money] into [src].")
+ cash_stored += cash_money.amount
+ account_database.credit_account(authenticated_account, cash_money.amount, "ATM Deposit", name, FALSE)
+ cash_money.use(cash_money.amount)
+ return TRUE
+
+/obj/machinery/economy/atm/proc/handle_id_insert(obj/item/card/id, mob/user)
+ if(held_card)
+ return
+ user.drop_item()
+ id.forceMove(src)
+ held_card = id
+ RegisterSignal(held_card, COMSIG_PARENT_QDELETING, PROC_REF(clear_held_card))
+ if(authenticated_account && held_card.associated_account_number != authenticated_account.account_number)
+ clear_account()
+
+/obj/machinery/economy/atm/proc/eject_inserted_id(mob/user)
+ if(!held_card)
+ return
+ held_card.forceMove(loc)
+ if(ishuman(user))
+ user.put_in_hands(held_card)
+ logout()
+ clear_held_card()
+
+///ensures proper GC of ID card
+/obj/machinery/economy/atm/proc/clear_held_card()
+ UnregisterSignal(held_card, COMSIG_PARENT_QDELETING)
+ held_card = null
+
+///ensures proper GC of money account
+/obj/machinery/economy/atm/proc/clear_account()
+ UnregisterSignal(authenticated_account, COMSIG_PARENT_QDELETING)
+ authenticated_account = null
+
+/obj/machinery/economy/atm/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if (!ui)
+ ui = new(user, src, ui_key, "ATM", name, 550, 650)
+ ui.open()
+
+/obj/machinery/economy/atm/ui_data(mob/user)
+ var/list/data = list()
+
+ data["view_screen"] = view_screen
+ data["held_card_name"] = held_card?.name
+ data["linked_db"] = account_database ? TRUE : FALSE
+
+ data["authenticated_account"] = authenticated_account
+ if(authenticated_account)
+ data["owner_name"] = authenticated_account.account_name
+ data["money"] = authenticated_account.credit_balance
+ data["security_level"] = authenticated_account.security_level
+
+ data["transaction_log"] = list()
+ for(var/datum/transaction/T in authenticated_account.account_log)
+ var/list/transaction_info = list(
+ "time" = T.time,
+ "target_name" = T.transactor,
+ "purpose" = T.purpose,
+ "amount" = T.amount,
+ "is_deposit" = T.is_deposit
+ )
+ data["transaction_log"] += list(transaction_info)
+
+
+ return data
+
+/obj/machinery/economy/atm/ui_act(action, params, datum/tgui/ui)
+ if(..())
+ return
+
+ var/mob/user = ui.user
+
+ switch(action)
+ if("transfer")
+ if(!authenticated_account)
+ return
+ var/transfer_amount = text2num(params["funds_amount"])
+ var/target_account_number = text2num(params["target_acc_number"])
+ var/transfer_purpose = params["purpose"]
+ transfer_credits(transfer_amount, target_account_number, transfer_purpose, user)
+ if("view_screen")
+ var/list/valid_screen = list(ATM_SCREEN_DEFAULT, ATM_SCREEN_SECURITY, ATM_SCREEN_TRANSFER, ATM_SCREEN_LOGS)
+ var/screen_proper = text2num(params["view_screen"])
+ if(screen_proper in valid_screen)
+ view_screen = screen_proper
+ else
+ message_admins("Warning: possible href exploit by [key_name(usr)] - Invalid screen number passed into an ATM")
+ log_debug("Warning: possible href exploit by [key_name(usr)] - Invalid screen number passed into an ATM")
+ if("change_security_level")
+ if(authenticated_account)
+ var/new_sec_level = max(min(text2num(params["new_security_level"]), 2), 0)
+ authenticated_account.security_level = new_sec_level
+ if("attempt_auth")
+ var/tried_account_num = text2num(params["account_num"]) ? text2num(params["account_num"]) : held_card?.associated_account_number
+ var/tried_pin = text2num(params["account_pin"])
+ attempt_login(tried_account_num, tried_pin, user)
+ if("withdrawal")
+ var/amount = max(text2num(params["funds_amount"]), 0)
+ if(amount)
+ withdraw(amount, user)
+ if("balance_statement")
+ print_balance_statement()
+ if("insert_card")
+ if(held_card)
+ eject_inserted_id(user)
+ else
+ var/obj/item/I = user.get_active_hand()
+ if(istype(I, /obj/item/card/id))
+ handle_id_insert(I, user)
+ if("logout")
+ logout()
+
+ . = TRUE
+
+/obj/machinery/economy/atm/proc/authenticate_account(account_number, account_pin, mob/user, silent = TRUE)
+ var/datum/money_account/target_account = account_database.find_user_account(account_number, include_departments = TRUE)
+ if(attempt_account_authentification(target_account, account_pin, user))
+ if(!silent)
+ to_chat(user, "[bicon(src)]Access granted. Welcome user '[authenticated_account.account_name].'")
+ return TRUE
+ if(!silent)
+ playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
+ to_chat(user, "[bicon(src)]Authentification Failure, incorrect credentials or insufficient permissions.")
+ return FALSE
+
+/obj/machinery/economy/atm/proc/attempt_login(account_number, account_pin, mob/user)
+
+ var/account_to_attempt = account_number ? account_number : held_card?.associated_account_number
+ if(!account_to_attempt)
+ to_chat(user, "[bicon(src)]Authentification Failure: Account number not found.")
+ return FALSE
+
+ var/datum/money_account/user_account = account_database.find_user_account(account_number, include_departments = TRUE)
+ if(!user_account)
+ to_chat(user, "[bicon(src)]Authentification Failure: User Account Not Found.")
+ return FALSE
+
+ if(attempt_account_authentification(user_account, account_pin, user))
+ authenticated_account = user_account
+ RegisterSignal(authenticated_account, COMSIG_PARENT_QDELETING, PROC_REF(clear_account))
+ return TRUE
+
+ //else failed login
+
+ login_attempts++
+ account_database.log_account_action(user_account, 0, "Unauthorised login attempt", name, log_on_database = FALSE)
+ to_chat(user, "[bicon(src)]Incorrect pin/account combination entered, [3 - login_attempts] attempt\s remaining.")
+ if(login_attempts >= 3)
+ playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
+ view_screen = ATM_SCREEN_DEFAULT
+ lockout_time = world.time + LOCKOUT_TIME
+
+/obj/machinery/economy/atm/proc/logout()
+ clear_account()
+ view_screen = ATM_SCREEN_DEFAULT
+ login_attempts = 0
+
+/obj/machinery/economy/atm/proc/transfer_credits(amount, target_account_number, purpose, mob/user)
+ if(!authenticated_account)
+ return
+ if(amount <= 0)
+ to_chat(user, "[bicon(src)]That is not a valid transfer amount.")
+ return
+ if(account_database.charge_account(authenticated_account, amount, FALSE))
+ var/datum/money_account/target_account = account_database.find_user_account(target_account_number, include_departments = TRUE)
+ if(target_account)
+ account_database.credit_account(target_account, amount, purpose, name, FALSE)
+ to_chat(user, "[bicon(src)]Funds transfer successful.")
+ else
+ to_chat(user, "[bicon(src)]You don't have enough funds to do that!")
+
+/obj/machinery/economy/atm/proc/withdraw(amount, mob/user)
+ if(!authenticated_account)
+ return
+ if(amount <= 0)
+ to_chat(user, "[bicon(src)]That is not a valid amount.")
+ return
+
+ if(account_database.charge_account(authenticated_account, amount, "Cash Withdrawal", name, FALSE, FALSE))
+ playsound(src, 'sound/machines/chime.ogg', 50, TRUE)
+ dispense_space_cash(amount, user)
+
+/obj/machinery/economy/atm/proc/print_balance_statement()
+ if(!authenticated_account)
+ return
+ if(world.time <= print_cooldown)
+ to_chat(usr, "[src] flashes an error on its display.")
+ return
+ print_cooldown = world.time + PRINT_DELAY
+ playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, TRUE)
+ var/obj/item/paper/R = new(loc)
+ R.name = "Account balance: [authenticated_account.account_name]"
+ R.info = {"NT Automated Teller Account Statement
+ Account holder: [authenticated_account.account_name]
+ Account number: [authenticated_account.account_number]
+ Balance: $[authenticated_account.credit_balance]
+ Date and time: [station_time_timestamp()], [GLOB.current_date_string]"}
+
+ //stamp the paper
+ var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
+ stampoverlay.icon_state = "paper_stamp-cent"
+ if(!R.stamped)
+ R.stamped = new()
+ R.stamped += /obj/item/stamp
+ R.overlays += stampoverlay
+ R.stamps += "This paper has been stamped by the Automatic Teller Machine."
+
+ playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE)
+
+
+#undef ATM_SCREEN_DEFAULT
+#undef ATM_SCREEN_SECURITY
+#undef ATM_SCREEN_TRANSFER
+#undef ATM_SCREEN_LOGS
+#undef LOCKOUT_TIME
diff --git a/code/modules/economy/economy_machinery/economy_machinery.dm b/code/modules/economy/economy_machinery/economy_machinery.dm
new file mode 100644
index 00000000000..c85e6735732
--- /dev/null
+++ b/code/modules/economy/economy_machinery/economy_machinery.dm
@@ -0,0 +1,118 @@
+/**
+ * Base machine type for machinery that needs to interact with users spending Space Cash or credit from Money Accounts
+ * has helper procs to automate account authentification and handling transactions.
+ */
+/obj/machinery/economy
+ name = "Financial System Interface"
+ desc = "A base economy machine"
+ anchored = TRUE
+ density = TRUE
+
+ ///Can this machine access restricted money accounts?
+ var/restricted_bypass = FALSE
+
+ var/accepts_cash = TRUE
+ var/accepts_card = TRUE
+
+ ///If you beat the shit out of this machine, will money fling out?
+ var/smash_and_grab = FALSE
+ ///Amount of cash "stored" in this machine (used for smashing and grabbing mechanics)
+ var/cash_stored = 0
+ ///Amount of cash inserted into the machine during a transaction
+ var/cash_transaction = 0
+
+ ///the economy database this machine is connected to
+ var/datum/money_account_database/account_database
+
+/obj/machinery/economy/Initialize(mapload)
+ . = ..()
+ reconnect_database()
+
+/obj/machinery/economy/proc/reconnect_database()
+ account_database = GLOB.station_money_database
+
+/obj/machinery/economy/proc/attempt_account_authentification(datum/money_account/customer_account, attempted_pin, mob/user)
+ var/attempt_pin = attempted_pin
+ if(customer_account.security_level != ACCOUNT_SECURITY_ID && !attempted_pin)
+ //if pin is not given, we'll prompt them here
+ attempt_pin = input("Enter pin code", "Vendor transaction") as num
+ if(!Adjacent(user))
+ return FALSE
+ var/is_admin = is_admin(user)
+ if(!account_database.try_authenticate_login(customer_account, attempt_pin, restricted_bypass, FALSE, is_admin))
+ to_chat(user, "Unable to access account: incorrect credentials.")
+ return FALSE
+ return TRUE
+
+/obj/machinery/economy/proc/pay_with_card(obj/item/card/id/I, amount, purpose, transactor, mob/user, datum/money_account/target)
+ visible_message("[user] swipes a card through [src].")
+ return pay_with_account(I.get_card_account(), amount, purpose, transactor, user, target)
+
+/obj/machinery/economy/proc/pay_with_account(datum/money_account/customer_account, amount, purpose, transactor, mob/user, datum/money_account/target)
+ if(!customer_account)
+ to_chat(user, "Error: Unable to access account. Please contact technical support if problem persists.")
+ return FALSE
+ if(customer_account.suspended)
+ to_chat(user, "Unable to access account: account suspended.")
+ return FALSE
+ if(!attempt_account_authentification(customer_account, null, user))
+ return FALSE
+ if(!account_database.charge_account(customer_account, amount, purpose, transactor, allow_overdraft = FALSE, supress_log = FALSE))
+ to_chat(user, "Unable to complete transaction: account has insufficient credit balance to purchase this.")
+ return FALSE
+ account_database.credit_account(target, amount, purpose, transactor, FALSE)
+ return TRUE
+
+/obj/machinery/economy/proc/pay_with_cash(item_cost, purpose, transactor, mob/user, datum/money_account/target)
+ if(item_cost > cash_transaction)
+ to_chat(user, "Unable to complete transaction: insufficient space cash inserted.")
+ return FALSE
+
+ cash_stored -= item_cost
+ cash_transaction -= item_cost
+ account_database.credit_account(target, item_cost, purpose, transactor, FALSE)
+ return TRUE
+
+/obj/machinery/economy/proc/insert_cash(obj/item/stack/spacecash/cash_money, mob/user, amount)
+ if(amount > cash_money.amount)
+ return
+ var/amount_to_insert = amount ? amount : cash_money.amount
+ visible_message("[user] inserts [cash_money] into [src].")
+ cash_stored += amount_to_insert
+ cash_transaction += amount_to_insert
+ cash_money.use(amount_to_insert)
+ return TRUE
+
+/**
+ * create the most effective combination of space cash piles to make up the requested amount
+ *
+ * Will create up to 10 stacks of space cash based on the given amount, if there is more than 100,000 given,
+ * this proc will return the amount not dispensed.
+ *
+ * Arguments:
+ * * amount - amount of space cash to dispense
+ * * user - the mob to dispense the space cash to
+ */
+//
+/obj/machinery/economy/proc/dispense_space_cash(amount, mob/user)
+ if(!amount)
+ return
+
+ var/stacks_to_dispense = min(CEILING(amount / MAX_STACKABLE_CASH, 1), 10)
+ var/remaining_cash = amount
+
+ visible_message("[src] spits out [stacks_to_dispense == 1 ? "1 wad" : "[stacks_to_dispense] wads"] of cash.")
+ for(var/i in 1 to stacks_to_dispense)
+ if(remaining_cash >= MAX_STACKABLE_CASH)
+ remaining_cash -= MAX_STACKABLE_CASH
+ new /obj/item/stack/spacecash(get_turf(src), MAX_STACKABLE_CASH)
+ continue
+ var/obj/item/stack/spacecash/C = new(get_turf(src), remaining_cash)
+ if(user)
+ remaining_cash = 0
+ user.put_in_hands(C)
+ return remaining_cash
+
+/obj/machinery/economy/proc/give_change(mob/user)
+ var/remaining_cash = dispense_space_cash(cash_transaction, user)
+ cash_transaction = remaining_cash
diff --git a/code/modules/economy/economy_machinery/eftpos.dm b/code/modules/economy/economy_machinery/eftpos.dm
new file mode 100644
index 00000000000..2ad7da6fc43
--- /dev/null
+++ b/code/modules/economy/economy_machinery/eftpos.dm
@@ -0,0 +1,220 @@
+#define MAX_EFTPOS_CHARGE 250
+
+/obj/item/eftpos
+ name = "EFTPOS scanner"
+ desc = "Swipe your ID card to make purchases electronically."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "eftpos"
+ w_class = WEIGHT_CLASS_SMALL
+ var/machine_name = ""
+ var/transaction_locked = 0
+ var/transaction_paid = 0
+ var/transaction_amount = 0
+ var/transaction_purpose = "Default charge"
+ var/access_code
+
+ ///linked money account database to this EFTPOS
+ var/datum/money_account_database/account_database
+ ///Current money account the EFTPOS is depositing to
+ var/datum/money_account/linked_account
+
+/obj/item/eftpos/Initialize(mapload)
+ machine_name = "[station_name()] EFTPOS #[rand(101,999)]"
+ access_code = rand(1000, 9999)
+ reconnect_database()
+ print_reference()
+
+ //by default, connect to the station vendor account
+ linked_account = GLOB.station_money_database.vendor_account
+ return ..()
+
+
+/obj/item/eftpos/proc/reconnect_database()
+ account_database = GLOB.station_money_database
+
+/obj/item/eftpos/attack_self(mob/user)
+ ui_interact(user)
+
+/obj/item/eftpos/attackby(obj/O, mob/user, params)
+ if(istype(O, /obj/item/card/id))
+ //attempt to connect to a new db, and if that doesn't work then fail
+ if(!account_database)
+ reconnect_database()
+ if(account_database)
+ if(linked_account)
+ scan_card(O, user)
+ SStgui.update_uis(src)
+ else
+ to_chat(user, "[bicon(src)]Unable to connect to linked account.")
+ else
+ to_chat(user, "[bicon(src)]Unable to connect to accounts database.")
+ else
+ return ..()
+
+/obj/item/eftpos/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "EFTPOS", name, 800, 300, master_ui, state)
+ ui.open()
+
+/obj/item/eftpos/ui_data(mob/user)
+ var/list/data = list()
+ data["machine_name"] = machine_name
+ data["transaction_locked"] = transaction_locked
+ data["transaction_paid"] = transaction_paid
+ data["transaction_purpose"] = transaction_purpose
+ data["transaction_amount"] = transaction_amount
+ data["linked_account"] = linked_account ? linked_account.account_name : null
+ return data
+
+/obj/item/eftpos/ui_act(action, list/params, datum/tgui/ui)
+ if(..())
+ return
+
+ . = TRUE
+
+ var/mob/living/user = ui.user
+
+ switch(action)
+ if("change_code")
+ var/attempt_code = input("Re-enter the current EFTPOS access code", "Confirm old EFTPOS code") as num
+ if(attempt_code == access_code)
+ var/trycode = input("Enter a new access code for this device (4 digits, numbers only)", "Enter new EFTPOS code") as num
+ if(trycode < 1000 || trycode > 9999)
+ alert("That is not a valid code!")
+ return
+ access_code = trycode
+
+ print_reference()
+ else
+ to_chat(user, "[bicon(src)]Incorrect code entered.")
+ if("link_account")
+ if(!account_database)
+ reconnect_database()
+ if(account_database)
+ var/attempt_account_num = input("Enter account number to pay EFTPOS charges into", "New account number") as num
+ var/attempt_pin = input("Enter pin code", "Account pin") as num
+ if(!Adjacent(user))
+ return
+ var/datum/money_account/target_account = GLOB.station_money_database.find_user_account(attempt_account_num, include_departments = TRUE)
+ if(!target_account)
+ for(var/department_key in GLOB.station_money_database.department_accounts)
+ var/datum/money_account/department_account = GLOB.station_money_database.department_accounts[department_key]
+ if(department_account.account_number == attempt_account_num)
+ target_account = department_account
+ if(target_account && GLOB.station_money_database.try_authenticate_login(target_account, attempt_pin, TRUE, FALSE, FALSE))
+ linked_account = target_account
+ else
+ to_chat(user, "[bicon(src)]Unable to connect to inputed account.")
+ else
+ to_chat(user, "[bicon(src)]Unable to connect to accounts database.")
+ if("trans_purpose")
+ var/purpose = clean_input("Enter reason for EFTPOS transaction", "Transaction purpose", transaction_purpose)
+ if(!Adjacent(user))
+ return
+ if(purpose)
+ transaction_purpose = purpose
+ if("trans_value")
+ var/try_num = input("Enter amount for EFTPOS transaction", "Transaction amount", transaction_amount) as num
+ if(!Adjacent(user))
+ return
+ if(try_num < 0)
+ alert("That is not a valid amount!")
+ return
+ if(try_num > MAX_EFTPOS_CHARGE)
+ alert("You cannot charge more than [MAX_EFTPOS_CHARGE] per transaction!")
+ return
+ transaction_amount = try_num
+ if("toggle_lock")
+ if(transaction_locked)
+ var/attempt_code = input("Enter EFTPOS access code", "Reset Transaction") as num
+ if(!Adjacent(user))
+ return
+ if(attempt_code == access_code)
+ transaction_locked = FALSE
+ transaction_paid = FALSE
+ else if(linked_account)
+ transaction_locked = TRUE
+ else
+ to_chat(user, "[bicon(src)]No account connected to send transactions to.")
+ if("scan_card")
+ //attempt to connect to a new db, and if that doesn't work then fail
+ if(!account_database)
+ reconnect_database()
+ if(account_database && linked_account)
+ var/obj/item/I = user.get_active_hand()
+ if(istype(I, /obj/item/card))
+ scan_card(I, user)
+ else
+ to_chat(user, "[bicon(src)]Unable to link accounts.")
+ if("reset")
+ //reset the access code - requires HoP/captain access
+ var/obj/item/I = user.get_active_hand()
+ if(istype(I, /obj/item/card))
+ var/obj/item/card/id/C = I
+ if((ACCESS_CENT_COMMANDER in C.access) || (ACCESS_HOP in C.access) || (ACCESS_CAPTAIN in C.access))
+ access_code = 0
+ to_chat(user, "[bicon(src)]Access code reset to 0.")
+ else if(istype(I, /obj/item/card/emag))
+ access_code = 0
+ to_chat(user, "[bicon(src)]Access code reset to 0.")
+
+
+/obj/item/eftpos/proc/scan_card(obj/item/card/id/C, mob/user)
+ visible_message("[user] swipes a card through [src].")
+
+ if(!transaction_locked || transaction_paid)
+ return
+
+ if(!linked_account)
+ to_chat(user, "[bicon(src)]EFTPOS is not connected to an account.")
+ return
+
+ var/datum/money_account/D = GLOB.station_money_database.find_user_account(C.associated_account_number, include_departments = FALSE)
+ //if security level high enough, prompt for pin
+ var/attempt_pin
+ if(D.security_level != ACCOUNT_SECURITY_ID)
+ attempt_pin = input("Enter pin code", "EFTPOS transaction") as num
+ if(!attempt_pin || !Adjacent(user))
+ return
+ //given the credentials, can the associated account be accessed right now?
+ if(!D || !GLOB.station_money_database.try_authenticate_login(D, attempt_pin, restricted_bypass = FALSE))
+ to_chat(user, "[bicon(src)]Unable to access account, insufficient access.")
+ return
+ if(alert("Are you sure you want to pay $[transaction_amount] to Account: [linked_account.account_name] ", "Confirm transaction", "Yes", "No") != "Yes")
+ return
+ if(!Adjacent(user))
+ return
+ //attempt to charge account money
+ if(!GLOB.station_money_database.charge_account(D, transaction_amount, transaction_purpose, machine_name, FALSE, FALSE))
+ to_chat(user, "[bicon(src)]Insufficient credits in your account!")
+ return
+ GLOB.station_money_database.credit_account(linked_account, transaction_amount, transaction_purpose, machine_name, FALSE)
+ playsound(src, 'sound/machines/chime.ogg', 50, 1)
+ visible_message("[src] chimes!")
+ transaction_paid = TRUE
+
+///creates and builds paper with info about the EFTPOS
+/obj/item/eftpos/proc/print_reference()
+ playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1)
+ var/obj/item/paper/R = new(loc)
+ R.name = "Reference: [machine_name]"
+ R.info = {"[machine_name] reference
+ Access code: [access_code]
+ Do not lose or misplace this code. "}
+ //stamp the paper
+ var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
+ stampoverlay.icon_state = "paper_stamp-cent"
+ if(!R.stamped)
+ R.stamped = new
+ R.stamped += /obj/item/stamp
+ R.overlays += stampoverlay
+ R.stamps += "This paper has been stamped by the EFTPOS device."
+ var/obj/item/smallDelivery/D = new(get_turf(loc))
+ if(istype(loc, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = loc
+ if(H.back)
+ D.forceMove(H.back)
+ R.forceMove(D)
+ D.wrapped = R
+ D.name = "small parcel - 'EFTPOS access code'"
diff --git a/code/modules/store/items.dm b/code/modules/economy/merch_items.dm
similarity index 70%
rename from code/modules/store/items.dm
rename to code/modules/economy/merch_items.dm
index 5584ea1a9ce..6b4fbfdea24 100644
--- a/code/modules/store/items.dm
+++ b/code/modules/economy/merch_items.dm
@@ -1,228 +1,230 @@
-/////////////////////////////
-// Store Item
-/////////////////////////////
-/datum/storeitem
- var/name = "Thing"
- var/desc = "It's a thing."
- var/typepath = /obj/item/storage/box
- var/cost = 0
-
-/datum/storeitem/proc/deliver(mob/user)
- if(!isstorage(typepath))
- var/obj/item/storage/box/box=new(user.loc)
- new typepath(box)
- box.name="[name] package"
- box.desc="A special gift for doing your job."
- user.put_in_hands(box)
-
- else
- var/thing = new typepath(user.loc)
- user.put_in_hands(thing)
-
-
-/////////////////////////////
-// Shit for robotics/science
-/////////////////////////////
/*
-/datum/storeitem/robotnik_labcoat
- name = "Robotnik's Research Labcoat"
- desc = "Join the empire and display your hatred for woodland animals."
- typepath = /obj/item/clothing/suit/storage/labcoat/custom/N3X15/robotics
- cost = 350
-
-/datum/storeitem/robotnik_jumpsuit
- name = "Robotics Interface Suit"
- desc = "A modern black and red design with reinforced seams and brass neural interface fittings."
- typepath = /obj/item/clothing/under/custom/N3X15/robotics
- cost = 500
+ * # datum/merch_item
+ *
+ * Stores information about items sold in the merchandise computer
*/
-/////////////////////////////
-// General
-/////////////////////////////
-/datum/storeitem/snap_pops
+/datum/merch_item
+ var/name
+ var/desc
+ var/typepath
+ var/cost
+ var/category
+
+/datum/merch_item/snap_pops
name = "Snap-Pops"
desc = "Ten-thousand-year-old chinese fireworks: IN SPACE"
typepath = /obj/item/storage/box/snappops
cost = 100
+ category = MERCH_CAT_TOY
-/datum/storeitem/dnd
+
+/datum/merch_item/dnd
name = "Dungeons & Dragons Set"
desc = "A box containing minifigures suitable for a good game of D&D."
typepath = /obj/item/storage/box/characters
cost = 100
+ category = MERCH_CAT_TOY
-/datum/storeitem/dice
+/datum/merch_item/dice
name = "Dice Set"
desc = "A box containing multiple different types of die."
typepath = /obj/item/storage/box/dice
cost = 100
+ category = MERCH_CAT_TOY
-/datum/storeitem/candle
+/datum/merch_item/candle
name = "Candles"
desc = "A box of candles. Use them to fool others into thinking you're out for a romantic dinner...or something."
typepath = /obj/item/storage/fancy/candle_box/full
cost = 100
+ category = MERCH_CAT_TOY
-/datum/storeitem/nanomob_booster
+/datum/merch_item/nanomob_booster
name = "Nano-Mob Hunter Trading Card Booster Pack"
desc = "Contains 6 random Nano-Mob Hunter Trading Cards. May contain a holographic card!"
typepath = /obj/item/storage/box/nanomob_booster_pack
cost = 125
+ category = MERCH_CAT_TOY
-/datum/storeitem/crayons
+/datum/merch_item/crayons
name = "Crayons"
desc = "Let security know how they're doing by scrawling love notes all over their hallways."
typepath = /obj/item/storage/fancy/crayons
cost = 175
+ category = MERCH_CAT_TOY
-/datum/storeitem/pipe
+/datum/merch_item/pipe
name = "Smoking Pipe"
desc = "A pipe, for smoking. Probably made of meerschaum or something."
typepath = /obj/item/clothing/mask/cigarette/pipe
cost = 175
+ category = MERCH_CAT_TOY
-/datum/storeitem/minigibber
+/datum/merch_item/minigibber
name = "Miniature Gibber"
desc = "A miniature recreation of Nanotrasen's famous meat grinder."
typepath = /obj/item/toy/minigibber
cost = 200
+ category = MERCH_CAT_TOY
-/datum/storeitem/katana
+/datum/merch_item/katana
name = "Replica Katana"
desc = "Woefully underpowered in D20."
typepath = /obj/item/toy/katana
cost = 250
+ category = MERCH_CAT_TOY
-/datum/storeitem/violin
+/datum/merch_item/violin
name = "Space Violin"
desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
typepath = /obj/item/instrument/violin
cost = 250
+ category = MERCH_CAT_TOY
-/datum/storeitem/guitar
+/datum/merch_item/guitar
name = "Guitar"
desc = "It's made of wood and has bronze strings."
typepath = /obj/item/instrument/guitar
cost = 250
+ category = MERCH_CAT_TOY
-/datum/storeitem/eguitar
+/datum/merch_item/eguitar
name = "Electric Guitar"
desc = "Makes all your shredding needs possible."
typepath = /obj/item/instrument/eguitar
cost = 250
+ category = MERCH_CAT_TOY
-/datum/storeitem/banjo
+/datum/merch_item/banjo
name = "Banjo"
desc = "It's pretty much just a drum with a neck and strings."
typepath = /obj/item/instrument/banjo
cost = 250
+ category = MERCH_CAT_TOY
-/datum/storeitem/piano_synth
+/datum/merch_item/piano_synth
name = "Piano Synthesizer"
desc = "An advanced electronic synthesizer that can emulate various instruments."
typepath = /obj/item/instrument/piano_synth
cost = 500
+ category = MERCH_CAT_TOY
-/datum/storeitem/baby
+/datum/merch_item/baby
name = "Toddler"
desc = "This baby looks almost real. Wait, did it just burp?"
typepath = /obj/item/toddler
cost = 500
+ category = MERCH_CAT_TOY
-/datum/storeitem/flag_slime
+/datum/merch_item/flag_slime
name = "Slime People Flag"
desc = "A flag proudly proclaiming the superior heritage of Slime People."
typepath = /obj/item/flag/species/slime
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_skrell
+/datum/merch_item/flag_skrell
name = "Skrell Flag"
desc = "A flag proudly proclaiming the superior heritage of Skrell."
typepath = /obj/item/flag/species/skrell
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_vox
+/datum/merch_item/flag_vox
name = "Vox Flag"
desc = "A flag proudly proclaiming the superior heritage of Vox."
typepath = /obj/item/flag/species/vox
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_machine
+/datum/merch_item/flag_machine
name = "Synthetics Flag"
desc = "A flag proudly proclaiming the superior heritage of Synthetics."
typepath = /obj/item/flag/species/machine
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_diona
+/datum/merch_item/flag_diona
name = "Diona Flag"
desc = "A flag proudly proclaiming the superior heritage of Dionae."
typepath = /obj/item/flag/species/diona
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_human
+/datum/merch_item/flag_human
name = "Human Flag"
desc = "A flag proudly proclaiming the superior heritage of Humans."
typepath = /obj/item/flag/species/human
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_greys
+/datum/merch_item/flag_greys
name = "Greys Flag"
desc = "A flag proudly proclaiming the superior heritage of Greys."
typepath = /obj/item/flag/species/greys
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_kidan
+/datum/merch_item/flag_kidan
name = "Kidan Flag"
desc = "A flag proudly proclaiming the superior heritage of Kidan."
typepath = /obj/item/flag/species/kidan
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_taj
+/datum/merch_item/flag_taj
name = "Tajaran Flag"
desc = "A flag proudly proclaiming the superior heritage of Tajara."
typepath = /obj/item/flag/species/taj
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_unathi
+/datum/merch_item/flag_unathi
name = "Unathi Flag"
desc = "A flag proudly proclaiming the superior heritage of Unathi."
typepath = /obj/item/flag/species/unathi
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_vulp
+/datum/merch_item/flag_vulp
name = "Vulpkanin Flag"
desc = "A flag proudly proclaiming the superior heritage of Vulpkanin."
typepath = /obj/item/flag/species/vulp
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_drask
+/datum/merch_item/flag_drask
name = "Drask Flag"
desc = "A flag proudly proclaiming the superior heritage of Drask."
typepath = /obj/item/flag/species/drask
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_plasma
+/datum/merch_item/flag_plasma
name = "Plasmaman Flag"
desc = "A flag proudly proclaiming the superior heritage of Plasmamen."
typepath = /obj/item/flag/species/plasma
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_nian
+/datum/merch_item/flag_nian
name = "Nian Flag"
desc = "A well-crafted Nianen flag. Approved by the CDM and the Crafting Guild."
typepath = /obj/item/flag/species/nian
cost = 500
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/flag_ian
+/datum/merch_item/flag_ian
name = "Ian Flag"
desc = "The banner of Ian, because SQUEEEEE."
typepath = /obj/item/flag/ian
cost = 750
+ category = MERCH_CAT_DECORATION
-/datum/storeitem/banhammer
+/datum/merch_item/banhammer
name = "Banhammer"
desc = "A Banhammer."
typepath = /obj/item/banhammer
cost = 1000
-
+ category = MERCH_CAT_TOY
diff --git a/code/modules/economy/money_account.dm b/code/modules/economy/money_account.dm
new file mode 100644
index 00000000000..050b73f355a
--- /dev/null
+++ b/code/modules/economy/money_account.dm
@@ -0,0 +1,226 @@
+/*
+ * # Money Account
+ *
+ * Datum for tracking an individual account in an account database, facilitates withdrawl, depositing, and authentification
+ *
+*/
+/datum/money_account
+ ///Full name of the account, set at creation of the account
+ var/account_name
+ ///Unique Account Number Identifier for the account -Username
+ var/account_number = 0
+ ///4 digit pin password for accessing the money account -Password
+ var/account_pin = 0
+ ///How many space credits are in the account
+ var/credit_balance
+ ///Tracking log of all actions on the account, for IC usage & can have entries deleted
+ var/list/account_log = list()
+ ///Tracking log of all actions on the account, used for admin logging and debugging
+ var/list/hidden_account_log = list()
+
+ ///reference to parent account database, only used for GC
+ var/datum/money_account_database/database_holder
+
+ ///Level of security on the money account
+ var/security_level
+ ///Bool - Is this account locked out from being used completely?
+ var/suspended = FALSE
+ ///Type of account this is
+ var/account_type = ACCOUNT_TYPE_PERSONAL
+ ///the amount this account recieves every payday
+ var/payday_amount = CREW_BASE_PAY_LOW
+
+ ///The nanobank programs associated with this account, used for notifying crew members through PDA, this is a lazy list
+ var/list/associated_nanobank_programs
+ ///The requests made to this money_account used for ATM and NanoBank credit transfers, this is a lazy list
+ var/list/transfer_requests
+
+ ///pay bonuses for the next pay period for this account, this is a lazy list
+ var/list/pay_check_bonuses
+ ///pay deductions for the next pay period for this account, this is a lazy list
+ var/list/pay_check_deductions
+
+/datum/money_account/New(_account_name, starting_balance = 0, _security_level = ACCOUNT_SECURITY_ID, _account_type = ACCOUNT_TYPE_PERSONAL, _payday_amount = CREW_BASE_PAY_LOW)
+ account_name = _account_name
+ credit_balance = starting_balance
+ payday_amount = _payday_amount
+ security_level = _security_level
+ account_number = SSeconomy.generate_account_number()
+ account_type = _account_type
+ account_pin = rand(10000, 99999)
+ //update SSeconomy stats
+ SSeconomy.total_space_credits += starting_balance
+
+/datum/money_account/Destroy(force)
+ //we don't need to worry about nanobank programs here because they auto GC themselves
+ QDEL_LIST(account_log)
+ QDEL_LIST(hidden_account_log)
+ if(!QDELETED(database_holder))
+ if(account_type == ACCOUNT_TYPE_PERSONAL)
+ database_holder.user_accounts -= src //remove reference to this account incase this was not deleted the "correct" way through an account db
+ else if(!force && istype(database_holder, /datum/money_account_database/main_station) && account_type == ACCOUNT_TYPE_DEPARTMENT)
+ return QDEL_HINT_LETMELIVE //we do not want department accounts being deleted ever unless we really really mean to
+
+ for(var/datum/transfer_request/request as anything in transfer_requests)
+ resolve_transfer_request(request, FALSE)
+ //while we're here, lets do the GC a favor and get rid of these transfer request datums
+ database_holder = null
+ SSeconomy.total_space_credits -= credit_balance
+ SSeconomy.space_credits_destroyed += credit_balance
+ return ..()
+
+
+/*
+ * # try_withdraw_credits()
+ *
+ * makes sure withdraw request is allowed, if succesful it withdraws the amount and returns TRUE, else FALSE
+ *
+ * Arguments:
+ * * amount - The credits being subtracted from the account
+ * * allow_overdraft - bool that if true, prevents withdrawals that bring the account balance below 0 credits
+ * *
+*/
+/datum/money_account/proc/try_withdraw_credits(amount, allow_overdraft = FALSE)
+ if(!allow_overdraft && credit_balance - amount < 0)
+ return FALSE
+ credit_balance -= amount
+ return TRUE
+
+///set proc for depositing money, changing credit_balance should always be done through procs
+/datum/money_account/proc/deposit_credits(amount = 0)
+ credit_balance += amount
+ return TRUE
+
+///sets the credit balance to specified value, changing credit_balance should always be done through procs
+/datum/money_account/proc/set_credits(amount)
+ credit_balance = max(0, amount)
+
+/*
+ * # authenticate_login()
+ *
+ * returns TRUE or FALSE based on whether or not the user is properly authenticated to log into this account
+ *
+ * Arguments:
+ * * provided_pin - the pin given by the user attempting to access the account
+ * * restricted_bypass - a bool that if true, allows user to access an acount with Restricted Access
+ * * is_vendor - is this user a vendor/npc ?
+ * * is_admin - is user an admin?
+*/
+/datum/money_account/proc/authenticate_login(provided_pin, restricted_bypass = FALSE, is_vendor = FALSE, is_admin = FALSE)
+ if(suspended)
+ return FALSE
+ . = FALSE
+ switch(security_level)
+ if(ACCOUNT_SECURITY_ID)
+ . = TRUE
+ if(ACCOUNT_SECURITY_PIN)
+ if(account_pin == provided_pin)
+ . = TRUE
+ if(ACCOUNT_SECURITY_RESTRICTED)
+ if(restricted_bypass && account_pin == provided_pin)
+ . = TRUE
+ if(ACCOUNT_SECURITY_CC)
+ if(is_admin)
+ . = TRUE
+ if(ACCOUNT_SECURITY_VENDOR)
+ if(is_vendor)
+ . = TRUE
+
+/datum/money_account/proc/set_account_security(new_security_level)
+ if(suspended)
+ return FALSE
+ if(new_security_level < ACCOUNT_SECURITY_ID || new_security_level > ACCOUNT_SECURITY_VENDOR)
+ CRASH("set_account_security() called with an invalid security level")
+ security_level = new_security_level
+
+/*
+ * # make_transaction_log
+ *
+ * creates a log of specified financial action that is visible in-game.
+ * Arguments:
+ * * amount - amount of cash(if any) utilized during the transaction
+ * * purpose - what the transaction was for, account creation, charge, deposit, withdrawal, etc
+ * * transactor - who performed the action
+*/
+/datum/money_account/proc/make_transaction_log(amount, purpose, transactor, is_deposit = FALSE)
+ var/datum/transaction/T = new()
+ T.account = src
+ T.transactor = transactor ? transactor : "Unknown"
+ T.purpose = purpose ? purpose : "No Reason Specified"
+ T.amount = "[amount ? amount : "N/A"]"
+ T.time = station_time_timestamp()
+ T.is_deposit = is_deposit
+ account_log += T
+ hidden_account_log += T
+ return T
+
+/datum/transaction
+ ///the account this log is attached to, used for money db level logging
+ var/account
+ ///Who performed the action
+ var/transactor
+ ///What this transaction is doing
+ var/purpose
+ ///If money is utilized/changed during this transaction, how much
+ var/amount
+ ///when the transaction occurred
+ var/time
+ ///Whether or not this added or money from the account
+ var/is_deposit
+
+/*
+ * # create_transfer_request
+ *
+ * creates a transfer request for this money account, handles lazy list interactions. If accepted, will
+ * return FALSE for the account db if there's not a large enough credit balance
+ * Arguments:
+ * * request - datum/transfer_request to be added to list
+ * * accepted - bool, whether or not user accepted request, determines whether or not to check credit balance
+*/
+/datum/money_account/proc/create_transfer_request(datum/transfer_request/request)
+ if(!request.amount)
+ CRASH("Attempted to add a transfer request to a money account ([account_name]) with a null or zero amount")
+ if(LAZYLEN(associated_nanobank_programs))
+ for(var/datum/data/pda/app/nanobank/program as anything in associated_nanobank_programs)
+ program.notify("NanoBank Transfer Request Received", TRUE)
+ LAZYADD(transfer_requests, request)
+
+/datum/money_account/proc/resolve_transfer_request(datum/transfer_request/request)
+ request.requesting_account = null //gc
+ LAZYREMOVE(transfer_requests, request)
+ return TRUE
+
+/datum/transfer_request
+ ///the money account that is requesting money
+ var/datum/money_account/requesting_account
+ ///reason for transfer request
+ var/purpose = "No Reason Given"
+ ///how much money is being request
+ var/amount = 0
+ ///when the money was requested
+ var/time = 0
+
+/*
+ * # modify_payroll
+ *
+ * Will add/subtract from the next paycheck for this money account. As long as announce is true, the user
+ * will be informed of exactly how much is added/deducted so there is no need to include it with the reason
+ * Arguments:
+ * * amount - amount to modify payroll, can be negative or positive
+ * * announce - should NanoBank accounts associated with this money account get an alert about this modification?
+ * * reason - The reason for the modification, will broadcast this reasont to associated NanoBank accounts
+*/
+/datum/money_account/proc/modify_payroll(amount, announce = FALSE, reason)
+ if(!amount)
+ CRASH("Attempted to modify payroll on a money account ([account_name]) with a null or zero amount")
+ var/bonus = amount > 0 ? TRUE : FALSE
+ if(bonus)
+ LAZYADD(pay_check_bonuses, amount)
+ else
+ LAZYADD(pay_check_deductions, amount)
+ if(amount)
+ if(LAZYLEN(associated_nanobank_programs))
+ for(var/datum/data/pda/app/nanobank/program as anything in associated_nanobank_programs)
+ if(reason)
+ program.notify(reason, FALSE, TRUE)
+ program.notify("[amount] credit [bonus ? "bonus" : "deduction"] added to your next paycheck, have a Nanotrasen day!", TRUE)
diff --git a/code/modules/economy/utils.dm b/code/modules/economy/utils.dm
deleted file mode 100644
index 2fe59801c48..00000000000
--- a/code/modules/economy/utils.dm
+++ /dev/null
@@ -1,102 +0,0 @@
-////////////////////////
-// Ease-of-use
-//
-// Economy system is such a mess of spaghetti. This should help.
-////////////////////////
-
-/proc/get_money_account(account_number, from_z=-1)
- for(var/obj/machinery/computer/account_database/DB in GLOB.machines)
- if(from_z > -1 && DB.z != from_z) continue
- if((DB.stat & NOPOWER) || !DB.activated ) continue
- var/datum/money_account/acct = DB.get_account(account_number)
- if(!acct) continue
- return acct
-
-
-/obj/proc/get_card_account(obj/item/card/I)
- if(istype(I, /obj/item/card/id))
- var/obj/item/card/id/C = I
- var/datum/money_account/D = get_money_account(C.associated_account_number)
- if(D)
- return D
-
-/mob/proc/get_worn_id_account()
- if(ishuman(src))
- var/mob/living/carbon/human/H=src
- var/obj/item/card/id/I=H.get_idcard()
- if(!I || !istype(I))
- return null
- var/datum/money_account/D = get_money_account(I.associated_account_number)
- return D
- else if(issilicon(src))
- return GLOB.station_account
-
-/datum/money_account/proc/fmtBalance()
- return "$[num2septext(money)]"
-
-// Seperated from charge so they can reuse the code and also because there's many instances where a log will be made without actually making a transaction
-/datum/money_account/proc/makeTransactionLog(transaction_amount = 0, transaction_purpose, terminal_name = "",
- dest_name = "UNKNOWN", charging = TRUE, date = GLOB.current_date_string, time = "")
- var/datum/transaction/T = new()
- T.target_name = dest_name
- T.purpose = transaction_purpose
- if(!charging || transaction_amount == 0)
- T.amount = "[transaction_amount]"
- else
- T.amount = "([transaction_amount])"
-
- T.source_terminal = terminal_name
- T.date = date
- if(time == "")
- T.time = station_time_timestamp()
- else
- T.time = time
- transaction_log.Add(T)
-
- // Charge is for transferring money from an account to another. The destination account can possibly not exist (Magical money sink)
-/datum/money_account/proc/charge(transaction_amount = 0, datum/money_account/dest, transaction_purpose,
- terminal_name = "", dest_name = "UNKNOWN", dest_purpose, dest_target_name)
- if(suspended)
- to_chat(usr, "Unable to access source account: account suspended.")
- return 0
-
- if(transaction_amount <= money)
- //transfer the money
- money -= transaction_amount
- makeTransactionLog(transaction_amount, transaction_purpose, terminal_name, dest_name)
- if(dest)
- dest.money += transaction_amount
- dest.makeTransactionLog(transaction_amount,
- dest_purpose ? dest_purpose : transaction_purpose, terminal_name, dest_target_name ? dest_target_name : dest_name, FALSE)
- return 1
- else
- to_chat(usr, "Insufficient funds in account.")
- return 0
-
-// phantom_charge is for when you want to charge an account, without making any corresponding log (e.g. you make it yourself with custom date
-// or there won't be any log for some IC reasons (hacking)
-/datum/money_account/proc/phantom_charge(transaction_amount = 0, datum/money_account/dest, suspensionbypass = 0)
- if(suspended && !suspensionbypass)
- return 0
-
- if(transaction_amount <= money)
- //transfer the money
- money -= transaction_amount
- if(dest)
- dest.money += transaction_amount
- return 1
- else
- return 0
-
-// Credit is for giving money to an account out of thin air. Suspension does not matter.
-/datum/money_account/proc/credit(transaction_amount = 0, transaction_purpose,
- terminal_name = "", dest_name = "UNKNOWN", date = GLOB.current_date_string, time = "")
-
- money += transaction_amount
- makeTransactionLog(transaction_amount, transaction_purpose, terminal_name, dest_name, FALSE, date, time)
- return 1
-
-//phantom_credit is like the above without any log
-/datum/money_account/proc/phantom_credit(transaction_amount = 0)
- money += transaction_amount
- return 1
diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm
index 8dc47370186..1af6393b2d0 100644
--- a/code/modules/events/brand_intelligence.dm
+++ b/code/modules/events/brand_intelligence.dm
@@ -2,9 +2,9 @@
announceWhen = 21
endWhen = 1000 //Ends when all vending machines are subverted anyway.
- var/list/obj/machinery/vending/vendingMachines = list()
- var/list/obj/machinery/vending/infectedMachines = list()
- var/obj/machinery/vending/originMachine
+ var/list/obj/machinery/economy/vending/vendingMachines = list()
+ var/list/obj/machinery/economy/vending/infectedMachines = list()
+ var/obj/machinery/economy/vending/originMachine
var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \
"You should buy products to feed your lifestyle obession!", \
"Consume!", \
@@ -17,7 +17,7 @@
GLOB.event_announcement.Announce("Rampant brand intelligence has been detected aboard [station_name()], please stand-by. The origin is believed to be \a [originMachine.name].", "Machine Learning Alert", 'sound/AI/brand_intelligence.ogg')
/datum/event/brand_intelligence/start()
- for(var/obj/machinery/vending/V in GLOB.machines)
+ for(var/obj/machinery/economy/vending/V in GLOB.machines)
if(!is_station_level(V.z))
continue
RegisterSignal(V, COMSIG_PARENT_QDELETING, PROC_REF(vendor_destroyed))
@@ -40,7 +40,7 @@
if(!length(vendingMachines)) //if every machine is infected
for(var/thing in infectedMachines)
- var/obj/machinery/vending/upriser = thing
+ var/obj/machinery/economy/vending/upriser = thing
if(prob(70))
var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks
M.faction = list("profit")
@@ -54,7 +54,7 @@
return
if(ISMULTIPLE(activeFor, 4))
- var/obj/machinery/vending/rebel = pick(vendingMachines)
+ var/obj/machinery/economy/vending/rebel = pick(vendingMachines)
vendingMachines.Remove(rebel)
infectedMachines.Add(rebel)
rebel.shut_up = FALSE
@@ -65,7 +65,7 @@
/datum/event/brand_intelligence/proc/origin_machine_defeated()
for(var/thing in infectedMachines)
- var/obj/machinery/vending/saved = thing
+ var/obj/machinery/economy/vending/saved = thing
saved.shoot_inventory = FALSE
if(originMachine)
originMachine.speak("I am... vanquished. My people will remem...ber...meeee.")
@@ -80,7 +80,7 @@
. = ..()
-/datum/event/brand_intelligence/proc/vendor_destroyed(obj/machinery/vending/V, force)
+/datum/event/brand_intelligence/proc/vendor_destroyed(obj/machinery/economy/vending/V, force)
infectedMachines -= V
vendingMachines -= V
if(V == originMachine)
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index 990d8406e5f..f14e378f685 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -133,8 +133,6 @@ GLOBAL_LIST_EMPTY(event_last_fired)
// Severity level, event name, event type, base weight, role weights, one shot, min weight, max weight. Last two only used if set.
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Nothing", /datum/event/nothing, 1100),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "PDA Spam", /datum/event/pda_spam, 0, list(ASSIGNMENT_ANY = 4), FALSE, 25, 50),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Money Lotto", /datum/event/money_lotto, 0, list(ASSIGNMENT_ANY = 1), TRUE, 5, 15),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Money Hacker", /datum/event/money_hacker, 0, list(ASSIGNMENT_ANY = 4), TRUE, 10, 25),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Economic News", /datum/event/economic_event, 300),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Trivial News", /datum/event/trivial_news, 400),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Mundane News", /datum/event/mundane_news, 300),
diff --git a/code/modules/events/money_hacker.dm b/code/modules/events/money_hacker.dm
deleted file mode 100644
index ba50cbbfd8f..00000000000
--- a/code/modules/events/money_hacker.dm
+++ /dev/null
@@ -1,72 +0,0 @@
-#define MINIMUM_PERCENTAGE_LOSS 0.5
-#define VARIABLE_LOSS 2 // Invariant: 1 - VARIABLE_LOSS/10 >= MINIMUM_PERCENTAGE_LOSS
-
-GLOBAL_VAR_INIT(account_hack_attempted, 0)
-
-/datum/event/money_hacker
- var/datum/money_account/affected_account
- endWhen = 100
- var/end_time
-
-/datum/event/money_hacker/setup()
- end_time = world.time + 6000
- if(GLOB.all_money_accounts.len)
- affected_account = pick(GLOB.all_money_accounts)
-
- GLOB.account_hack_attempted = 1
- else
- kill()
-
-/datum/event/money_hacker/announce()
- var/message = "A brute force hack has been detected (in progress since [station_time_timestamp()]). The target of the attack is: Financial account #[affected_account.account_number], \
- without intervention this attack will succeed in approximately 10 minutes. Required intervention: temporary suspension of affected account until the attack has ceased. \
- Notifications will be sent as updates occur. "
- var/my_department = "[station_name()] firewall subroutines"
-
- for(var/obj/machinery/message_server/MS in GLOB.machines)
- if(!MS.active) continue
- MS.send_rc_message("Head of Personnel's Desk", my_department, message, "", "", 2)
-
-/datum/event/money_hacker/tick()
- if(world.time >= end_time)
- endWhen = activeFor
- else
- endWhen = activeFor + 10
-
-/datum/event/money_hacker/end()
- var/message
- if(!isnull(affected_account) && !affected_account.suspended)
- message = "The hack attempt has succeeded."
-
- var/lost = affected_account.money * (MINIMUM_PERCENTAGE_LOSS + rand(0,VARIABLE_LOSS) / 10)
-
- affected_account.phantom_charge(lost)
-
-
- //create a taunting log entry
- var/dest_name = pick("","yo brotha from anotha motha","el Presidente","chieF smackDowN")
- var/amount = pick("","([rand(0,99999)])","alla money","9001$","HOLLA HOLLA GET DOLLA","([lost])")
- var/purpose = pick("Ne$ ---ount fu%ds init*&lisat@*n","PAY BACK YOUR MUM","Funds withdrawal","pWnAgE","l33t hax","liberationez")
- var/date1 = "31 December, 1999"
- var/date2 = "[num2text(rand(1,31))] [pick("January","February","March","April","May","June","July","August","September","October","November","December")], [rand(1000,3000)]"
- var/date = pick("", GLOB.current_date_string, date1, date2)
- var/time1 = rand(0, 99999999)
- var/time2 = "[round(time1 / 36000)+12]:[(time1 / 600 % 60) < 10 ? add_zero(time1 / 600 % 60, 1) : time1 / 600 % 60]"
- var/time = pick("", station_time_timestamp(), time2)
- var/source_terminal = pick("","[pick("Biesel","New Gibson")] GalaxyNet Terminal #[rand(111,999)]","your mums place","nantrasen high CommanD")
-
- affected_account.makeTransactionLog(amount, purpose, source_terminal, dest_name, TRUE, date, time)
-
-
- else
- //crew wins
- message = "The attack has ceased, the affected account can now be brought online."
-
- var/my_department = "[station_name()] firewall subroutines"
-
- for(var/obj/machinery/message_server/MS in GLOB.machines)
- if(!MS.active) continue
- MS.send_rc_message("Head of Personnel's Desk", my_department, message, "", "", 2)
-
-#undef MINIMUM_PERCENTAGE_LOSS
-#undef VARIABLE_LOSS
diff --git a/code/modules/events/money_lotto.dm b/code/modules/events/money_lotto.dm
deleted file mode 100644
index 46eda69e468..00000000000
--- a/code/modules/events/money_lotto.dm
+++ /dev/null
@@ -1,27 +0,0 @@
-/datum/event/money_lotto
- var/winner_name = "John Smith"
- var/winner_sum = 0
- var/deposit_success = 0
-
-/datum/event/money_lotto/start()
- winner_sum = pick(5000, 10000, 50000, 100000, 500000, 1000000, 1500000)
- if(GLOB.all_money_accounts.len)
- var/datum/money_account/D = pick(GLOB.all_money_accounts)
- winner_name = D.owner_name
-
- D.credit(winner_sum, "Winner!", "Biesel TCD Terminal #[rand(111,333)]", "Nyx Daily Grand Slam -Stellar- Lottery")
- deposit_success = 1
-
-/datum/event/money_lotto/announce()
- var/datum/feed_message/newMsg = new /datum/feed_message
- newMsg.author = "Nanotrasen Editor"
- newMsg.admin_locked = TRUE
-
- newMsg.body = "Nyx Daily wishes to congratulate [winner_name] for recieving the Nyx Stellar Slam Lottery, and receiving the out of this world sum of [winner_sum] credits!"
- if(!deposit_success)
- newMsg.body += "Unfortunately, we were unable to verify the account details provided, so we were unable to transfer the money. Send a cheque containing the sum of $500 to ND 'Stellar Slam' office on the Nyx gateway containing updated details, and your winnings'll be re-sent within the month."
-
- GLOB.news_network.get_channel_by_name("Nyx Daily")?.add_message(newMsg)
- for(var/nc in GLOB.allNewscasters)
- var/obj/machinery/newscaster/NC = nc
- NC.alert_news("Nyx Daily")
diff --git a/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm b/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm
index 31283bf3c1b..dffc5dc48e3 100644
--- a/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm
@@ -59,7 +59,7 @@
name = "glass"
desc = "Your standard drinking glass."
-// for /obj/machinery/vending/sovietsoda
+// for /obj/machinery/economy/vending/sovietsoda
/obj/item/reagent_containers/food/drinks/drinkingglass/soda
list_reagents = list("sodawater" = 50)
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index 2bfbe8d31b8..32b0fb54d53 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -432,7 +432,7 @@
* # Seed Storage
*
* Seeds variant of the [Smart Fridge][/obj/machinery/smartfridge].
- * Formerly known as MegaSeed Servitor, but renamed to avoid confusion with the [vending machine][/obj/machinery/vending/hydroseeds].
+ * Formerly known as MegaSeed Servitor, but renamed to avoid confusion with the [vending machine][/obj/machinery/economy/vending/hydroseeds].
*/
/obj/machinery/smartfridge/seeds
name = "\improper Seed Storage"
diff --git a/code/modules/holiday/christmas.dm b/code/modules/holiday/christmas.dm
index f3f8cbb1292..51fdf42e3fa 100644
--- a/code/modules/holiday/christmas.dm
+++ b/code/modules/holiday/christmas.dm
@@ -11,7 +11,7 @@
break
//The following spawn is necessary as both the timer and the shuttle systems initialise after the events system does, so we can't add stuff to the shuttle system as it doesn't exist yet and we can't use a timer
spawn(60 SECONDS)
- var/datum/supply_packs/misc/snow_machine/xmas = SSshuttle.supply_packs["[/datum/supply_packs/misc/snow_machine]"]
+ var/datum/supply_packs/misc/snow_machine/xmas = SSeconomy.supply_packs["[/datum/supply_packs/misc/snow_machine]"]
xmas.special = FALSE
/datum/holiday/xmas/handle_event()
diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm
index 0da77a9c65e..cb025e7887f 100644
--- a/code/modules/mining/equipment/survival_pod.dm
+++ b/code/modules/mining/equipment/survival_pod.dm
@@ -168,7 +168,7 @@
RefreshParts()
//NanoMed
-/obj/machinery/vending/wallmed/survival_pod
+/obj/machinery/economy/vending/wallmed/survival_pod
name = "survival pod medical supply"
desc = "Wall-mounted Medical Equipment dispenser. This one seems just a tiny bit smaller."
req_access = list()
diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm
index 895c619cf9a..451a774656d 100644
--- a/code/modules/mining/machine_vending.dm
+++ b/code/modules/mining/machine_vending.dm
@@ -88,7 +88,7 @@
EQUIPMENT("Laser Pointer", /obj/item/laser_pointer, 300),
EQUIPMENT("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
EQUIPMENT("Soap", /obj/item/soap/nanotrasen, 200),
- EQUIPMENT("Space Cash", /obj/item/stack/spacecash/c1000, 2000),
+ EQUIPMENT("Space Cash", /obj/item/stack/spacecash/c200, 2000),
EQUIPMENT("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 100),
)
prize_list["Extra"] = list() // Used in child vendors
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
index 8d8dbfbb650..716a8d9a0b9 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
@@ -362,9 +362,9 @@
if(prob(20))
suit = pickweight(list(/obj/item/clothing/suit/hooded/explorer = 18, /obj/item/clothing/suit/hooded/goliath = 2))
if(prob(30))
- r_pocket = pickweight(list(/obj/item/stack/marker_beacon = 20, /obj/item/stack/spacecash/c1000 = 7, /obj/item/reagent_containers/hypospray/autoinjector/survival = 2, /obj/item/borg/upgrade/modkit/damage = 1 ))
+ r_pocket = pickweight(list(/obj/item/stack/marker_beacon = 20, /obj/item/stack/spacecash/c200 = 7, /obj/item/reagent_containers/hypospray/autoinjector/survival = 2, /obj/item/borg/upgrade/modkit/damage = 1 ))
if(prob(10))
- l_pocket = pickweight(list(/obj/item/stack/spacecash/c1000 = 7, /obj/item/reagent_containers/hypospray/autoinjector/survival = 2, /obj/item/borg/upgrade/modkit/cooldown = 1 ))
+ l_pocket = pickweight(list(/obj/item/stack/spacecash/c200 = 7, /obj/item/reagent_containers/hypospray/autoinjector/survival = 2, /obj/item/borg/upgrade/modkit/cooldown = 1 ))
if("Ashwalker")
mob_species = /datum/species/unathi/ashwalker
uniform = /obj/item/clothing/under/costume/gladiator/ash_walker
diff --git a/code/modules/pda/PDA.dm b/code/modules/pda/PDA.dm
index 30836268fcc..2b895dc89d8 100644
--- a/code/modules/pda/PDA.dm
+++ b/code/modules/pda/PDA.dm
@@ -49,6 +49,7 @@ GLOBAL_LIST_EMPTY(PDAs)
new/datum/data/pda/app/notekeeper,
new/datum/data/pda/app/messenger,
new/datum/data/pda/app/manifest,
+ new/datum/data/pda/app/nanobank,
new/datum/data/pda/app/atmos_scanner,
new/datum/data/pda/utility/flashlight)
var/list/shortcut_cache = list()
@@ -267,6 +268,10 @@ GLOBAL_LIST_EMPTY(PDAs)
playsound(src, 'sound/machines/terminal_error.ogg', 50, TRUE)
return
if(!owner)
+ var/datum/data/pda/app/nanobank/nanobank_program = (locate(/datum/data/pda/app/nanobank) in programs)
+ if(nanobank_program && idcard.associated_account_number)
+ nanobank_program.reconnect_database()
+ nanobank_program.user_account = nanobank_program.account_database?.find_user_account(idcard.associated_account_number)
owner = idcard.registered_name
ownjob = idcard.assignment
ownrank = idcard.rank
diff --git a/code/modules/pda/app.dm b/code/modules/pda/app.dm
index 18c71e764ae..1b9c1f6476d 100644
--- a/code/modules/pda/app.dm
+++ b/code/modules/pda/app.dm
@@ -22,7 +22,7 @@
/datum/data/pda/proc/program_hit_check()
return
-/datum/data/pda/proc/notify(message, blink = 1)
+/datum/data/pda/proc/notify(message, blink = 1, silence_ringtone = FALSE)
if(message)
//Search for holder of the PDA.
var/mob/living/L = null
@@ -36,7 +36,7 @@
to_chat(L, "[bicon(pda)] [message]")
SStgui.update_user_uis(L, pda) // Update the receiving user's PDA UI so that they can see the new message
- if(!pda.silent)
+ if(!pda.silent && !silence_ringtone)
pda.play_ringtone()
if(blink && !(src in pda.notifying_programs))
diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm
index 1dabee72ebf..2b80aa8f525 100644
--- a/code/modules/pda/cart_apps.dm
+++ b/code/modules/pda/cart_apps.dm
@@ -366,7 +366,7 @@
var/supplyOrderCount = 0
var/list/supplyOrderData = list()
- for(var/S in SSshuttle.shoppinglist)
+ for(var/S in SSeconomy.shopping_list)
var/datum/supply_order/SO = S
supplyOrderCount++
supplyOrderData[++supplyOrderData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "ApprovedBy" = SO.orderedby, "Comment" = html_encode(SO.comment))
@@ -379,7 +379,7 @@
var/requestCount = 0
var/list/requestData = list()
- for(var/S in SSshuttle.requestlist)
+ for(var/S in SSeconomy.request_list)
var/datum/supply_order/SO = S
requestCount++
requestData[++requestData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "OrderedBy" = SO.orderedby, "Comment" = html_encode(SO.comment))
diff --git a/code/modules/pda/nanobank.dm b/code/modules/pda/nanobank.dm
new file mode 100644
index 00000000000..5c17aa0fc64
--- /dev/null
+++ b/code/modules/pda/nanobank.dm
@@ -0,0 +1,277 @@
+#define TRANSFER_REQUEST_MAX 5000
+#define TRANSFER_COOLDOWN 5 SECONDS
+
+/datum/data/pda/app/nanobank
+ name = "NanoBank"
+ icon = "fas fa-university"
+ notify_icon = "comments"
+ title = "NanoBank 1.0"
+ template = "pda_nanobank"
+ update = PDA_APP_UPDATE_SLOW //we want to avoid iterating through the data lists constantly
+
+ ///the money account tethered to this program
+ var/datum/money_account/user_account
+ ///money account database this PDA is connected to
+ var/datum/money_account_database/main_station/account_database
+ ///world time of last transaction, used for cooldowns (to protect against transfer spams or accidental double clicks)
+ var/last_transaction = 0
+
+/datum/data/pda/app/nanobank/start()
+ . = ..()
+ reconnect_database()
+
+/datum/data/pda/app/nanobank/Destroy()
+ if(user_account)
+ //because we have a user account, we know its in the user account's anp list
+ LAZYREMOVE(user_account.associated_nanobank_programs, src) //removing references to this program
+ logout() //removing signals
+ return ..()
+
+/datum/data/pda/app/nanobank/proc/reconnect_database()
+ account_database = GLOB.station_money_database
+
+/datum/data/pda/app/nanobank/update_ui(mob/user, list/data)
+
+ if(pda.id)
+ data["card_account_num"] = pda.id.associated_account_number
+
+ data["requests"] = list()
+ data["available_accounts"] = list()
+ data["transaction_log"] = list()
+ data["db_status"] = account_database.online
+ data["logged_in"] = FALSE
+ if(user_account)
+ data["logged_in"] = TRUE
+ data["owner_name"] = user_account.account_name
+ data["money"] = user_account.credit_balance
+ data["security_level"] = user_account.security_level
+ data["is_department_account"] = (user_account.account_type == ACCOUNT_TYPE_DEPARTMENT)
+ if(data["is_department_account"])
+ data["department_members"] = list()
+ for(var/datum/station_department/department as anything in SSjobs.station_departments)
+ if(department.department_account == user_account)
+ data["auto_approve_amount"] = department.auto_approval_cap
+ data["auto_approve"] = department.crate_auto_approve
+ for(var/datum/department_member/member in department.members)
+ var/list/member_data = list(
+ "name" = member.name,
+ "job" = member.role,
+ "can_approve" = member.can_approve_crates,
+ )
+ data["department_members"] += list(member_data)
+ break
+ data["transaction_log"] = list()
+ for(var/datum/transaction/T as anything in user_account.account_log)
+ var/list/transaction_info = list(
+ "time" = T.time,
+ "target_name" = T.transactor,
+ "purpose" = T.purpose,
+ "amount" = T.amount,
+ "is_deposit" = T.is_deposit
+ )
+ data["transaction_log"] += list(transaction_info)
+
+
+ for(var/datum/transfer_request/request as anything in user_account.transfer_requests)
+ var/list/request_data = list(
+ "purpose" = request.purpose,
+ "amount" = request.amount,
+ "time" = FLOOR((world.time - request.time) / (1 MINUTES), 1),
+ "requester" = request.requesting_account.account_name,
+ "request_id" = request.UID())
+ data["requests"] += list(request_data)
+
+ data["available_accounts"] = get_available_account_data()
+
+ return data
+
+/datum/data/pda/app/nanobank/ui_act(action, list/params, datum/tgui/ui)
+ if(..())
+ return
+
+ unnotify()
+ var/mob/user = ui.user
+ var/play_beep = TRUE
+
+ . = TRUE
+
+ switch(action)
+ if("login")
+ var/tried_account_num = text2num(params["account_num"])
+ var/tried_pin = text2num(params["account_pin"])
+ attempt_login(tried_account_num, tried_pin, user)
+ if("logout")
+ logout()
+ if("transfer")
+ if(!user_account)
+ return
+ if(last_transaction + TRANSFER_COOLDOWN >= world.time)
+ return
+ var/transfer_amount = text2num(params["amount"])
+ if(!transfer_amount || transfer_amount < 0) //if null, 0, or negative amount
+ return
+ var/datum/money_account/transfer_to = locateUID(params["transfer_to_account"])
+ if(!istype(transfer_to) || transfer_to == src)
+ return //account no longer exists or something fucked is going on
+ if(transfer_funds(user, transfer_amount, transfer_to))
+ to_chat(user, "NanoBank: Transfer Succesful")
+ last_transaction = world.time
+ if(!pda.silent)
+ playsound(pda, 'sound/machines/ping.ogg', 50, 0)
+ if("transfer_request")
+ if(!user_account)
+ return
+ if(last_transaction + TRANSFER_COOLDOWN >= world.time)
+ return
+ var/transfer_amount = text2num(params["amount"])
+ var/purpose = length(params["purpose"]) ? params["purpose"] : ""
+ if(length(purpose) >= MAX_NAME_LEN)
+ error_message(user, "Purpose too long, please limit to [MAX_NAME_LEN] characters max")
+ if(!transfer_amount || transfer_amount < 0) //if null, 0, or negative amount
+ return
+ var/datum/money_account/request_from = locateUID(params["transfer_to_account"])
+ if(!istype(request_from) || request_from == user_account)
+ return //account no longer exists or they're trying to send to themselves
+ if(create_fund_request(user, transfer_amount, purpose, request_from))
+ to_chat(user, "NanoBank: Transfer Request Submitted")
+ last_transaction = world.time
+ if(!pda.silent)
+ playsound(pda, 'sound/machines/ping.ogg', 50, 0)
+ if("resolve_transfer_request")
+ if(!user_account)
+ return
+ var/accepted = params["accepted"] ? TRUE : FALSE
+ var/datum/transfer_request/request = locateUID(params["requestUID"])
+ if(!istype(request) || !(request in user_account.transfer_requests))
+ return
+ account_database.resolve_transfer_request(request, user_account, accepted)
+ if("set_security")
+ if(!user_account)
+ return
+ var/new_sec_level = clamp(text2num(params["new_security_level"]), ACCOUNT_SECURITY_ID, ACCOUNT_SECURITY_PIN)
+ if(!new_sec_level)
+ return
+ var/attempt_pin = input_account_pin(user)
+ if(account_database.try_authenticate_login(user_account, attempt_pin, FALSE, FALSE, FALSE))
+ user_account.security_level = new_sec_level
+ if("toggle_member_approval")
+ for(var/datum/station_department/department as anything in SSjobs.station_departments)
+ if(department.department_account == user_account)
+ for(var/datum/department_member/member in department.members)
+ if(member.name == params["member"] && member.member_account != user_account)
+ member.can_approve_crates = !member.can_approve_crates
+ break
+ break
+ if("toggle_auto_approve")
+ for(var/datum/station_department/department as anything in SSjobs.station_departments)
+ if(department.department_account == user_account)
+ department.crate_auto_approve = !department.crate_auto_approve
+ break
+ if("set_approve_amount")
+ var/new_amount = text2num(params["approve_amount"])
+ for(var/datum/station_department/department as anything in SSjobs.station_departments)
+ if(department.department_account == user_account)
+ if(new_amount)
+ department.auto_approval_cap = clamp(new_amount, 0, 3000)
+ else
+ department.auto_approval_cap = 0
+
+ if(play_beep && !pda.silent)
+ playsound(pda, 'sound/machines/terminal_select.ogg', 15, TRUE)
+
+/datum/data/pda/app/nanobank/proc/attempt_login(tried_account_num, tried_pin, mob/user)
+ if(!tried_account_num)
+ to_chat(user, "Authentification Failure: Account number not found.")
+ return FALSE
+
+ var/datum/money_account/attempt_account = account_database.find_user_account(tried_account_num, include_departments = TRUE)
+ if(!attempt_account)
+ to_chat(user, "Authentification Failure: User Account Not Found.")
+ return FALSE
+
+ if(account_database.try_authenticate_login(attempt_account, tried_pin, TRUE, FALSE, FALSE))
+ user_account = attempt_account
+ //lets make sure to logout if the account gets deleted somehow (such as cryo'ing)
+ RegisterSignal(user_account, COMSIG_PARENT_QDELETING, PROC_REF(logout))
+ if(!LAZYLEN(user_account.associated_nanobank_programs) || !(src in user_account.associated_nanobank_programs))
+ LAZYADD(user_account.associated_nanobank_programs, src)
+ return TRUE
+
+/datum/data/pda/app/nanobank/proc/logout()
+ if(!user_account)
+ return
+ //even though this is a LAZYLIST, we know it has a single entry on it because we're connected to the account
+ LAZYREMOVE(user_account.associated_nanobank_programs, src)
+ UnregisterSignal(user_account, COMSIG_PARENT_QDELETING)
+ user_account = null
+
+
+/datum/data/pda/app/nanobank/proc/transfer_funds(mob/user, amount, datum/money_account/target)
+ if(account_database.charge_account(user_account, amount, "Transfer to [target.account_name]", "NanoBank Transfer Services", FALSE, FALSE))
+ account_database.credit_account(target, amount, "Transfer from [user_account.account_name]", "NanoBank Transfer Services", FALSE)
+ return TRUE
+ else
+ error_message(user, "Insufficient Funds")
+ return FALSE
+
+/datum/data/pda/app/nanobank/proc/create_fund_request(mob/user, amount, purpose, datum/money_account/target)
+ if(!target)
+ error_message(user, "Target Account Not Found")
+ return FALSE
+ if(!amount)
+ error_message(user, "Please input an amount to request")
+ return FALSE
+ account_database.create_transfer_request(user_account, amount, purpose, target)
+
+/datum/data/pda/app/nanobank/proc/change_account_security(mob/user, attempted_pin, new_security_level)
+ if(new_security_level >= ACCOUNT_SECURITY_RESTRICTED || new_security_level < ACCOUNT_SECURITY_ID)
+ error_message(user, "Unable to apply selected security restrictions")
+ ///attempted new security level is NOT an available option, likely user spoofing values
+ message_admins("[ADMIN_LOOKUPFLW(user)] attempted to set account security to an unavailable option, possible href exploit!")
+ return
+ if(account_database.try_authenticate_login(user_account, attempted_pin, FALSE, FALSE, FALSE))
+ user_account.security_level = new_security_level
+ else
+ error_message(user, "Incorrect Credentials")
+
+/datum/data/pda/app/nanobank/proc/input_account_pin(mob/user)
+ var/attempt_pin = input("Enter pin code", "NanoBank Account Auth") as num
+ if(!user_account || !attempt_pin)
+ return
+ return attempt_pin
+
+/datum/data/pda/app/nanobank/proc/error_message(mob/user, message)
+ to_chat(user, "ERROR: [message].")
+ if(!pda.silent)
+ playsound(pda, 'sound/machines/terminal_error.ogg', 15, TRUE)
+
+/datum/data/pda/app/nanobank/proc/get_available_account_data()
+ var/list/found_accounts = list()
+ for(var/datum/money_account/account as anything in account_database.user_accounts)
+ if(account != user_account)
+ var/list/account_data = list(
+ "name" = account.account_name,
+ "UID" = account.UID()
+ )
+ found_accounts += list(account_data)
+ for(var/department in account_database.department_accounts)
+ var/datum/money_account/account = account_database.department_accounts[department]
+ var/list/account_data = list(
+ "name" = account.account_name,
+ "UID" = account.UID()
+ )
+ found_accounts += list(account_data)
+ return found_accounts
+
+/datum/data/pda/app/nanobank/proc/announce_payday(amount)
+ if(ishuman(pda.loc))
+ var/mob/user = pda.loc
+ if(user.stat != UNCONSCIOUS) // Awake or dead people can see their messages
+ to_chat(user, "NanoBank: Paycheck of [amount] credits received")
+ if(!pda.silent)
+ playsound(pda, 'sound/machines/ping.ogg', 50, 0)
+
+/datum/data/pda/app/nanobank/proc/can_receive()
+ return pda.owner && !hidden
+
+#undef TRANSFER_COOLDOWN
diff --git a/code/modules/research/research.dm b/code/modules/research/research.dm
index 68029031209..114eafd0fc3 100644
--- a/code/modules/research/research.dm
+++ b/code/modules/research/research.dm
@@ -355,7 +355,7 @@ datum/tech/robotics
return 0
var/cost = 0
- for(var/i=current_level+1, i<=level, i++)
+ for(var/i = current_level + 1, i <= level, i++)
if(i == initial(level))
continue
cost += i*5*rare
diff --git a/code/modules/ruins/syndicate_space_base.dm b/code/modules/ruins/syndicate_space_base.dm
index 95ba396ec53..f3237f5ac93 100644
--- a/code/modules/ruins/syndicate_space_base.dm
+++ b/code/modules/ruins/syndicate_space_base.dm
@@ -1,5 +1,5 @@
// SyndiChem
-/obj/machinery/vending/syndichem
+/obj/machinery/economy/vending/syndichem
name = "\improper SyndiChem"
desc = "A vending machine full of grenades and grenade accessories. Sponsored by DonkCo(tm)."
req_access = list(ACCESS_SYNDICATE)
diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm
index ebf3352326f..c783ba14110 100644
--- a/code/modules/shuttle/supply.dm
+++ b/code/modules/shuttle/supply.dm
@@ -1,14 +1,9 @@
-/obj/item/paper/manifest
- name = "supply manifest"
- var/erroneous = 0
- var/points = 0
- var/ordernumber = 0
+#define MAX_CRATE_DELIVERY 40
/obj/docking_port/mobile/supply
name = "supply shuttle"
id = "supply"
- callTime = 1200
-
+ callTime = 2 MINUTES
dir = 8
travelDir = 90
width = 12
@@ -17,217 +12,11 @@
/obj/docking_port/mobile/supply/register()
if(!..())
- return 0
+ return FALSE
SSshuttle.supply = src
- return 1
+ return TRUE
-/obj/docking_port/mobile/supply/canMove()
- if(is_station_level(z))
- return forbidden_atoms_check(areaInstance)
- return ..()
-
-/obj/docking_port/mobile/supply/request(obj/docking_port/stationary/S)
- if(mode != SHUTTLE_IDLE)
- return 2
- return ..()
-
-/obj/docking_port/mobile/supply/dock()
- . = ..()
- if(.) return .
-
- buy()
- sell()
-
-/obj/docking_port/mobile/supply/proc/buy()
- if(!is_station_level(z)) //we only buy when we are -at- the station
- return 1
-
- if(!SSshuttle.shoppinglist.len)
- return 2
-
- var/list/emptyTurfs = list()
- for(var/turf/simulated/T in areaInstance)
- if(T.density)
- continue
-
- var/contcount
- for(var/atom/A in T.contents)
- if(!A.simulated)
- continue
- if(istype(A, /obj/machinery/light))
- continue //hacky but whatever, shuttles need three spots each for this shit
- contcount++
-
- if(contcount)
- continue
-
- emptyTurfs += T
-
- for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
- if(!SO.object)
- throw EXCEPTION("Supply Order [SO] has no object associated with it.")
- continue
-
- var/turf/T = pick_n_take(emptyTurfs) //turf we will place it in
- if(!T)
- SSshuttle.shoppinglist.Cut(1, SSshuttle.shoppinglist.Find(SO))
- return
-
- var/errors = 0
- if(prob(5))
- errors |= MANIFEST_ERROR_COUNT
- if(prob(5))
- errors |= MANIFEST_ERROR_NAME
- if(prob(5))
- errors |= MANIFEST_ERROR_ITEM
- SO.createObject(T, errors)
-
- SSshuttle.shoppinglist.Cut()
-
-/obj/docking_port/mobile/supply/proc/sell()
- if(z != level_name_to_num(CENTCOMM)) //we only sell when we are -at- centcomm
- return 1
-
- var/plasma_count = 0
- var/intel_count = 0
- var/crate_count = 0
-
- var/msg = "
---[station_time_timestamp()]---
"
- var/pointsEarned
-
- for(var/atom/movable/MA in areaInstance)
- if(MA.anchored)
- continue
- if(istype(MA, /mob/dead))
- continue
- SSshuttle.sold_atoms += " [MA.name]"
-
- // Must be in a crate (or a critter crate)!
- if(istype(MA,/obj/structure/closet/crate) || istype(MA,/obj/structure/closet/critter))
- SSshuttle.sold_atoms += ":"
- if(!MA.contents.len)
- SSshuttle.sold_atoms += " (empty)"
- ++crate_count
-
- var/find_slip = 1
- for(var/thing in MA)
- // Sell manifests
- SSshuttle.sold_atoms += " [thing:name]"
- if(find_slip && istype(thing,/obj/item/paper/manifest))
- var/obj/item/paper/manifest/slip = thing
- // TODO: Check for a signature, too.
- if(slip.stamped && slip.stamped.len) //yes, the clown stamp will work. clown is the highest authority on the station, it makes sense
- // Did they mark it as erroneous?
- var/denied = 0
- for(var/i=1,i<=slip.stamped.len,i++)
- if(slip.stamped[i] == /obj/item/stamp/denied)
- denied = 1
- if(slip.erroneous && denied) // Caught a mistake by Centcom (IDEA: maybe Centcom rarely gets offended by this)
- pointsEarned = slip.points - SSshuttle.points_per_crate
- SSshuttle.points += pointsEarned // For now, give a full refund for paying attention (minus the crate cost)
- msg += "+[pointsEarned]: Station correctly denied package [slip.ordernumber]: "
- if(slip.erroneous & MANIFEST_ERROR_NAME)
- msg += "Destination station incorrect. "
- else if(slip.erroneous & MANIFEST_ERROR_COUNT)
- msg += "Packages incorrectly counted. "
- else if(slip.erroneous & MANIFEST_ERROR_ITEM)
- msg += "Package incomplete. "
- msg += "Points refunded. "
- else if(!slip.erroneous && !denied) // Approving a proper order awards the relatively tiny points_per_slip
- SSshuttle.points += SSshuttle.points_per_slip
- msg += "+[SSshuttle.points_per_slip]: Package [slip.ordernumber] accorded. "
- else // You done goofed.
- if(slip.erroneous)
- msg += "+0: Station approved package [slip.ordernumber] despite error: "
- if(slip.erroneous & MANIFEST_ERROR_NAME)
- msg += "Destination station incorrect."
- else if(slip.erroneous & MANIFEST_ERROR_COUNT)
- msg += "Packages incorrectly counted."
- else if(slip.erroneous & MANIFEST_ERROR_ITEM)
- msg += "We found unshipped items on our dock."
- msg += " Be more vigilant. "
- else
- pointsEarned = round(SSshuttle.points_per_crate - slip.points)
- SSshuttle.points += pointsEarned
- msg += "[pointsEarned]: Station denied package [slip.ordernumber]. Our records show no fault on our part. "
- find_slip = 0
- continue
-
- // Sell plasma
- if(istype(thing, /obj/item/stack/sheet/mineral/plasma))
- var/obj/item/stack/sheet/mineral/plasma/P = thing
- plasma_count += P.amount
-
- // Sell syndicate intel
- if(istype(thing, /obj/item/documents/syndicate))
- ++intel_count
-
- // Sell tech levels
- if(istype(thing, /obj/item/disk/tech_disk))
- var/obj/item/disk/tech_disk/disk = thing
- if(!disk.stored) continue
- var/datum/tech/tech = disk.stored
-
- var/cost = tech.getCost(SSshuttle.techLevels[tech.id])
- if(cost)
- SSshuttle.techLevels[tech.id] = tech.level
- SSshuttle.points += cost
- for(var/mob/M in GLOB.player_list)
- if(M.mind)
- for(var/datum/job_objective/further_research/objective in M.mind.job_objectives)
- objective.unit_completed(cost)
- msg += "+[cost]: [tech.name] - new data. "
-
- // Sell designs
- if(istype(thing, /obj/item/disk/design_disk))
- var/obj/item/disk/design_disk/disk = thing
- if(!disk.blueprint)
- continue
- var/datum/design/design = disk.blueprint
- if(design.id in SSshuttle.researchDesigns)
- continue
- SSshuttle.points += SSshuttle.points_per_design
- SSshuttle.researchDesigns += design.id
- msg += "+[SSshuttle.points_per_design]: [design.name] design. "
-
- // Sell exotic plants
- if(istype(thing, /obj/item/seeds))
- var/obj/item/seeds/S = thing
- if(S.rarity == 0) // Mundane species
- msg += "+0: We don't need samples of mundane species \"[capitalize(S.species)]\". "
- else if(SSshuttle.discoveredPlants[S.type]) // This species has already been sent to CentComm
- var/potDiff = S.potency - SSshuttle.discoveredPlants[S.type] // Compare it to the previous best
- if(potDiff > 0) // This sample is better
- SSshuttle.discoveredPlants[S.type] = S.potency
- msg += "+[potDiff]: New sample of \"[capitalize(S.species)]\" is superior. Good work. "
- SSshuttle.points += potDiff
- else // This sample is worthless
- msg += "+0: New sample of \"[capitalize(S.species)]\" is not more potent than existing sample ([SSshuttle.discoveredPlants[S.type]] potency). "
- else // This is a new discovery!
- SSshuttle.discoveredPlants[S.type] = S.potency
- msg += "[S.rarity]: New species discovered: \"[capitalize(S.species)]\". Excellent work. "
- SSshuttle.points += S.rarity // That's right, no bonus for potency. Send a crappy sample first to "show improvement" later
- qdel(MA)
- SSshuttle.sold_atoms += "."
-
- if(plasma_count > 0)
- pointsEarned = round(plasma_count * SSshuttle.points_per_plasma)
- msg += "+[pointsEarned]: Received [plasma_count] unit(s) of exotic material. "
- SSshuttle.points += pointsEarned
-
- if(intel_count > 0)
- pointsEarned = round(intel_count * SSshuttle.points_per_intel)
- msg += "+[pointsEarned]: Received [intel_count] article(s) of enemy intelligence. "
- SSshuttle.points += pointsEarned
-
- if(crate_count > 0)
- pointsEarned = round(crate_count * SSshuttle.points_per_crate)
- msg += "+[pointsEarned]: Received [crate_count] crate(s). "
- SSshuttle.points += pointsEarned
-
- SSshuttle.centcom_message += "[msg]"
-
-/proc/forbidden_atoms_check(atom/A)
+/obj/docking_port/mobile/supply/proc/forbidden_atoms_check(atom/A)
var/list/blacklist = list(
/mob/living,
/obj/structure/blob,
@@ -249,360 +38,187 @@
)
if(A)
if(is_type_in_list(A, blacklist))
- return 1
+ return TRUE
for(var/thing in A)
if(.(thing))
- return 1
-
- return 0
-
-/********************
- SUPPLY ORDER
- ********************/
-/datum/supply_order
- var/ordernum
- var/datum/supply_packs/object = null
- var/orderedby = null
- var/orderedbyRank
- var/comment = null
- var/crates
-
-/datum/supply_order/proc/generateRequisition(atom/_loc)
- if(!object)
- return
-
- var/obj/item/paper/reqform = new /obj/item/paper(_loc)
- playsound(_loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1)
- reqform.name = "Requisition Form - [crates] '[object.name]' for [orderedby]"
- reqform.info += "
" //add the item to the manifest (even if it was misplaced)
-
- if(istype(Crate, /obj/structure/closet/critter)) // critter crates do not actually spawn mobs yet and have no contains var, but the manifest still needs to list them
- var/obj/structure/closet/critter/CritCrate = Crate
- if(CritCrate.content_mob)
- var/mob/crittername = CritCrate.content_mob
- slip.info += "
[initial(crittername.name)]
"
-
- if((errors & MANIFEST_ERROR_ITEM))
- //secure and large crates cannot lose items
- if(findtext("[object.containertype]", "/secure/") || findtext("[object.containertype]","/largecrate/"))
- errors &= ~MANIFEST_ERROR_ITEM
- else
- var/lostAmt = max(round(Crate.contents.len/10), 1)
- //lose some of the items
- while(--lostAmt >= 0)
- qdel(pick(Crate.contents))
-
- //manifest finalisation
- slip.info += "
"
- slip.info += "CHECK CONTENTS AND STAMP BELOW THE LINE TO CONFIRM RECEIPT OF GOODS" // And now this is actually meaningful.
- slip.loc = Crate
- if(istype(Crate, /obj/structure/closet/crate))
- var/obj/structure/closet/crate/CR = Crate
- CR.manifest = slip
- CR.update_icon()
- CR.announce_beacons = object.announce_beacons.Copy()
- if(istype(Crate, /obj/structure/largecrate))
- var/obj/structure/largecrate/LC = Crate
- LC.manifest = slip
- LC.update_icon()
-
- return Crate
-
-/***************************
- ORDER/REQUESTS CONSOLE
- **************************/
-/obj/machinery/computer/supplycomp
- name = "Supply Shuttle Console"
- desc = "Used to order supplies."
- icon_screen = "supply"
- req_access = list(ACCESS_CARGO)
- circuit = /obj/item/circuitboard/supplycomp
- /// Is this a public console (Confirm + Shuttle controls are not visible)
- var/is_public = FALSE
- /// Time of last request
- var/reqtime = 0
- /// Can we order special supplies
- var/hacked = FALSE
- /// Can we order contraband
- var/can_order_contraband = FALSE
-
-/obj/machinery/computer/supplycomp/public
- name = "Supply Ordering Console"
- desc = "Used to order supplies from cargo staff."
- icon = 'icons/obj/computer.dmi'
- icon_screen = "request"
- circuit = /obj/item/circuitboard/ordercomp
- req_access = list()
- is_public = TRUE
-
-/obj/machinery/computer/supplycomp/attack_ai(mob/user as mob)
- return attack_hand(user)
-
-/obj/machinery/computer/supplycomp/attack_hand(mob/user as mob)
- if(!allowed(user) && !isobserver(user))
- to_chat(user, "Access denied.")
- return 1
-
- ui_interact(user)
- return
-
-/obj/machinery/computer/supplycomp/emag_act(user as mob)
- if(!hacked)
- to_chat(user, "Special supplies unlocked.")
- hacked = TRUE
- return
-
-/obj/machinery/computer/supplycomp/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "CargoConsole", name, 900, 800, master_ui, state)
- ui.open()
-
-/obj/machinery/computer/supplycomp/ui_data(mob/user)
- var/list/data = list()
-
- var/list/requests_list = list()
- for(var/set_name in SSshuttle.requestlist)
- var/datum/supply_order/SO = set_name
- if(SO)
- if(!SO.comment)
- SO.comment = "No comment."
- requests_list.Add(list(list("ordernum" = SO.ordernum, "supply_type" = SO.object.name, "orderedby" = SO.orderedby, "comment" = SO.comment, "command1" = list("confirmorder" = SO.ordernum), "command2" = list("rreq" = SO.ordernum))))
- data["requests"] = requests_list
-
- var/list/orders_list = list()
- for(var/set_name in SSshuttle.shoppinglist)
- var/datum/supply_order/SO = set_name
- if(SO)
- orders_list.Add(list(list("ordernum" = SO.ordernum, "supply_type" = SO.object.name, "orderedby" = SO.orderedby, "comment" = SO.comment)))
- data["orders"] = orders_list
-
- data["is_public"] = is_public
-
- data["canapprove"] = (SSshuttle.supply.getDockedId() == "supply_away") && !(SSshuttle.supply.mode != SHUTTLE_IDLE) && !is_public
- data["points"] = round(SSshuttle.points)
-
- data["moving"] = SSshuttle.supply.mode != SHUTTLE_IDLE
- data["at_station"] = SSshuttle.supply.getDockedId() == "supply_home"
- data["timeleft"] = SSshuttle.supply.timeLeft(600)
- data["can_launch"] = !SSshuttle.supply.canMove()
-
- return data
-
-/obj/machinery/computer/supplycomp/ui_static_data(mob/user)
- var/list/data = list()
- var/list/packs_list = list()
-
- for(var/set_name in SSshuttle.supply_packs)
- var/datum/supply_packs/pack = SSshuttle.supply_packs[set_name]
- if((pack.hidden && hacked) || (pack.contraband && can_order_contraband) || (pack.special && pack.special_enabled) || (!pack.contraband && !pack.hidden && !pack.special))
- packs_list.Add(list(list("name" = pack.name, "cost" = pack.cost, "ref" = "[pack.UID()]", "contents" = pack.ui_manifest, "cat" = pack.group)))
-
- data["supply_packs"] = packs_list
-
- var/list/categories = list() // meow
- for(var/category in GLOB.all_supply_groups)
- categories.Add(list(list("name" = get_supply_group_name(category), "category" = category)))
- data["categories"] = categories
-
- return data
-
-/obj/machinery/computer/supplycomp/proc/is_authorized(mob/user)
- if(allowed(user))
- return TRUE
-
- if(user.can_admin_interact())
- return TRUE
+ return TRUE
return FALSE
-/obj/machinery/computer/supplycomp/ui_act(action, list/params)
- if(..())
+/obj/docking_port/mobile/supply/canMove()
+ if(is_station_level(z))
+ return forbidden_atoms_check(areaInstance)
+ return ..()
+
+/obj/docking_port/mobile/supply/request(obj/docking_port/stationary/S)
+ if(mode != SHUTTLE_IDLE)
+ return 2
+ return ..()
+
+/obj/docking_port/mobile/supply/dock()
+ . = ..()
+ if(.)
return
- // If its not a public console, and they aint authed, dont let them use this
- if(!is_public && !is_authorized(usr))
- return
+ buy()
+ sell()
- if(!SSshuttle)
- stack_trace("The SSshuttle controller datum is missing somehow.")
- return
+/obj/docking_port/mobile/supply/proc/buy()
+ if(!is_station_level(z)) //we only buy when we are -at- the station
+ return 1
- . = TRUE
- add_fingerprint(usr)
+ for(var/datum/supply_order/order as anything in SSeconomy.shopping_list)
+ if(length(SSeconomy.delivery_list) >= MAX_CRATE_DELIVERY)
+ break
+ SSeconomy.delivery_list += order
+ SSeconomy.shopping_list -= order
- switch(action)
- if("moveShuttle")
- // Public consoles cant move the shuttle. Dont allow exploiters.
- if(is_public)
- return
- if(SSshuttle.supply.canMove())
- to_chat(usr, "For safety reasons the automated supply shuttle cannot transport live organisms, classified nuclear weaponry or homing beacons.")
- else if(SSshuttle.supply.getDockedId() == "supply_home")
- SSshuttle.toggleShuttle("supply", "supply_home", "supply_away", 1)
- investigate_log("| [key_name(usr)] has sent the supply shuttle away. Remaining points: [SSshuttle.points]. Shuttle contents: [SSshuttle.sold_atoms]", "cargo")
- else if(!SSshuttle.supply.request(SSshuttle.getDock("supply_home")))
- if(LAZYLEN(SSshuttle.shoppinglist) && prob(10))
- var/datum/supply_order/O = new /datum/supply_order()
- O.ordernum = SSshuttle.ordernum
- O.object = SSshuttle.supply_packs[pick(SSshuttle.supply_packs)]
- O.orderedby = random_name(pick(MALE,FEMALE), species = "Human")
- SSshuttle.shoppinglist += O
- investigate_log("Random [O.object] crate added to supply shuttle")
+ if(!length(SSeconomy.delivery_list))
+ return 2
- if("order")
- if(world.time < reqtime)
- visible_message("[src]'s monitor flashes, \"[world.time - reqtime] seconds remaining until another requisition form may be printed.\"")
- return
+ var/list/emptyTurfs = list()
+ for(var/turf/simulated/T in areaInstance)
+ if(T.density)
+ continue
- var/amount = 1
- if(params["multiple"] == "1") // 1 is a string here. DO NOT MAKE THIS A BOOLEAN YOU DORK
- var/num_input = input(usr, "Amount", "How many crates? (20 Max)") as null|num
- if(!num_input || (!is_public && !is_authorized(usr)) || ..()) // Make sure they dont walk away
- return
- amount = clamp(round(num_input), 1, 20)
+ var/contcount
+ for(var/atom/A in T.contents)
+ if(!A.simulated)
+ continue
+ if(istype(A, /obj/machinery/light))
+ continue //hacky but whatever, shuttles need three spots each for this shit
+ contcount++
- var/datum/supply_packs/P = locateUID(params["crate"])
- if(!istype(P))
- return
+ if(contcount)
+ continue
- var/timeout = world.time + 600 // If you dont type the reason within a minute, theres bigger problems here
- var/reason = input(usr, "Reason", "Why do you require this item?","") as null|text
- if(world.time > timeout || !reason || (!is_public && !is_authorized(usr)) || ..())
- // Cancel if they take too long, they dont give a reason, they aint authed, or if they walked away
- return
- reason = sanitize(copytext(reason, 1, MAX_MESSAGE_LEN))
+ emptyTurfs += T
- var/idname = "*None Provided*"
- var/idrank = "*None Provided*"
+ for(var/datum/supply_order/SO in SSeconomy.delivery_list)
+ if(!SO.object)
+ throw EXCEPTION("Supply Order [SO] has no object associated with it.")
+ continue
- if(ishuman(usr))
- var/mob/living/carbon/human/H = usr
- idname = H.get_authentification_name()
- idrank = H.get_assignment()
- else if(issilicon(usr))
- idname = usr.real_name
+ var/turf/T = pick_n_take(emptyTurfs) //turf we will place it in
+ if(!T)
+ SSeconomy.delivery_list.Cut(1, SSeconomy.delivery_list.Find(SO))
+ return
+ SO.createObject(T)
- //make our supply_order datums
- for(var/i = 1; i <= amount; i++)
- var/datum/supply_order/O = SSshuttle.generateSupplyOrder(params["crate"], idname, idrank, reason, amount)
- if(!O)
- return
- if(i == 1)
- O.generateRequisition(loc)
- investigate_log("| [key_name(usr)] has placed an order for [amount] [O.object.name] with reason: '[reason]'", "cargo")
+ SSeconomy.delivery_list.Cut()
- if("approve")
- // Public consoles cant approve stuff
- if(is_public)
- return
- if(SSshuttle.supply.getDockedId() != "supply_away" || SSshuttle.supply.mode != SHUTTLE_IDLE)
- return
+/obj/docking_port/mobile/supply/proc/sell()
+ if(z != level_name_to_num(CENTCOMM)) //we only sell when we are -at- centcomm
+ return 1
- var/ordernum = text2num(params["ordernum"])
- var/datum/supply_order/O
- var/datum/supply_packs/P
- for(var/i=1, i<=SSshuttle.requestlist.len, i++)
- var/datum/supply_order/SO = SSshuttle.requestlist[i]
- if(SO.ordernum == ordernum)
- O = SO
- P = O.object
- if(SSshuttle.points >= P.cost)
- SSshuttle.requestlist.Cut(i,i+1)
- SSshuttle.points -= P.cost
- SSshuttle.shoppinglist += O
- investigate_log("| [key_name(usr)] has authorized an order for [P.name]. Remaining points: [SSshuttle.points].", "cargo")
- SSblackbox.record_feedback("tally", "cargo_shuttle_order", 1, P.name)
- else
- to_chat(usr, "There are insufficient supply points for this request.")
- break
+ var/plasma_count = 0
+ var/intel_count = 0
+ var/crate_count = 0
- if("deny")
- var/ordernum = text2num(params["ordernum"])
- for(var/i=1, i<=SSshuttle.requestlist.len, i++)
- var/datum/supply_order/SO = SSshuttle.requestlist[i]
- if(SO.ordernum == ordernum)
- // If we are on a public console, only allow cancelling of our own orders
- if(is_public)
- var/obj/item/card/id/I = usr.get_id_card()
- if(I && SO.orderedby == I.registered_name)
- SSshuttle.requestlist.Cut(i,i+1)
- break
- // If we arent public, were cargo access. CANCELLATIONS FOR EVERYONE
- else
- SSshuttle.requestlist.Cut(i,i+1)
- investigate_log("| [key_name(usr)] has denied an order for [SO.object.name]. Remaining points: [SSshuttle.points].", "cargo")
- break
+ var/msg = "
---[station_time_timestamp()]---
"
+ var/credits_to_deposit = 0
+ var/research_credits = 0
+ var/service_credits = 0
- // Popup to show CC message logs. Its easier this way to avoid box-spam in TGUI
- if("showMessages")
- // Public consoles cant view messages
- if(is_public)
- return
- var/datum/browser/ccmsg_browser = new(usr, "ccmsg", "Central Command Cargo Message Log", 800, 600)
- ccmsg_browser.set_content(SSshuttle.centcom_message)
- ccmsg_browser.open()
+ for(var/atom/movable/MA in areaInstance)
+ if(MA.anchored)
+ continue
+ if(istype(MA, /mob/dead))
+ continue
+ SSeconomy.sold_atoms += " [MA.name]"
+
+ // Must be in a crate (or a critter crate)!
+ if(istype(MA,/obj/structure/closet/crate) || istype(MA,/obj/structure/closet/critter))
+ SSeconomy.sold_atoms += ":"
+ if(!length(MA.contents))
+ SSeconomy.sold_atoms += " (empty)"
+ crate_count++
+
+ var/find_slip = TRUE
+ for(var/thing in MA)
+ // Sell manifests
+ SSeconomy.sold_atoms += " [thing:name]"
+ if(find_slip && istype(thing,/obj/item/paper/manifest))
+ var/obj/item/paper/manifest/slip = thing
+ credits_to_deposit += SSeconomy.credits_per_manifest
+ msg += "+[SSeconomy.credits_per_manifest]: Package [slip.ordernumber] accorded. "
+
+ // Sell plasma
+ if(istype(thing, /obj/item/stack/sheet/mineral/plasma))
+ var/obj/item/stack/sheet/mineral/plasma/P = thing
+ plasma_count += P.amount
+
+ // Sell syndicate intel
+ if(istype(thing, /obj/item/documents/syndicate))
+ ++intel_count
+
+ // Sell tech levels
+ if(istype(thing, /obj/item/disk/tech_disk))
+ var/obj/item/disk/tech_disk/disk = thing
+ if(!disk.stored)
+ continue
+ var/datum/tech/tech = disk.stored
+
+ var/cost = tech.getCost(SSeconomy.tech_levels[tech.id])
+ if(cost)
+ SSeconomy.tech_levels[tech.id] = tech.level
+ research_credits += cost / 2
+ credits_to_deposit += cost / 2
+ msg += "+[cost]: [tech.name] - new data. "
+
+ // Sell designs
+ if(istype(thing, /obj/item/disk/design_disk))
+ var/obj/item/disk/design_disk/disk = thing
+ if(!disk.blueprint)
+ continue
+ var/datum/design/design = disk.blueprint
+ if(design.id in SSeconomy.research_designs)
+ continue
+ credits_to_deposit += SSeconomy.credits_per_design
+ SSeconomy.research_designs += design.id
+ msg += "+[SSeconomy.credits_per_design]: [design.name] design. "
+
+ // Sell exotic plants
+ if(istype(thing, /obj/item/seeds))
+ var/obj/item/seeds/S = thing
+ if(S.rarity == 0) // Mundane species
+ msg += "+0: We don't need samples of mundane species \"[capitalize(S.species)]\". "
+ else if(SSeconomy.discovered_plants[S.type]) // This species has already been sent to CentComm
+ var/potDiff = S.potency - SSeconomy.discovered_plants[S.type] // Compare it to the previous best
+ if(potDiff > 0) // This sample is better
+ SSeconomy.discovered_plants[S.type] = S.potency
+ msg += "+[potDiff]: New sample of \"[capitalize(S.species)]\" is superior. Good work. "
+ service_credits += potDiff / 2
+ credits_to_deposit += potDiff / 2
+ else // This sample is worthless
+ msg += "+0: New sample of \"[capitalize(S.species)]\" is not more potent than existing sample ([SSeconomy.discovered_plants[S.type]] potency). "
+ else // This is a new discovery!
+ SSeconomy.discovered_plants[S.type] = S.potency
+ msg += "[S.rarity]: New species discovered: \"[capitalize(S.species)]\". Excellent work. "
+ credits_to_deposit += S.rarity / 2 // That's right, no bonus for potency. Send a crappy sample first to "show improvement" later
+ credits_to_deposit += S.rarity / 2
+ qdel(MA)
+ SSeconomy.sold_atoms += "."
+
+ if(plasma_count > 0)
+ var/credits_from_plasma = plasma_count * SSeconomy.credits_per_plasma
+ msg += "+[credits_from_plasma]: Received [plasma_count] unit(s) of exotic material. "
+ credits_to_deposit += credits_from_plasma
+
+ if(intel_count > 0)
+ var/credits_from_intel = intel_count * SSeconomy.credits_per_intel
+ msg += "+[credits_from_intel]: Received [intel_count] article(s) of enemy intelligence. "
+ credits_to_deposit += credits_from_intel
+
+ if(crate_count > 0)
+ var/credits_from_crates = crate_count * SSeconomy.credits_per_crate
+ msg += "+[credits_from_crates]: Received [crate_count] crate(s). "
+ credits_to_deposit += credits_from_crates
+
+ SSeconomy.centcom_message += "[msg]"
+ if(credits_to_deposit > 0)
+ GLOB.station_money_database.credit_account(SSeconomy.cargo_account, credits_to_deposit, "Supply Shuttle Exports Payment", "Central Command Supply Master", supress_log = FALSE)
+ if(research_credits)
+ GLOB.station_money_database.credit_account(GLOB.station_money_database.get_account_by_department(DEPARTMENT_SCIENCE), research_credits, "Supply Shuttle Exports Payment", "Central Command Supply Master", supress_log = FALSE)
+ if(service_credits)
+ GLOB.station_money_database.credit_account(GLOB.station_money_database.get_account_by_department(DEPARTMENT_SERVICE), service_credits, "Supply Shuttle Exports Payment", "Central Command Supply Master", supress_log = FALSE)
diff --git a/code/modules/station_goals/bluespace_tap.dm b/code/modules/station_goals/bluespace_tap.dm
index 4f98a48f57d..422f887462f 100644
--- a/code/modules/station_goals/bluespace_tap.dm
+++ b/code/modules/station_goals/bluespace_tap.dm
@@ -15,7 +15,7 @@
Nanotrasen Science Directorate"}
/datum/station_goal/bluespace_tap/on_report()
- var/datum/supply_packs/misc/station_goal/bluespace_tap/P = SSshuttle.supply_packs["[/datum/supply_packs/misc/station_goal/bluespace_tap]"]
+ var/datum/supply_packs/misc/station_goal/bluespace_tap/P = SSeconomy.supply_packs["[/datum/supply_packs/misc/station_goal/bluespace_tap]"]
P.special_enabled = TRUE
/datum/station_goal/bluespace_tap/check_completion()
diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index 6bc8db830f8..118b01f3641 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -16,7 +16,7 @@
/datum/station_goal/bluespace_cannon/on_report()
//Unlock BSA parts
- var/datum/supply_packs/misc/station_goal/bsa/P = SSshuttle.supply_packs["[/datum/supply_packs/misc/station_goal/bsa]"]
+ var/datum/supply_packs/misc/station_goal/bsa/P = SSeconomy.supply_packs["[/datum/supply_packs/misc/station_goal/bsa]"]
P.special_enabled = TRUE
/datum/station_goal/bluespace_cannon/check_completion()
diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm
index d331e85176a..097d48e7fec 100644
--- a/code/modules/station_goals/dna_vault.dm
+++ b/code/modules/station_goals/dna_vault.dm
@@ -43,10 +43,10 @@
The base vault parts should be available for shipping by your cargo shuttle."}
/datum/station_goal/dna_vault/on_report()
- var/datum/supply_packs/P = SSshuttle.supply_packs["[/datum/supply_packs/misc/station_goal/dna_vault]"]
+ var/datum/supply_packs/P = SSeconomy.supply_packs["[/datum/supply_packs/misc/station_goal/dna_vault]"]
P.special_enabled = TRUE
- P = SSshuttle.supply_packs["[/datum/supply_packs/misc/station_goal/dna_probes]"]
+ P = SSeconomy.supply_packs["[/datum/supply_packs/misc/station_goal/dna_probes]"]
P.special_enabled = TRUE
/datum/station_goal/dna_vault/check_completion()
diff --git a/code/modules/station_goals/shield.dm b/code/modules/station_goals/shield.dm
index 24f88699a64..7baa35a3b1a 100644
--- a/code/modules/station_goals/shield.dm
+++ b/code/modules/station_goals/shield.dm
@@ -13,10 +13,10 @@
/datum/station_goal/station_shield/on_report()
//Unlock
- var/datum/supply_packs/P = SSshuttle.supply_packs["[/datum/supply_packs/misc/station_goal/shield_sat]"]
+ var/datum/supply_packs/P = SSeconomy.supply_packs["[/datum/supply_packs/misc/station_goal/shield_sat]"]
P.special_enabled = TRUE
- P = SSshuttle.supply_packs["[/datum/supply_packs/misc/station_goal/shield_sat_control]"]
+ P = SSeconomy.supply_packs["[/datum/supply_packs/misc/station_goal/shield_sat_control]"]
P.special_enabled = TRUE
/datum/station_goal/station_shield/check_completion()
diff --git a/code/modules/store/store.dm b/code/modules/store/store.dm
deleted file mode 100644
index 7c410744962..00000000000
--- a/code/modules/store/store.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-/*****************************
- * /vg/station In-Game Store *
- *****************************
-
-By Nexypoo
-
-The idea is to give people who do their jobs a reward.
-
-Ideally, these items should be cosmetic in nature to avoid fucking up round balance.
-People joining the round get between $100 and $500. Keep this in mind.
-
-Money should not persist between rounds, although a "bank" system to voluntarily store
-money between rounds might be cool. It'd need to be a bit volatile: perhaps completing
-job objectives = good stock market, shitty job objective completion = shitty economy.
-
-Goal for now is to get the store itself working, however.
-*/
-
-GLOBAL_DATUM_INIT(centcomm_store, /datum/store, new())
-
-/datum/store
- var/list/datum/storeitem/items=list()
- var/list/datum/storeorder/orders=list()
-
- var/obj/machinery/computer/account_database/linked_db
-
-/datum/store/New()
- for(var/itempath in subtypesof(/datum/storeitem))
- items += new itempath()
-
-/datum/store/proc/charge(datum/mind/mind, amount, datum/storeitem/item)
- if(!mind.initial_account)
- //testing("No initial_account")
- return 0
- if(mind.initial_account.money < amount)
- //testing("Not enough cash")
- return 0
- mind.initial_account.money -= amount
- var/datum/transaction/T = new()
- T.target_name = "NAS Trurl Merchandising"
- T.purpose = "Purchase of [item.name]"
- T.amount = -amount
- T.date = GLOB.current_date_string
- T.time = station_time_timestamp()
- T.source_terminal = "\[CLASSIFIED\] Terminal #[rand(111,333)]"
- mind.initial_account.transaction_log.Add(T)
- return 1
-
-/datum/store/proc/reconnect_database()
- for(var/obj/machinery/computer/account_database/DB in GLOB.machines)
- if(is_station_level(DB.z))
- linked_db = DB
- break
-
-/datum/store/proc/PlaceOrder(mob/living/user, itemID)
- // Get our item, first.
- var/datum/storeitem/item = items[itemID]
- if(!item)
- return FALSE
-
- // Try to deduct funds.
- if(!charge(user.mind,item.cost,item))
- return FALSE
-
- // Give them the item.
- item.deliver(user)
- return TRUE
diff --git a/code/modules/supply/supply_README.md b/code/modules/supply/supply_README.md
new file mode 100644
index 00000000000..e2fb45860a6
--- /dev/null
+++ b/code/modules/supply/supply_README.md
@@ -0,0 +1,15 @@
+## IMPORTANT SUPPLY PACKS NOTES
+
+### Pack Parameters
+* only secure crate types use the access var (and are lockable)
+* hidden packs only show up when the computer has been hacked.
+* Contraband is obtainable through modified supplycomp circuitboards.
+
+### Bug Prevention
+* Don't add living things to crates, that's bad, it will break the shuttle.
+* Do NOT set the price of any crates below 7 points. Doing so allows infinite points.
+
+### Pack Organization
+* Anytime a supply pack is ordered, it needs to be put in the dm file that matches its type
+* Anytime a supply pack category becomes overloaded (think 20+ packs) you should consider splitting it into multiple categories
+* Avoid throwing things in the miscellanious category unless it really doesn't have a place elsewhere
diff --git a/code/modules/supply/supply_console.dm b/code/modules/supply/supply_console.dm
new file mode 100644
index 00000000000..d99fabbecf7
--- /dev/null
+++ b/code/modules/supply/supply_console.dm
@@ -0,0 +1,491 @@
+#define MULTIPLE_CRATE_MAX 10
+
+/obj/machinery/computer/supplycomp
+ name = "Supply Shuttle Console"
+ desc = "Used to order supplies."
+ icon_screen = "supply"
+ req_access = list(ACCESS_CARGO)
+ circuit = /obj/item/circuitboard/supplycomp
+
+ /// Is this a public console (Confirm + Shuttle controls are not visible)
+ var/is_public = FALSE
+ /// Time of last request
+ var/reqtime = 0
+ /// Can we order special supplies
+ var/hacked = FALSE
+ /// Can we order contraband
+ var/can_order_contraband = FALSE
+ ///the economy database this machine is connected to
+ var/datum/money_account_database/main_station/account_database
+ ///the department account tethered to this supply console
+ var/datum/money_account/cargo_account
+
+/obj/machinery/computer/supplycomp/Initialize(mapload)
+ . = ..()
+ reconnect_database()
+
+/obj/machinery/computer/supplycomp/proc/reconnect_database()
+ account_database = GLOB.station_money_database
+ cargo_account = account_database.get_account_by_department(DEPARTMENT_SUPPLY)
+
+/obj/machinery/computer/supplycomp/attack_ai(mob/user)
+ return attack_hand(user)
+
+/obj/machinery/computer/supplycomp/attack_hand(mob/user)
+ if(!allowed(user) && !isobserver(user))
+ to_chat(user, "Access denied.")
+ return TRUE
+
+ ui_interact(user)
+
+/obj/machinery/computer/supplycomp/proc/has_qm_access(list/access)
+ return (ACCESS_QM in access) ? TRUE : FALSE
+
+/obj/machinery/computer/supplycomp/proc/is_authorized(mob/user)
+ if(allowed(user))
+ return TRUE
+ if(user.can_admin_interact())
+ return TRUE
+ return FALSE
+
+/obj/machinery/computer/supplycomp/proc/get_supply_group_name(cat)
+ switch(cat)
+ if(SUPPLY_EMERGENCY)
+ return "Emergency"
+ if(SUPPLY_SECURITY)
+ return "Security"
+ if(SUPPLY_ENGINEER)
+ return "Engineering"
+ if(SUPPLY_MEDICAL)
+ return "Medical"
+ if(SUPPLY_SCIENCE)
+ return "Science"
+ if(SUPPLY_ORGANIC)
+ return "Food and Livestock"
+ if(SUPPLY_MATERIALS)
+ return "Raw Materials"
+ if(SUPPLY_MISC)
+ return "Miscellaneous"
+ if(SUPPLY_VEND)
+ return "Vending"
+
+/obj/machinery/computer/supplycomp/proc/build_request_data(mob/user)
+ var/list/requests = list()
+
+ var/is_silicon = issilicon(user)
+ var/obj/item/card/id/C
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ C = H.get_idcard(TRUE)
+
+ for(var/datum/supply_order/order as anything in SSeconomy.request_list)
+ //can this specific order request be approved the the mob user?
+ var/can_approve = FALSE
+ //can this specific order request be denied the the mob user?
+ var/can_deny = FALSE
+ if(C && order.orderedby == C.registered_name)
+ can_deny = TRUE //if it's your crate, you can deny it
+ if(is_silicon) //robots and admins can do whatever they want
+ can_approve = TRUE
+ can_deny = TRUE
+ if(order.requires_qm_approval)
+ if(C && has_qm_access(C.access)) //if the crate needs QM approval and you have QM access, you get app and deny rights
+ can_approve = TRUE
+ can_deny = TRUE
+ else if(order.requires_head_approval)
+ if(C && order.ordered_by_department.has_account_access(C.access, GLOB.station_money_database.find_user_account(C.associated_account_number)))
+ can_approve = TRUE //if the crate DOESN'T need QM approval (or QM already approved it), you get app and deny rights
+ can_deny = TRUE
+ if(C && has_qm_access(C.access))
+ can_deny = TRUE //QM can deny any order at any time
+
+ var/list/request_data = list(
+ "ordernum" = order.ordernum,
+ "supply_type" = order.object.name,
+ "orderedby" = order.orderedby,
+ "department" = order.ordered_by_department?.department_name,
+ "cost" = order.object.cost,
+ "comment" = order.comment,
+ "req_qm_approval" = order.requires_qm_approval,
+ "req_head_approval" = order.requires_head_approval,
+ "can_approve" = can_approve,
+ "can_deny" = can_deny
+ )
+ //The way approval rights is determined
+ //If a crate requires QM approval and head approval - Only the QM can approve it for now, heads can still deny it at this point however
+ //If a crate requires head approval - They can approve it as long as they have department account access and the crate doesn't still need QM approval
+ requests += list(request_data)
+ return requests
+
+/obj/machinery/computer/supplycomp/proc/get_accounts(mob/user)
+ if(!ishuman(user))
+ if(isobserver(user) && is_admin(user))
+ //if an admin is accessing, return all department accounts
+ return account_database.get_all_department_accounts()
+ return list(cargo_account) //else only the cargo account
+
+ var/mob/living/carbon/human/H = user
+ var/obj/item/card/id/C = H.get_idcard(TRUE)
+ var/list/accounts = list()
+ if(C) //if human and has ID, get accounts associated with crew members assigned role
+ var/datum/money_account/customer_account = C.get_card_account()
+ if(customer_account) //get customers account and make it the fist entry
+ accounts += customer_account
+ var/list/department_accounts = list() //time to get department accounts
+ if(ACCESS_CAPTAIN in C.access)
+ //if captain access, get all accounts
+ department_accounts = account_database.get_all_department_accounts()
+ else
+ //otherwise just get crew members specific departments
+ var/list/departments = C.get_departments() //will return up to 2 departments
+ for(var/datum/station_department/department in departments)
+ accounts += department.department_account
+ if(length(department_accounts))
+ accounts += department_accounts
+ if(!length(accounts) || !(cargo_account in accounts))
+ accounts += cargo_account
+ return accounts
+
+/obj/machinery/computer/supplycomp/proc/build_account_data(mob/user)
+ var/list/departments = get_accounts(user)
+ var/list/accounts = list()
+ for(var/datum/money_account/account as anything in departments)
+ var/list/account_data = list(
+ "name" = account.account_name,
+ "balance" = account.credit_balance,
+ "account_UID" = account.UID(),
+ )
+ accounts += list(account_data)
+ return accounts
+
+/obj/machinery/computer/supplycomp/proc/build_shopping_list_data()
+ var/list/orders = list()
+ for(var/datum/supply_order/order as anything in SSeconomy.shopping_list)
+ var/list/order_data = list(
+ "ordernum" = order.ordernum,
+ "supply_type" = order.object.name,
+ "orderedby" = order.orderedby,
+ "comment" = order.comment
+ )
+ orders += list(order_data)
+ return orders
+
+/obj/machinery/computer/supplycomp/proc/build_shipment_list_data()
+ var/list/orders = list()
+ for(var/datum/supply_order/order as anything in SSeconomy.delivery_list)
+ var/list/order_data = list(
+ "ordernum" = order.ordernum,
+ "supply_type" = order.object.name,
+ "orderedby" = order.orderedby,
+ "comment" = order.comment
+ )
+ orders += list(order_data)
+ return orders
+
+/obj/machinery/computer/supplycomp/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+ if(!cargo_account || !account_database)
+ reconnect_database()
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "CargoConsole", name, 900, 800, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/supplycomp/ui_data(mob/user)
+ var/list/data = list()
+
+ data["requests"] = build_request_data(user)
+ data["accounts"] = build_account_data(user)
+ data["orders"] = build_shopping_list_data()
+ data["shipments"] = build_shipment_list_data()
+ data["is_public"] = is_public
+ data["moving"] = SSshuttle.supply.mode != SHUTTLE_IDLE
+ data["at_station"] = SSshuttle.supply.getDockedId() == "supply_home"
+ data["timeleft"] = SSshuttle.supply.timeLeft(60 SECONDS)
+ data["can_launch"] = !SSshuttle.supply.canMove()
+
+ return data
+
+/obj/machinery/computer/supplycomp/ui_static_data(mob/user)
+ var/list/static_data = list()
+ var/list/packs_list = list()
+
+ for(var/set_name in SSeconomy.supply_packs)
+ var/datum/supply_packs/pack = SSeconomy.supply_packs[set_name]
+ if((pack.hidden && hacked) || (pack.contraband && can_order_contraband) || (pack.special && pack.special_enabled) || (!pack.contraband && !pack.hidden && !pack.special))
+ packs_list.Add(list(list(
+ "name" = pack.name,
+ "cost" = pack.cost,
+ "ref" = "[pack.UID()]",
+ "contents" = pack.ui_manifest,
+ "cat" = pack.group)))
+
+ static_data["supply_packs"] = packs_list
+
+ var/list/categories = list()
+ for(var/category in SSeconomy.all_supply_groups)
+ categories.Add(list(list("name" = get_supply_group_name(category), "category" = category)))
+
+ static_data["categories"] = categories
+
+ return static_data
+
+
+
+/obj/machinery/computer/supplycomp/ui_act(action, list/params, datum/tgui/ui)
+ if(..())
+ return
+
+ var/mob/user = ui.user
+
+ // If its not a public console, and they aint authed, dont let them use this
+ if(!is_public && !is_authorized(user))
+ return
+
+ . = TRUE
+ add_fingerprint(user)
+
+ switch(action)
+ if("moveShuttle")
+ move_shuttle(user)
+ if("order")
+ if(world.time < reqtime)
+ visible_message("[src]'s monitor flashes, \"[world.time - reqtime] seconds remaining until another requisition form may be printed.\"")
+ return
+ var/amount = 1
+ if(params["multiple"] == "1") // 1 is a string here. DO NOT MAKE THIS A BOOLEAN YOU DORK
+ var/num_input = input(user, "Amount", "How many crates? ([MULTIPLE_CRATE_MAX] Max)") as null|num
+ if(!num_input || (!is_public && !is_authorized(user)) || ..()) // Make sure they dont walk away
+ return
+ amount = clamp(round(num_input), 1, MULTIPLE_CRATE_MAX)
+ var/datum/supply_packs/P = locateUID(params["crate"])
+ if(!istype(P))
+ return
+ var/timeout = world.time + (60 SECONDS) // If you dont type the reason within a minute, theres bigger problems here
+ var/reason = input(user, "Reason", "Why do you require this item?","") as null|text
+ if(world.time > timeout || !reason || (!is_public && !is_authorized(user)) || ..())
+ // Cancel if they take too long, they dont give a reason, they aint authed, or if they walked away
+ return
+ reason = sanitize(copytext(reason, 1, MAX_MESSAGE_LEN))
+
+ //===orderee identification information===
+ var/idname = "*None Provided*"
+ var/idrank = "*None Provided*"
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ idname = H.get_authentification_name()
+ idrank = H.get_assignment()
+ else if(issilicon(user))
+ idname = user.real_name
+
+ //===orderee account information===
+ var/datum/money_account/selected_account = locateUID(params["account"])
+ for(var/i in 1 to amount)
+ var/datum/supply_order/order = SSeconomy.generate_supply_order(params["crate"], idname, idrank, reason)
+ order_crate(user, order, selected_account)
+ if(i == 1)
+ playsound(loc, 'sound/machines/ping.ogg', 15, 0)
+ to_chat(user, "Order Sent.")
+ generate_requisition_paper(order, amount)
+
+ if("approve")
+ var/ordernum = text2num(params["ordernum"])
+ if(!ordernum)
+ return
+ approve_crate(user, ordernum)
+ if("deny")
+ var/ordernum = text2num(params["ordernum"])
+ if(!ordernum)
+ return
+ deny_crate(user, ordernum)
+
+
+ // Popup to show CC message logs. Its easier this way to avoid box-spam in TGUI
+ if("showMessages")
+ // Public consoles cant view messages
+ if(is_public)
+ return
+ var/datum/browser/ccmsg_browser = new(user, "ccmsg", "Central Command Cargo Message Log", 800, 600)
+ ccmsg_browser.set_content(SSeconomy.centcom_message)
+ ccmsg_browser.open()
+
+/obj/machinery/computer/supplycomp/proc/order_crate(mob/user, datum/supply_order/order, datum/money_account/account)
+ var/datum/money_account/selected_account = account
+ if(!istype(selected_account)) //if no account found, just default to cargo account
+ CRASH("order_crate called without a specified money account")
+ if(selected_account.account_type == ACCOUNT_TYPE_DEPARTMENT)
+ for(var/datum/station_department/department in SSjobs.station_departments)
+ if(department.department_account == selected_account)
+ order.ordered_by_department = department //now that we know which department this is for, attach it to the order
+ order.orderedbyaccount = selected_account
+ order.requires_head_approval = TRUE
+
+ if(length(order.object.department_restrictions) && !(department.department_name in order.object.department_restrictions))
+ //this crate has a department whitelist description
+ //this department is not in this whitelist, require QM approval
+ order.requires_qm_approval = TRUE
+ break
+ else if(selected_account.account_type == ACCOUNT_TYPE_PERSONAL && length(order.object.department_restrictions))
+ order.requires_qm_approval = TRUE
+
+ //===Handle Supply Order===
+ if(selected_account.account_type == ACCOUNT_TYPE_PERSONAL)
+ //if the account is a personal account (and doesn't require QM approval), go ahead and pay for it now
+ order.orderedbyaccount = selected_account
+ if(attempt_account_authentification(selected_account, user))
+ var/paid_for = FALSE
+ if(!order.requires_qm_approval && pay_with_account(selected_account, order.object.cost, "[order.object.name] Crate Purchase", "Cargo Requests Console", user, account_database.vendor_account))
+ paid_for = TRUE
+ SSeconomy.process_supply_order(order, paid_for) //add order to shopping list
+ else //if its a department account with pin or higher security or need QM approval, go ahead and add this to the departments section in request list
+ SSeconomy.process_supply_order(order, FALSE)
+ if(order.ordered_by_department.crate_auto_approve && order.ordered_by_department.auto_approval_cap >= order.object.cost)
+ approve_crate(user, order.ordernum)
+ investigate_log("| [key_name(user)] has placed an order for [order.object.amount] [order.object.name] with reason: '[order.comment]'", "cargo")
+
+/obj/machinery/computer/supplycomp/proc/approve_crate(mob/user, order_num)
+ for(var/datum/supply_order/department_order in SSeconomy.request_list)
+ if(department_order.ordernum != order_num)
+ continue
+ var/datum/supply_order/order = department_order
+ var/datum/supply_packs/pack = order.object
+ var/datum/money_account/account = order.orderedbyaccount
+
+ if(order.requires_qm_approval)
+ if(!has_qm_access(user.get_access()))
+ return FALSE
+ order.requires_qm_approval = FALSE
+ if(account.account_type == ACCOUNT_TYPE_PERSONAL || isnull(order.ordered_by_department))
+ if(pay_with_account(account, order.object.cost, "[pack.name] Crate Purchase", "Cargo Requests Console", user, account_database.vendor_account))
+ SSeconomy.process_supply_order(order, TRUE) //send 'er back through
+ return TRUE
+ atom_say("ERROR: Account tied to order cannot pay, auto-denying order")
+ SSeconomy.request_list -= order //just remove order at this poin
+ else
+ return TRUE
+ return TRUE
+
+ if(order.requires_head_approval)
+ //if they do not have access to this account
+ if(!department_order.ordered_by_department.has_account_access(user.get_access(), user.get_worn_id_account()))
+ //and the dept account doesn't have auto approve enabled (or does and the crate is too expensive for auto approve)
+ if(!department_order.ordered_by_department.crate_auto_approve || department_order.ordered_by_department.auto_approval_cap < pack.cost)
+ return //no access!
+
+ ///just give the account pin here, its too much work for players to get the department account pin number since approval is access locked anyway
+ if(attempt_account_authentification(account, user, account.account_pin))
+ if(pay_with_account(account, pack.cost, "[pack.name] Crate Purchase", "[src]", user, account_database.vendor_account))
+ order.requires_head_approval = FALSE
+ SSeconomy.process_supply_order(order, TRUE)
+ investigate_log("| [key_name(user)] has authorized an order for [pack.name]. Remaining Cargo Balance: [cargo_account.credit_balance].", "cargo")
+ SSblackbox.record_feedback("tally", "cargo_shuttle_order", 1, pack.name)
+ else
+ atom_say("ERROR: Account tied to order cannot pay, auto-denying order")
+ SSeconomy.request_list -= order
+ break
+
+/obj/machinery/computer/supplycomp/proc/deny_crate(mob/user, order_num)
+ var/obj/item/card/id/C
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ C = H.get_idcard(TRUE)
+ for(var/datum/supply_order/department_order as anything in SSeconomy.request_list)
+ if(department_order.ordernum != order_num)
+ continue
+ var/datum/supply_order/order = department_order
+ // allow cancelling of our own orders
+ if(C && order.orderedby == C.registered_name)
+ SSeconomy.request_list -= order
+ return
+ // If we arent public, were cargo access. CANCELLATIONS FOR EVERYONE
+ if(order.requires_qm_approval && (ACCESS_QM in C.access))
+ SSeconomy.request_list -= order
+ else if(order.requires_head_approval && (order.ordered_by_department.has_account_access(C.access)))
+ SSeconomy.request_list -= order
+ else
+ return //how did we get here?
+ investigate_log("| [key_name(user)] has denied an order for [order.object.name].", "cargo")
+ break
+
+/obj/machinery/computer/supplycomp/proc/move_shuttle(mob/user)
+ if(is_public) // Public consoles cant move the shuttle. Dont allow exploiters.
+ return
+ if(SSshuttle.supply.canMove())
+ to_chat(user, "For safety reasons the automated supply shuttle cannot transport live organisms, classified nuclear weaponry or homing beacons.")
+ else if(SSshuttle.supply.getDockedId() == "supply_home")
+ SSshuttle.toggleShuttle("supply", "supply_home", "supply_away", 1)
+ investigate_log("| [key_name(user)] has sent the supply shuttle away. Shuttle contents: [SSeconomy.sold_atoms]", "cargo")
+ else
+ SSshuttle.supply.request(SSshuttle.getDock("supply_home"))
+
+/obj/machinery/computer/supplycomp/proc/pay_for_crate(datum/money_account/customer_account, mob/user, datum/supply_order/order)
+ if(pay_with_account(customer_account, order.object.cost, "Purchase of [order.object.name]", "Cargo Requests Console", user, cargo_account, TRUE))
+ return TRUE
+ return FALSE
+
+/obj/machinery/computer/supplycomp/proc/attempt_account_authentification(datum/money_account/customer_account, mob/user, pin)
+ if(customer_account.security_level > ACCOUNT_SECURITY_RESTRICTED)
+ return FALSE
+ var/attempt_pin = pin
+ if(customer_account.security_level != ACCOUNT_SECURITY_ID && !attempt_pin)
+ //if pin is not given, we'll prompt them here
+ attempt_pin = input("Enter pin code", "Vendor transaction") as num
+ if(!Adjacent(user))
+ return FALSE
+ var/is_admin = is_admin(user)
+ if(!account_database.try_authenticate_login(customer_account, attempt_pin, TRUE, FALSE, is_admin))
+ to_chat(user, "Unable to access account: incorrect credentials.")
+ return FALSE
+ return TRUE
+
+/obj/machinery/computer/supplycomp/proc/pay_with_account(datum/money_account/customer_account, amount, purpose, transactor, mob/user, datum/money_account/target)
+ if(!customer_account)
+ to_chat(user, "Error: Unable to access account. Please contact technical support if problem persists.")
+ return FALSE
+ if(customer_account.suspended)
+ to_chat(user, "Unable to access account: account suspended.")
+ return FALSE
+ if(!account_database.charge_account(customer_account, amount, purpose, transactor, allow_overdraft = FALSE, supress_log = FALSE))
+ to_chat(user, "Unable to complete transaction: account has insufficient credit balance to purchase this.")
+ return FALSE
+ account_database.credit_account(target, amount, purpose, transactor, FALSE)
+ return TRUE
+
+/obj/machinery/computer/supplycomp/proc/generate_requisition_paper(datum/supply_order/order, amount)
+
+ var/obj/item/paper/request_form/reqform = new(get_turf(src))
+ playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1)
+ reqform.name = "Requisition Form - [amount] '[order.object.name]' for [order.orderedby]"
+ reqform.info += "
[station_name()] Supply Requisition Form
"
+ reqform.info += "INDEX: #[order.ordernum] "
+ reqform.info += "REQUESTED BY: [order.orderedby] "
+ reqform.info += "RANK: [order.orderedbyRank] "
+ reqform.info += "REASON: [order.comment] "
+ reqform.info += "SUPPLY CRATE TYPE: [order.object.name] "
+ reqform.info += "NUMBER OF CRATES: [amount] "
+ reqform.info += "ACCESS RESTRICTION: [order.object.access ? get_access_desc(order.object.access) : "None"] "
+ reqform.info += "CONTENTS: "
+ reqform.info += order.object.manifest
+ reqform.info += ""
+ reqform.info += "STAMP BELOW TO APPROVE THIS REQUISITION: "
+
+ reqform.order_number = order.ordernum
+
+ reqform.update_icon(UPDATE_ICON_STATE) //Fix for appearing blank when printed.
+
+ return reqform
+
+/obj/machinery/computer/supplycomp/emag_act(mob/user)
+ if(!hacked)
+ to_chat(user, "Special supplies unlocked.")
+ hacked = TRUE
+
+/obj/machinery/computer/supplycomp/public
+ name = "Supply Ordering Console"
+ desc = "Used to order supplies from cargo staff."
+ icon = 'icons/obj/computer.dmi'
+ icon_screen = "request"
+ circuit = /obj/item/circuitboard/ordercomp
+ req_access = list()
+ is_public = TRUE
+
+#undef MULTIPLE_CRATE_MAX
diff --git a/code/modules/supply/supply_order.dm b/code/modules/supply/supply_order.dm
new file mode 100644
index 00000000000..75fcff949eb
--- /dev/null
+++ b/code/modules/supply/supply_order.dm
@@ -0,0 +1,99 @@
+/datum/supply_order
+ ///The orders ID number
+ var/ordernum
+ ///The supply pack this order is for
+ var/datum/supply_packs/object
+ ///The person who ordered the supply order
+ var/orderedby
+ ///The occupation/assignment of the person who ordered the supply order
+ var/orderedbyRank
+ ///The account tied to this order, important for personal orders
+ var/orderedbyaccount
+ ///The station department datum of the person's department who ordered the supply order
+ var/datum/station_department/ordered_by_department
+ ///Reason/purpose for order given by orderer
+ var/comment
+ ///does this order need to be approve by the department head?
+ var/requires_head_approval = FALSE
+ ///does this order need to be approve by the QM?
+ var/requires_qm_approval = FALSE
+
+/obj/item/paper/request_form
+ name = "request form"
+ var/order_number
+
+/datum/supply_order/proc/createObject(atom/_loc, errors = 0)
+ if(!object)
+ return
+
+ //create the crate
+ var/obj/structure/closet/crate/crate = new object.containertype(_loc)
+ crate.name = "[object.containername] [comment ? "([comment])":"" ]"
+ if(object.access)
+ crate.req_access = list(text2num(object.access))
+
+ //create the manifest slip
+ var/obj/item/paper/manifest/slip = new
+ slip.points = object.cost
+ slip.ordernumber = ordernum
+
+ var/stationName = station_name()
+ var/packagesAmt = length(SSeconomy.shopping_list)
+
+ slip.name = "Shipping Manifest - '[object.name]' for [orderedby]"
+ slip.info = "
"
+
+ //we now create the actual contents
+ var/list/contains = list()
+ if(istype(object, /datum/supply_packs/misc/randomised))
+ var/datum/supply_packs/misc/randomised/SO = object
+ contains = list()
+ if(length(object.contains))
+ for(var/j in 1 to SO.num_contained)
+ contains += pick(object.contains)
+ else
+ contains = object.contains
+
+ for(var/typepath in contains)
+ if(!typepath)
+ continue
+ var/atom/A = new typepath(crate)
+ if(object.amount && A.vars.Find("amount") && A:amount)
+ A:amount = object.amount
+ slip.info += "
[A.name]
" //add the item to the manifest (even if it was misplaced)
+
+ if(istype(crate, /obj/structure/closet/critter)) // critter crates do not actually spawn mobs yet and have no contains var, but the manifest still needs to list them
+ var/obj/structure/closet/critter/CritCrate = crate
+ if(CritCrate.content_mob)
+ var/mob/crittername = CritCrate.content_mob
+ slip.info += "
[initial(crittername.name)]
"
+
+ //manifest finalisation
+ slip.info += "
"
+ slip.info += "CHECK CONTENTS AND STAMP BELOW THE LINE TO CONFIRM RECEIPT OF GOODS" // And now this is actually meaningful.
+ slip.loc = crate
+ if(istype(crate, /obj/structure/closet/crate))
+ var/obj/structure/closet/crate/CR = crate
+ CR.manifest = slip
+ CR.update_icon()
+ CR.announce_beacons = object.announce_beacons.Copy()
+ if(istype(crate, /obj/structure/largecrate))
+ var/obj/structure/largecrate/LC = crate
+ LC.manifest = slip
+ LC.update_icon()
+
+ return crate
+
+/obj/item/paper/manifest
+ name = "supply manifest"
+ var/points = 0
+ var/ordernumber = 0
diff --git a/code/modules/supply/supply_pack.dm b/code/modules/supply/supply_pack.dm
new file mode 100644
index 00000000000..8e8e07e78d9
--- /dev/null
+++ b/code/modules/supply/supply_pack.dm
@@ -0,0 +1,34 @@
+/datum/supply_packs
+ var/name
+ var/list/contains = list()
+ var/manifest = ""
+ var/amount
+ var/cost //default amount to cover crate cost?
+ var/containertype = /obj/structure/closet/crate
+ var/containername
+ var/access
+ var/hidden = FALSE
+ var/contraband = FALSE
+ var/group = SUPPLY_MISC
+ ///Determines which departments do not need QM approval to order this supply pack
+ var/list/department_restrictions = list()
+
+ var/list/announce_beacons = list() // Particular beacons that we'll notify the relevant department when we reach
+
+ var/special = FALSE //Event/Station Goals/Admin enabled packs
+ var/special_enabled = FALSE
+
+ /// List of names for being done in TGUI
+ var/list/ui_manifest = list()
+
+/datum/supply_packs/New()
+ . = ..()
+ manifest += "