From 9cc0e03c1016b305b06f280f53c834c7ad34ab1b Mon Sep 17 00:00:00 2001 From: Kariya Date: Sun, 14 Dec 2025 06:45:17 +0000 Subject: [PATCH] feat: Add Cooldown utility and integrate into Blink, Double Jump, and Grappling Hook. --- .../content/components/BlinkComponent.kt | 20 ++++++ .../content/components/DoubleJumpComponent.kt | 16 +++++ .../hcu/items/content/items/GrapplingItem.kt | 9 +++ .../net/hareworks/hcu/items/util/Cooldown.kt | 62 +++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 src/main/kotlin/net/hareworks/hcu/items/util/Cooldown.kt diff --git a/src/main/kotlin/net/hareworks/hcu/items/content/components/BlinkComponent.kt b/src/main/kotlin/net/hareworks/hcu/items/content/components/BlinkComponent.kt index 09b4b50..c6e6df3 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/content/components/BlinkComponent.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/content/components/BlinkComponent.kt @@ -20,6 +20,9 @@ import java.util.UUID import java.util.concurrent.ConcurrentHashMap import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType +import net.hareworks.hcu.items.util.Cooldown +import kotlin.time.Duration.Companion.milliseconds + class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink_component"), EquippableComponent { @@ -32,6 +35,7 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink companion object { // Track players who have used their blink (GLIDER is removed until landing) private val usedBlink = ConcurrentHashMap.newKeySet() + private val cooldown = Cooldown(1500) // Color scheme - Purple/Magenta for teleport/blink theme private val PRIMARY_COLOR = TextColor.color(0xDA70D6) // Orchid @@ -67,8 +71,14 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink } // Landing feedback + } + + // Check for cooldown finish + if (cooldown.checkFinished(player)) { showReadyMessage(player) player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_BELL, 0.5f, 1.5f) + // Allow re-use immediately + usedBlink.remove(player.uniqueId) } } @@ -88,6 +98,13 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink event.isCancelled = true return } + + // Check cooldown + if (!cooldown.checkAndWarn(player)) { + item.unsetData(DataComponentTypes.GLIDER) + event.isCancelled = true + return + } // Cancel the glide start event.isCancelled = true @@ -95,6 +112,9 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink // Mark as used usedBlink.add(player.uniqueId) + // Start cooldown immediately + cooldown.start(player) + // Execute Blink handleBlink(player, item) diff --git a/src/main/kotlin/net/hareworks/hcu/items/content/components/DoubleJumpComponent.kt b/src/main/kotlin/net/hareworks/hcu/items/content/components/DoubleJumpComponent.kt index 2d903e9..763fbce 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/content/components/DoubleJumpComponent.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/content/components/DoubleJumpComponent.kt @@ -19,6 +19,9 @@ import org.bukkit.inventory.ItemStack import org.bukkit.util.Vector import java.util.UUID import java.util.concurrent.ConcurrentHashMap +import net.hareworks.hcu.items.util.Cooldown +import kotlin.time.Duration.Companion.milliseconds + class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, "double_jump_component"), EquippableComponent { @@ -31,6 +34,7 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, " companion object { // Track players who have used their double jump (GLIDER is removed) private val usedDoubleJump = ConcurrentHashMap.newKeySet() + private val cooldown = Cooldown(1500) // Color scheme private val PRIMARY_COLOR = TextColor.color(0x7DF9FF) // Electric Cyan @@ -67,6 +71,10 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, " } // Landing feedback + } + + // Check for cooldown finish + if (cooldown.checkFinished(player)) { showReadyMessage(player) player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_CHIME, 0.5f, 1.5f) } @@ -89,6 +97,13 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, " event.isCancelled = true return } + + // Check cooldown + if (!cooldown.checkAndWarn(player)) { + item.unsetData(DataComponentTypes.GLIDER) + event.isCancelled = true + return + } // Cancel the glide start event.isCancelled = true @@ -98,6 +113,7 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, " // Execute Double Jump handleDoubleJump(player, item) + cooldown.start(player) // Remove GLIDER component to prevent further glide triggers item.unsetData(DataComponentTypes.GLIDER) diff --git a/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt b/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt index 2ff3b83..a519181 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt @@ -8,6 +8,9 @@ import org.bukkit.Location import org.bukkit.Material import org.bukkit.event.player.PlayerFishEvent import org.bukkit.inventory.ItemStack +import net.hareworks.hcu.items.util.Cooldown +import kotlin.time.Duration.Companion.seconds + class GrapplingItem : AbstractItem("grappling_hook") { @@ -49,6 +52,11 @@ class GrapplingItem : AbstractItem("grappling_hook") { val isStuck = hook.persistentDataContainer.has(KEY_HOOK_STUCK, org.bukkit.persistence.PersistentDataType.BYTE) if (event.state == PlayerFishEvent.State.REEL_IN && isStuck) { + if (!cooldown.checkAndWarn(player)) { + return + } + cooldown.start(player) + val playerLoc = player.location val hookLoc = hook.location @@ -236,5 +244,6 @@ class GrapplingItem : AbstractItem("grappling_hook") { companion object { val KEY_HOOK_STUCK = org.bukkit.NamespacedKey("hcu_items", "hook_stuck") val KEY_ANCHOR_ID = org.bukkit.NamespacedKey("hcu_items", "anchor_id") + private val cooldown = Cooldown(2000) } } diff --git a/src/main/kotlin/net/hareworks/hcu/items/util/Cooldown.kt b/src/main/kotlin/net/hareworks/hcu/items/util/Cooldown.kt new file mode 100644 index 0000000..4747b89 --- /dev/null +++ b/src/main/kotlin/net/hareworks/hcu/items/util/Cooldown.kt @@ -0,0 +1,62 @@ +package net.hareworks.hcu.items.util + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import org.bukkit.Sound +import org.bukkit.entity.Player +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit + +class Cooldown(private val defaultDurationMs: Long) { + private val lastUsed = ConcurrentHashMap() + private val activeCooldowns = ConcurrentHashMap.newKeySet() + + fun start(player: Player, durationMs: Long = defaultDurationMs) { + val expire = System.currentTimeMillis() + durationMs + lastUsed[player.uniqueId] = expire + activeCooldowns.add(player.uniqueId) + } + + fun isReady(player: Player): Boolean { + val expireTime = lastUsed[player.uniqueId] ?: return true + val current = System.currentTimeMillis() + val ready = current >= expireTime + return ready + } + + fun getRemainingSeconds(player: Player): Double { + val expireTime = lastUsed[player.uniqueId] ?: return 0.0 + val diff = expireTime - System.currentTimeMillis() + if (diff <= 0) return 0.0 + return diff / 1000.0 + } + + fun checkAndWarn(player: Player): Boolean { + if (isReady(player)) return true + + val remaining = getRemainingSeconds(player) + player.sendActionBar( + Component.text("⏳ ", NamedTextColor.RED) + .append(Component.text("クールダウン中... ", NamedTextColor.GRAY)) + .append(Component.text(String.format("%.1fs", remaining), NamedTextColor.RED)) + ) + player.playSound(player.location, Sound.UI_BUTTON_CLICK, 0.5f, 0.5f) // Failure sound + return false + } + + fun checkFinished(player: Player): Boolean { + if (activeCooldowns.contains(player.uniqueId) && isReady(player)) { + activeCooldowns.remove(player.uniqueId) + return true + } + return false + } + + fun clear(player: Player) { + lastUsed.remove(player.uniqueId) + activeCooldowns.remove(player.uniqueId) + } +}