feat: Add Cooldown utility and integrate into Blink, Double Jump, and Grappling Hook.

This commit is contained in:
Kariya 2025-12-14 06:45:17 +00:00
parent e5d89e41b6
commit 9cc0e03c10
4 changed files with 107 additions and 0 deletions

View File

@ -20,6 +20,9 @@ import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType 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 { 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 { companion object {
// Track players who have used their blink (GLIDER is removed until landing) // Track players who have used their blink (GLIDER is removed until landing)
private val usedBlink = ConcurrentHashMap.newKeySet<UUID>() private val usedBlink = ConcurrentHashMap.newKeySet<UUID>()
private val cooldown = Cooldown(1500)
// Color scheme - Purple/Magenta for teleport/blink theme // Color scheme - Purple/Magenta for teleport/blink theme
private val PRIMARY_COLOR = TextColor.color(0xDA70D6) // Orchid private val PRIMARY_COLOR = TextColor.color(0xDA70D6) // Orchid
@ -67,8 +71,14 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink
} }
// Landing feedback // Landing feedback
}
// Check for cooldown finish
if (cooldown.checkFinished(player)) {
showReadyMessage(player) showReadyMessage(player)
player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_BELL, 0.5f, 1.5f) player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_BELL, 0.5f, 1.5f)
// Allow re-use immediately
usedBlink.remove(player.uniqueId)
} }
} }
@ -89,12 +99,22 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink
return return
} }
// Check cooldown
if (!cooldown.checkAndWarn(player)) {
item.unsetData(DataComponentTypes.GLIDER)
event.isCancelled = true
return
}
// Cancel the glide start // Cancel the glide start
event.isCancelled = true event.isCancelled = true
// Mark as used // Mark as used
usedBlink.add(player.uniqueId) usedBlink.add(player.uniqueId)
// Start cooldown immediately
cooldown.start(player)
// Execute Blink // Execute Blink
handleBlink(player, item) handleBlink(player, item)

View File

@ -19,6 +19,9 @@ import org.bukkit.inventory.ItemStack
import org.bukkit.util.Vector import org.bukkit.util.Vector
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap 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 { 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 { companion object {
// Track players who have used their double jump (GLIDER is removed) // Track players who have used their double jump (GLIDER is removed)
private val usedDoubleJump = ConcurrentHashMap.newKeySet<UUID>() private val usedDoubleJump = ConcurrentHashMap.newKeySet<UUID>()
private val cooldown = Cooldown(1500)
// Color scheme // Color scheme
private val PRIMARY_COLOR = TextColor.color(0x7DF9FF) // Electric Cyan private val PRIMARY_COLOR = TextColor.color(0x7DF9FF) // Electric Cyan
@ -67,6 +71,10 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, "
} }
// Landing feedback // Landing feedback
}
// Check for cooldown finish
if (cooldown.checkFinished(player)) {
showReadyMessage(player) showReadyMessage(player)
player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_CHIME, 0.5f, 1.5f) player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_CHIME, 0.5f, 1.5f)
} }
@ -90,6 +98,13 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, "
return return
} }
// Check cooldown
if (!cooldown.checkAndWarn(player)) {
item.unsetData(DataComponentTypes.GLIDER)
event.isCancelled = true
return
}
// Cancel the glide start // Cancel the glide start
event.isCancelled = true event.isCancelled = true
@ -98,6 +113,7 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, "
// Execute Double Jump // Execute Double Jump
handleDoubleJump(player, item) handleDoubleJump(player, item)
cooldown.start(player)
// Remove GLIDER component to prevent further glide triggers // Remove GLIDER component to prevent further glide triggers
item.unsetData(DataComponentTypes.GLIDER) item.unsetData(DataComponentTypes.GLIDER)

View File

@ -8,6 +8,9 @@ import org.bukkit.Location
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.event.player.PlayerFishEvent import org.bukkit.event.player.PlayerFishEvent
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import net.hareworks.hcu.items.util.Cooldown
import kotlin.time.Duration.Companion.seconds
class GrapplingItem : AbstractItem("grappling_hook") { 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) val isStuck = hook.persistentDataContainer.has(KEY_HOOK_STUCK, org.bukkit.persistence.PersistentDataType.BYTE)
if (event.state == PlayerFishEvent.State.REEL_IN && isStuck) { if (event.state == PlayerFishEvent.State.REEL_IN && isStuck) {
if (!cooldown.checkAndWarn(player)) {
return
}
cooldown.start(player)
val playerLoc = player.location val playerLoc = player.location
val hookLoc = hook.location val hookLoc = hook.location
@ -236,5 +244,6 @@ class GrapplingItem : AbstractItem("grappling_hook") {
companion object { companion object {
val KEY_HOOK_STUCK = org.bukkit.NamespacedKey("hcu_items", "hook_stuck") val KEY_HOOK_STUCK = org.bukkit.NamespacedKey("hcu_items", "hook_stuck")
val KEY_ANCHOR_ID = org.bukkit.NamespacedKey("hcu_items", "anchor_id") val KEY_ANCHOR_ID = org.bukkit.NamespacedKey("hcu_items", "anchor_id")
private val cooldown = Cooldown(2000)
} }
} }

View File

@ -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<UUID, Long>()
private val activeCooldowns = ConcurrentHashMap.newKeySet<UUID>()
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)
}
}