feat: Add onToggleSneak event to CustomComponent and refactor GliderComponent activation to use it with a global ticker.

This commit is contained in:
Kariya 2025-12-10 12:42:53 +00:00
parent ee4ac4467e
commit d4e84fea1b
3 changed files with 217 additions and 173 deletions

View File

@ -17,4 +17,5 @@ interface CustomComponent {
fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {} fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {}
fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {} fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {}
fun onPlayerMove(event: org.bukkit.event.player.PlayerMoveEvent) {} fun onPlayerMove(event: org.bukkit.event.player.PlayerMoveEvent) {}
fun onToggleSneak(player: org.bukkit.entity.Player, item: org.bukkit.inventory.ItemStack, event: org.bukkit.event.player.PlayerToggleSneakEvent) {}
} }

View File

@ -13,17 +13,16 @@ import org.bukkit.block.data.Lightable
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.Damageable import org.bukkit.inventory.meta.Damageable
import org.bukkit.util.Vector
import org.bukkit.persistence.PersistentDataType import org.bukkit.persistence.PersistentDataType
import org.bukkit.util.Vector
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.math.sqrt import kotlin.math.sqrt
import kotlin.math.pow import kotlin.math.pow
class GliderComponent(plugin: App) : AbstractComponent(plugin, "glider_component"), EquippableComponent { class GliderComponent(private val plugin: App) : AbstractComponent(plugin, "glider_component"), EquippableComponent {
override val displayName: String = "Glider" override val displayName: String = "Glider"
// Custom maxTier for Glider
override val maxTier: Int = 10 override val maxTier: Int = 10
companion object { companion object {
@ -39,151 +38,145 @@ class GliderComponent(plugin: App) : AbstractComponent(plugin, "glider_component
) )
} }
// Dynamic Tier attributes
private fun getMaxDurability(tier: Tier): Int = (64 * 2.0.pow(tier.level - 1)).toInt()
private fun getFallSpeed(tier: Tier): Double {
// -0.08 base, getting closer to 0 by 0.015~ per level, capped at -0.01
// Simulating the map: 1->-0.08, 2->-0.065, 3->-0.05, 4->-0.04, 5->-0.03
return when (tier.level) {
1 -> -0.08
2 -> -0.065
3 -> -0.05
4 -> -0.04
5 -> -0.03
else -> (-0.03 + (tier.level - 5) * 0.005).coerceAtMost(-0.005)
}
}
private fun getHungerInterval(tier: Tier): Int = 40 + (tier.level - 1) * 20 // 1->40, 5->120... close approximation or simplier formula
data class GliderState( data class GliderState(
var ticksGliding: Int = 0, var ticksGliding: Int = 0,
var lastTickTime: Long = System.currentTimeMillis() var lastTickTime: Long = System.currentTimeMillis()
) )
override fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) { init {
val player = event.player // Global Ticker for active gliders
val item = event.item ?: return plugin.server.scheduler.runTaskTimer(plugin, Runnable {
if (activeGliders.isEmpty()) return@Runnable
// Only handle Right Click // Validate and Tick
if (event.action != org.bukkit.event.block.Action.RIGHT_CLICK_AIR && event.action != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK) return val iterator = activeGliders.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val uuid = entry.key
val state = entry.value
val player = plugin.server.getPlayer(uuid)
val tier = getTier(item) ?: Tier.ONE if (player == null) {
iterator.remove()
if (!canDeploy(player)) { continue
player.sendActionBar(Component.text("", NamedTextColor.RED).append(Component.text("空中にいる必要があります!", NamedTextColor.YELLOW)))
player.playSound(player.location, Sound.ENTITY_VILLAGER_NO, 0.5f, 1.0f)
return
} }
if (isBroken(item)) { val item = findGliderItem(player)
// Validation: Must have item equipped, not be grounded/blocked
if (item == null) {
disableGliderMemoryOnly(player) // Removed without item reference
iterator.remove() // Remove from map
continue
}
if (isFlyingBlocked(player)) {
disableGlider(player, item, silent = true)
continue
}
// Physics and Logic
tickGliderPhysics(player, item, state)
}
}, 1L, 1L)
}
override fun apply(item: ItemStack, tier: Tier?) {
if (isEquipment(item.type)) {
super.apply(item, tier)
}
}
override fun onToggleSneak(player: Player, item: ItemStack, event: org.bukkit.event.player.PlayerToggleSneakEvent) {
// Only trigger when STARTING to sneak
if (event.isSneaking) {
if (activeGliders.containsKey(player.uniqueId)) {
// If already gliding, stow it
disableGlider(player, item)
} else {
// If not gliding, verify conditions to deploy
if (canDeploy(player) && !isBroken(item)) {
enableGlider(player, item)
} else if (isBroken(item)) {
player.sendActionBar(Component.text("", NamedTextColor.RED).append(Component.text("グライダーが壊れています!", NamedTextColor.YELLOW))) player.sendActionBar(Component.text("", NamedTextColor.RED).append(Component.text("グライダーが壊れています!", NamedTextColor.YELLOW)))
player.playSound(player.location, Sound.ENTITY_ITEM_BREAK, 0.5f, 0.8f) player.playSound(player.location, Sound.ENTITY_ITEM_BREAK, 0.5f, 0.8f)
return
} }
val isEnabled = isGliderEnabled(item)
if (isEnabled) {
disableGlider(player, item)
player.sendActionBar(Component.text("", NamedTextColor.GRAY).append(Component.text("グライダー収納", NamedTextColor.YELLOW)))
player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 0.8f, 0.8f)
} else {
enableGlider(player, item)
player.sendActionBar(Component.text("", NamedTextColor.GREEN).append(Component.text("グライダー展開!", NamedTextColor.AQUA)).append(Component.text(" [Tier ${tier.level}]", tier.color)))
player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1.0f, 1.2f)
}
// event.isCancelled = true // Maybe? GliderItem did it.
}
override fun onPlayerMove(event: org.bukkit.event.player.PlayerMoveEvent) {
val player = event.player
val item = player.inventory.itemInMainHand
// Safety check if method called correctly
if (!has(item)) return
if (!isGliderEnabled(item)) return
if (isFlyingBlocked(player)) {
disableGlider(player, item, silent = true)
return
}
}
override fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
if (event.cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL) {
val entity = event.entity
if (entity is Player && activeGliders.containsKey(entity.uniqueId)) {
event.isCancelled = true
} }
} }
} }
override fun onTick(player: Player, item: ItemStack) { // Logic handled in global ticker now, but required by interface
if (activeGliders.containsKey(player.uniqueId)) { override fun onTick(player: Player, item: ItemStack) {}
val state = activeGliders[player.uniqueId]!!
// Try to get Tier from Component, fallback to Tier.ONE // Only allow attaching to equipment types
private fun isEquipment(material: Material): Boolean {
val name = material.name
return name.endsWith("_HELMET") || name.endsWith("_CHESTPLATE") ||
name.endsWith("_LEGGINGS") || name.endsWith("_BOOTS") ||
material == Material.ELYTRA || material == Material.SHIELD ||
material == Material.CARVED_PUMPKIN || material == Material.TURTLE_HELMET
}
// Find the itemStack that has this component in user's equipment
private fun findGliderItem(player: Player): ItemStack? {
val equipment = player.equipment ?: return null
val candidates = mutableListOf<ItemStack>()
candidates.addAll(equipment.armorContents.filterNotNull())
candidates.add(equipment.itemInMainHand)
candidates.add(equipment.itemInOffHand)
return candidates.find { has(it) && isGliderEnabled(it) }
}
private fun tickGliderPhysics(player: Player, item: ItemStack, state: GliderState) {
val tier = getTier(item) ?: Tier.ONE val tier = getTier(item) ?: Tier.ONE
if (isFlyingBlocked(player)) {
disableGlider(player, item, silent = true)
return
}
state.ticksGliding++ state.ticksGliding++
player.fallDistance = 0f player.fallDistance = 0f
// Updraft
if (checkAndApplyUpdraft(player)) { if (checkAndApplyUpdraft(player)) {
spawnUpdraftParticles(player) spawnUpdraftParticles(player)
return return
} }
// Physics
applyGlidingPhysics(player, tier) applyGlidingPhysics(player, tier)
// Effects
if (state.ticksGliding % 5 == 0) spawnGlidingParticles(player) if (state.ticksGliding % 5 == 0) spawnGlidingParticles(player)
if (state.ticksGliding % 10 == 0) updateGliderActionBar(player, item, tier, state) if (state.ticksGliding % 10 == 0) updateGliderActionBar(player, item, tier)
// Hunger
val hungerInterval = getHungerInterval(tier) val hungerInterval = getHungerInterval(tier)
if (state.ticksGliding % hungerInterval == 0) consumeHunger(player) if (state.ticksGliding % hungerInterval == 0) consumeHunger(player)
if (state.ticksGliding % DURABILITY_TICK_INTERVAL == 0) consumeDurability(player, item, tier) // Durability
} else { if (state.ticksGliding % DURABILITY_TICK_INTERVAL == 0) consumeDurability(player, item)
// Activation Logic for Equippable (Sneak + Jump/Drop)
if (player.isSneaking && canDeploy(player)) {
enableGlider(player, item)
}
}
} }
private fun enableGlider(player: Player, item: ItemStack, silent: Boolean = false) { private fun enableGlider(player: Player, item: ItemStack) {
val meta = item.itemMeta ?: return val meta = item.itemMeta ?: return
meta.persistentDataContainer.set(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN, true) meta.persistentDataContainer.set(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN, true)
item.itemMeta = meta item.itemMeta = meta
// Memory update
if (!activeGliders.containsKey(player.uniqueId)) { if (!activeGliders.containsKey(player.uniqueId)) {
val tier = getTier(item) ?: Tier.ONE val tier = getTier(item) ?: Tier.ONE
activeGliders[player.uniqueId] = GliderState() activeGliders[player.uniqueId] = GliderState()
// Visuals
if (!silent) {
player.sendActionBar(Component.text("", NamedTextColor.GREEN).append(Component.text("グライダー展開!", NamedTextColor.AQUA)).append(Component.text(" [Tier ${tier.level}]", tier.color))) player.sendActionBar(Component.text("", NamedTextColor.GREEN).append(Component.text("グライダー展開!", NamedTextColor.AQUA)).append(Component.text(" [Tier ${tier.level}]", tier.color)))
player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1.0f, 1.2f) player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1.0f, 1.2f)
} }
} }
}
private fun disableGlider(player: Player, item: ItemStack, silent: Boolean = false) { private fun disableGlider(player: Player, item: ItemStack, silent: Boolean = false) {
val meta = item.itemMeta ?: return val meta = item.itemMeta ?: return
meta.persistentDataContainer.set(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN, false) meta.persistentDataContainer.set(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN, false)
item.itemMeta = meta item.itemMeta = meta
// Memory update
if (activeGliders.containsKey(player.uniqueId)) { if (activeGliders.containsKey(player.uniqueId)) {
activeGliders.remove(player.uniqueId) activeGliders.remove(player.uniqueId)
// Visuals
if (!silent) { if (!silent) {
player.sendActionBar(Component.text("", NamedTextColor.GRAY).append(Component.text("グライダー収納", NamedTextColor.YELLOW))) player.sendActionBar(Component.text("", NamedTextColor.GRAY).append(Component.text("グライダー収納", NamedTextColor.YELLOW)))
player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 0.8f, 0.8f) player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 0.8f, 0.8f)
@ -191,11 +184,12 @@ class GliderComponent(plugin: App) : AbstractComponent(plugin, "glider_component
} }
} }
// Check if item has 'enabled' state in PDC private fun disableGliderMemoryOnly(player: Player) {
private fun isGliderEnabled(item: ItemStack): Boolean { if (activeGliders.containsKey(player.uniqueId)) {
if (item.type.isAir) return false player.sendActionBar(Component.text("", NamedTextColor.GRAY).append(Component.text("グライダー収納 (装備解除)", NamedTextColor.YELLOW)))
val meta = item.itemMeta ?: return false player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 0.8f, 0.8f)
return meta.persistentDataContainer.get(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN) ?: false // Removal from map happens in caller
}
} }
private fun canDeploy(player: Player): Boolean { private fun canDeploy(player: Player): Boolean {
@ -206,41 +200,17 @@ class GliderComponent(plugin: App) : AbstractComponent(plugin, "glider_component
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
val isInAir = !player.isOnGround && belowOne.type.isAir && belowTwo.type.isAir val isInAir = !player.isOnGround && belowOne.type.isAir && belowTwo.type.isAir
val isFalling = player.fallDistance > 1.5 val isFalling = player.fallDistance > 1.0
return isInAir || isFalling return isInAir || isFalling
} }
private fun isBroken(item: ItemStack): Boolean {
// Simple check: is it broken based on our custom max durability?
// Actually for now false is safe default if we don't strictly track custom durability item destruction
// Users might want custom durability handling -> consuming durability updates the item damage.
// If item is fully damaged (vanilla), it might break.
return false
}
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun isFlyingBlocked(player: Player): Boolean { private fun isFlyingBlocked(player: Player): Boolean {
return player.isOnGround || player.isInWater || player.isSwimming || (player.isGliding && !activeGliders.containsKey(player.uniqueId)) return player.isOnGround || player.isInWater || player.isSwimming || (player.isGliding && !activeGliders.containsKey(player.uniqueId))
} }
private fun checkAndApplyUpdraft(player: Player): Boolean { // Physics Helpers
val location = player.location
for (y in 0 until 10) {
val checkLoc = location.clone().subtract(0.0, y.toDouble(), 0.0)
val block = checkLoc.block
if (UPDRAFT_BLOCKS.contains(block.type)) {
val blockData = block.blockData
if (blockData is Lightable && !blockData.isLit) continue
player.velocity = Vector(player.velocity.x, UPDRAFT_BOOST, player.velocity.z)
player.playSound(player.location, Sound.BLOCK_FIRE_AMBIENT, 0.3f, 1.5f)
return true
}
}
return false
}
private fun applyGlidingPhysics(player: Player, tier: Tier) { private fun applyGlidingPhysics(player: Player, tier: Tier) {
val velocity = player.velocity val velocity = player.velocity
val fallSpeed = getFallSpeed(tier) val fallSpeed = getFallSpeed(tier)
@ -266,7 +236,6 @@ class GliderComponent(plugin: App) : AbstractComponent(plugin, "glider_component
horizontalDir.z * newHorizontalSpeed horizontalDir.z * newHorizontalSpeed
) )
} }
private fun spawnGlidingParticles(player: Player) { private fun spawnGlidingParticles(player: Player) {
player.world.spawnParticle(Particle.CLOUD, player.location.add(0.0, 2.0, 0.0), 1, 0.3, 0.0, 0.3, 0.01) player.world.spawnParticle(Particle.CLOUD, player.location.add(0.0, 2.0, 0.0), 1, 0.3, 0.0, 0.3, 0.01)
} }
@ -275,16 +244,55 @@ class GliderComponent(plugin: App) : AbstractComponent(plugin, "glider_component
player.world.spawnParticle(Particle.FLAME, player.location.add(0.0, 1.0, 0.0), 5, 0.3, 0.5, 0.3, 0.02) player.world.spawnParticle(Particle.FLAME, player.location.add(0.0, 1.0, 0.0), 5, 0.3, 0.5, 0.3, 0.02)
} }
private fun updateGliderActionBar(player: Player, item: ItemStack, tier: Tier, state: GliderState) { private fun checkAndApplyUpdraft(player: Player): Boolean {
val location = player.location
for (y in 0 until 10) {
val checkLoc = location.clone().subtract(0.0, y.toDouble(), 0.0)
val block = checkLoc.block
if (UPDRAFT_BLOCKS.contains(block.type)) {
val blockData = block.blockData
if (blockData is Lightable && !blockData.isLit) continue
player.velocity = Vector(player.velocity.x, UPDRAFT_BOOST, player.velocity.z)
player.playSound(player.location, Sound.BLOCK_FIRE_AMBIENT, 0.3f, 1.5f)
return true
}
}
return false
}
private fun consumeHunger(player: Player) {
player.exhaustion += HUNGER_EXHAUSTION
}
private fun consumeDurability(player: Player, item: ItemStack) {
val meta = item.itemMeta ?: return
if (meta is Damageable) {
val maxDurability = item.type.maxDurability
if (maxDurability <= 0) return
val currentDamage = meta.damage
if (currentDamage >= maxDurability - 1) {
meta.damage = maxDurability.toInt()
disableGlider(player, item)
player.playSound(player.location, Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f)
} else {
meta.damage = currentDamage + 1
}
item.itemMeta = meta
}
}
private fun updateGliderActionBar(player: Player, item: ItemStack, tier: Tier) {
val speed = player.velocity.length() * 20 val speed = player.velocity.length() * 20
val altitude = player.location.y val altitude = player.location.y
val meta = item.itemMeta val meta = item.itemMeta
val durabilityText = if (meta is Damageable) { val durabilityText = if (meta is Damageable) {
val maxDurability = getMaxDurability(tier) val maxDurability = item.type.maxDurability
val currentDamage = meta.damage val currentDamage = meta.damage
val remaining = maxDurability - currentDamage val remaining = (maxDurability - currentDamage).coerceAtLeast(0)
"$remaining/$maxDurability" if (maxDurability > 0) "$remaining/$maxDurability" else ""
} else { } else {
"" ""
} }
@ -299,23 +307,38 @@ class GliderComponent(plugin: App) : AbstractComponent(plugin, "glider_component
) )
} }
private fun consumeHunger(player: Player) { private fun getFallSpeed(tier: Tier): Double {
player.exhaustion += HUNGER_EXHAUSTION return when (tier.level) {
1 -> -0.08
2 -> -0.065
3 -> -0.05
4 -> -0.04
5 -> -0.03
else -> (-0.03 + (tier.level - 5) * 0.005).coerceAtMost(-0.005)
}
} }
private fun consumeDurability(player: Player, item: ItemStack, tier: Tier) { private fun getHungerInterval(tier: Tier): Int = 40 + (tier.level - 1) * 20
val meta = item.itemMeta ?: return
if (meta is Damageable) { override fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
val maxDamage = getMaxDurability(tier) if (event.cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL) {
val currentDamage = meta.damage val entity = event.entity
if (currentDamage >= maxDamage - 1) { if (entity is Player && activeGliders.containsKey(entity.uniqueId)) {
meta.damage = maxDamage event.isCancelled = true
disableGlider(player, item) // Use item aware disable
player.playSound(player.location, Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f)
} else {
meta.damage = currentDamage + 1
}
item.itemMeta = meta
} }
} }
} }
private fun isGliderEnabled(item: ItemStack): Boolean {
if (item.type.isAir) return false
val meta = item.itemMeta ?: return false
return meta.persistentDataContainer.get(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN) ?: false
}
private fun isBroken(item: ItemStack): Boolean {
val meta = item.itemMeta as? Damageable ?: return false
val maxDurability = item.type.maxDurability
if (maxDurability <= 0) return false
return meta.damage >= maxDurability
}
}

View File

@ -119,4 +119,24 @@ class EventListener(private val plugin: App) : Listener {
} }
} }
} }
@EventHandler
fun onToggleSneak(event: org.bukkit.event.player.PlayerToggleSneakEvent) {
val player = event.player
val equipment = player.equipment ?: return
val itemsToCheck = mutableListOf<org.bukkit.inventory.ItemStack>()
itemsToCheck.addAll(equipment.armorContents.filterNotNull())
itemsToCheck.add(equipment.itemInMainHand)
itemsToCheck.add(equipment.itemInOffHand)
for (item in itemsToCheck) {
if (item.type.isAir) continue
for (component in net.hareworks.hcu.items.registry.ComponentRegistry.getAll()) {
if (component.has(item)) {
component.onToggleSneak(player, item, event)
}
}
}
}
} }