feat: Improve Glider item user feedback with action bar messages, sounds, and particles, add a detailed gliding status display, and update Grappling Hook lore with tier-based stats.

This commit is contained in:
Kariya 2025-12-08 13:20:41 +00:00
parent 7cc8ccc395
commit 283961652c
2 changed files with 227 additions and 20 deletions

View File

@ -131,15 +131,21 @@ class GliderItem : SpecialItem("glider") {
val item = event.item ?: return val item = event.item ?: return
val tier = SpecialItem.getTier(item) val tier = SpecialItem.getTier(item)
if (!canDeploy(player)) { if (!canDeploy(player)) {
player.sendMessage(Component.text("グライダーを展開するには空中にいる必要があります!", NamedTextColor.RED)) player.sendActionBar(
Component.text("", NamedTextColor.RED)
.append(Component.text("空中にいる必要があります!", NamedTextColor.YELLOW))
)
player.playSound(player.location, Sound.ENTITY_VILLAGER_NO, 0.5f, 1.0f)
return return
} }
if (isBroken(item)) { if (isBroken(item)) {
player.sendMessage(Component.text("グライダーが壊れています!修理が必要です。", NamedTextColor.RED)) player.sendActionBar(
Component.text("", NamedTextColor.RED)
.append(Component.text("グライダーが壊れています!", NamedTextColor.YELLOW))
)
player.playSound(player.location, Sound.ENTITY_ITEM_BREAK, 0.5f, 0.8f)
return return
} }
@ -147,14 +153,47 @@ class GliderItem : SpecialItem("glider") {
val isEnabled = meta.persistentDataContainer.get(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN) ?: false val isEnabled = meta.persistentDataContainer.get(KEY_GLIDER_ENABLED, PersistentDataType.BOOLEAN) ?: false
if (isEnabled) { if (isEnabled) {
disableGlider(player, item) disableGlider(player, item)
player.sendMessage(Component.text("グライダーを収納しました", NamedTextColor.YELLOW))
} else {
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.BLOCK_WOOL_PLACE, 1.0f, 1.2f)
player.world.spawnParticle(
Particle.CLOUD,
player.location.add(0.0, 1.5, 0.0),
10,
0.3, 0.3, 0.3,
0.02
)
} else {
enableGlider(player, item) enableGlider(player, item)
player.sendMessage(Component.text("グライダーを展開しました!", NamedTextColor.GREEN))
player.playSound(player.location, Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1.0f, 1.0f) 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.ENTITY_PHANTOM_FLAP, 0.7f, 1.5f)
player.world.spawnParticle(
Particle.CLOUD,
player.location.add(0.0, 1.5, 0.0),
20,
0.5, 0.3, 0.5,
0.05
)
player.world.spawnParticle(
Particle.FIREWORK,
player.location.add(0.0, 1.5, 0.0),
5,
0.3, 0.3, 0.3,
0.02
)
} }
event.isCancelled = true event.isCancelled = true
@ -206,6 +245,10 @@ class GliderItem : SpecialItem("glider") {
spawnGlidingParticles(player) spawnGlidingParticles(player)
} }
if (state.ticksGliding % 10 == 0) {
updateGliderActionBar(player, item, tier, state)
}
val hungerInterval = TIER_HUNGER_INTERVAL[tier] ?: 80 val hungerInterval = TIER_HUNGER_INTERVAL[tier] ?: 80
if (state.ticksGliding % hungerInterval == 0) { if (state.ticksGliding % hungerInterval == 0) {
@ -218,6 +261,43 @@ class GliderItem : SpecialItem("glider") {
} }
} }
private fun updateGliderActionBar(player: Player, item: ItemStack, tier: Tier, state: GliderState) {
val meta = item.itemMeta
val currentDurability = if (meta is Damageable) {
val maxDamage = TIER_MAX_DURABILITY[tier] ?: 64
maxDamage - meta.damage
} else {
0
}
val maxDurability = TIER_MAX_DURABILITY[tier] ?: 64
val durabilityPercent = (currentDurability.toDouble() / maxDurability * 100).toInt()
val durabilityColor = when {
durabilityPercent > 66 -> NamedTextColor.GREEN
durabilityPercent > 33 -> NamedTextColor.YELLOW
else -> NamedTextColor.RED
}
val altitude = player.location.y.toInt()
val velocity = player.velocity
val speed = sqrt(velocity.x * velocity.x + velocity.z * velocity.z)
player.sendActionBar(
Component.text("", NamedTextColor.AQUA)
.append(Component.text("滑空中 ", NamedTextColor.WHITE))
.append(Component.text("| ", NamedTextColor.DARK_GRAY))
.append(Component.text("速度: ", NamedTextColor.GRAY))
.append(Component.text(String.format("%.1f", speed * 20), tier.color))
.append(Component.text(" m/s ", NamedTextColor.GRAY))
.append(Component.text("| ", NamedTextColor.DARK_GRAY))
.append(Component.text("高度: ", NamedTextColor.GRAY))
.append(Component.text("${altitude}m ", NamedTextColor.YELLOW))
.append(Component.text("| ", NamedTextColor.DARK_GRAY))
.append(Component.text("耐久: ", NamedTextColor.GRAY))
.append(Component.text("${durabilityPercent}%", durabilityColor))
)
}
private fun applyGlidingPhysics(player: Player, tier: Tier) { private fun applyGlidingPhysics(player: Player, tier: Tier) {
val velocity = player.velocity val velocity = player.velocity
val fallSpeed = TIER_FALL_SPEED[tier] ?: -0.05 val fallSpeed = TIER_FALL_SPEED[tier] ?: -0.05
@ -255,7 +335,6 @@ class GliderItem : SpecialItem("glider") {
maxOf(velocity.y, fallSpeed), maxOf(velocity.y, fallSpeed),
horizontalDir.z * newHorizontalSpeed horizontalDir.z * newHorizontalSpeed
) )
player.sendActionBar(Component.text("Gliding at ${newHorizontalSpeed} m/s", NamedTextColor.GREEN))
player.velocity = newVelocity player.velocity = newVelocity
} }
@ -290,6 +369,15 @@ class GliderItem : SpecialItem("glider") {
player.velocity = Vector(player.velocity.x, UPDRAFT_BOOST, player.velocity.z) player.velocity = Vector(player.velocity.x, UPDRAFT_BOOST, player.velocity.z)
player.playSound(player.location, Sound.BLOCK_FIRE_AMBIENT, 0.3f, 1.5f)
player.sendActionBar(
Component.text("🔥 ", NamedTextColor.GOLD)
.append(Component.text("上昇気流!", NamedTextColor.YELLOW))
.append(Component.text("", NamedTextColor.GREEN))
)
return true return true
} }
} }
@ -307,6 +395,15 @@ class GliderItem : SpecialItem("glider") {
continue continue
} }
player.velocity = Vector(player.velocity.x, UPDRAFT_BOOST, player.velocity.z) player.velocity = Vector(player.velocity.x, UPDRAFT_BOOST, player.velocity.z)
player.playSound(player.location, Sound.BLOCK_FIRE_AMBIENT, 0.3f, 1.5f)
player.sendActionBar(
Component.text("🔥 ", NamedTextColor.GOLD)
.append(Component.text("上昇気流!", NamedTextColor.YELLOW))
.append(Component.text("", NamedTextColor.GREEN))
)
return true return true
} }
} }
@ -326,15 +423,40 @@ class GliderItem : SpecialItem("glider") {
val currentDamage = meta.damage val currentDamage = meta.damage
if (currentDamage >= maxDamage - 1) { if (currentDamage >= maxDamage - 1) {
meta.damage = maxDamage meta.damage = maxDamage
item.itemMeta = meta item.itemMeta = meta
disableGlider(player, item) disableGlider(player, item)
player.playSound(player.location, Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f) player.playSound(player.location, Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f)
player.sendMessage(Component.text("グライダーが壊れました!", NamedTextColor.RED)) player.playSound(player.location, Sound.BLOCK_WOOL_BREAK, 1.0f, 0.5f)
player.sendActionBar(
Component.text("💥 ", NamedTextColor.RED)
.append(Component.text("グライダーが壊れました!", NamedTextColor.DARK_RED))
)
player.world.spawnParticle(
Particle.ITEM,
player.location.add(0.0, 1.5, 0.0),
30,
0.5, 0.5, 0.5,
0.1,
ItemStack(Material.PHANTOM_MEMBRANE)
)
} else { } else {
meta.damage = currentDamage + 1 meta.damage = currentDamage + 1
item.itemMeta = meta item.itemMeta = meta
val durabilityPercent = ((maxDamage - currentDamage - 1).toDouble() / maxDamage * 100).toInt()
if (durabilityPercent <= 20 && currentDamage % 10 == 0) {
player.sendActionBar(
Component.text("", NamedTextColor.YELLOW)
.append(Component.text("耐久値低下! ", NamedTextColor.RED))
.append(Component.text("残り${durabilityPercent}%", NamedTextColor.GOLD))
)
player.playSound(player.location, Sound.BLOCK_ANVIL_LAND, 0.3f, 2.0f)
}
} }
} }
} }

View File

@ -15,10 +15,25 @@ class GrapplingItem : SpecialItem("grappling_hook") {
val item = ItemStack(Material.FISHING_ROD) val item = ItemStack(Material.FISHING_ROD)
val meta = item.itemMeta ?: return item val meta = item.itemMeta ?: return item
meta.displayName(Component.text("Grappling Hook", tier.color)) val speed = 1.0 + (tier.level * 0.4)
val efficiency = 1.0 + (tier.level * 0.1)
val speedStars = "".repeat(tier.level) + "".repeat(5 - tier.level)
val efficiencyStars = "".repeat(tier.level) + "".repeat(5 - tier.level)
meta.displayName(Component.text("グラップリングフック", tier.color))
meta.lore(listOf( meta.lore(listOf(
Component.text("Cast and reel in to pull yourself!", NamedTextColor.GRAY), Component.empty(),
Component.text("Tier: ${tier.level}", tier.color) Component.text("投げて引き寄せられる!", NamedTextColor.GRAY),
Component.text("落下ダメージを軽減", NamedTextColor.GRAY),
Component.empty(),
Component.text("【性能】", NamedTextColor.WHITE),
Component.text("ティア: ${tier.name}", tier.color),
Component.text("引き寄せ速度: $speedStars", NamedTextColor.AQUA),
Component.text("燃費: $efficiencyStars", NamedTextColor.GREEN),
Component.empty(),
Component.text("右クリックで投げる", NamedTextColor.YELLOW),
Component.text("もう一度右クリックで引き寄せ", NamedTextColor.YELLOW)
)) ))
item.itemMeta = meta item.itemMeta = meta
@ -27,11 +42,11 @@ class GrapplingItem : SpecialItem("grappling_hook") {
override fun onFish(event: PlayerFishEvent) { override fun onFish(event: PlayerFishEvent) {
val hook = event.hook val hook = event.hook
val player = event.player
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) {
val player = event.player
val playerLoc = player.location val playerLoc = player.location
val hookLoc = hook.location val hookLoc = hook.location
@ -53,6 +68,30 @@ class GrapplingItem : SpecialItem("grappling_hook") {
val hungerCost = (velocity.length() * hungerCostBase / efficiency).toInt().coerceAtLeast(1) val hungerCost = (velocity.length() * hungerCostBase / efficiency).toInt().coerceAtLeast(1)
player.foodLevel = (player.foodLevel - hungerCost).coerceAtLeast(0) player.foodLevel = (player.foodLevel - hungerCost).coerceAtLeast(0)
player.playSound(playerLoc, org.bukkit.Sound.ENTITY_FISHING_BOBBER_RETRIEVE, 1.0f, 1.2f)
player.playSound(playerLoc, org.bukkit.Sound.ENTITY_ENDER_DRAGON_FLAP, 0.5f, 1.5f)
val distance = playerLoc.distance(hookLoc)
player.sendActionBar(
Component.text("引き寄せ中! ", NamedTextColor.AQUA)
.append(Component.text("距離: ${String.format("%.1f", distance)}m", NamedTextColor.WHITE))
.append(Component.text(" | ", NamedTextColor.DARK_GRAY))
.append(Component.text("速度: ${String.format("%.1f", speed)}x", tier.color))
)
for (i in 0..5) {
val particleLoc = playerLoc.clone().add(
vector.clone().normalize().multiply(i * distance / 5.0)
)
player.world.spawnParticle(
org.bukkit.Particle.CRIT,
particleLoc,
3,
0.1, 0.1, 0.1,
0.0
)
}
} }
if (event.state == PlayerFishEvent.State.REEL_IN || if (event.state == PlayerFishEvent.State.REEL_IN ||
@ -71,8 +110,10 @@ class GrapplingItem : SpecialItem("grappling_hook") {
val shooter = projectile.shooter val shooter = projectile.shooter
if (shooter is org.bukkit.entity.Player) { if (shooter is org.bukkit.entity.Player) {
projectile.velocity = projectile.velocity.add(shooter.velocity)
projectile.velocity = projectile.velocity.add(shooter.velocity) shooter.playSound(shooter.location, org.bukkit.Sound.ENTITY_FISHING_BOBBER_THROW, 1.0f, 0.8f)
shooter.playSound(shooter.location, org.bukkit.Sound.ENTITY_ARROW_SHOOT, 0.5f, 1.2f)
} }
} }
@ -91,9 +132,34 @@ class GrapplingItem : SpecialItem("grappling_hook") {
stand.persistentDataContainer.set(KEY_ANCHOR_ID, org.bukkit.persistence.PersistentDataType.STRING, "grapple") stand.persistentDataContainer.set(KEY_ANCHOR_ID, org.bukkit.persistence.PersistentDataType.STRING, "grapple")
} }
if (anchor.addPassenger(hook)) { if (anchor.addPassenger(hook)) {
hook.persistentDataContainer.set(KEY_HOOK_STUCK, org.bukkit.persistence.PersistentDataType.BYTE, 1) hook.persistentDataContainer.set(KEY_HOOK_STUCK, org.bukkit.persistence.PersistentDataType.BYTE, 1)
location.world.playSound(location, org.bukkit.Sound.BLOCK_ANVIL_LAND, 0.5f, 2.0f)
location.world.playSound(location, org.bukkit.Sound.BLOCK_CHAIN_PLACE, 1.0f, 1.5f)
location.world.spawnParticle(
org.bukkit.Particle.ENCHANTED_HIT,
location,
15,
0.2, 0.2, 0.2,
0.1
)
location.world.spawnParticle(
org.bukkit.Particle.SMOKE,
location,
5,
0.1, 0.1, 0.1,
0.02
)
val shooter = hook.shooter
if (shooter is org.bukkit.entity.Player) {
shooter.sendActionBar(
Component.text("", NamedTextColor.GREEN)
.append(Component.text("フック固定!", NamedTextColor.WHITE))
)
}
} else { } else {
anchor.remove() anchor.remove()
} }
@ -106,6 +172,8 @@ class GrapplingItem : SpecialItem("grappling_hook") {
val player = event.entity val player = event.entity
if (player is org.bukkit.entity.Player) { if (player is org.bukkit.entity.Player) {
val item = player.inventory.itemInMainHand val item = player.inventory.itemInMainHand
if (!SpecialItem.isSpecialItem(item) || SpecialItem.getId(item) != "grappling_hook") return
val tier = SpecialItem.getTier(item) val tier = SpecialItem.getTier(item)
val originalDamage = event.damage val originalDamage = event.damage
@ -119,14 +187,31 @@ class GrapplingItem : SpecialItem("grappling_hook") {
if (player.foodLevel >= totalHungerCost) { if (player.foodLevel >= totalHungerCost) {
player.foodLevel = (player.foodLevel - totalHungerCost).coerceAtLeast(0) player.foodLevel = (player.foodLevel - totalHungerCost).coerceAtLeast(0)
event.damage = reducedDamage event.damage = reducedDamage
player.playSound(player.location, org.bukkit.Sound.BLOCK_WOOL_FALL, 1.0f, 0.8f)
player.playSound(player.location, org.bukkit.Sound.ENTITY_PLAYER_ATTACK_NODAMAGE, 0.5f, 1.5f)
player.sendActionBar(
Component.text("🛡 ", NamedTextColor.GOLD)
.append(Component.text("落下ダメージ軽減! ", NamedTextColor.GREEN))
.append(Component.text("-${String.format("%.1f", damageToAbsorb)}", NamedTextColor.RED))
)
} else { } else {
val availableFood = player.foodLevel val availableFood = player.foodLevel
val damageWeCanAbsorb = availableFood / hungerCostPerDamage val damageWeCanAbsorb = availableFood / hungerCostPerDamage
val damageWeCannotAbsorb = damageToAbsorb - damageWeCanAbsorb val damageWeCannotAbsorb = damageToAbsorb - damageWeCanAbsorb
player.foodLevel = 0 player.foodLevel = 0
event.damage = reducedDamage + damageWeCannotAbsorb event.damage = reducedDamage + damageWeCannotAbsorb
if (damageWeCanAbsorb > 0) {
player.playSound(player.location, org.bukkit.Sound.BLOCK_WOOL_FALL, 0.5f, 0.6f)
player.sendActionBar(
Component.text("", NamedTextColor.YELLOW)
.append(Component.text("空腹不足! ", NamedTextColor.RED))
.append(Component.text("部分軽減のみ", NamedTextColor.GRAY))
)
}
} }
} }
} }