/obj/item/circuitboard/machine/export_gate name = "Export Gate" greyscale_colors = CIRCUIT_COLOR_SUPPLY build_path = /obj/machinery/export_gate req_components = list( /datum/stock_part/scanning_module = 3, /datum/stock_part/card_reader = 1, ) /datum/supply_pack/service/export_gate name = "Bounty Cube Export Gate" desc = "Automatically registers bounty cube exports, for the logistics automation nerd in you." cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/flatpack/export_gate) crate_name = "export gate crate" /obj/machinery/export_gate name = "export gate" desc = "Automatically registers bounty cube exports, for the logistics automation nerd in you. Pays out to active cargo techs." icon = 'icons/obj/machines/scangate.dmi' icon_state = "scangate_black" circuit = /obj/item/circuitboard/machine/export_gate /// Used to secure changing of payment mode req_one_access = list(ACCESS_QM, ACCESS_ALL_PERSONAL_LOCKERS) /// Cooldown on the scanner's beep COOLDOWN_DECLARE(scanner_beep) /// Cooldown on payout calculation COOLDOWN_DECLARE(export_payout) /// Internal timer for scanlines var/scanline_timer /// Current scanline icon state var/scanline_state /// Bool to check if the scanner's controls are locked by an ID. var/locked = FALSE /// The holding bank account used for the export gate var/datum/bank_account/holding_account /// Which crew members receive a share of the scanned bounties var/payment_mode = EX_CARGO_TECHNICIAN | EX_CUSTOMS_AGENT /// List of crew accounts associated with the current payment mode var/list/payment_accounts ///Our internal radio var/obj/item/radio/radio ///The key our internal radio uses var/radio_key = /obj/item/encryptionkey/headset_cargo /// The amount of cubes scanned var/cubes_registered = 0 /// The share paid to each cargo tech last cycle var/income_share = 0 /// Total payments sent out last cycle var/income_previous = 0 /// Total payments sent lifetime var/income_total = 0 /obj/machinery/export_gate/Initialize(mapload) . = ..() id_tag = assign_random_name() if(name == initial(name)) name = "[initial(name)] [id_tag]" var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_entered), ) register_context() AddElement(/datum/element/connect_loc, loc_connections) holding_account = new(name, player_account = FALSE) holding_account.replaceable = FALSE radio = new(src) radio.keyslot = new radio_key radio.set_listening(FALSE) radio.recalculateChannels() align_to_belt() return INITIALIZE_HINT_LATELOAD /obj/machinery/export_gate/post_machine_initialize() . = ..() set_scanline("passive") /obj/machinery/export_gate/proc/align_to_belt() if(isturf(loc)) var/turf/location = loc var/obj/machinery/conveyor/my_conveyor = locate(/obj/machinery/conveyor) in location if(my_conveyor) dir = my_conveyor.dir set_scanline("passive") else clear_scanline() /obj/machinery/export_gate/Destroy() disperse_earnings() QDEL_NULL(radio) return ..() /obj/machinery/export_gate/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() if(held_item?.tool_behaviour == TOOL_WRENCH) context[SCREENTIP_CONTEXT_RMB] = "[anchored ? "un" : ""]anchor gate" return CONTEXTUAL_SCREENTIP_SET if(held_item?.tool_behaviour == TOOL_SCREWDRIVER) context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "close" : "open"] maintenance panel" return CONTEXTUAL_SCREENTIP_SET if(held_item?.tool_behaviour == TOOL_SCREWDRIVER && panel_open) context[SCREENTIP_CONTEXT_LMB] = "deconstruct gate" return CONTEXTUAL_SCREENTIP_SET /obj/machinery/export_gate/proc/on_entered(datum/source, atom/movable/package) SIGNAL_HANDLER INVOKE_ASYNC(src, PROC_REF(auto_scan), package) /obj/machinery/export_gate/examine() . = ..() . += span_notice("The status display reads:") . += span_info("Income last cycle: [income_previous] cr.") . += span_info("Share per account last cycle: [income_share] cr.") . += span_info("Payment mode: [display_payment_mode()].") . += span_info("Registered crew accounts last cycle: [LAZYLEN(payment_accounts)]") . += span_info("Total cubes registered: [cubes_registered]") . += span_info("Total income: [income_total] cr.") . += span_notice("The payment mode can be changed by swiping an authorized command ID card.") /obj/machinery/export_gate/wrench_act(mob/living/user, obj/item/tool) default_unfasten_wrench(user, tool) density = !anchored if(anchored) align_to_belt() else clear_scanline() return ITEM_INTERACT_SUCCESS /obj/machinery/export_gate/crowbar_act(mob/living/user, obj/item/tool) default_deconstruction_crowbar(tool) return TRUE /obj/machinery/export_gate/screwdriver_act(mob/living/user, obj/item/tool) return default_deconstruction_screwdriver(user, tool) /obj/machinery/export_gate/update_icon_state() . = ..() if(panel_open) icon_state = "[initial(icon_state)]_open" return icon_state = initial(icon_state) /obj/machinery/export_gate/update_overlays() . = ..() if(scanline_state && !panel_open) . += scanline_state /obj/machinery/export_gate/proc/clear_scanline() scanline_state = null deltimer(scanline_timer) update_appearance() /obj/machinery/export_gate/proc/set_scanline(type, duration) scanline_state = type deltimer(scanline_timer) update_appearance() if(duration) scanline_timer = addtimer(CALLBACK(src, PROC_REF(set_scanline), "passive"), duration, TIMER_STOPPABLE) if(COOLDOWN_FINISHED(src, scanner_beep) && type != "passive") COOLDOWN_START(src, scanner_beep, 0.5 SECONDS) playsound(src, type == "alarm" ? 'sound/machines/buzz/buzz-sigh.ogg' : 'sound/machines/chime.ogg', 45, TRUE) /obj/machinery/export_gate/proc/auto_scan(atom/movable/package) if(is_operational && istype(package, /obj/item/bounty_cube) && (!panel_open) && anchored) perform_scan(package) /obj/machinery/export_gate/proc/perform_scan(obj/item/package) var/obj/item/bounty_cube/cube = package if(cube.bounty_handler_account) set_scanline("alarm", 1 SECONDS) say("Scan rejected. Error 403: Bank account for handling tip already registered!") else if(cube.bounty_value) set_scanline("scanning", 1 SECONDS) process_cube(cube) else if(cube.bounty_value == 0) set_scanline("alarm", 1 SECONDS) say("Scan rejected. Error 404: Bounty value not found!") else set_scanline("alarm", 1 SECONDS) say("Scan rejected. Error 500: Check cube for errors and try again.") use_energy(active_power_usage) /obj/machinery/export_gate/proc/process_cube(obj/item/incoming_cube) var/obj/item/bounty_cube/cube = incoming_cube var/datum/export_report/cube_contents = export_item_and_contents(cube, dry_run = TRUE) var/cube_value = 0 for(var/exported_datum in cube_contents.total_amount) cube_value += cube_contents.total_value[exported_datum] cube.AddComponent(/datum/component/pricetag, holding_account, cube.handler_tip, FALSE) cube.bounty_handler_account = holding_account cubes_registered += 1 var/message = "Cube value of [cube_value ? "[floor(cube_value * cube.handler_tip)] credits " : ""]successfully registered." say(message) for(var/datum/bank_account/tech_account as anything in payment_accounts) tech_account.bank_card_talk(message) #define MODE_CARGO_TECH "Cargo Technician" #define MODE_CUSTOMS_AGENT "Cargo Technician / Customs Agent" #define MODE_QUARTERMASTER "Cargo Technician / Customs Agent / Quartermaster" /obj/machinery/export_gate/proc/set_payment_mode(mob/user, obj/item/card/id/id_card) var/list/payment_modes = list(MODE_CARGO_TECH, MODE_CUSTOMS_AGENT, MODE_QUARTERMASTER) var/selected_mode = tgui_input_list(user, "Set bounty payment mode:", src.name, payment_modes) if(!selected_mode) return switch(selected_mode) if(MODE_CARGO_TECH) payment_mode = EX_CARGO_TECHNICIAN if(MODE_CUSTOMS_AGENT) payment_mode = EX_CARGO_TECHNICIAN | EX_CUSTOMS_AGENT if(MODE_QUARTERMASTER) payment_mode = EX_CARGO_TECHNICIAN | EX_CUSTOMS_AGENT | EX_QUARTERMASTER log_econ("[src.name] payment mode changed by [user] ([id_card.registered_name]) to [display_payment_mode()].") radio.talk_into(src, "Payment mode set to [display_payment_mode()] by authorized ID [id_card.registered_name].", RADIO_CHANNEL_SUPPLY) /obj/machinery/export_gate/proc/display_payment_mode() if(payment_mode & EX_QUARTERMASTER) return MODE_QUARTERMASTER if(payment_mode & EX_CUSTOMS_AGENT) return MODE_CUSTOMS_AGENT else return MODE_CARGO_TECH #undef MODE_CARGO_TECH #undef MODE_CUSTOMS_AGENT #undef MODE_QUARTERMASTER /obj/machinery/export_gate/proc/refresh_payment_accounts(retry = FALSE) LAZYINITLIST(payment_accounts) var/list/manifest_accounts = list() var/list/CT_accounts = SSeconomy.bank_accounts_by_job[/datum/job/cargo_technician] var/list/CA_accounts = SSeconomy.bank_accounts_by_job[/datum/job/customs_agent] var/list/QM_accounts = SSeconomy.bank_accounts_by_job[/datum/job/quartermaster] if(isnull(CT_accounts) && isnull(CA_accounts) && isnull(QM_accounts)) return if(payment_mode & EX_CARGO_TECHNICIAN) for(var/datum/bank_account/CT_account as anything in CT_accounts) if(CT_account.off_duty_check()) continue manifest_accounts += CT_account if(payment_mode & EX_CUSTOMS_AGENT) for(var/datum/bank_account/CA_account as anything in CA_accounts) if(CA_account.off_duty_check()) continue manifest_accounts += CA_account if(payment_mode & EX_QUARTERMASTER) for(var/datum/bank_account/QM_account as anything in QM_accounts) if(QM_account.off_duty_check()) continue manifest_accounts += QM_account // If there's nobody to pay, fallback to paying everyone if(LAZYLEN(manifest_accounts) == 0 && !retry) payment_mode = EX_CARGO_TECHNICIAN | EX_CUSTOMS_AGENT | EX_QUARTERMASTER log_econ("[src.name] payment mode falling back to [display_payment_mode()] due to lack of crew") radio.talk_into(src, "Payment mode falling back to [display_payment_mode()] due to lack of crew.", RADIO_CHANNEL_SUPPLY) refresh_payment_accounts(retry = TRUE) return payment_accounts = manifest_accounts /obj/machinery/export_gate/proc/disperse_earnings() if(!COOLDOWN_FINISHED(src, export_payout)) return COOLDOWN_START(src, export_payout, 3 MINUTES) refresh_payment_accounts() var/total_accounts = LAZYLEN(payment_accounts) if(!total_accounts) return if(holding_account.account_balance < 50) radio.talk_into(src, "No export scan income processed this cycle.", RADIO_CHANNEL_SUPPLY) return income_previous = 0 income_share = floor(holding_account.account_balance / total_accounts) log_econ("[src.name] is dispersing [income_share * total_accounts] credits to associated accounts.") radio.talk_into(src, "Dispersing bounty cube export scan income of [income_share * total_accounts] credits to associated accounts.", RADIO_CHANNEL_SUPPLY) for(var/datum/bank_account/payout_account as anything in payment_accounts) if(payout_account.transfer_money(from = holding_account, amount = income_share, transfer_reason = "Export gate: bounty cube earnings")) income_previous += income_share income_total += income_share payout_account.bank_card_talk("Export gate payment of [income_share] cr. processed, account now holds [payout_account.account_balance] cr.") else stack_trace("[src.name] attempted to perform a bounty cube export payment to [payout_account.account_holder], but it failed!") /obj/machinery/export_gate/item_interaction(mob/living/user, obj/item/tool, list/modifiers) update_last_used(user) var/obj/item/card/id/id_card = tool.GetID() if(istype(id_card)) if(check_access(id_card)) set_payment_mode(user, id_card) return ITEM_INTERACT_SUCCESS else user.balloon_alert(user, "insufficient access!") return ITEM_INTERACT_BLOCKING /datum/controller/subsystem/economy/issue_paydays() for(var/obj/machinery/export_gate/payout_gate as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/export_gate)) payout_gate.disperse_earnings() return ..() /obj/machinery/conveyor speed = 0.55 // the items move at the same speed as the belt animation. ~aesthetics~ /obj/machinery/conveyor_switch conveyor_speed = 0.55 // the items move at the same speed as the belt animation. ~aesthetics~ /datum/bank_account/proc/off_duty_check() for(var/obj/item/card/id/registered_card in bank_cards) var/datum/component/off_duty_timer/id_component = registered_card.GetComponent(/datum/component/off_duty_timer) if(!isnull(id_component)) return TRUE return FALSE /// Small explanation for engineering on how to set-up the radioactive nebula shielding /obj/item/paper/fluff/export_gate name = "bounty cube export gate" default_raw_text = {"Export Gate Setup:
Set up the export gate on a conveyor belt, and anchor it to start scanning bounty cubes.
The gate will automatically register bounty cube exports and pay out to active cargo techs, split equally.
Never manually scan a bounty cube ever again! Get those belts and disposal pipes running. "} /datum/area_spawn/export_gate target_areas = list(/area/station/cargo/storage) desired_atom = /obj/item/flatpack/export_gate /datum/area_spawn/export_gate/try_spawn() // Turfs that are available var/list/available_turfs for(var/area_type in target_areas) var/area/found_area = GLOB.areas_by_type[area_type] if(!found_area) continue available_turfs = SSarea_spawn.get_turf_candidates(found_area, mode) if(LAZYLEN(available_turfs)) break if(!LAZYLEN(available_turfs)) return for(var/i in 1 to amount_to_spawn) var/turf/candidate_turf = SSarea_spawn.pick_turf_candidate(available_turfs) var/final_desired_atom = desired_atom new final_desired_atom(candidate_turf) /datum/area_spawn/export_gate/materials desired_atom = /obj/item/flatpack/materials_market /datum/design/board/export_gate name = "Export Gate Board" desc = "The circuit board for an export gate." id = "export_gate" build_path = /obj/item/circuitboard/machine/export_gate category = list( RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO /datum/techweb_node/office_equip/New() . = ..() design_ids += list( "export_gate", )