From b5a51fad9657298adb9f1b92cf8e329f1d22c017 Mon Sep 17 00:00:00 2001 From: Kariya Date: Fri, 12 Dec 2025 12:25:07 +0000 Subject: [PATCH] feat: add BlinkComponent and refine DoubleJumpComponent's glide event handling and GLIDER data component management. --- .../kotlin/net/hareworks/hcu/items/App.kt | 2 + .../content/components/BlinkComponent.kt | 254 ++++++++++++++++++ .../content/components/DoubleJumpComponent.kt | 7 + 3 files changed, 263 insertions(+) create mode 100644 src/main/kotlin/net/hareworks/hcu/items/content/components/BlinkComponent.kt diff --git a/src/main/kotlin/net/hareworks/hcu/items/App.kt b/src/main/kotlin/net/hareworks/hcu/items/App.kt index aaa433d..0a0c7fd 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/App.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/App.kt @@ -10,6 +10,7 @@ import net.hareworks.hcu.items.content.items.TestItem import net.hareworks.hcu.items.content.items.GrapplingItem import net.hareworks.hcu.items.content.components.GliderComponent import net.hareworks.hcu.items.content.components.DoubleJumpComponent +import net.hareworks.hcu.items.content.components.BlinkComponent import org.bukkit.permissions.PermissionDefault import org.bukkit.plugin.java.JavaPlugin @@ -39,6 +40,7 @@ public class App : JavaPlugin() { // Register Components ComponentRegistry.register(GliderComponent(this)) ComponentRegistry.register(DoubleJumpComponent(this)) + ComponentRegistry.register(BlinkComponent(this)) } } 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 new file mode 100644 index 0000000..09b4b50 --- /dev/null +++ b/src/main/kotlin/net/hareworks/hcu/items/content/components/BlinkComponent.kt @@ -0,0 +1,254 @@ +package net.hareworks.hcu.items.content.components + +import io.papermc.paper.datacomponent.DataComponentType +import io.papermc.paper.datacomponent.DataComponentTypes +import net.hareworks.hcu.items.App +import net.hareworks.hcu.items.api.Tier +import net.hareworks.hcu.items.api.component.AbstractComponent +import net.hareworks.hcu.items.api.component.EquippableComponent +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor +import org.bukkit.Material +import org.bukkit.Particle +import org.bukkit.Sound +import org.bukkit.entity.Player +import org.bukkit.event.entity.EntityToggleGlideEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.util.Vector +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType + +class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink_component"), EquippableComponent { + + override val displayName: String = "Blink" + override val maxTier: Int = 3 + + override val dataComponentDependencies: List + get() = listOf(DataComponentTypes.GLIDER) + + companion object { + // Track players who have used their blink (GLIDER is removed until landing) + private val usedBlink = ConcurrentHashMap.newKeySet() + + // Color scheme - Purple/Magenta for teleport/blink theme + private val PRIMARY_COLOR = TextColor.color(0xDA70D6) // Orchid + private val SECONDARY_COLOR = TextColor.color(0xE6E6FA) // Lavender + private val READY_COLOR = NamedTextColor.LIGHT_PURPLE + private val TRAIL_COLOR = TextColor.color(0x9400D3) // Dark Violet + } + + override fun apply(item: ItemStack, tier: Tier?) { + if (isBoots(item.type)) { + super.apply(item, tier) + } + } + + private fun isBoots(material: Material): Boolean { + return material.name.endsWith("_BOOTS") + } + + @Suppress("DEPRECATION") + override fun onTick(player: Player, item: ItemStack) { + // Ensure GLIDER is present if this component is on the item + if (!item.hasData(DataComponentTypes.GLIDER) && !usedBlink.contains(player.uniqueId)) { + item.setData(DataComponentTypes.GLIDER) + } + + // Reset blink when player lands on ground + if (player.isOnGround && usedBlink.contains(player.uniqueId)) { + usedBlink.remove(player.uniqueId) + + // Re-add GLIDER component + if (!item.hasData(DataComponentTypes.GLIDER)) { + item.setData(DataComponentTypes.GLIDER) + } + + // Landing feedback + showReadyMessage(player) + player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_BELL, 0.5f, 1.5f) + } + } + + override fun onToggleGlide(player: Player, item: ItemStack, event: EntityToggleGlideEvent) { + // Only process if this item actually has this component + if (!has(item)) return + + // Skip if another component already handled this event + if (event.isCancelled) return + + // Only handle glide start events + if (!event.isGliding) return + + // If player has already used blink, cancel the event to prevent vanilla gliding + if (usedBlink.contains(player.uniqueId)) { + item.unsetData(DataComponentTypes.GLIDER) + event.isCancelled = true + return + } + + // Cancel the glide start + event.isCancelled = true + + // Mark as used + usedBlink.add(player.uniqueId) + + // Execute Blink + handleBlink(player, item) + + // Remove GLIDER component to prevent further glide triggers + item.unsetData(DataComponentTypes.GLIDER) + } + + private fun handleBlink(player: Player, item: ItemStack) { + val tier = getTier(item) ?: Tier.ONE + val currentVelocity = player.velocity + + // Get movement direction from player's keyboard input + val inputDirection = getMovementDirectionFromInput(player) + + // Determine boost direction + val boostDirection: Vector + val usingLookDirection: Boolean + + if (inputDirection != null) { + // Player is pressing movement keys - use input direction + boostDirection = inputDirection + usingLookDirection = false + } else { + // Player is not pressing any movement keys - use look direction + val lookDirection = player.location.direction.clone() + boostDirection = Vector(lookDirection.x, 0.0, lookDirection.z).normalize() + usingLookDirection = true + } + + // Calculate boost power based on tier + val baseBoost = 1.2 + (tier.level * 0.3) // Base horizontal acceleration + + // Apply velocity boost - replace horizontal velocity with boost direction + val newVelocity = boostDirection.clone().multiply(baseBoost) + player.addPotionEffect(PotionEffect(PotionEffectType.SLOW_FALLING, 5, 1)) + player.velocity = newVelocity + + // Visual Effects + spawnBlinkParticles(player, boostDirection, usingLookDirection) + + // Sound Effects + player.playSound(player.location, Sound.ENTITY_ENDERMAN_TELEPORT, 0.6f, 1.8f) + player.playSound(player.location, Sound.ENTITY_BREEZE_SHOOT, 0.4f, 1.5f) + player.playSound(player.location, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.8f, 1.2f) + + // Action Bar message + showBlinkMessage(player, usingLookDirection) + } + + /** + * Calculate movement direction based on player's current keyboard input (W/A/S/D) + * combined with the player's facing direction. + * Returns null if no movement keys are pressed. + */ + private fun getMovementDirectionFromInput(player: Player): Vector? { + val input = player.currentInput + + // Check if any movement keys are pressed + val forward = input.isForward + val backward = input.isBackward + val left = input.isLeft + val right = input.isRight + + if (!forward && !backward && !left && !right) { + return null // No movement input + } + + // Get player's horizontal facing direction + val yaw = Math.toRadians(player.location.yaw.toDouble()) + + // Calculate forward vector (where player is looking horizontally) + val forwardX = -kotlin.math.sin(yaw) + val forwardZ = kotlin.math.cos(yaw) + + // Calculate right vector (perpendicular to forward) + val rightX = -forwardZ + val rightZ = forwardX + + // Build movement direction from input + var moveX = 0.0 + var moveZ = 0.0 + + if (forward) { + moveX += forwardX + moveZ += forwardZ + } + if (backward) { + moveX -= forwardX + moveZ -= forwardZ + } + if (left) { + moveX -= rightX + moveZ -= rightZ + } + if (right) { + moveX += rightX + moveZ += rightZ + } + + val result = Vector(moveX, 0.0, moveZ) + + // Normalize if there's any movement + if (result.lengthSquared() > 0) { + return result.normalize() + } + + return null + } + + private fun spawnBlinkParticles(player: Player, direction: Vector, usingLook: Boolean) { + val loc = player.location + val world = player.world + + // Origin burst - purple spiral + for (i in 0 until 16) { + val angle = (i * 22.5) * Math.PI / 180 + val radius = 0.5 + (i * 0.05) + val x = kotlin.math.cos(angle) * radius + val z = kotlin.math.sin(angle) * radius + val y = i * 0.08 + world.spawnParticle(Particle.REVERSE_PORTAL, loc.clone().add(x, y, z), 2, 0.0, 0.0, 0.0, 0.01) + } + + // Directional trail particles + val trailDirection = direction.clone().normalize() + for (i in 1..8) { + val trailPos = loc.clone().add(trailDirection.clone().multiply(i * 0.5)) + world.spawnParticle(Particle.ENCHANT, trailPos.add(0.0, 1.0, 0.0), 5, 0.1, 0.3, 0.1, 0.5) + world.spawnParticle(Particle.PORTAL, trailPos, 3, 0.1, 0.2, 0.1, 0.3) + } + + // Central effect + world.spawnParticle(Particle.DRAGON_BREATH, loc.clone().add(0.0, 1.0, 0.0), 10, 0.2, 0.3, 0.2, 0.02) + + // Look direction indicator if applicable + if (usingLook) { + world.spawnParticle(Particle.END_ROD, loc.clone().add(direction.clone().multiply(1.5)).add(0.0, 1.0, 0.0), 5, 0.1, 0.1, 0.1, 0.02) + } + } + + private fun showBlinkMessage(player: Player, usingLook: Boolean) { + val modeText = if (usingLook) "視線方向" else "進行方向" + player.sendActionBar( + Component.text("⚡ ", PRIMARY_COLOR) + .append(Component.text("ブリンク!", SECONDARY_COLOR)) + .append(Component.text(" [$modeText]", NamedTextColor.GRAY)) + .append(Component.text(" ⚡", PRIMARY_COLOR)) + ) + } + + private fun showReadyMessage(player: Player) { + player.sendActionBar( + Component.text("✓ ", READY_COLOR) + .append(Component.text("ブリンク準備完了", NamedTextColor.LIGHT_PURPLE)) + ) + } +} 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 bf9b468..2d903e9 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 @@ -73,6 +73,11 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, " } override fun onToggleGlide(player: Player, item: ItemStack, event: EntityToggleGlideEvent) { + // Only process if this item actually has this component + if (!has(item)) return + + // Skip if another component already handled this event + if (event.isCancelled) return // Only handle glide start events if (!event.isGliding) return @@ -80,6 +85,8 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, " // If player has already used double jump, don't cancel - let them glide normally // (though GLIDER should be removed, so this shouldn't fire anyway) if (usedDoubleJump.contains(player.uniqueId)) { + item.unsetData(DataComponentTypes.GLIDER) + event.isCancelled = true return }