diff --git a/build.gradle b/build.gradle index 6f2897d..3c70736 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ repositories { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "com.github.hazae41:mc-kutils:master-SNAPSHOT" + compileOnly "com.github.MilkBowl:VaultAPI:1.7" compileOnly "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT" } diff --git a/src/main/kotlin/pw.hamzantal.shopreborn/ClickListeners.kt b/src/main/kotlin/pw.hamzantal.shopreborn/ClickListeners.kt index c971ab2..7a83da4 100644 --- a/src/main/kotlin/pw.hamzantal.shopreborn/ClickListeners.kt +++ b/src/main/kotlin/pw.hamzantal.shopreborn/ClickListeners.kt @@ -1,7 +1,9 @@ package pw.hamzantal.shopreborn +import hazae41.minecraft.kutils.bukkit.execute +import hazae41.minecraft.kutils.bukkit.msg import org.bukkit.Bukkit -import org.bukkit.Material +import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import org.bukkit.event.inventory.ClickType.* import org.bukkit.event.inventory.InventoryClickEvent @@ -84,14 +86,26 @@ fun shopClick(e: InventoryClickEvent, shop: ShopConfig) { //Buy / Sell Item val block = shop.blocks.firstOrNull { it.item == e.currentItem } ?: return if (block is ShopConfig.Command) { - TODO() + block.commands.forEach { + if (it.contains("%PLAYER%")) + Bukkit.getConsoleSender().execute(it.replace("%PLAYER%", p.name)) + else p.chat(it) + } } - when (e.click) { - LEFT -> buy(e, p, shop, block as ShopConfig.Item) - RIGHT -> TODO() - MIDDLE -> TODO() - else -> return + if (block is ShopConfig.Item) { + when (e.click) { + LEFT -> buy(e, p, shop, block) + RIGHT -> sell(e, p, shop, block) + MIDDLE, SHIFT_LEFT -> { + val raw = block.raw + val howMany = p containing raw + val single = block.sell / block.item.amount + val op = Bukkit.getOfflinePlayer(p.uniqueId) + executeSell(raw, howMany, single, p, op) + } + else -> return + } } } @@ -99,6 +113,7 @@ fun clickInventory(e: InventoryClickEvent, pe: PurchaseEvent) { e.isCancelled = true val block = pe.block val origin = block.item + val p = pe.p val singleCost = if (pe.type == PurchaseType.BUY) pe.block.buy / origin.amount @@ -108,6 +123,10 @@ fun clickInventory(e: InventoryClickEvent, pe: PurchaseEvent) { if (pe.type == PurchaseType.BUY) GlobalConfig.messages.buyLore else GlobalConfig.messages.sellLore + val op = Bukkit.getOfflinePlayer(p.uniqueId) + if (!econ.hasAccount(op)) + econ.createPlayerAccount(op) + when (e.currentItem) { PurchaseItems.set1 -> { pe.item.amount = 1 @@ -137,8 +156,80 @@ fun clickInventory(e: InventoryClickEvent, pe: PurchaseEvent) { pe.item.amount = 64 pe.item.setLore(lore.mixPlaceholder(price = singleCost * 64)) } + PurchaseItems.cancel -> { + pe.p.openInventory(pe.shop.inventory) + purchases -= pe + } + PurchaseItems.sellAll -> { + val howMany = p containing block.raw + executeSell(block.raw, howMany, singleCost, p, op) + } } pe.inv.setItem(22, pe.item) + + if (e.currentItem == PurchaseItems.sellConfirm && pe.type == PurchaseType.SELL) { + val howMany = pe.item.amount + executeSell(block.raw, howMany, singleCost, p, op) + } + if (e.currentItem == PurchaseItems.buyConfirm && pe.type == PurchaseType.BUY) { + val item = block.raw.clone() + val name = if (item.itemMeta.hasDisplayName()) item.itemMeta.displayName else item.type.prettyName() + val balance = econ.getBalance(op) + val amount = pe.item.amount + val cost = singleCost * amount + + if (cost > balance) { + p.msg(GlobalConfig.messages.buyNotEnough) + return + } + + if (p.inventory.firstEmpty() == -1) { + p.msg(GlobalConfig.messages.buyInvFull) + return + } + + val r = econ.withdrawPlayer(op, cost) + if (r.transactionSuccess()) { + p.inventory.addItem(item.apply { this.amount = amount }) + p.msg( + GlobalConfig.messages.buySuccess + .replace("%AMOUNT%", amount.toString()) + .replace("%NAME%", name) + .replace("%COST%", cost.withCurrency()) + ) + + return + } + p.msg(GlobalConfig.messages.purchaseError.replace("%MSG%", r.errorMessage)) + } +} + +fun executeSell(item: ItemStack, howMany: Int, singleCost: Double, p: Player, op: OfflinePlayer) { + val name = if (item.itemMeta.hasDisplayName()) item.itemMeta.displayName else item.type.prettyName() + if (howMany <= 0) { + p.msg(GlobalConfig.messages.sellNotEnough.replace("%NAME%", name)) + return + } + + if (singleCost < 0) { + p.msg(GlobalConfig.messages.sellUnavailable) + return + } + + val total = singleCost * howMany + val r = econ.depositPlayer(op, total) + if (r.transactionSuccess()) { + p.inventory.removeItem(item.clone().apply { amount = howMany }) + p.msg( + GlobalConfig.messages.sellSuccess + .replace("%AMOUNT%", howMany.toString()) + .replace("%NAME%", name) + .replace("%COST%", total.withCurrency()) + ) + return + } + + p.msg(GlobalConfig.messages.purchaseError.replace("%MSG%", r.errorMessage)) } val base = Array(54) { null }.apply { @@ -150,19 +241,24 @@ val base = Array(54) { null }.apply { set(26, PurchaseItems.set64) } -fun Material.prettyName() = name.split("_").joinToString(" ") { it.toLowerCase().capitalize() } - fun buy(e: InventoryClickEvent, p: Player, shop: ShopConfig, block: ShopConfig.Item) { val item = e.currentItem.clone() - if (block.buy == -1.0) return + if (block.buy < 0) { + p.msg(GlobalConfig.messages.buyUnavailable) + return + } val name = if (item.itemMeta.hasDisplayName()) item.itemMeta.displayName else item.type.prettyName() - val inv = Bukkit.createInventory(null, 54, "&2Buying $name".c).apply { + val inv = Bukkit.createInventory( + null, + 54, + GlobalConfig.messages.buyShopTitle.replace("%NAME%", name).c + ).apply { contents = base.clone() val buyLore = GlobalConfig.messages.buyLore.mixPlaceholder(price = block.buy) - setItem(22, e.currentItem.setLore(buyLore)) + setItem(22, item.setLore(buyLore)) setItem(39, PurchaseItems.buyConfirm) setItem(41, PurchaseItems.cancel) } @@ -171,3 +267,33 @@ fun buy(e: InventoryClickEvent, p: Player, shop: ShopConfig, block: ShopConfig.I purchases.add(PurchaseEvent(inv, item, shop, p, block, PurchaseType.BUY)) } +fun sell(e: InventoryClickEvent, p: Player, shop: ShopConfig, block: ShopConfig.Item) { + val item = e.currentItem.clone() + if (block.sell == -1.0) { + p.msg(GlobalConfig.messages.sellUnavailable) + return + } + + val name = if (item.itemMeta.hasDisplayName()) item.itemMeta.displayName else item.type.prettyName() + + val inv = Bukkit.createInventory( + null, + 54, + GlobalConfig.messages.sellShopTitle.replace("%NAME%", name).c + ).apply { + contents = base.clone() + + val sellLore = GlobalConfig.messages.sellLore.mixPlaceholder(price = block.sell) + val possibleSellAll = p containing block.raw + val cost = block.sell / item.amount * possibleSellAll + val sellAllLore = GlobalConfig.messages.sellAllLore.mixPlaceholder(possibleSellAll, cost) + + setItem(22, item.setLore(sellLore)) + setItem(39, PurchaseItems.sellConfirm) + setItem(40, PurchaseItems.sellAll.setLore(sellAllLore)) + setItem(41, PurchaseItems.cancel) + } + + p.openInventory(inv) + purchases.add(PurchaseEvent(inv, item, shop, p, block, PurchaseType.SELL)) +} \ No newline at end of file diff --git a/src/main/kotlin/pw.hamzantal.shopreborn/Configuration.kt b/src/main/kotlin/pw.hamzantal.shopreborn/Configuration.kt index dcd62b2..a1c6c6f 100644 --- a/src/main/kotlin/pw.hamzantal.shopreborn/Configuration.kt +++ b/src/main/kotlin/pw.hamzantal.shopreborn/Configuration.kt @@ -60,6 +60,7 @@ class MainConfig(file: File) : ConfigFile(file) { } fun addShops() { + GlobalConfig.shops.clear() GlobalConfig.shops.addAll(shops!!.keys.map { ShopConfig( it, @@ -71,7 +72,7 @@ class MainConfig(file: File) : ConfigFile(file) { class ShopConfig(val name: String, file: File) : ConfigFile(file) { open class Block(val item: ItemStack) - class Item(item: ItemStack, val buy: Double, val sell: Double) : Block(item) + class Item(item: ItemStack, val raw: ItemStack, val buy: Double, val sell: Double) : Block(item) class Command(item: ItemStack, val asPlayer: Boolean, val commands: List) : Block(item) init { @@ -106,17 +107,18 @@ class ShopConfig(val name: String, file: File) : ConfigFile(file) { val type = it.getString("type") val buy = it.getDouble("buyPrice", -1.0) - val sell = it.getDouble("sellPrice", -.01) + val sell = it.getDouble("sellPrice", -1.0) val item = it.getConfigurationSection("item").asItem(buy, sell) when (type) { "command" -> Command( - item, + it.getConfigurationSection("item").asItem(), it.getBoolean("asplayer"), it.getStringList("commands") ) "item" -> Item( - item, + item.addLore(GlobalConfig.messages.itemLore), + it.getConfigurationSection("item").asItem(), buy, sell ) @@ -138,40 +140,3 @@ class ShopConfig(val name: String, file: File) : ConfigFile(file) { } } } - -fun makeInventory(size: Int, title: String, menuRow: Int = -1, items: List, page: Int): Inventory { - return Bukkit.createInventory(null, size, title.c).apply { - if (menuRow > 0) { - val buttons = GlobalConfig.main.buttons - for (i in menuRow until menuRow + 9) { - setItem(i, buttons.pane) - } - setItem(menuRow, buttons.previous) - setItem(menuRow + 4, buttons.menu) - setItem(menuRow + 8, buttons.forward) - } - - val dropping = (page - 1) * (size - max(0, menuRow)) - items.drop(dropping).forEach { - if (firstEmpty() != -1) setItem(firstEmpty(), it.item) - else return@apply - } - } -} - -fun ConfigurationSection.asItem(buy: Double = -1.0, sell: Double = -1.0): ItemStack { - val material = Material.valueOf(getString("material").toUpperCase()) - val item = ItemStack(material, getInt("quantity", 1), getInt("damage", 0).toShort()) - - item.itemMeta = item.itemMeta.apply { - displayName = getString("name", "").c - val configLore = getStringList("lore").map { it.c }.toMutableList() - val messages = GlobalConfig.messages - - if (buy > 0) configLore += messages.buyLore.mixPlaceholder(price = buy) - if (sell > 0) configLore += messages.sellLore.mixPlaceholder(price = sell) - - lore = configLore - } - return item -} \ No newline at end of file diff --git a/src/main/kotlin/pw.hamzantal.shopreborn/Messages.kt b/src/main/kotlin/pw.hamzantal.shopreborn/Messages.kt index acf9823..98ec10b 100644 --- a/src/main/kotlin/pw.hamzantal.shopreborn/Messages.kt +++ b/src/main/kotlin/pw.hamzantal.shopreborn/Messages.kt @@ -7,7 +7,7 @@ class Messages(file: File, pl: ShopReborn) : ConfigFile(file) { val currency by string("currency", "$") val buyLore by string("buyLore") - val sellLore by string("stringLore") + val sellLore by string("sellLore") val itemLore by string("itemLore") val set1 by string("set1") @@ -19,13 +19,26 @@ class Messages(file: File, pl: ShopReborn) : ConfigFile(file) { val set64 by string("set64") val sellConfirm by string("sell.confirm") - val sellAll by string("sell.sellAll") - val sellAllLore by string("sell.sellAllLore") + val sellAll by string("sell.all") + val sellAllLore by string("sell.allLore") val buyConfirm by string("buy.confirm") val cancel by string("cancel") + val buyShopTitle by string("title.buy") + val sellShopTitle by string("title.sell") + + val sellNotEnough by string("sell.notEnoughItems") + val sellUnavailable by string("sell.itemNotSellable") + val sellSuccess by string("sell.success") + + val buyNotEnough by string("buy.notEnoughBalance") + val buyInvFull by string("buy.invFull") + val buyUnavailable by string("buy.itemNotPurchasable") + val buySuccess by string("buy.success") + + val purchaseError by string("purchaseError") init { if (!file.exists()) { val messages = pl.getResource("messages.yml").bufferedReader().readLines().joinToString("\n") @@ -33,12 +46,4 @@ class Messages(file: File, pl: ShopReborn) : ConfigFile(file) { file.writeText(messages) } } -} - -fun String.mixPlaceholder(amount: Int = 0, price: Double = 0.0): String = - replace("%AMOUNT%", amount.toString()) - .replace( - "%PRICE%", - String.format("%s%.2f", GlobalConfig.messages.currency, price) - ) - .c \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/pw.hamzantal.shopreborn/PurchaseItems.kt b/src/main/kotlin/pw.hamzantal.shopreborn/PurchaseItems.kt index e1fd931..8e4d5ed 100644 --- a/src/main/kotlin/pw.hamzantal.shopreborn/PurchaseItems.kt +++ b/src/main/kotlin/pw.hamzantal.shopreborn/PurchaseItems.kt @@ -20,8 +20,12 @@ object PurchaseItems { } } + fun ItemStack.name(name: String) = apply { + itemMeta = itemMeta.apply { displayName = name.c } + } + - val msgs = GlobalConfig.messages + var msgs = GlobalConfig.messages val set1 = paneStack(14, msgs.set1) val sub10 = paneStack(14, msgs.sub10) val sub1 = paneStack(14, msgs.sub1) @@ -36,12 +40,20 @@ object PurchaseItems { val buyConfirm = blockStack(5, msgs.buyConfirm) val cancel = blockStack(14, msgs.cancel) -} -fun ItemStack.setLore(s: String): ItemStack { - return apply { - itemMeta = itemMeta.apply { - lore = listOf(s.c) - } + + fun reload() { + + msgs = GlobalConfig.messages + set1.name(msgs.set1) + sub10.name(msgs.sub10) + sub1.name(msgs.sub1) + add1.name(msgs.add1) + add10.name(msgs.add10) + set64.name(msgs.set64) + sellConfirm.name(msgs.sellConfirm) + sellAll.name(msgs.sellAll) + buyConfirm.name(msgs.buyConfirm) + cancel.name(msgs.cancel) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/pw.hamzantal.shopreborn/ShopReborn.kt b/src/main/kotlin/pw.hamzantal.shopreborn/ShopReborn.kt index 3677083..0b30411 100644 --- a/src/main/kotlin/pw.hamzantal.shopreborn/ShopReborn.kt +++ b/src/main/kotlin/pw.hamzantal.shopreborn/ShopReborn.kt @@ -2,20 +2,27 @@ package pw.hamzantal.shopreborn import hazae41.minecraft.kutils.bukkit.command import hazae41.minecraft.kutils.bukkit.listen +import hazae41.minecraft.kutils.bukkit.msg import hazae41.minecraft.kutils.get +import net.milkbowl.vault.economy.Economy import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin +import kotlin.system.measureTimeMillis + +lateinit var econ: Economy class ShopReborn : JavaPlugin() { override fun onEnable() { saveDefaultConfig() - GlobalConfig.dataFolder = dataFolder - GlobalConfig.messages = Messages(dataFolder["messages.yml"], this) - GlobalConfig.main = - MainConfig(dataFolder["config.yml"]) - GlobalConfig.main.addShops() + val rsp = + server.servicesManager.getRegistration( + Economy::class.java + ) ?: hazae41.minecraft.kutils.error("Economy not found".c) + econ = rsp.provider + + initConfig() listen(callback = ::baseListener) listen(callback = ::closeListener) @@ -27,8 +34,34 @@ class ShopReborn : JavaPlugin() { return@command } + if (args.component1() == "reload") { + if (sender.hasPermission("shop.reload")) sender.msg("&cPlugin reloaded in ${reload()}ms.") + else sender.msg("&cYou can't do this") + } + val shop = GlobalConfig.shops.firstOrNull { it.name == args.component1() } ?: return@command sender.openInventory(shop.inventory) } } + + fun initConfig() { + GlobalConfig.dataFolder = dataFolder + GlobalConfig.messages = Messages(dataFolder["messages.yml"], this) + GlobalConfig.main = + MainConfig(dataFolder["config.yml"]) + GlobalConfig.main.addShops() + } + + fun reload() = measureTimeMillis { + initConfig() + PurchaseItems.reload() + base.apply { + set(18, PurchaseItems.set1) + set(19, PurchaseItems.sub10) + set(20, PurchaseItems.sub1) + set(24, PurchaseItems.add1) + set(25, PurchaseItems.add10) + set(26, PurchaseItems.set64) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/pw.hamzantal.shopreborn/Util.kt b/src/main/kotlin/pw.hamzantal.shopreborn/Util.kt index 5ea1d6d..c58b734 100644 --- a/src/main/kotlin/pw.hamzantal.shopreborn/Util.kt +++ b/src/main/kotlin/pw.hamzantal.shopreborn/Util.kt @@ -1,6 +1,81 @@ package pw.hamzantal.shopreborn +import org.bukkit.Bukkit import org.bukkit.ChatColor +import org.bukkit.Material +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.entity.Player +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack -val String.c - get() = ChatColor.translateAlternateColorCodes('&', this) \ No newline at end of file +val String.c: String + get() = ChatColor.translateAlternateColorCodes('&', this) + +fun Material.prettyName() = name.split("_").joinToString(" ") { it.toLowerCase().capitalize() } + +fun ItemStack.setLore(s: String): ItemStack { + return apply { + itemMeta = itemMeta.apply { + lore = listOf(s.c) + } + } +} + +fun ItemStack.addLore(s: String): ItemStack { + return apply { + itemMeta = itemMeta.apply { + lore = lore + s.c + } + } +} + +fun makeInventory(size: Int, title: String, menuRow: Int = -1, items: List, page: Int): Inventory { + return Bukkit.createInventory(null, size, title.c).apply { + if (menuRow > 0) { + val buttons = GlobalConfig.main.buttons + for (i in menuRow until menuRow + 9) { + setItem(i, buttons.pane) + } + setItem(menuRow, buttons.previous) + setItem(menuRow + 4, buttons.menu) + setItem(menuRow + 8, buttons.forward) + } + + val dropping = (page - 1) * (size - kotlin.math.max(0, menuRow)) + items.drop(dropping).forEach { + if (firstEmpty() != -1) setItem(firstEmpty(), it.item) + else return@apply + } + } +} + +fun String.mixPlaceholder(amount: Int = 0, price: Double = 0.0): String = + replace("%AMOUNT%", amount.toString()) + .replace( + "%PRICE%", + price.withCurrency() + ) + .c + +fun Double.withCurrency() = String.format("%s%.2f", GlobalConfig.messages.currency, this) + +fun ConfigurationSection.asItem(buy: Double = -1.0, sell: Double = -1.0): ItemStack { + val material = Material.valueOf(getString("material").toUpperCase()) + val item = ItemStack(material, getInt("quantity", 1), getInt("damage", 0).toShort()) + + item.itemMeta = item.itemMeta.apply { + displayName = getString("name", "").c + val configLore = getStringList("lore").map { it.c }.toMutableList() + val messages = GlobalConfig.messages + + if (buy > 0) configLore += messages.buyLore.mixPlaceholder(price = buy) + if (sell > 0) configLore += messages.sellLore.mixPlaceholder(price = sell) + + lore = configLore + } + return item +} + +infix fun Player.containing(item: ItemStack): Int { + return inventory.contents.sumBy { if (it?.isSimilar(item) == true) it.amount else 0 } +} \ No newline at end of file diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index b9f2e3e..b397f99 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -10,13 +10,38 @@ sub1: "&c&lRemove 1" add1: "&a&lAdd 1" add10: "&a&lAdd 10" -set64: "&a&lSet 64" +set64: "&a&lSet to 64" + + +# Title for inventory +title: + buy: "&2Buying %NAME%" + sell: "&2Selling %NAME%" + -# Confirm for selling/buying (after selecting the item) sell: - confirm: "&a&lConfirm" - sellAll: "&a&lSell all" - sellAllLore: "&7Sell all for &a%PRICE%" + # Confirm stained glass block within sell menu + confirm: "&a&lConfirm Sell" + all: "&a&lSell all" + allLore: "&7Sell all (%AMOUNT%) for &a%PRICE%" + + + #Sell event player messages + notEnoughItems: "&c&lYou do not have enough %NAME%!" + itemNotSellable: "&cYou are not allowed to sell this item." + success: "&a&lSuccessfully sold %AMOUNT% %NAME% for %COST%" + buy: - confirm: "&a&lConfirm" -cancel: "&c&lCancel" \ No newline at end of file + # Confirm stained glass block within buy menu + confirm: "&a&lConfirm Purchase" + notEnoughBalance: "&c&lYou do not have enough balance!" + itemNotPurchasable: "&cYou are not allowed to purchase this item." + invFull: "&c&lYour inventory is full." + success: "&a&lSuccessfully purchased %AMOUNT% %NAME% for %COST%" + + +purchaseError: "&cAn error has occurred while completing the transaction: %MSG%" + +cancel: "&c&lCancel" + + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index bc58769..b969a06 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,6 +3,7 @@ version: 1.0.0 description: Custom Shop GUI Plugin created for Premiere Setups author: hhhapz main: pw.hamzantal.shopreborn.ShopReborn +depend: [Vault] commands: shop: description: "Shop GUI Basic command" \ No newline at end of file