Добавление покупки по табличке.
This commit is contained in:
parent
79d21942bb
commit
5bd184ec2a
163
src/main/kotlin/main/VpcSpigotIntegration/SignHandler.kt
Normal file
163
src/main/kotlin/main/VpcSpigotIntegration/SignHandler.kt
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -40,6 +43,7 @@ class VpcServerIntegration() : JavaPlugin(), CommandExecutor {
|
||||
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"
|
||||
@ -55,6 +59,7 @@ class VpcServerIntegration() : JavaPlugin(), CommandExecutor {
|
||||
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"
|
||||
@ -70,11 +75,17 @@ class VpcServerIntegration() : JavaPlugin(), CommandExecutor {
|
||||
var TO_AUTH_PLAYERS: MutableMap<String, String> = mutableMapOf() // Pair: {player: vpc_username}
|
||||
@JvmStatic
|
||||
var TO_AUTH_PLAYERS_INVOICES: MutableMap<String, String> = mutableMapOf() // Pair: {player: invoice_id}
|
||||
|
||||
@JvmStatic
|
||||
var TO_PAY_INVOICES: MutableMap<String, String> = mutableMapOf() // Pair: {player: invoice_id}
|
||||
@JvmStatic
|
||||
var INVOICES_AMOUNT: MutableMap<String, Double> = mutableMapOf() // How many should we pay to player after invoice?
|
||||
|
||||
@JvmStatic
|
||||
var TO_PAY_SIGN_INVOICES: MutableMap<String, String> = mutableMapOf() // Pair: {player: invoice_id}
|
||||
@JvmStatic
|
||||
var SIGN_PAYMENT_INFO: MutableMap<String, SignPaymentInfo> = mutableMapOf() // Information about amount, type and block
|
||||
|
||||
// Track invoice creation times for timeout handling
|
||||
@JvmStatic
|
||||
var INVOICE_CREATION_TIMES: MutableMap<String, Long> = mutableMapOf() // Pair: {invoice_id: creation_timestamp}
|
||||
@ -115,6 +126,7 @@ class VpcServerIntegration() : JavaPlugin(), CommandExecutor {
|
||||
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) {
|
||||
@ -184,6 +196,11 @@ 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()
|
||||
)
|
||||
}
|
||||
@ -201,6 +218,8 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
|
||||
// Register command executor
|
||||
getCommand("vpi")?.setExecutor(this)
|
||||
// Register clicking on sign
|
||||
server.pluginManager.registerEvents(SignHandler(), this)
|
||||
|
||||
// Background checks such a auth, invoice check, ...
|
||||
startBackgroundChecks()
|
||||
@ -563,6 +582,8 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
override fun run() {
|
||||
processAuthenticationInvoices()
|
||||
processPaymentInvoices()
|
||||
processSignInvoices()
|
||||
|
||||
cleanupExpiredInvoices()
|
||||
}
|
||||
}.runTaskTimerAsynchronously(this, 0L, 20L)
|
||||
@ -661,6 +682,69 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
val transferResult = VpcApi.transfer_coins(paymentInfo.dst_username, paymentInfo.cost).toString()
|
||||
if (transferResult != "OK") {
|
||||
LOGGER.error("Can't transfer VPC, result: $transferResult")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 expired invoices based on timeout configuration
|
||||
*/
|
||||
@ -729,5 +813,36 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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Ваша оплата истекла. Нажмите ещё раз на табличку.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user