From 41506f281aee40fc63191cc26cac309f1f2376ed Mon Sep 17 00:00:00 2001 From: TomTom <93038247+AverageGithub@users.noreply.github.com> Date: Sat, 16 Mar 2024 17:27:56 +0100 Subject: [PATCH] Add charge feature --- .../axminions/api/config/Config.kt | 10 ++++ .../axminions/api/config/Messages.kt | 14 +++++ .../axminions/api/minions/Minion.kt | 4 ++ .../api/minions/miniontype/MinionType.kt | 4 +- .../axminions/api/utils/TimeUtils.kt | 55 +++++++++++++++++++ .../axminions/api/warnings/Warnings.kt | 4 ++ .../api/warnings/impl/WarningNoCharge.kt | 13 +++++ .../axminions/data/H2DataHandler.kt | 13 ++++- .../listeners/MinionInventoryListener.kt | 35 +++++++++++- .../axminions/minions/Minion.kt | 54 ++++++++++++++++-- common/src/main/resources/config.yml | 22 +++++++- common/src/main/resources/messages.yml | 13 ++++- .../axminions/nms/v1_20_R3/DamageHandler.kt | 2 +- 13 files changed, 229 insertions(+), 14 deletions(-) create mode 100644 api/src/main/kotlin/com/artillexstudios/axminions/api/utils/TimeUtils.kt create mode 100644 api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/impl/WarningNoCharge.kt diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Config.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Config.kt index 88d741c..16dc25e 100644 --- a/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Config.kt +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Config.kt @@ -40,6 +40,16 @@ class Config(file: File, stream: InputStream) { @JvmStatic fun PRICES_HOOK() = AxMinionsAPI.INSTANCE.getConfig().get("hooks.prices", "ShopGUIPlus") @JvmStatic + fun CHARGE_ENABLED() = AxMinionsAPI.INSTANCE.getConfig().get("charge.enabled", false) + @JvmStatic + fun CHARGE_AMOUNT() = AxMinionsAPI.INSTANCE.getConfig().get("charge.amount", 1800) + @JvmStatic + fun MAX_CHARGE() = AxMinionsAPI.INSTANCE.getConfig().get("charge.max-charge", 1440) + @JvmStatic + fun CHARGE_PRICE() = AxMinionsAPI.INSTANCE.getConfig().get("charge.price", 10000.0) + @JvmStatic + fun TIMER_FORMAT() = AxMinionsAPI.INSTANCE.getConfig().get("timer-format", 1) + @JvmStatic fun GUI_SIZE() = AxMinionsAPI.INSTANCE.getConfig().get("gui.size") @JvmStatic fun PULL_FROM_CHEST() = AxMinionsAPI.INSTANCE.getConfig().get("pull-tools-from-chest", false) diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Messages.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Messages.kt index e0d67fd..38f520a 100644 --- a/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Messages.kt +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/config/Messages.kt @@ -27,6 +27,16 @@ class Messages(file: File, stream: InputStream) { @JvmStatic fun CONTAINER_FULL_WARNING() = AxMinionsAPI.INSTANCE.getMessages().get("warnings.container-full") @JvmStatic + fun TIME_DAY() = AxMinionsAPI.INSTANCE.getMessages().get("time.day") + @JvmStatic + fun TIME_HOUR() = AxMinionsAPI.INSTANCE.getMessages().get("time.hour") + @JvmStatic + fun TIME_MINUTE() = AxMinionsAPI.INSTANCE.getMessages().get("time.minute") + @JvmStatic + fun TIME_SECOND() = AxMinionsAPI.INSTANCE.getMessages().get("time.second") + @JvmStatic + fun NO_CHARGE_WARNING() = AxMinionsAPI.INSTANCE.getMessages().get("warnings.no-charge") + @JvmStatic fun RELOAD_SUCCESS() = AxMinionsAPI.INSTANCE.getMessages().get("reload") @JvmStatic fun PLACE_SUCCESS() = AxMinionsAPI.INSTANCE.getMessages().get("place.success") @@ -46,6 +56,10 @@ class Messages(file: File, stream: InputStream) { fun ROTATION_NAME(direction: Direction) = AxMinionsAPI.INSTANCE.getMessages().get("directions.${direction.name.lowercase( Locale.ENGLISH)}", direction.name) @JvmStatic + fun CHARGE() = AxMinionsAPI.INSTANCE.getMessages().get("charge.charge") + @JvmStatic + fun CHARGE_FAIL() = AxMinionsAPI.INSTANCE.getMessages().get("charge.not-enough-money") + @JvmStatic fun WRONG_TOOL() = AxMinionsAPI.INSTANCE.getMessages().get("tools.wrong-tool") @JvmStatic fun ERROR_INVENTORY_FULL() = AxMinionsAPI.INSTANCE.getMessages().get("errors.inventory-full") diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt index dfb20ec..9b8ce97 100644 --- a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/Minion.kt @@ -113,4 +113,8 @@ interface Minion : InventoryHolder { fun isOwnerOnline(): Boolean fun setOwnerOnline(online: Boolean) + + fun getCharge(): Long + + fun setCharge(charge: Long) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt index fc9a377..85a905d 100644 --- a/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/minions/miniontype/MinionType.kt @@ -73,11 +73,11 @@ abstract class MinionType(private val name: String, private val defaults: InputS } fun getDouble(key: String, level: Int): Double { - return get(key, level, -1.0, Double::class.java)!! + return get(key, level, 0.0, Double::class.java)!! } fun getLong(key: String, level: Int): Long { - return get(key, level, -1, Long::class.java)!! + return get(key, level, 0, Long::class.java)!! } fun getSection(key: String, level: Int): Section? { diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/utils/TimeUtils.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/utils/TimeUtils.kt new file mode 100644 index 0000000..3a034bc --- /dev/null +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/utils/TimeUtils.kt @@ -0,0 +1,55 @@ +package com.artillexstudios.axminions.api.utils + +import com.artillexstudios.axminions.api.config.Config +import com.artillexstudios.axminions.api.config.Messages +import java.time.Duration + +object TimeUtils { + + fun format(time: Long): String { + if (time < 0) return "---" + + val remainingTime: Duration = Duration.ofMillis(time) + val total: Long = remainingTime.seconds + val days = total / 86400 + val hours = (total % 86400) / 3600 + val minutes = (total % 3600) / 60 + val seconds = total % 60 + + when (Config.TIMER_FORMAT()) { + 1 -> { + if (days > 0) return String.format("%02d:%02d:%02d:%02d", days, hours, minutes, seconds) + if (hours > 0) return String.format("%02d:%02d:%02d", hours, minutes, seconds) + return String.format("%02d:%02d", minutes, seconds) + } + + 2 -> { + if (days > 0) return days.toString() + Messages.TIME_DAY() + if (hours > 0) return hours.toString() + Messages.TIME_HOUR() + if (minutes > 0) return minutes.toString() + Messages.TIME_MINUTE() + return seconds.toString() + Messages.TIME_SECOND() + } + + else -> { + if (days > 0) return java.lang.String.format( + ((("%02d" + Messages.TIME_DAY()) + " %02d" + Messages.TIME_HOUR()) + " %02d" + Messages.TIME_MINUTE()) + " %02d" + Messages.TIME_SECOND(), + days, + hours, + minutes, + seconds + ) + if (hours > 0) return java.lang.String.format( + (("%02d" + Messages.TIME_HOUR()) + " %02d" + Messages.TIME_MINUTE()) + " %02d" + Messages.TIME_SECOND(), + hours, + minutes, + seconds + ) + return java.lang.String.format( + ("%02d" + Messages.TIME_MINUTE()) + " %02d" + Messages.TIME_SECOND(), + minutes, + seconds + ) + } + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/Warnings.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/Warnings.kt index 5044edd..362045e 100644 --- a/api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/Warnings.kt +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/Warnings.kt @@ -2,6 +2,7 @@ package com.artillexstudios.axminions.api.warnings import com.artillexstudios.axminions.api.minions.Minion import com.artillexstudios.axminions.api.warnings.impl.WarningContainerFull +import com.artillexstudios.axminions.api.warnings.impl.WarningNoCharge import com.artillexstudios.axminions.api.warnings.impl.WarningNoContainer import com.artillexstudios.axminions.api.warnings.impl.WarningNoTool import com.artillexstudios.axminions.api.warnings.impl.WarningNoWaterNearby @@ -21,6 +22,9 @@ object Warnings { @JvmField val NO_WATER_NEARBY = register(WarningNoWaterNearby()) + @JvmField + val NO_CHARGE = register(WarningNoCharge()) + @JvmStatic fun register(warning: Warning): Warning { WARNINGS[warning.getName()] = warning diff --git a/api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/impl/WarningNoCharge.kt b/api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/impl/WarningNoCharge.kt new file mode 100644 index 0000000..7419fc6 --- /dev/null +++ b/api/src/main/kotlin/com/artillexstudios/axminions/api/warnings/impl/WarningNoCharge.kt @@ -0,0 +1,13 @@ +package com.artillexstudios.axminions.api.warnings.impl + +import com.artillexstudios.axapi.utils.StringUtils +import com.artillexstudios.axminions.api.config.Messages +import com.artillexstudios.axminions.api.warnings.Warning +import net.kyori.adventure.text.Component + +class WarningNoCharge : Warning("no_charge") { + + override fun getContent(): Component { + return StringUtils.format(Messages.NO_CHARGE_WARNING()) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/data/H2DataHandler.kt b/common/src/main/kotlin/com/artillexstudios/axminions/data/H2DataHandler.kt index 99e759a..828ad8f 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/data/H2DataHandler.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/data/H2DataHandler.kt @@ -67,6 +67,12 @@ class H2DataHandler : DataHandler { it.executeUpdate() } } + + dataSource.connection.use { connection -> + connection.prepareStatement("ALTER TABLE `axminions_minions` ADD COLUMN IF NOT EXISTS `charge` BIGINT DEFAULT(0);").use { + it.executeUpdate() + } + } } override fun insertType(minionType: MinionType) { @@ -105,6 +111,7 @@ class H2DataHandler : DataHandler { val storage = resultSet.getDouble("storage") val actions = resultSet.getLong("actions") val tool = resultSet.getString("tool") + val charge = resultSet.getLong("charge") val location = getLocation(locationId) var chestLocation: Location? = null @@ -129,7 +136,8 @@ class H2DataHandler : DataHandler { actions, storage, locationId, - chestLocationId + chestLocationId, + charge ) } } @@ -255,7 +263,7 @@ class H2DataHandler : DataHandler { } dataSource.connection.use { connection -> - connection.prepareStatement("MERGE INTO `axminions_minions`(`location_id`, `chest_location_id`, `owner_id`, `type_id`, `direction`, `level`, `storage`, `actions`, `tool`) KEY(`location_id`) VALUES(?,?,?,?,?,?,?,?,?)") + connection.prepareStatement("MERGE INTO `axminions_minions`(`location_id`, `chest_location_id`, `owner_id`, `type_id`, `direction`, `level`, `storage`, `actions`, `tool`, `charge`) KEY(`location_id`) VALUES(?,?,?,?,?,?,?,?,?,?)") .use { statement -> statement.setInt(1, locationId) if (linkedChestId == null) { @@ -274,6 +282,7 @@ class H2DataHandler : DataHandler { } else { statement.setString(9, Serializers.ITEM_STACK.serialize(minion.getTool())) } + statement.setLong(10, minion.getCharge()) statement.executeUpdate() } } diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionInventoryListener.kt b/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionInventoryListener.kt index ffe7987..608651a 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionInventoryListener.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/listeners/MinionInventoryListener.kt @@ -11,6 +11,7 @@ import com.artillexstudios.axminions.api.minions.miniontype.MinionTypes import com.artillexstudios.axminions.api.utils.CoolDown import com.artillexstudios.axminions.api.utils.Keys import com.artillexstudios.axminions.api.utils.fastFor +import java.util.Locale import net.md_5.bungee.api.ChatMessageType import net.md_5.bungee.api.chat.TextComponent import org.bukkit.Material @@ -146,7 +147,7 @@ class MinionInventoryListener : Listener { } if (Config.UPGRADE_SOUND().isNotBlank()) { - player.playSound(player, Config.UPGRADE_SOUND(), 1.0f, 1.0f) + player.playSound(player, Sound.valueOf(Config.UPGRADE_SOUND().uppercase(Locale.ENGLISH)), 1.0f, 1.0f) } minion.setLevel(minion.getLevel() + 1) @@ -171,6 +172,38 @@ class MinionInventoryListener : Listener { minion.setStorage(0.0) } } + + "charge" -> { + if ((AxMinionsPlugin.integrations.getEconomyIntegration()?.getBalance(player) + ?: return) < Config.CHARGE_PRICE() + ) { + player.sendMessage(StringUtils.formatToString(Messages.PREFIX() + Messages.CHARGE_FAIL())) + return + } + + AxMinionsPlugin.integrations.getEconomyIntegration()?.let { + minion.getOwner()?.let { player -> + it.takeBalance(player, Config.CHARGE_PRICE()) + } + } + + val chargeSeconds = (minion.getCharge() - System.currentTimeMillis()) / 1000 + + if (chargeSeconds + Config.CHARGE_AMOUNT() > Config.MAX_CHARGE() * 60L) { + minion.setCharge(System.currentTimeMillis() + Config.MAX_CHARGE() * 60L * 1000L) + return + } + + if (minion.getCharge() < System.currentTimeMillis()) { + minion.setCharge(System.currentTimeMillis() + Config.CHARGE_AMOUNT() * 1000) + } else { + minion.setCharge(minion.getCharge() + Config.CHARGE_AMOUNT() * 1000) + } + + if (Messages.CHARGE().isNotBlank()) { + player.sendMessage(StringUtils.formatToString(Messages.PREFIX() + Messages.CHARGE())) + } + } } minion.updateInventories() diff --git a/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt b/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt index 9a0dfcb..73993c1 100644 --- a/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt +++ b/common/src/main/kotlin/com/artillexstudios/axminions/minions/Minion.kt @@ -21,6 +21,7 @@ import com.artillexstudios.axminions.api.minions.Minion import com.artillexstudios.axminions.api.minions.miniontype.MinionType import com.artillexstudios.axminions.api.minions.miniontype.MinionTypes import com.artillexstudios.axminions.api.utils.Keys +import com.artillexstudios.axminions.api.utils.TimeUtils import com.artillexstudios.axminions.api.utils.fastFor import com.artillexstudios.axminions.api.warnings.Warning import com.artillexstudios.axminions.api.warnings.Warnings @@ -40,6 +41,7 @@ import org.bukkit.entity.Player import org.bukkit.inventory.Inventory import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Damageable +import org.bukkit.inventory.meta.ItemMeta import org.bukkit.persistence.PersistentDataType import org.bukkit.util.EulerAngle @@ -55,7 +57,8 @@ class Minion( private var actions: Long, private var storage: Double, private val locationID: Int, - private var chestLocationId: Int + private var chestLocationId: Int, + private var charge: Long ) : Minion { companion object { private val numberFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT) @@ -72,6 +75,7 @@ class Minion( private val extraData = hashMapOf() private var linkedInventory: Inventory? = null internal val openInventories = mutableListOf() + private var toolMeta: ItemMeta? = null @Volatile private var ticking = false @@ -188,6 +192,13 @@ class Minion( debugHologram?.setLine(0, StringUtils.format("Ticking: $ticking")) } + if (Config.CHARGE_ENABLED() && getCharge() < System.currentTimeMillis()) { + Warnings.NO_CHARGE.display(this) + return + } + + Warnings.remove(this, Warnings.NO_CHARGE) + Scheduler.get().executeAt(location) { type.tick(this) } @@ -207,7 +218,7 @@ class Minion( private fun updateInventory(inventory: Inventory) { AxMinionsAPI.INSTANCE.getConfig().getConfig().getSection("gui.items").getRoutesAsStrings(false).forEach { if (it.equals("filler")) return@forEach - val item: ItemStack + val item: ItemStack? if (it.equals("upgrade", true) || it.equals("statistics", true)) { val level = Placeholder.parsed("level", level.toString()) val nextLevel = Placeholder.parsed( @@ -287,6 +298,15 @@ class Minion( ).get() } else if (it.equals("item")) { item = tool?.clone() ?: ItemStack(Material.AIR) + } else if (it.equals("charge")) { + if (Config.CHARGE_ENABLED()) { + val charge = Placeholder.parsed("charge", TimeUtils.format(charge - System.currentTimeMillis())) + item = ItemBuilder(type.getConfig().getSection("gui.$it"), charge).storePersistentData( + Keys.GUI, PersistentDataType.STRING, it + ).get() + } else { + item = null + } } else { val rotation = Placeholder.unparsed("direction", Messages.ROTATION_NAME(direction)) val linked = Placeholder.unparsed( @@ -304,7 +324,9 @@ class Minion( ).get() } - inventory.setItem(AxMinionsAPI.INSTANCE.getConfig().get("gui.items.$it.slot"), item) + if (item != null) { + inventory.setItem(AxMinionsAPI.INSTANCE.getConfig().get("gui.items.$it.slot"), item) + } } } @@ -373,6 +395,12 @@ class Minion( override fun setTool(tool: ItemStack, save: Boolean) { this.tool = tool.clone() + toolMeta = if (!tool.type.isAir) { + tool.itemMeta + } else { + null + } + dirty = true if (this.tool?.type == Material.AIR) { @@ -456,6 +484,8 @@ class Minion( updateInventories() } + } else { + linkedInventory = null } AxMinionsPlugin.dataQueue.submit { @@ -595,7 +625,7 @@ class Minion( override fun damageTool(amount: Int) { if (!Config.USE_DURABILITY()) return - val meta = this.tool?.itemMeta as? Damageable ?: return + val meta = toolMeta as? Damageable ?: return if (Math.random() > 1f / (meta.getEnchantLevel(Enchantment.DURABILITY) + 1)) return @@ -641,7 +671,7 @@ class Minion( } override fun canUseTool(): Boolean { - val meta = this.tool?.itemMeta ?: return false + val meta = toolMeta ?: return false if (!Config.USE_DURABILITY() && meta is Damageable) { return true @@ -688,7 +718,7 @@ class Minion( } } - return false + return true } override fun isOwnerOnline(): Boolean { @@ -699,6 +729,18 @@ class Minion( ownerOnline = online } + override fun getCharge(): Long { + return charge + } + + override fun setCharge(charge: Long) { + this.charge = charge + + AxMinionsPlugin.dataQueue.submit { + AxMinionsPlugin.dataHandler.saveMinion(this) + } + } + override fun getInventory(): Inventory { return Bukkit.createInventory(this, 9) } diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml index 65e3e4b..5c8c1b6 100644 --- a/common/src/main/resources/config.yml +++ b/common/src/main/resources/config.yml @@ -67,6 +67,17 @@ hooks: # Supported prices providers: ShopGUIPlus, Essentials, EconomyShopGUI, CMI, custom prices: "ShopGUIPlus" +charge: + enabled: false + amount: 1800 # in seconds + max-charge: 1440 # in minutes + price: 10000 + +# 1 - HH:MM:SS, for example 01:25:35 +# 2 - short format, for example 20m +# 3 - text format, for example 01h 25m 35s +timer-format: 1 + gui: size: 27 items: @@ -100,10 +111,19 @@ gui: slot: 15 statistics: slot: 16 + charge: + type: "lava_bucket" + name: "<#FB8E08>Charge" + lore: + - "" + - " - Current charge: <#FFCC00>" + - "" + - "<#FB8E08>(!) Click here to charge your minion for 10 minutes for $10,000!" + slot: 9 # Only change this, if you are asked to! This will make your performance worse, and will spam messages in your console! # Requires restart! debug: false # Do not change! -config-version: 6 \ No newline at end of file +config-version: 7 \ No newline at end of file diff --git a/common/src/main/resources/messages.yml b/common/src/main/resources/messages.yml index 706122e..9245d03 100644 --- a/common/src/main/resources/messages.yml +++ b/common/src/main/resources/messages.yml @@ -23,6 +23,13 @@ warnings: no-tool: "<#FF3333>⚠ <#FFAAAA>The minion needs a tool ⚠" no-water-nearby: "<#FF3333>⚠ <#FFAAAA>The minion needs water nearby ⚠" container-full: "<#FF3333>⚠ <#FFAAAA>The container of this minion is full ⚠" + no-charge: "<#FF3333>⚠ <#FFAAAA>The minion ran out of charge! Refuel it!⚠" + +time: + day: "d" + hour: "h" + minute: "m" + second: "s" statistics: | Ticking minions: @@ -58,8 +65,12 @@ directions: south: "South" west: "West" +charge: + charge: "You have added 30 minutes of charge to your minion!" + not-enough-money: "You don't have enough money to charge this minion!" + tools: wrong-tool: "You can't place this item as the tool if this minion!" # Do not change! -config-version: 2 \ No newline at end of file +config-version: 3 \ No newline at end of file diff --git a/nms/v1_20_R3/src/main/kotlin/com/artillexstudios/axminions/nms/v1_20_R3/DamageHandler.kt b/nms/v1_20_R3/src/main/kotlin/com/artillexstudios/axminions/nms/v1_20_R3/DamageHandler.kt index 38c7820..9b302bd 100644 --- a/nms/v1_20_R3/src/main/kotlin/com/artillexstudios/axminions/nms/v1_20_R3/DamageHandler.kt +++ b/nms/v1_20_R3/src/main/kotlin/com/artillexstudios/axminions/nms/v1_20_R3/DamageHandler.kt @@ -63,7 +63,7 @@ object DamageHandler { DUMMY_ENTITY?.setItemSlot(EquipmentSlot.MAINHAND, nmsItem) - if (!nmsEntity.isAttackable) return + if (!nmsEntity.isAttackable || entity is Player) return val f2 = 1.0f var f1 = if (nmsEntity is LivingEntity) {