// Config Defines #define SUPPLY_DOCKZ 1 // Z-level of the dock. #define SUPPLY_STATIONZ 6 // Z-level of the main map. #define SUPPLY_STATION_AREATYPE /area/supply/station // Type of the cargo elevator area for station. #define SUPPLY_DOCK_AREATYPE /area/supply/dock // Type of the cargo elevator area for dock. SUBSYSTEM_DEF(cargo) name = "Cargo" wait = 30 SECONDS flags = SS_NO_FIRE init_order = SS_INIT_CARGO // Shipment Variables and Lists var/shipmentnum var/list/cargo_shipments = list() // List of the shipments to the main map. var/datum/cargo_shipment/current_shipment = null // The current cargo shipment. var/list/queued_mining_equipment = list() // Order Variables and Lists var/ordernum var/list/cargo_items = list() // The list of items. var/list/cargo_categories = list() // The list of categories. var/list/cargo_suppliers = list() // The list of suppliers. var/list/all_orders = list() // All orders. // Fee Variables var/credits_per_crate = 15 // "Cost / Payment" per crate shipped from or to centcomm. var/credits_per_platinum = 140 // Per sheet. var/credits_per_phoron = 100 // Per sheet. var/cargo_handlingfee = 5 // The handling fee cargo takes per crate, as a percentage of the order's value. var/cargo_handlingfee_min = 0 // The minimum handling fee. var/cargo_handlingfee_max = 500 // The maximum handling fee. var/cargo_handlingfee_change = 1 // If the handling fee can be changed -> for a random event. var/datum/money_account/supply_account // Elevator Movement Variables var/movetime = 300 var/min_movetime = 300 var/max_movetime = 2400 var/datum/shuttle/autodock/ferry/supply/shuttle // Item Variables var/last_item_id = 0 // The ID of the last item that has been added. /// Indicates if orders have been dumped. var/dumped_orders = FALSE // Exports and Bounties var/list/exports_list = list() var/list/bounties_list = list() /datum/controller/subsystem/cargo/Recover() src.shuttle = SScargo.shuttle src.cargo_items = SScargo.cargo_items src.cargo_categories = SScargo.cargo_categories src.cargo_shipments = SScargo.cargo_shipments src.all_orders = SScargo.all_orders src.cargo_handlingfee = SScargo.cargo_handlingfee src.cargo_handlingfee_change = SScargo.cargo_handlingfee_change src.supply_account = SScargo.supply_account src.last_item_id = SScargo.last_item_id src.exports_list = SScargo.exports_list src.bounties_list = SScargo.bounties_list /datum/controller/subsystem/cargo/Initialize(timeofday) // Generate the order number and shipment number to start with. ordernum = rand(1, 8000) shipmentnum = rand(500, 700) supply_account = SSeconomy.get_department_account("Operations") // Load in the cargo items config. load_cargo_files() setupExports() setupBounties() // Spawn in the warehouse objects and items. var/datum/cargospawner/spawner = new spawner.start() qdel(spawner) return SS_INIT_SUCCESS /* Loading Data */ // Reset cargo to prep for loading in new items. /datum/controller/subsystem/cargo/proc/reset_cargo() cargo_shipments = list() // List of the shipments to the main map. current_shipment = null // The current cargo shipment. all_orders = list() // All orders. last_item_id = 0 // Load Categories /datum/controller/subsystem/cargo/proc/load_cargo_categories() for(var/singleton/cargo_category/C as anything in (GET_SINGLETON_SUBTYPE_LIST(/singleton/cargo_category))) log_subsystem_cargo("Loading category '[C.name]'.") SScargo.cargo_categories[C.name] = C // Load Suppliers /datum/controller/subsystem/cargo/proc/load_cargo_suppliers() for(var/singleton/cargo_supplier/S as anything in (GET_SINGLETON_SUBTYPE_LIST(/singleton/cargo_supplier))) log_subsystem_cargo("Loading supplier '[S.name]', with short name '[S.short_name]'.") S.price_modifier = S.get_total_price_coefficient() SScargo.cargo_suppliers[S.short_name] = S /datum/controller/subsystem/cargo/proc/load_cargo_items() log_subsystem_cargo("Loading cargo items.") var/id = 1 reset_cargo() // Get the list of all valid cargo items. for(var/singleton/cargo_item/I as anything in (GET_SINGLETON_SUBTYPE_LIST(/singleton/cargo_item))) cargo_items[I.name] = I I.id = id++ I.get_adjusted_price() // Check if the category exists in "SScargo.cargo_categories". if(!(I.category || SScargo.cargo_categories[I.category])) log_subsystem_cargo("Error: Unable to find category '[I.category]' for item '[I.name]. Skipping.") continue else var/singleton/cargo_category/item_category = SScargo.cargo_categories[I.category] if(!item_category.items) item_category.items = list() item_category.items += I log_subsystem_cargo("Inserted item '[I.name]' into category '[I.category]' with ID '[I.id]'.") if(!(I.supplier || SScargo.cargo_suppliers[I.supplier])) log_subsystem_cargo("Error: Unable to find supplier '[I.supplier]' for item '[I.name]. Skipping.") continue else var/singleton/cargo_supplier/item_supplier = SScargo.cargo_suppliers[I.supplier] if(!item_supplier.items) item_supplier.items = list() item_supplier.items += I I.supplier_data = item_supplier log_subsystem_cargo("Finished loading cargo items.") // Load cargo data from "cargo_items.dm". /datum/controller/subsystem/cargo/proc/load_cargo_files() log_subsystem_cargo("Starting to load cargo data from files.") // Reset the loaded cargo data. reset_cargo() // Add categories. load_cargo_categories() // Load suppliers. load_cargo_suppliers() // Load cargo items. load_cargo_items() log_subsystem_cargo("Finished loading cargo data from files.") /* Getting Items, Categories, Suppliers, and Shipments */ /datum/controller/subsystem/cargo/proc/get_order_count() return all_orders.len // Returns the order ID and increments it. /datum/controller/subsystem/cargo/proc/get_next_order_id() . = ordernum ordernum++ return . // Increments the item ID and returns it. /datum/controller/subsystem/cargo/proc/get_next_item_id() last_item_id++ return last_item_id /// Returns a default cargo_category for usage in situations where one is needed. /datum/controller/subsystem/cargo/proc/get_default_category() var/list/categories = GET_SINGLETON_SUBTYPE_LIST(/singleton/cargo_category) var/singleton/cargo_category/default_cat = categories[1] log_subsystem_cargo("Default category set to '[initial(default_cat.name)]'.") return initial(default_cat.name) /// Returns a string-formatted list of data for all suppliers and their information for use in TGUI. /datum/controller/subsystem/cargo/proc/get_supplier_data(var/supplier_short_name) var/list/supplier_data = list() var/singleton/cargo_supplier/S = cargo_suppliers[supplier_short_name] if(!S) log_subsystem_cargo("Error: Unable to find supplier '[supplier_short_name]'.") return supplier_data += list( "short_name" = S.short_name, "name" = S.name, "description" = S.description, "tag_line" = S.tag_line, "shuttle_time" = S.shuttle_time, "shuttle_price" = S.shuttle_price, "available" = S.available, "price_modifier" = S.price_modifier, ) return supplier_data /// Returns a string-formatted list of every item in each category and their information for use in TGUI. /datum/controller/subsystem/cargo/proc/get_items_for_category(var/category_name) var/list/item_list = list() var/singleton/cargo_category/C = cargo_categories[category_name] if(!C) log_subsystem_cargo("Error: get_items_for_category() was unable to find category '[category_name]'.") return for(var/singleton/cargo_item/ci as anything in C.items) item_list += list(list( "name" = ci.name, "description" = ci.description, "price" = ci.price, "id" = ci.id, "price_adjusted" = ci.adjusted_price, "supplier" = ci.supplier, "supplier_data" = get_supplier_data(ci.supplier), "access" = ci.access != 0 ? get_access_desc(ci.access) : "none", )) return item_list /// Returns a string-formatted list of categories for use in TGUI. /datum/controller/subsystem/cargo/proc/get_category_list() var/list/category_list = list() for (var/cat_name in cargo_categories) // Get the singleton instance for the current category. var/singleton/cargo_category/cc = cargo_categories[cat_name] category_list += list(list( "name" = cc.name, "display_name" = cc.display_name, "description" = cc.description, "icon" = cc.icon, "price_modifier" = cc.price_modifier, )) return category_list // Get category names. /datum/controller/subsystem/cargo/proc/get_category_by_name(var/name) // Check if the category exists in cargo_categories. if(!cargo_categories[name]) log_subsystem_cargo("Error: Requested category '[name]' does not exist.") return // Return the category if it exists. return cargo_categories[name] // Gets a order by order ID. /datum/controller/subsystem/cargo/proc/get_order_by_id(var/id) for (var/datum/cargo_order/co in all_orders) if(co.order_id == id) return co return null // Gets a supplier by name. /datum/controller/subsystem/cargo/proc/get_supplier_by_name(var/name) return cargo_suppliers[name] // Gets all the shipments sent to or from the main map. /datum/controller/subsystem/cargo/proc/get_shipment_list() var/list/shipment_list = list() for(var/datum/cargo_shipment/cs in cargo_shipments) if(cs.get_list() != null) shipment_list.Add(list(cs.get_list())) return shipment_list // Get a shipment by shipment ID. /datum/controller/subsystem/cargo/proc/get_shipment_by_id(var/id) for(var/datum/cargo_shipment/cs in cargo_shipments) if(cs.shipment_num == id) return cs return null /* Submitting, Approving, Rejecting and Shipping Orders */ // Gets the orders based on their status (submitted, approved, shipped). /datum/controller/subsystem/cargo/proc/get_orders_by_status(status, data_list = FALSE, list/avoid) if(!status) log_subsystem_cargo("get_orders_by_status has been called with a invalid status") return list() var/list/orders = list() for (var/datum/cargo_order/co in all_orders) if(co.status == status || co.get_payment_status() == status) if(avoid && (avoid.Find(co.status) || avoid.Find(co.get_payment_status()))) continue if(data_list) orders.Add(list(co.get_list())) else orders.Add(co) return orders // Gets the value of orders based on their status, type is passed on to co.get_value. /datum/controller/subsystem/cargo/proc/get_orders_value_by_status(var/status, var/type=0) if(!status) log_subsystem_cargo("get_orders_value_by_status has been called with a invalid status") return 0 var/value = 0 for (var/datum/cargo_order/co in all_orders) if(co.status == status) value += co.get_value(type) return value // Gets the suppliers of the orders of a specific type. /datum/controller/subsystem/cargo/proc/get_order_suppliers_by_status(var/status, var/pretty_names=0) if(!status) log_subsystem_cargo("get_order_suppliers_by_status has been called with a invalid status") return list() var/list/suppliers = list() for(var/datum/cargo_order/co in all_orders) if(co.status == status) // Get the list of supplirs and add it to the suppliers list. for(var/supplier in co.get_supplier_list()) if(pretty_names) var/singleton/cargo_supplier/cs = SScargo.cargo_suppliers[supplier] suppliers[cs.short_name] = cs.name else suppliers[supplier] = supplier return suppliers // Checks if theorder can be shipped and marks it as shipped if possible. /datum/controller/subsystem/cargo/proc/ship_order(var/datum/cargo_order/co) // Get the price cargo has to pay for the order. var/item_price = co.get_value(1) // Get the maximum shipment costs for the order. var/shipment_cost = co.get_shipment_cost() // Check if cargo has enough money to pay for the shipment of the item and the maximum shipment cost. if(item_price + shipment_cost > get_cargo_money()) log_subsystem_cargo("Order could not be shipped. Insufficient money. [item_price] + [shipment_cost] > [get_cargo_money()].") return 0 co.set_shipped() current_shipment.shipment_cost_purchase += item_price // Increase the price of the shipment. current_shipment.orders.Add(co) // Add the order to the order list. return 1 // Generate a new cargo shipment. /datum/controller/subsystem/cargo/proc/new_shipment() current_shipment = new() shipmentnum ++ cargo_shipments.Add(current_shipment) current_shipment.shipment_num = shipmentnum /* Changing Settings */ // Gets the current handlingfee. /datum/controller/subsystem/cargo/proc/get_handlingfee() return cargo_handlingfee /datum/controller/subsystem/cargo/proc/get_handlingfee_cost(shipment_cost) if(shipment_cost) return (cargo_handlingfee / 100) * shipment_cost return 0 // Sets the handling fee and returns a status message. /datum/controller/subsystem/cargo/proc/set_handlingfee(var/fee) if(!fee) return "Unable to set handlingfee - Can not be NULL" if(fee < cargo_handlingfee_min) return "Unable to set handlingfee - Smaller than minimum: [cargo_handlingfee_min]" if(fee > cargo_handlingfee_max) return "Unable to set handlingfee - Higher than maximum: [cargo_handlingfee_max]" if(cargo_handlingfee_change != 1) return "Unable to set handlingfee - Changing the handlingfee is currently not possible" cargo_handlingfee = fee return //Gets the current crate fee /datum/controller/subsystem/cargo/proc/get_cratefee() return credits_per_crate /* Money Stuff */ //Gets the current money on the cargo account /datum/controller/subsystem/cargo/proc/get_cargo_money() return supply_account.money //Charges cargo. Accepts a text that should appear as charge and the numer of credits to charge /datum/controller/subsystem/cargo/proc/charge_cargo(var/charge_text, var/charge_credits) if(!supply_account) log_subsystem_cargo("Warning: Tried to charge supply account but supply acount doesnt exist") return 0 return SSeconomy.charge_to_account(supply_account.account_number, "[commstation_name()] - Operations", "[charge_text]", "[commstation_name()] - Banking System", -charge_credits) //Gets the pending shipment costs for the items that are about to be shipped to the station /datum/controller/subsystem/cargo/proc/get_pending_shipment_cost(var/status="approved") //Loop through all the orders marked as shipped and get the suppliers into a list of involved suppliers var/list/suppliers = get_order_suppliers_by_status(status) var/price = 0 for(var/supplier in suppliers) var/singleton/cargo_supplier/cs = SScargo.cargo_suppliers[supplier] if(cs) price += cs.shuttle_price return price //Gets the pending shipment time for the items that are about to be shipped to the station /datum/controller/subsystem/cargo/proc/get_pending_shipment_time(var/status="approved") //Loop through all the orders marked as shipped and get the suppliers into a list of involved suppliers var/list/suppliers = get_order_suppliers_by_status(status) var/time = 0 for(var/supplier in suppliers) var/singleton/cargo_supplier/cs = SScargo.cargo_suppliers[supplier] if(cs) time += cs.shuttle_time return time /* Shuttle Operations - Calling, Forcing, Canceling, Buying / Selling */ // Calls the shuttle and returns a status message. /datum/controller/subsystem/cargo/proc/shuttle_call(var/requester_name) if(shuttle.at_station()) if(shuttle.forbidden_atoms_check()) . = "For safety reasons the cargo elevator cannot transport live organisms, classified nuclear weaponry, nor homing beacons." else movetime = min_movetime // It always takes two minutes to get to centcomm. shuttle.launch(src) . = "Initiating cargo elevator." current_shipment.shuttle_recalled_by = requester_name else // Check if there is enough money in the cargo account for the current shipment. var/shipment_cost = get_pending_shipment_cost() if(shipment_cost > get_cargo_money()) . = "The cargo elevator could not be called due to insufficient funds. [shipment_cost] credits required." else // Create a new shipment if one does not exist already. if(!current_shipment) new_shipment() // Set the elevator movement time. current_shipment.shuttle_time = get_pending_shipment_time() + min_movetime current_shipment.shuttle_fee = shipment_cost if(current_shipment.shuttle_time < min_movetime) log_subsystem_cargo("Cargo elevator time less than [min_movetime]: [current_shipment.shuttle_time]. Setting to [min_movetime].") current_shipment.shuttle_time = min_movetime if(current_shipment.shuttle_time > max_movetime) log_subsystem_cargo("Cargo elevator time larger than [max_movetime]: [current_shipment.shuttle_time]. Setting to [max_movetime].") current_shipment.shuttle_time = max_movetime movetime = current_shipment.shuttle_time //Launch it shuttle.launch(src) . = "The cargo elevator has been called and will arrive in approximately [shuttle.launch_estimate(TRUE)] seconds." current_shipment.shuttle_called_by = requester_name // Cancels the elevator. Can return a status message. /datum/controller/subsystem/cargo/proc/shuttle_cancel() shuttle.cancel_launch(src) // Forces the eleavtor. Can return a status message. /datum/controller/subsystem/cargo/proc/shuttle_force() shuttle.force_launch(src) // To stop things being sent to centcomm which should not be sent to centcomm. Recursively checks for these types. /datum/controller/subsystem/cargo/proc/forbidden_atoms_check(atom/A) if(istype(A, /mob/living)) var/mob/living/mob_to_send = A var/mob_is_for_bounty = FALSE if(!mob_to_send.mind) for(var/bounty in bounties_list) var/datum/bounty/B = bounty if(B.applies_to(mob_to_send)) mob_is_for_bounty = TRUE if(!mob_is_for_bounty) return 1 if(istype(A, /obj/item/disk/nuclear)) return 1 if(istype(A, /obj/structure/machinery/nuclearbomb)) return 1 if(istype(A, /obj/item/radio/beacon)) return 1 for(var/i=1, i<=A.contents.len, i++) var/atom/B = A.contents[i] if(.(B)) return 1 // Sells stuff on the elevator to centcomm. /datum/controller/subsystem/cargo/proc/sell() if(!shuttle.shuttle_area) return var/msg = "" var/matched_bounty = FALSE var/sold_atoms = "" for(var/area/subarea in shuttle.shuttle_area) for(var/atom/movable/AM in subarea) if(bounty_ship_item_and_contents(AM, dry_run = FALSE)) matched_bounty = TRUE if(!AM.anchored) sold_atoms += export_item_and_contents(AM, FALSE, FALSE, dry_run = FALSE) if(sold_atoms) sold_atoms += "." if(matched_bounty) msg += "Bounty items received. An update has been sent to all bounty consoles.\n" for(var/a in exports_list) var/datum/export/E = a var/export_text = E.total_printout() if(!export_text) continue msg += export_text + "\n" current_shipment.shipment_cost_sell += E.total_cost E.export_end() charge_cargo("Shipment #[current_shipment.shipment_num] - Income", -current_shipment.shipment_cost_sell) current_shipment.message = msg current_shipment.generate_invoice() current_shipment = null // Null the current shipment because it is completed. /datum/controller/subsystem/cargo/proc/order_mining(var/equip_path) if(!ispath(equip_path)) return FALSE queued_mining_equipment += equip_path return TRUE // Buys the item and places them on the elevator. // Returns 0 if unsuccessful and returns 1 if the elevator can be sent. /datum/controller/subsystem/cargo/proc/buy() if(!current_shipment) new_shipment() var/list/approved_orders = get_orders_by_status("approved", 0) if(!shuttle.shuttle_area) return var/list/clear_turfs = list() for(var/area/subarea in shuttle.shuttle_area) for(var/turf/T in subarea) if(T.density) continue var/contcount for(var/atom/A in T.contents) if(!A.simulated) continue contcount++ if(contcount) continue clear_turfs += T for(var/E in queued_mining_equipment) if(!ispath(E)) continue var/i = rand(1,clear_turfs.len) var/turf/pickedloc = clear_turfs[i] clear_turfs.Cut(i,i+1) if(isturf(pickedloc)) new E(pickedloc) queued_mining_equipment -= E for(var/datum/cargo_order/co in approved_orders) if(!co) continue // Check if theres space to place the order. if(!clear_turfs.len) log_subsystem_cargo("Order [co.order_id] could not be placed on the cargo elevator because it is full.") break // Check if the supplier is still available. for(var/datum/cargo_order_item/coi in co.items) if(!coi.ci.supplier_data.available) log_subsystem_cargo("Order [co.order_id] could not be placed on the cargo elevator because supplier [coi.ci.supplier_data.name] for item [coi.ci.name] is unavailable.") continue // Check if there is enough money to ship the order. if(!ship_order(co)) continue var/i = rand(1,clear_turfs.len) var/turf/pickedloc = clear_turfs[i] clear_turfs.Cut(i,i+1) // Spawn the crate. var/containertype = co.get_container_type() var/obj/crate = new containertype(pickedloc) // Label the crate. crate.name_unlabel = crate.name crate.name = "[crate.name] ([co.order_id] - [co.ordered_by])" crate.verbs += /atom/proc/remove_label // Set the access requirement. if(co.required_access.len > 0) crate.req_access = co.required_access.Copy() // Loop through the items and spawn them. for(var/datum/cargo_order_item/coi in co.items) if(!coi) continue for(var/_ in 1 to coi.ci.spawn_amount) for(var/item_typepath in coi.ci.items) new item_typepath(crate) // Spawn the paper inside. var/obj/item/paper/P = new(crate) P.set_content_unsafe("[co.order_id] - [co.ordered_by]", co.get_report_delivery_order()) // Shuttle is loaded now, charge operations for it. charge_cargo("Shipment #[current_shipment.shipment_num] - Expense", current_shipment.shipment_cost_purchase) return 1 /// Dumps the cargo orders to the database when the round ends. /datum/controller/subsystem/cargo/Shutdown() if(dumped_orders) log_subsystem_cargo("SQL: Orders already dumped. Cargo data dump has been aborted.") return if(!SSdbcore.Connect()) log_subsystem_cargo("SQL: Unable to connect to SQL database. Cargo data dump has been aborted.") return dumped_orders = TRUE // Loop through all the orders and dump them all. for(var/datum/cargo_order/co in all_orders) // Iterate over the items in the order and build the a list with the item count. var/list/itemcount = list() var/list/itemnames = list() for(var/datum/cargo_order_item/coi in co.items) if(!isnull(itemcount["[coi.ci.id]"])) itemcount["[coi.ci.id]"] = itemcount["[coi.ci.id]"] + 1 else itemcount["[coi.ci.id]"] = 1 itemnames["[coi.ci.id]"] = coi.ci.name var/datum/db_query/dump_query = SSdbcore.NewQuery("INSERT INTO `ss13_cargo_orderlog` (`game_id`, `order_id`, `status`, `price`, `ordered_by_id`, `ordered_by`, `authorized_by_id`, `authorized_by`, `received_by_id`, `received_by`, `paid_by_id`, `paid_by`, `time_submitted`, `time_approved`, `time_shipped`, `time_delivered`, `time_paid`, `reason`) \ VALUES (:game_id, :order_id, :status, :price, :ordered_by_id, :ordered_by, :authorized_by_id, :authorized_by, :received_by_id, :received_by, :paid_by_id, :paid_by, :time_submitted, :time_approved, :time_shipped, :time_delivered, :time_paid, :reason)", list( "game_id"=GLOB.round_id, "order_id"=co.order_id, "status"=co.status, "price"=co.price, "ordered_by_id"=co.ordered_by_id, "ordered_by"=co.ordered_by, "authorized_by_id"=co.authorized_by_id, "authorized_by"=co.authorized_by, "received_by_id"=co.received_by_id, "received_by"=co.received_by, "paid_by_id"=co.paid_by_id, "paid_by"=co.paid_by, "time_submitted"=co.time_submitted, "time_approved"=co.time_approved, "time_shipped"=co.time_shipped, "time_delivered"=co.time_delivered, "time_paid"=co.time_paid, "reason"=co.reason ), TRUE) if(!dump_query.Execute()) log_subsystem_cargo("SQL: Could not write order to database. Cargo data dump has been aborted.") continue if(dump_query.last_insert_id) for(var/item_id in itemcount) var/datum/db_query/dump_item_query = SSdbcore.NewQuery("INSERT INTO `ss13_cargo_item_orderlog` (`cargo_orderlog_id`, `item_name`, `amount`) \ VALUES (:cargo_orderlog_id, :item_name, :amount)", list( "cargo_orderlog_id" = dump_query.last_insert_id, "item_name"= itemnames[item_id], "amount" = itemcount[item_id] ), TRUE) if(!dump_item_query.Execute()) log_subsystem_cargo("SQL: Cound not write details of a cargo order.") qdel(dump_item_query) continue qdel(dump_query) log_subsystem_cargo("SQL: Saved cargo order log to database.")