diff --git a/src/main/kotlin/main/VpcSpigotIntegration/SignHandler.kt b/src/main/kotlin/main/VpcSpigotIntegration/SignHandler.kt new file mode 100644 index 0000000..684b651 --- /dev/null +++ b/src/main/kotlin/main/VpcSpigotIntegration/SignHandler.kt @@ -0,0 +1,163 @@ +package main.VpcSpigotIntegration + +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.INVOICE_CREATION_TIMES +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.LOGGER +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.PAY_BY_SIGN +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.PLUGIN +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.PREFIX +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.SIGN_PAYMENT_INFO +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.TO_PAY_SIGN_INVOICES +import main.VpcSpigotIntegration.VpcServerIntegration.Companion.USERNAME +import net.md_5.bungee.api.chat.ClickEvent +import net.md_5.bungee.api.chat.ComponentBuilder +import net.md_5.bungee.api.chat.HoverEvent +import net.md_5.bungee.api.chat.TextComponent +import org.bukkit.Bukkit +import org.bukkit.Bukkit.getWorld +import org.bukkit.ChatColor +import org.bukkit.Location +import org.bukkit.event.EventHandler +import org.bukkit.event.block.BlockEvent +import org.bukkit.event.player.PlayerInteractEvent + +import org.bukkit.Material +import org.bukkit.block.Chest +import org.bukkit.block.Container +import org.bukkit.block.Sign +import org.bukkit.entity.Player +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.block.BlockPlaceEvent +import org.bukkit.event.block.SignChangeEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.java.JavaPlugin +import java.util.Locale +import java.util.Locale.getDefault + +class SignPaymentInfo( + public val amount: Int, + public val type: Material, + public val container: Chest, + public val player: Player, + public val cost: Double, + public val dst_username: String +) + +class SignHandler: Listener { + @EventHandler + fun onClick(event: PlayerInteractEvent) { + // Check if feature enabled + if (!PAY_BY_SIGN) return + + // Check if clicked block is a sign + val block = event.clickedBlock!! + if (block.type != Material.SIGN_POST && block.type != Material.WALL_SIGN) return + // Verify it's actually a sign state + val state = block.state + if (state !is Sign) return + // Check if it is RIGHT CLICK + if (event.action != Action.RIGHT_CLICK_BLOCK) return + // Check if this is good formatted sign + val sign = block.state as Sign + val clearFirstLine = ChatColor.stripColor(sign.getLine(0)).trim() + val clearPrefix = ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', PREFIX)).trim() + if (clearFirstLine != clearPrefix) return + + // Check if player already have open invoices + if (TO_PAY_SIGN_INVOICES.contains(event.player.name)) { + Utils.send(event.player, "&cУ вас уже есть открытый счёт, сначала оплатите его.") + return + } + + val blockFace = event.blockFace.toString() + LOGGER.info("Block face: $blockFace") + var cordX: Int = event.clickedBlock.location.x.toInt() + var cordY: Int = event.clickedBlock.location.y.toInt() + var cordZ: Int = event.clickedBlock.location.z.toInt() + LOGGER.info("Cords: $cordX $cordY $cordZ") + when { + blockFace == "EAST" -> { + cordX -= 1 + } + blockFace == "NORTH" -> { + cordZ += 1 + } + blockFace == "WEST" -> { + cordX += 1 + } + blockFace == "SOUTH" -> { + cordZ -= 1 + } + } + val containerBlock = getWorld(event.clickedBlock.world.uid).getBlockAt(cordX, cordY, cordZ) + LOGGER.info("Container block: $cordX $cordY $cordZ") + + val container: Chest = containerBlock.state as Chest + val firstItem = container.blockInventory.contents[0] + LOGGER.info("firstItem: ${firstItem.type} | amount: ${firstItem.amount}") + + // Get info + val vpcUsername = sign.getLine(1) + val costPerItem: Double? = sign.getLine(2).toDoubleOrNull() + val amount: Int? = sign.getLine(3).toIntOrNull() + if (vpcUsername.isEmpty() || costPerItem == null || amount == null) return + LOGGER.info("vpcUsername: $vpcUsername") + LOGGER.info("costPerItem: $costPerItem") + LOGGER.info("amount: $amount") + + // Fixed: Properly check if there are enough items of the required type + val availableAmount = container.inventory.all(firstItem.type).values.sumOf { it.amount } + if (availableAmount < amount) { + LOGGER.info("Not enough items: [type: ${firstItem.type}, requested: $amount, available: $availableAmount]") + Utils.send(event.player,"&cВ контейнере не хватает предметов для покупки.") + return + } + + // Generate invoice etc. + Bukkit.getScheduler().runTaskAsynchronously(PLUGIN, Runnable { + val cost = costPerItem*amount + val invoiceId = VpcApi.create_invoice(cost).toString() + LOGGER.info("Invoice id: $invoiceId") + if (invoiceId == "null") { + Utils.send(event.player,"&cПроизошла ошибка при генерации счёта. Обратитесь к администратору или повторите позже.") + return@Runnable + } + // Add data to map + TO_PAY_SIGN_INVOICES[event.player.name] = invoiceId + INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis() + SIGN_PAYMENT_INFO[invoiceId] = SignPaymentInfo(amount, firstItem.type, container, event.player, cost, vpcUsername) + + val colored = ChatColor.translateAlternateColorCodes('&', + "${PREFIX}Нажмите здесь, оплатить &6$cost VPC") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME $cost $invoiceId") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, + ComponentBuilder("/vpc pay $USERNAME $cost $invoiceId").create()) + event.player.spigot().sendMessage(message) + }) + + } + + @EventHandler + fun onSignChange(event: SignChangeEvent) { + // Check if feature enabled + if (!PAY_BY_SIGN) return + if (event.getLine(0).contains("[VPC]")) { + val vpcUsername = event.getLine(1) + val cost: Double? = event.getLine(2).toDoubleOrNull() + val amount: Int? = event.getLine(3).toIntOrNull() + + var coloredLine: String + // Check if some data is missing + if (vpcUsername.isEmpty() || cost == null || amount == null) { + coloredLine = ChatColor.translateAlternateColorCodes('&', "$PREFIX&cError") + event.setLine(0, coloredLine) + return + } + + coloredLine = ChatColor.translateAlternateColorCodes('&', PREFIX) + event.setLine(0, coloredLine) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/main/VpcSpigotIntegration/VpcServerIntegration.kt b/src/main/kotlin/main/VpcSpigotIntegration/VpcServerIntegration.kt index c5d0563..67eec6f 100644 --- a/src/main/kotlin/main/VpcSpigotIntegration/VpcServerIntegration.kt +++ b/src/main/kotlin/main/VpcSpigotIntegration/VpcServerIntegration.kt @@ -4,6 +4,7 @@ package main.VpcSpigotIntegration // Here you will see the main logic // ---------------------------------------------- +import com.sun.org.apache.xpath.internal.operations.Bool import org.bukkit.plugin.java.JavaPlugin import org.bukkit.Bukkit import org.bukkit.command.Command @@ -15,6 +16,8 @@ import net.md_5.bungee.api.chat.HoverEvent import net.md_5.bungee.api.chat.ClickEvent import net.md_5.bungee.api.chat.TextComponent import org.bukkit.ChatColor +import org.bukkit.Material +import org.bukkit.inventory.ItemStack import org.bukkit.scheduler.BukkitRunnable import java.io.File import java.io.FileInputStream @@ -24,113 +27,122 @@ import kotlin.math.abs import kotlin.text.isEmpty class VpcServerIntegration() : JavaPlugin(), CommandExecutor { - companion object { - // Configuration settings - var DEBUG_FLAG: Boolean = false - lateinit var USERNAME: String - lateinit var USER_TOKEN: String - lateinit var USER_API_URL: String - lateinit var COMMAND_ADD_COINS: String - lateinit var COMMAND_REMOVE_MODE: String - lateinit var COMMAND_REMOVE_COINS: String - lateinit var COMMAND_REMOVE_ERROR: String - var LOCAL_CURRENCY_MINIMUM: Double = DEFAULT_LOCAL_CURRENCY_MINIMUM - lateinit var COURSE_MODE: String - var COURSE_STATIC_VALUE: Double = DEFAULT_COURSE_STATIC_VALUE - lateinit var COURSE_DYNAMIC_COMMAND: String - var COURSE_COMMISSION: Float = DEFAULT_COURSE_COMMISSION - var INVOICE_TIMEOUT_SECONDS: Long = DEFAULT_INVOICE_TIMEOUT_SECONDS + companion object { + // Configuration settings + var DEBUG_FLAG: Boolean = false + lateinit var USERNAME: String + lateinit var USER_TOKEN: String + lateinit var USER_API_URL: String + lateinit var COMMAND_ADD_COINS: String + lateinit var COMMAND_REMOVE_MODE: String + lateinit var COMMAND_REMOVE_COINS: String + lateinit var COMMAND_REMOVE_ERROR: String + var LOCAL_CURRENCY_MINIMUM: Double = DEFAULT_LOCAL_CURRENCY_MINIMUM + lateinit var COURSE_MODE: String + var COURSE_STATIC_VALUE: Double = DEFAULT_COURSE_STATIC_VALUE + lateinit var COURSE_DYNAMIC_COMMAND: String + var COURSE_COMMISSION: Float = DEFAULT_COURSE_COMMISSION + var INVOICE_TIMEOUT_SECONDS: Long = DEFAULT_INVOICE_TIMEOUT_SECONDS + var PAY_BY_SIGN: Boolean = true - // Default configuration values - const val DEFAULT_USERNAME: String = "test" - const val DEFAULT_USER_TOKEN: String = "test" - const val DEFAULT_USER_API_URL: String = "http://127.0.0.1:8010/api/" - const val DEFAULT_COMMAND_ADD_COINS: String = "eco give %player% %amount%" - const val DEFAULT_COMMAND_REMOVE_MODE: String = "console" - const val DEFAULT_COMMAND_REMOVE_COINS: String = "eco take %player% %amount%" - const val DEFAULT_COMMAND_REMOVE_ERROR: String = "Error:" - const val DEFAULT_LOCAL_CURRENCY_MINIMUM: Double = 1.00 - const val DEFAULT_COURSE_MODE: String = "dynamic" - const val DEFAULT_COURSE_STATIC_VALUE: Double = 1000.0 - const val DEFAULT_COURSE_DYNAMIC_COMMAND: String = "baltop force" - const val DEFAULT_COURSE_COMMISSION: Float = 5.0f - const val DEFAULT_INVOICE_TIMEOUT_SECONDS: Long = 300 // 5 minutes + // Default configuration values + const val DEFAULT_USERNAME: String = "test" + const val DEFAULT_USER_TOKEN: String = "test" + const val DEFAULT_USER_API_URL: String = "http://127.0.0.1:8010/api/" + const val DEFAULT_COMMAND_ADD_COINS: String = "eco give %player% %amount%" + const val DEFAULT_COMMAND_REMOVE_MODE: String = "console" + const val DEFAULT_COMMAND_REMOVE_COINS: String = "eco take %player% %amount%" + const val DEFAULT_COMMAND_REMOVE_ERROR: String = "Error:" + const val DEFAULT_LOCAL_CURRENCY_MINIMUM: Double = 1.00 + const val DEFAULT_COURSE_MODE: String = "dynamic" + const val DEFAULT_COURSE_STATIC_VALUE: Double = 1000.0 + const val DEFAULT_COURSE_DYNAMIC_COMMAND: String = "baltop force" + const val DEFAULT_COURSE_COMMISSION: Float = 5.0f + const val DEFAULT_INVOICE_TIMEOUT_SECONDS: Long = 300 // 5 minutes + const val DEFAULT_PAY_BY_SIGN: Boolean = true - // Static configurations - const val PREFIX = "&9[&bVPC&7-&6I&9] &3" - const val NUM_AFTER_DOT = 4 + // Static configurations + const val PREFIX = "&9[&bVPC&7-&6I&9] &3" + const val NUM_AFTER_DOT = 4 - // Utility instances - lateinit var LOGGER: MyLogger - lateinit var SERVER: org.bukkit.Server - lateinit var PLUGIN: org.bukkit.plugin.Plugin + // Utility instances + lateinit var LOGGER: MyLogger + lateinit var SERVER: org.bukkit.Server + lateinit var PLUGIN: org.bukkit.plugin.Plugin - // For background checks - @JvmStatic - var TO_AUTH_PLAYERS: MutableMap = mutableMapOf() // Pair: {player: vpc_username} - @JvmStatic - var TO_AUTH_PLAYERS_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} - @JvmStatic - var TO_PAY_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} - @JvmStatic - var INVOICES_AMOUNT: MutableMap = mutableMapOf() // How many should we pay to player after invoice? + // For background checks + @JvmStatic + var TO_AUTH_PLAYERS: MutableMap = mutableMapOf() // Pair: {player: vpc_username} + @JvmStatic + var TO_AUTH_PLAYERS_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} - // Track invoice creation times for timeout handling - @JvmStatic - var INVOICE_CREATION_TIMES: MutableMap = mutableMapOf() // Pair: {invoice_id: creation_timestamp} - } + @JvmStatic + var TO_PAY_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} + @JvmStatic + var INVOICES_AMOUNT: MutableMap = mutableMapOf() // How many should we pay to player after invoice? - /** - * Load configuration from config.properties file - */ - private fun loadConfiguration() { - try { - // In Spigot, we use getDataFolder() instead of dataDirectory - val dataFolder = getDataFolder() - if (!dataFolder.exists()) { - dataFolder.mkdirs() - } + @JvmStatic + var TO_PAY_SIGN_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} + @JvmStatic + var SIGN_PAYMENT_INFO: MutableMap = mutableMapOf() // Information about amount, type and block - val configFile = File(dataFolder, "config.properties") - if (!configFile.exists()) { - createDefaultConfig(configFile) - } + // Track invoice creation times for timeout handling + @JvmStatic + var INVOICE_CREATION_TIMES: MutableMap = mutableMapOf() // Pair: {invoice_id: creation_timestamp} + } - // Load properties from file - val properties = Properties() - FileInputStream(configFile).use { properties.load(it) } + /** + * Load configuration from config.properties file + */ + private fun loadConfiguration() { + try { + // In Spigot, we use getDataFolder() instead of dataDirectory + val dataFolder = getDataFolder() + if (!dataFolder.exists()) { + dataFolder.mkdirs() + } - // Parse configuration values - DEBUG_FLAG = properties.getProperty("debug", false.toString()).replace("'", "").toBoolean() - USERNAME = properties.getProperty("username", DEFAULT_USERNAME).replace("'", "") - USER_TOKEN = properties.getProperty("user_token", DEFAULT_USER_TOKEN).replace("'", "") - USER_API_URL = properties.getProperty("user_api_url", DEFAULT_USER_API_URL).replace("'", "") - COMMAND_ADD_COINS = properties.getProperty("command_add_coins", DEFAULT_COMMAND_ADD_COINS).replace("'", "") - COMMAND_REMOVE_MODE = properties.getProperty("command_remove_mode", DEFAULT_COMMAND_REMOVE_MODE).replace("'", "") - COMMAND_REMOVE_COINS = properties.getProperty("command_remove_coins", DEFAULT_COMMAND_REMOVE_COINS).replace("'", "") - COMMAND_REMOVE_ERROR = properties.getProperty("command_remove_error", DEFAULT_COMMAND_REMOVE_ERROR).replace("'", "") - LOCAL_CURRENCY_MINIMUM = properties.getProperty("local_currency_minimum", DEFAULT_LOCAL_CURRENCY_MINIMUM.toString()).replace("'", "").toDouble() - COURSE_MODE = properties.getProperty("course_mode", DEFAULT_COURSE_MODE).replace("'", "") - COURSE_STATIC_VALUE = properties.getProperty("course_static_value", DEFAULT_COURSE_STATIC_VALUE.toString()).replace("'", "").toDouble() - COURSE_DYNAMIC_COMMAND = properties.getProperty("course_dynamic_command", DEFAULT_COURSE_DYNAMIC_COMMAND).replace("'", "") - COURSE_COMMISSION = properties.getProperty("course_commission", DEFAULT_COURSE_COMMISSION.toString()).replace("'", "").toFloat() - INVOICE_TIMEOUT_SECONDS = properties.getProperty("invoice_timeout_seconds", DEFAULT_INVOICE_TIMEOUT_SECONDS.toString()).replace("'", "").toLong() + val configFile = File(dataFolder, "config.properties") + if (!configFile.exists()) { + createDefaultConfig(configFile) + } - logger.info("Configuration loaded successfully - Username: $USERNAME, API URL: $USER_API_URL") - } catch (e: Exception) { - logger.severe("Failed to load configuration, using defaults: ${e.message}") - USERNAME = DEFAULT_USERNAME - USER_TOKEN = DEFAULT_USER_TOKEN - USER_API_URL = DEFAULT_USER_API_URL - } - } + // Load properties from file + val properties = Properties() + FileInputStream(configFile).use { properties.load(it) } - /** - * Create default configuration file with documentation - */ - private fun createDefaultConfig(configFile: File) { - configFile.writeText( - """ + // Parse configuration values + DEBUG_FLAG = properties.getProperty("debug", false.toString()).replace("'", "").toBoolean() + USERNAME = properties.getProperty("username", DEFAULT_USERNAME).replace("'", "") + USER_TOKEN = properties.getProperty("user_token", DEFAULT_USER_TOKEN).replace("'", "") + USER_API_URL = properties.getProperty("user_api_url", DEFAULT_USER_API_URL).replace("'", "") + COMMAND_ADD_COINS = properties.getProperty("command_add_coins", DEFAULT_COMMAND_ADD_COINS).replace("'", "") + COMMAND_REMOVE_MODE = properties.getProperty("command_remove_mode", DEFAULT_COMMAND_REMOVE_MODE).replace("'", "") + COMMAND_REMOVE_COINS = properties.getProperty("command_remove_coins", DEFAULT_COMMAND_REMOVE_COINS).replace("'", "") + COMMAND_REMOVE_ERROR = properties.getProperty("command_remove_error", DEFAULT_COMMAND_REMOVE_ERROR).replace("'", "") + LOCAL_CURRENCY_MINIMUM = properties.getProperty("local_currency_minimum", DEFAULT_LOCAL_CURRENCY_MINIMUM.toString()).replace("'", "").toDouble() + COURSE_MODE = properties.getProperty("course_mode", DEFAULT_COURSE_MODE).replace("'", "") + COURSE_STATIC_VALUE = properties.getProperty("course_static_value", DEFAULT_COURSE_STATIC_VALUE.toString()).replace("'", "").toDouble() + COURSE_DYNAMIC_COMMAND = properties.getProperty("course_dynamic_command", DEFAULT_COURSE_DYNAMIC_COMMAND).replace("'", "") + COURSE_COMMISSION = properties.getProperty("course_commission", DEFAULT_COURSE_COMMISSION.toString()).replace("'", "").toFloat() + INVOICE_TIMEOUT_SECONDS = properties.getProperty("invoice_timeout_seconds", DEFAULT_INVOICE_TIMEOUT_SECONDS.toString()).replace("'", "").toLong() + PAY_BY_SIGN = properties.getProperty("pay_by_sign", DEFAULT_PAY_BY_SIGN.toString()).replace("'", "").toBoolean() + + logger.info("Configuration loaded successfully - Username: $USERNAME, API URL: $USER_API_URL") + } catch (e: Exception) { + logger.severe("Failed to load configuration, using defaults: ${e.message}") + USERNAME = DEFAULT_USERNAME + USER_TOKEN = DEFAULT_USER_TOKEN + USER_API_URL = DEFAULT_USER_API_URL + } + } + + /** + * Create default configuration file with documentation + */ + private fun createDefaultConfig(configFile: File) { + configFile.writeText( + """ # VPC Integration Configuration # For get extra output debug=false @@ -184,550 +196,653 @@ course_commission=$DEFAULT_COURSE_COMMISSION # After how many seconds unpaid invoices will be deleted (default: 300 seconds = 5 minutes) invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS # -------------------- END ------------------------ + +# ------------------ Features --------------------- +# Should we enable pay by signs from containers (chest/etc.)? +pay_by_sign=${DEFAULT_PAY_BY_SIGN} +# -------------------- END ------------------------ """.trimIndent() - ) - } + ) + } - override fun onEnable() { - // Get server instance - SERVER = Bukkit.getServer() - PLUGIN = this + override fun onEnable() { + // Get server instance + SERVER = Bukkit.getServer() + PLUGIN = this - // Load configuration - loadConfiguration() + // Load configuration + loadConfiguration() - // Initialize logger - LOGGER = MyLogger(DEBUG_FLAG, this.logger) + // Initialize logger + LOGGER = MyLogger(DEBUG_FLAG, this.logger) - // Register command executor - getCommand("vpi")?.setExecutor(this) + // Register command executor + getCommand("vpi")?.setExecutor(this) + // Register clicking on sign + server.pluginManager.registerEvents(SignHandler(), this) - // Background checks such a auth, invoice check, ... - startBackgroundChecks() - // Init data manager - DataManager.initialize(getDataFolder()) + // Background checks such a auth, invoice check, ... + startBackgroundChecks() + // Init data manager + DataManager.initialize(getDataFolder()) - // Plugin startup logic - LOGGER.info("VP Server Integration plugin enabled!") - } + // Plugin startup logic + LOGGER.info("VP Server Integration plugin enabled!") + } - override fun onDisable() { - // Plugin shutdown logic - LOGGER.info("VP Server Integration plugin disabled!") - } + override fun onDisable() { + // Plugin shutdown logic + LOGGER.info("VP Server Integration plugin disabled!") + } - override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { - if (!command.name.equals("vpi", ignoreCase = true)) return false + override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { + if (!command.name.equals("vpi", ignoreCase = true)) return false - if (sender !is Player) { - sender.sendMessage("&cТолько игроки могут выполнять данные команды.") - return true - } + if (sender !is Player) { + sender.sendMessage("&cТолько игроки могут выполнять данные команды.") + return true + } - // Run all logic asynchronously - Bukkit.getScheduler().runTaskAsynchronously(PLUGIN, Runnable { - when { - // Currency conversion - args.size in 3..4 && args[0] == "convert" -> { - handleCurrencyConversion(sender, args) - } - args.isNotEmpty() && args[0] == "convert" -> { - Utils.send(sender, "/vpi convert <сумма>") - } + // Run all logic asynchronously + Bukkit.getScheduler().runTaskAsynchronously(PLUGIN, Runnable { + when { + // Currency conversion + args.size in 3..4 && args[0] == "convert" -> { + handleCurrencyConversion(sender, args) + } + args.isNotEmpty() && args[0] == "convert" -> { + Utils.send(sender, "/vpi convert <сумма>") + } - // Course info - args.isNotEmpty() && args[0] == "course" -> { - val course = calculateExchangeRate() - LOGGER.info("Exchange rate calculated: $course") + // Course info + args.isNotEmpty() && args[0] == "course" -> { + val course = calculateExchangeRate() + LOGGER.info("Exchange rate calculated: $course") - if (course.isInfinite() || course == 0.0) { - LOGGER.error(if (course.isInfinite()) "Zero global balance?" else "Infinite global balance?") - Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.") - } else { - val course2VPC = Utils.round(course * (1 - COURSE_COMMISSION / 100), NUM_AFTER_DOT) - val course2LC = Utils.round(course * (1 + COURSE_COMMISSION / 100), NUM_AFTER_DOT) - Utils.send(sender, "VPC->LC = &6$course2VPC&3 | LC->VPC = &6$course2LC") - } - } + if (course.isInfinite() || course == 0.0) { + LOGGER.error(if (course.isInfinite()) "Zero global balance?" else "Infinite global balance?") + Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.") + } else { + val course2VPC = Utils.round(course * (1 - COURSE_COMMISSION / 100), NUM_AFTER_DOT) + val course2LC = Utils.round(course * (1 + COURSE_COMMISSION / 100), NUM_AFTER_DOT) + Utils.send(sender, "VPC->LC = &6$course2VPC&3 | LC->VPC = &6$course2LC") + } + } - // Authentication - args.size == 2 && args[0] == "auth" -> { - handleAuthentication(sender, args) - } - args.isNotEmpty() && args[0] == "auth" -> { - Utils.send(sender, "/vpi auth <ник>") - } + // Authentication + args.size == 2 && args[0] == "auth" -> { + handleAuthentication(sender, args) + } + args.isNotEmpty() && args[0] == "auth" -> { + Utils.send(sender, "/vpi auth <ник>") + } - // Default help menu - else -> { - showHelpMenu(sender) - } - } - }) + // Default help menu + else -> { + showHelpMenu(sender) + } + } + }) - return true - } + return true + } - /** - * Handle currency conversion logic - */ - private fun handleCurrencyConversion(sender: Player, args: Array) { - LOGGER.info("Starting currency conversion process - Step 1") + /** + * Handle currency conversion logic + */ + private fun handleCurrencyConversion(sender: Player, args: Array) { + LOGGER.info("Starting currency conversion process - Step 1") - // Check if player is authenticated - val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) - if (vpcUsername == null) { - Utils.send(sender, "&cВы должны авторизоваться сначала: /vpi auth") - return - } + // Check if player is authenticated + val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) + if (vpcUsername == null) { + Utils.send(sender, "&cВы должны авторизоваться сначала: /vpi auth") + return + } - LOGGER.info("Player is authenticated - Step 2") + LOGGER.info("Player is authenticated - Step 2") - val direction = args[1] - val amount = abs(args[2].toDouble()) + val direction = args[1] + val amount = abs(args[2].toDouble()) - // Validate conversion direction - if (!listOf("vpc", "lc").contains(direction)) { - Utils.send(sender, "&cНеверное направление. Используйте: vpc or lc") - return - } + // Validate conversion direction + if (!listOf("vpc", "lc").contains(direction)) { + Utils.send(sender, "&cНеверное направление. Используйте: vpc or lc") + return + } - LOGGER.info("Valid direction provided - Step 3") + LOGGER.info("Valid direction provided - Step 3") - // Calculate exchange rate - val course = calculateExchangeRate() - LOGGER.info("Exchange rate calculated: $course") - if (course.isInfinite()) { - LOGGER.error("Zero global balance?") - Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.") - return - } else if (course == 0.0) { - LOGGER.error("Infinite global balance?") - Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.") - return - } + // Calculate exchange rate + val course = calculateExchangeRate() + LOGGER.info("Exchange rate calculated: $course") + if (course.isInfinite()) { + LOGGER.error("Zero global balance?") + Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.") + return + } else if (course == 0.0) { + LOGGER.error("Infinite global balance?") + Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.") + return + } - when (direction) { - "vpc" -> handleVpcConversion(sender, Utils.round(amount, NUM_AFTER_DOT), course, args) - "lc" -> handleLcConversion(sender, Utils.round(amount, NUM_AFTER_DOT), course, args) - } - } + when (direction) { + "vpc" -> handleVpcConversion(sender, Utils.round(amount, NUM_AFTER_DOT), course, args) + "lc" -> handleLcConversion(sender, Utils.round(amount, NUM_AFTER_DOT), course, args) + } + } - /** - * Calculate exchange rate based on configuration - */ - private fun calculateExchangeRate(): Double { - return if (COURSE_MODE == "static") { - COURSE_STATIC_VALUE - } else { - val dynamicRate: Any = calculateDynamicRate() - if (dynamicRate != 0.0) Utils.round(dynamicRate, NUM_AFTER_DOT) - else return 0.0 - } - } + /** + * Calculate exchange rate based on configuration + */ + private fun calculateExchangeRate(): Double { + return if (COURSE_MODE == "static") { + COURSE_STATIC_VALUE + } else { + val dynamicRate: Any = calculateDynamicRate() + if (dynamicRate != 0.0) Utils.round(dynamicRate, NUM_AFTER_DOT) + else return 0.0 + } + } - /** - * Calculate dynamic exchange rate - */ - private fun calculateDynamicRate(): Double { - return if (COURSE_DYNAMIC_COMMAND == "baltop force") { - val globalBalance = TotalBalanceModules.getEssentialsBalance() - LOGGER.info("Global balance: $globalBalance") + /** + * Calculate dynamic exchange rate + */ + private fun calculateDynamicRate(): Double { + return if (COURSE_DYNAMIC_COMMAND == "baltop force") { + val globalBalance = TotalBalanceModules.getEssentialsBalance() + LOGGER.info("Global balance: $globalBalance") - val vpcUser = VpcApi.user_in_db(username = USERNAME) - if (vpcUser == null) { - return 0.0 + val vpcUser = VpcApi.user_in_db(username = USERNAME) + if (vpcUser == null) { + return 0.0 // throw Exception("VPC user not found") - } + } - val vpcBalance = vpcUser["balance"].toString().toDouble() - LOGGER.info("VPC balance: $vpcBalance") + val vpcBalance = vpcUser["balance"].toString().toDouble() + LOGGER.info("VPC balance: $vpcBalance") - val rate = globalBalance / vpcBalance - LOGGER.info("Calculated exchange rate: $rate") - rate - } else { - LOGGER.info("Calculating dynamic rate using custom command - Step 4.2") - val globalBalance = CommandCapture.execute(COURSE_DYNAMIC_COMMAND).toString().toDouble() - val vpcUser = VpcApi.user_in_db(username = USERNAME) + val rate = globalBalance / vpcBalance + LOGGER.info("Calculated exchange rate: $rate") + rate + } else { + LOGGER.info("Calculating dynamic rate using custom command - Step 4.2") + val globalBalance = CommandCapture.execute(COURSE_DYNAMIC_COMMAND).toString().toDouble() + val vpcUser = VpcApi.user_in_db(username = USERNAME) - if (vpcUser == null) { - return 0.0 + if (vpcUser == null) { + return 0.0 // throw Exception("VPC user not found") - } + } - val vpcBalance = vpcUser["balance"].toString().toDouble() - globalBalance / vpcBalance - } - } + val vpcBalance = vpcUser["balance"].toString().toDouble() + globalBalance / vpcBalance + } + } - /** - * Handle VPC to local currency conversion - */ - private fun handleVpcConversion(sender: Player, amount: Double, course: Double, args: Array) { - val amountVPC = Utils.round(amount / course * (1 - COURSE_COMMISSION / 100), NUM_AFTER_DOT) - val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) - if (vpcUsername == null) { - Utils.send(sender, "&cВы должны авторизоваться сначала: /vpi auth") - return - } + /** + * Handle VPC to local currency conversion + */ + private fun handleVpcConversion(sender: Player, amount: Double, course: Double, args: Array) { + val amountVPC = Utils.round(amount / course * (1 - COURSE_COMMISSION / 100), NUM_AFTER_DOT) + val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) + if (vpcUsername == null) { + Utils.send(sender, "&cВы должны авторизоваться сначала: /vpi auth") + return + } - if (amountVPC < 0.0001 || amount < LOCAL_CURRENCY_MINIMUM) { - Utils.send(sender, "&cСлишком маленькая входная или выходная сумма.") - return - } + if (amountVPC < 0.0001 || amount < LOCAL_CURRENCY_MINIMUM) { + Utils.send(sender, "&cСлишком маленькая входная или выходная сумма.") + return + } - if (args.size == 4) { - // Execute confirmed conversion - LOGGER.info("Executing confirmed conversion - Step 5") - val result = CommandCapture.execute( - COMMAND_REMOVE_COINS - .replace("%player%", sender.name) - .replace("%amount%", amount.toString()) - )[0] + if (args.size == 4) { + // Execute confirmed conversion + LOGGER.info("Executing confirmed conversion - Step 5") + val result = CommandCapture.execute( + COMMAND_REMOVE_COINS + .replace("%player%", sender.name) + .replace("%amount%", amount.toString()) + )[0] - LOGGER.info("Command executed - Step 6") - if (result.contains(COMMAND_REMOVE_ERROR)) { - Utils.send(sender, "&cОшибка. Возможно недостаточно средств.") - return - } + LOGGER.info("Command executed - Step 6") + if (result.contains(COMMAND_REMOVE_ERROR)) { + Utils.send(sender, "&cОшибка. Возможно недостаточно средств.") + return + } - LOGGER.info("Transferring coins to VPC - Step 8") - VpcApi.transfer_coins(vpcUsername, amountVPC) - } else { - // Show confirmation prompt - val colored = ChatColor.translateAlternateColorCodes('&', - "${PREFIX}Нажмите здесь, чтобы конвертировать &6$amount&3 в &6${String.format("%.4f", amountVPC)} VPC") - val message = TextComponent(colored) - message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpi convert vpc $amount confirm") - message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, - ComponentBuilder("/vpi convert vpc $amount confirm").create()) - sender.spigot().sendMessage(message) - } - } + LOGGER.info("Transferring coins to VPC - Step 8") + VpcApi.transfer_coins(vpcUsername, amountVPC) + } else { + // Show confirmation prompt + val colored = ChatColor.translateAlternateColorCodes('&', + "${PREFIX}Нажмите здесь, чтобы конвертировать &6$amount&3 в &6${String.format("%.4f", amountVPC)} VPC") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpi convert vpc $amount confirm") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, + ComponentBuilder("/vpi convert vpc $amount confirm").create()) + sender.spigot().sendMessage(message) + } + } - /** - * Handle local currency to VPC conversion - */ - private fun handleLcConversion(sender: Player, amount: Double, course: Double, args: Array) { - // Check if there's an existing pending invoice - if (sender.name in TO_PAY_INVOICES) { - handleExistingInvoice(sender) - return - } + /** + * Handle local currency to VPC conversion + */ + private fun handleLcConversion(sender: Player, amount: Double, course: Double, args: Array) { + // Check if there's an existing pending invoice + if (sender.name in TO_PAY_INVOICES) { + handleExistingInvoice(sender) + return + } - LOGGER.info("Amount: $amount, Course: $course") - val amountLC = Utils.round(amount * course * (1 + COURSE_COMMISSION / 100), NUM_AFTER_DOT) - LOGGER.info("Converted amount: $amountLC") + LOGGER.info("Amount: $amount, Course: $course") + val amountLC = Utils.round(amount * course * (1 + COURSE_COMMISSION / 100), NUM_AFTER_DOT) + LOGGER.info("Converted amount: $amountLC") - if (amount < 0.0001 || amountLC < LOCAL_CURRENCY_MINIMUM) { - Utils.send(sender, "&cСлишком маленькая входная или выходная сумма.") - return - } + if (amount < 0.0001 || amountLC < LOCAL_CURRENCY_MINIMUM) { + Utils.send(sender, "&cСлишком маленькая входная или выходная сумма.") + return + } - // Create new invoice - val invoiceId = VpcApi.create_invoice(amount).toString() - LOGGER.info("Invoice id: $invoiceId") - if (invoiceId == "null") { - Utils.send(sender,"&cПроизошла ошибка при генерации счёта. Обратитесь к администратору или повторите позже.") - return - } - TO_PAY_INVOICES[sender.name] = invoiceId - INVOICES_AMOUNT[invoiceId] = amountLC - INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis() + // Create new invoice + val invoiceId = VpcApi.create_invoice(amount).toString() + LOGGER.info("Invoice id: $invoiceId") + if (invoiceId == "null") { + Utils.send(sender,"&cПроизошла ошибка при генерации счёта. Обратитесь к администратору или повторите позже.") + return + } + TO_PAY_INVOICES[sender.name] = invoiceId + INVOICES_AMOUNT[invoiceId] = amountLC + INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis() - sendPaymentPrompt(sender, amount, amountLC, invoiceId) - } + sendPaymentPrompt(sender, amount, amountLC, invoiceId) + } - /** - * Handle existing payment invoice - */ - private fun handleExistingInvoice(sender: Player) { - val invoiceId = TO_PAY_INVOICES[sender.name] - val amount = INVOICES_AMOUNT[invoiceId] - if (amount == null) return + /** + * Handle existing payment invoice + */ + private fun handleExistingInvoice(sender: Player) { + val invoiceId = TO_PAY_INVOICES[sender.name] + val amount = INVOICES_AMOUNT[invoiceId] + if (amount == null) return - val course = if (COURSE_MODE == "static") COURSE_STATIC_VALUE else calculateDynamicRate() - val amountLC = amount * course * (1 + COURSE_COMMISSION / 100) + val course = if (COURSE_MODE == "static") COURSE_STATIC_VALUE else calculateDynamicRate() + val amountLC = amount * course * (1 + COURSE_COMMISSION / 100) - sendPaymentPrompt(sender, amount, amountLC, invoiceId) - } + sendPaymentPrompt(sender, amount, amountLC, invoiceId) + } - /** - * Send payment prompt to player - */ - private fun sendPaymentPrompt(sender: Player, amount: Double, amountLC: Double, invoiceId: String?) { - val colored = ChatColor.translateAlternateColorCodes('&', - "${PREFIX}Нажмите здесь, чтобы конвертировать &6$amount VPC &3в &6${String.format("%.4f", amountLC)}") - val message = TextComponent(colored) - message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME $amount $invoiceId") - message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, - ComponentBuilder("/vpc pay $USERNAME $amount $invoiceId").create()) - sender.spigot().sendMessage(message) - } + /** + * Send payment prompt to player + */ + private fun sendPaymentPrompt(sender: Player, amount: Double, amountLC: Double, invoiceId: String?) { + val colored = ChatColor.translateAlternateColorCodes('&', + "${PREFIX}Нажмите здесь, чтобы конвертировать &6$amount VPC &3в &6${String.format("%.4f", amountLC)}") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME $amount $invoiceId") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, + ComponentBuilder("/vpc pay $USERNAME $amount $invoiceId").create()) + sender.spigot().sendMessage(message) + } - /** - * Handle player authentication process - */ - private fun handleAuthentication(sender: Player, args: Array) { - val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) - if (vpcUsername != null) { - Utils.send(sender, "&cВы уже авторизованы!") - return - } + /** + * Handle player authentication process + */ + private fun handleAuthentication(sender: Player, args: Array) { + val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) + if (vpcUsername != null) { + Utils.send(sender, "&cВы уже авторизованы!") + return + } - // Check if authentication is already in progress - if (sender.name in TO_AUTH_PLAYERS) { - val invoiceId = TO_AUTH_PLAYERS_INVOICES[sender.name] - sendAuthPrompt(sender, invoiceId ?: "") - return - } + // Check if authentication is already in progress + if (sender.name in TO_AUTH_PLAYERS) { + val invoiceId = TO_AUTH_PLAYERS_INVOICES[sender.name] + sendAuthPrompt(sender, invoiceId ?: "") + return + } - // Start new authentication process - val invoiceId = VpcApi.create_invoice(0.001).toString() - val vpcUsernameInput = args[1] - LOGGER.info("Invoice id: $invoiceId") - if (invoiceId == "null") { - Utils.send(sender,"&cПроизошла ошибка при генерации счёта. Обратитесь к администратору или повторите позже.") - return - } + // Start new authentication process + val invoiceId = VpcApi.create_invoice(0.001).toString() + val vpcUsernameInput = args[1] + LOGGER.info("Invoice id: $invoiceId") + if (invoiceId == "null") { + Utils.send(sender,"&cПроизошла ошибка при генерации счёта. Обратитесь к администратору или повторите позже.") + return + } - LOGGER.info("Adding player to authentication lists") - synchronized(TO_AUTH_PLAYERS) { - TO_AUTH_PLAYERS[sender.name] = vpcUsernameInput - TO_AUTH_PLAYERS_INVOICES[sender.name] = invoiceId - INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis() - } + LOGGER.info("Adding player to authentication lists") + synchronized(TO_AUTH_PLAYERS) { + TO_AUTH_PLAYERS[sender.name] = vpcUsernameInput + TO_AUTH_PLAYERS_INVOICES[sender.name] = invoiceId + INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis() + } - LOGGER.info("Authentication lists updated") - sendAuthPrompt(sender, invoiceId) - } + LOGGER.info("Authentication lists updated") + sendAuthPrompt(sender, invoiceId) + } - /** - * Send authentication prompt to player - */ - private fun sendAuthPrompt(sender: Player, invoiceId: String) { - val colored = ChatColor.translateAlternateColorCodes('&', - "${PREFIX}Нажмите, чтобы перевести &60.001 VPC&3 для авторизации") - val message = TextComponent(colored) - message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME 0.001 $invoiceId") - message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, - ComponentBuilder("/vpc pay $USERNAME 0.001 $invoiceId").create()) - sender.spigot().sendMessage(message) - } + /** + * Send authentication prompt to player + */ + private fun sendAuthPrompt(sender: Player, invoiceId: String) { + val colored = ChatColor.translateAlternateColorCodes('&', + "${PREFIX}Нажмите, чтобы перевести &60.001 VPC&3 для авторизации") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME 0.001 $invoiceId") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, + ComponentBuilder("/vpc pay $USERNAME 0.001 $invoiceId").create()) + sender.spigot().sendMessage(message) + } - /** - * Display help menu to player - */ - private fun showHelpMenu(sender: CommandSender) { - Utils.send(sender, """Использование команд: - /vpi auth <ник> - Авторизация - /vpi course - Текущий курс - /vpi convert <куда: vpc/lc> <сумма> - Обмен VPC на локальную валюту или наоборот - - Почему 'VPC-I'? Потому что это интеграция на конечном сервере - 'VPC Integration' - - Кошелёк VPC: - ТГ: https://t.me/vp_coin_bot - ДС: Кошелёк VPC#3531""".trimIndent()) - } + /** + * Display help menu to player + */ + private fun showHelpMenu(sender: CommandSender) { + Utils.send(sender, """Использование команд: + /vpi auth <ник> - Авторизация + /vpi course - Текущий курс + /vpi convert <куда: vpc/lc> <сумма> - Обмен VPC на локальную валюту или наоборот + + Почему 'VPC-I'? Потому что это интеграция на конечном сервере - 'VPC Integration' + + Кошелёк VPC: + ТГ: https://t.me/vp_coin_bot + ДС: Кошелёк VPC#3531""".trimIndent()) + } - override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array): List { - val completions = mutableListOf() - if (command.name.equals("vpi", ignoreCase = true)) { - when (args.size) { - 1 -> { - completions.addAll(listOf("help", "convert", "auth", "course")) - } - 2 -> { - if (args[0].equals("convert", ignoreCase = true)) { - completions.add("<куда: vpc/lc>") - } - } - 3 -> { - if (args[0].equals("convert", ignoreCase = true)) { - completions.add("<сумма>") - } - } - } - } + override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array): List { + val completions = mutableListOf() + if (command.name.equals("vpi", ignoreCase = true)) { + when (args.size) { + 1 -> { + completions.addAll(listOf("help", "convert", "auth", "course")) + } + 2 -> { + if (args[0].equals("convert", ignoreCase = true)) { + completions.add("<куда: vpc/lc>") + } + } + 3 -> { + if (args[0].equals("convert", ignoreCase = true)) { + completions.add("<сумма>") + } + } + } + } - return completions.filter { it.startsWith(args.lastOrNull()?.lowercase() ?: "") }.sorted() - } + return completions.filter { it.startsWith(args.lastOrNull()?.lowercase() ?: "") }.sorted() + } - /** - * Start background checks for invoice processing - */ - private fun startBackgroundChecks() { - object : BukkitRunnable() { - override fun run() { - processAuthenticationInvoices() - processPaymentInvoices() - cleanupExpiredInvoices() - } - }.runTaskTimerAsynchronously(this, 0L, 20L) - } + /** + * Start background checks for invoice processing + */ + private fun startBackgroundChecks() { + object : BukkitRunnable() { + override fun run() { + processAuthenticationInvoices() + processPaymentInvoices() + processSignInvoices() - /** - * Process authentication invoices in background - */ - private fun processAuthenticationInvoices() { - synchronized(TO_AUTH_PLAYERS_INVOICES) { - if (TO_AUTH_PLAYERS_INVOICES.isNotEmpty()) { - val iterator = TO_AUTH_PLAYERS_INVOICES.iterator() - while (iterator.hasNext()) { - val entry = iterator.next() - try { - val result = VpcApi.get_invoice(entry.value) as Map<*, *>? + cleanupExpiredInvoices() + } + }.runTaskTimerAsynchronously(this, 0L, 20L) + } - if (result != null) { - val status = result["status"] - LOGGER.info("Auth invoice ${entry.value} status: $status") + /** + * Process authentication invoices in background + */ + private fun processAuthenticationInvoices() { + synchronized(TO_AUTH_PLAYERS_INVOICES) { + if (TO_AUTH_PLAYERS_INVOICES.isNotEmpty()) { + val iterator = TO_AUTH_PLAYERS_INVOICES.iterator() + while (iterator.hasNext()) { + val entry = iterator.next() + try { + val result = VpcApi.get_invoice(entry.value) as Map<*, *>? - if (status != null && status.toString() == "true") { - LOGGER.info("Processing successful authentication") - VpcApi.delete_invoice(entry.value) + if (result != null) { + val status = result["status"] + LOGGER.info("Auth invoice ${entry.value} status: $status") - DataManager.setPlayerVPCUsername(entry.key, TO_AUTH_PLAYERS[entry.key].toString()) - Utils.send(Bukkit.getPlayer(entry.key.toString()), "&aУспешная авторизация!") + if (status != null && status.toString() == "true") { + LOGGER.info("Processing successful authentication") + VpcApi.delete_invoice(entry.value) - // Clean up tracking maps - TO_AUTH_PLAYERS.remove(entry.key) - INVOICE_CREATION_TIMES.remove(entry.value) - iterator.remove() - } - } else { - logger.warning("Received null result for auth invoice ${entry.value}") - } - } catch (e: Exception) { - LOGGER.error("Error processing auth invoice ${entry.value}: ${e.message}") - e.printStackTrace() - } - } - } - } - } + DataManager.setPlayerVPCUsername(entry.key, TO_AUTH_PLAYERS[entry.key].toString()) + Utils.send(Bukkit.getPlayer(entry.key.toString()), "&aУспешная авторизация!") - /** - * Process payment invoices in background - */ - private fun processPaymentInvoices() { - synchronized(TO_PAY_INVOICES) { - if (TO_PAY_INVOICES.isNotEmpty()) { - val iterator = TO_PAY_INVOICES.iterator() - while (iterator.hasNext()) { - val entry = iterator.next() - try { - val result = VpcApi.get_invoice(entry.value) as Map<*, *>? + // Clean up tracking maps + TO_AUTH_PLAYERS.remove(entry.key) + INVOICE_CREATION_TIMES.remove(entry.value) + iterator.remove() + } + } else { + logger.warning("Received null result for auth invoice ${entry.value}") + } + } catch (e: Exception) { + LOGGER.error("Error processing auth invoice ${entry.value}: ${e.message}") + e.printStackTrace() + } + } + } + } + } - if (result != null) { - val status = result["status"] - LOGGER.info("Payment invoice ${entry.value} status: $status") + /** + * Process payment invoices in background + */ + private fun processPaymentInvoices() { + synchronized(TO_PAY_INVOICES) { + if (TO_PAY_INVOICES.isNotEmpty()) { + val iterator = TO_PAY_INVOICES.iterator() + while (iterator.hasNext()) { + val entry = iterator.next() + try { + val result = VpcApi.get_invoice(entry.value) as Map<*, *>? - if (status != null && status.toString() == "true") { - LOGGER.info("Processing successful payment") - VpcApi.delete_invoice(entry.value) + if (result != null) { + val status = result["status"] + LOGGER.info("Payment invoice ${entry.value} status: $status") - val amountLC = INVOICES_AMOUNT[entry.value] + if (status != null && status.toString() == "true") { + LOGGER.info("Processing successful payment") + VpcApi.delete_invoice(entry.value) - // Execute coin addition on main thread - val commandToAddCoins = COMMAND_ADD_COINS - .replace("%player%", entry.key) - .replace("%amount%", amountLC.toString()) + val amountLC = INVOICES_AMOUNT[entry.value] - Bukkit.getScheduler().runTask(this@VpcServerIntegration, Runnable { - try { - CommandCapture.execute(commandToAddCoins) - } catch (e: Exception) { - LOGGER.error("Error executing coin addition command: ${e.message}") - e.printStackTrace() + // Execute coin addition on main thread + val commandToAddCoins = COMMAND_ADD_COINS + .replace("%player%", entry.key) + .replace("%amount%", amountLC.toString()) + + Bukkit.getScheduler().runTask(this@VpcServerIntegration, Runnable { + try { + CommandCapture.execute(commandToAddCoins) + } catch (e: Exception) { + LOGGER.error("Error executing coin addition command: ${e.message}") + e.printStackTrace() + } + }) + + // Clean up tracking maps + INVOICES_AMOUNT.remove(entry.key) + INVOICE_CREATION_TIMES.remove(entry.value) + iterator.remove() + } + } else { + logger.warning("Received null result for payment invoice ${entry.value}") + } + } catch (e: Exception) { + LOGGER.error("Error processing payment invoice ${entry.value}: ${e.message}") + e.printStackTrace() + } + } + } + } + } + + private fun processSignInvoices() { + synchronized(TO_PAY_SIGN_INVOICES) { + if (TO_PAY_SIGN_INVOICES.isNotEmpty()) { + val iterator = TO_PAY_SIGN_INVOICES.iterator() + while (iterator.hasNext()) { + val entry = iterator.next() + try { + val result = VpcApi.get_invoice(entry.value) as Map<*, *>? + + if (result != null) { + val status = result["status"] + LOGGER.info("Payment invoice ${entry.value} status: $status") + + if (status != null && status.toString() == "true") { + LOGGER.info("Processing successful payment") + VpcApi.delete_invoice(entry.value) + + // Check if we have payment info for this entry + val paymentInfo = SIGN_PAYMENT_INFO[entry.value] + if (paymentInfo == null) { + LOGGER.error("No payment info found for key: ${entry.value}") + return + } + + val itemStack = ItemStack(paymentInfo.type, paymentInfo.amount) + val type = paymentInfo.type + // Check if container have needed items + val availableAmount = paymentInfo.container.inventory.all(type).values.sumOf { it.amount } + if (availableAmount < paymentInfo.amount) { + LOGGER.error("Not enough items after pay invoice, try to return VPC...") + val vpcUsername = DataManager.getPlayerVPCUsername(paymentInfo.player.name).toString() + val transferResult = VpcApi.transfer_coins(vpcUsername, paymentInfo.cost).toString() + if (transferResult != "OK") { + LOGGER.error("Can't return VPC, result: $transferResult") } - }) + } else { + // If we have needed items + paymentInfo.player.inventory.addItem(itemStack) + paymentInfo.container.inventory.removeItem(itemStack) - // Clean up tracking maps - INVOICES_AMOUNT.remove(entry.key) - INVOICE_CREATION_TIMES.remove(entry.value) - iterator.remove() - } - } else { - logger.warning("Received null result for payment invoice ${entry.value}") - } - } catch (e: Exception) { - LOGGER.error("Error processing payment invoice ${entry.value}: ${e.message}") - e.printStackTrace() - } - } - } - } - } + val transferResult = VpcApi.transfer_coins(paymentInfo.dst_username, paymentInfo.cost).toString() + if (transferResult != "OK") { + LOGGER.error("Can't transfer VPC, result: $transferResult") + } + } - /** - * Cleanup expired invoices based on timeout configuration - */ - private fun cleanupExpiredInvoices() { - val currentTime = System.currentTimeMillis() - val timeoutMillis = INVOICE_TIMEOUT_SECONDS * 1000 + // Clean up tracking maps + INVOICE_CREATION_TIMES.remove(entry.value) + SIGN_PAYMENT_INFO.remove(entry.value) + iterator.remove() + } + } else { + logger.warning("Received null result for payment invoice ${entry.value}") + } + } catch (e: Exception) { + LOGGER.error("Error processing payment invoice ${entry.value}: ${e.message}") + e.printStackTrace() + } + } + } + } + } - // Cleanup auth invoices - synchronized(TO_AUTH_PLAYERS_INVOICES) { - val authIterator = TO_AUTH_PLAYERS_INVOICES.iterator() - while (authIterator.hasNext()) { - val entry = authIterator.next() - val invoiceId = entry.value - val creationTime = INVOICE_CREATION_TIMES[invoiceId] ?: continue - LOGGER.info("invoice ( $invoiceId ) left to live: ${timeoutMillis - (currentTime - creationTime)}") + /** + * Cleanup expired invoices based on timeout configuration + */ + private fun cleanupExpiredInvoices() { + val currentTime = System.currentTimeMillis() + val timeoutMillis = INVOICE_TIMEOUT_SECONDS * 1000 - if (currentTime - creationTime > timeoutMillis) { - LOGGER.info("Deleting expired auth invoice: $invoiceId") - try { - VpcApi.delete_invoice(invoiceId) - } catch (e: Exception) { - LOGGER.error("Error deleting expired auth invoice $invoiceId: ${e.message}") - } + // Cleanup auth invoices + synchronized(TO_AUTH_PLAYERS_INVOICES) { + val authIterator = TO_AUTH_PLAYERS_INVOICES.iterator() + while (authIterator.hasNext()) { + val entry = authIterator.next() + val invoiceId = entry.value + val creationTime = INVOICE_CREATION_TIMES[invoiceId] ?: continue + LOGGER.info("invoice ( $invoiceId ) left to live: ${timeoutMillis - (currentTime - creationTime)}") - // Clean up tracking maps - TO_AUTH_PLAYERS.remove(entry.key) - INVOICE_CREATION_TIMES.remove(invoiceId) - authIterator.remove() + if (currentTime - creationTime > timeoutMillis) { + LOGGER.info("Deleting expired auth invoice: $invoiceId") + try { + VpcApi.delete_invoice(invoiceId) + } catch (e: Exception) { + LOGGER.error("Error deleting expired auth invoice $invoiceId: ${e.message}") + } - // Notify player if online - val player = Bukkit.getPlayer(entry.key) - if (player != null && player.isOnline) { - Utils.send(player, "&cВаша авторизационная оплата истекла. Пожалуйста, попробуйте снова: /vpi auth <ник>") - } - } - } - } + // Clean up tracking maps + TO_AUTH_PLAYERS.remove(entry.key) + INVOICE_CREATION_TIMES.remove(invoiceId) + authIterator.remove() - // Cleanup payment invoices - synchronized(TO_PAY_INVOICES) { - val paymentIterator = TO_PAY_INVOICES.iterator() - while (paymentIterator.hasNext()) { - val entry = paymentIterator.next() - val invoiceId = entry.value - val creationTime = INVOICE_CREATION_TIMES[invoiceId] ?: continue - LOGGER.info("invoice ( $invoiceId ) left to live: ${timeoutMillis - (currentTime - creationTime)}") + // Notify player if online + val player = Bukkit.getPlayer(entry.key) + if (player != null && player.isOnline) { + Utils.send(player, "&cВаша авторизационная оплата истекла. Пожалуйста, попробуйте снова: /vpi auth <ник>") + } + } + } + } - if (currentTime - creationTime > timeoutMillis) { - LOGGER.info("Deleting expired payment invoice: $invoiceId") - try { - VpcApi.delete_invoice(invoiceId) - } catch (e: Exception) { - LOGGER.error("Error deleting expired payment invoice $invoiceId: ${e.message}") - } + // Cleanup payment invoices + synchronized(TO_PAY_INVOICES) { + val paymentIterator = TO_PAY_INVOICES.iterator() + while (paymentIterator.hasNext()) { + val entry = paymentIterator.next() + val invoiceId = entry.value + val creationTime = INVOICE_CREATION_TIMES[invoiceId] ?: continue + LOGGER.info("invoice ( $invoiceId ) left to live: ${timeoutMillis - (currentTime - creationTime)}") - // Clean up tracking maps - INVOICES_AMOUNT.remove(invoiceId) - INVOICE_CREATION_TIMES.remove(invoiceId) - paymentIterator.remove() + if (currentTime - creationTime > timeoutMillis) { + LOGGER.info("Deleting expired payment invoice: $invoiceId") + try { + VpcApi.delete_invoice(invoiceId) + } catch (e: Exception) { + LOGGER.error("Error deleting expired payment invoice $invoiceId: ${e.message}") + } - // Notify player if online - val player = Bukkit.getPlayer(entry.key) - if (player != null && player.isOnline) { - Utils.send(player, "&cВаша оплата истекла. Пожалуйста, создайте новую: /vpi convert lc <сумма>") - } - } - } - } - } + // Clean up tracking maps + INVOICES_AMOUNT.remove(invoiceId) + INVOICE_CREATION_TIMES.remove(invoiceId) + paymentIterator.remove() + + // Notify player if online + val player = Bukkit.getPlayer(entry.key) + if (player != null && player.isOnline) { + Utils.send(player, "&cВаша оплата истекла. Пожалуйста, создайте новую: /vpi convert lc <сумма>") + } + } + } + } + + // Cleanup sign invoices + synchronized(TO_PAY_SIGN_INVOICES) { + val paymentIterator = TO_PAY_SIGN_INVOICES.iterator() + while (paymentIterator.hasNext()) { + val entry = paymentIterator.next() + val invoiceId = entry.value + val creationTime = INVOICE_CREATION_TIMES[invoiceId] ?: continue + LOGGER.info("invoice ( $invoiceId ) left to live: ${timeoutMillis - (currentTime - creationTime)}") + + if (currentTime - creationTime > timeoutMillis) { + LOGGER.info("Deleting expired payment invoice: $invoiceId") + try { + VpcApi.delete_invoice(invoiceId) + } catch (e: Exception) { + LOGGER.error("Error deleting expired payment invoice $invoiceId: ${e.message}") + } + + // Clean up tracking maps + SIGN_PAYMENT_INFO.remove(invoiceId) + INVOICE_CREATION_TIMES.remove(invoiceId) + paymentIterator.remove() + + // Notify player if online + val player = Bukkit.getPlayer(entry.key) + if (player != null && player.isOnline) { + Utils.send(player, "&cВаша оплата истекла. Нажмите ещё раз на табличку.") + } + } + } + } + } } \ No newline at end of file