feat: Implement a grappling hook special item with dedicated event handling for its mechanics.
This commit is contained in:
parent
553f4589a6
commit
a47930ac87
|
|
@ -6,6 +6,7 @@ import net.hareworks.permits_lib.PermitsLib
|
||||||
import net.hareworks.permits_lib.domain.NodeRegistration
|
import net.hareworks.permits_lib.domain.NodeRegistration
|
||||||
import net.hareworks.hcu.items.domain.ItemRegistry
|
import net.hareworks.hcu.items.domain.ItemRegistry
|
||||||
import net.hareworks.hcu.items.domain.impl.TestItem
|
import net.hareworks.hcu.items.domain.impl.TestItem
|
||||||
|
import net.hareworks.hcu.items.domain.impl.GrapplingItem
|
||||||
|
|
||||||
import org.bukkit.permissions.PermissionDefault
|
import org.bukkit.permissions.PermissionDefault
|
||||||
import org.bukkit.plugin.java.JavaPlugin
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
|
@ -30,5 +31,6 @@ public class App : JavaPlugin() {
|
||||||
|
|
||||||
// Register items
|
// Register items
|
||||||
ItemRegistry.register(TestItem())
|
ItemRegistry.register(TestItem())
|
||||||
|
ItemRegistry.register(GrapplingItem())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,26 @@ abstract class SpecialItem(val id: String) {
|
||||||
*/
|
*/
|
||||||
open fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {}
|
open fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a player uses a fishing rod (casts, reels in, catches, etc).
|
||||||
|
*/
|
||||||
|
open fun onFish(event: org.bukkit.event.player.PlayerFishEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a projectile from this item hits something.
|
||||||
|
*/
|
||||||
|
open fun onProjectileHit(event: org.bukkit.event.entity.ProjectileHitEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a projectile from this item is launched.
|
||||||
|
*/
|
||||||
|
open fun onProjectileLaunch(event: org.bukkit.event.entity.ProjectileLaunchEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the player holding this item takes damage.
|
||||||
|
*/
|
||||||
|
open fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val KEY_HCU_ITEM_ID = NamespacedKey("hcu_items", "id")
|
val KEY_HCU_ITEM_ID = NamespacedKey("hcu_items", "id")
|
||||||
val KEY_HCU_ITEM_TIER = NamespacedKey("hcu_items", "tier")
|
val KEY_HCU_ITEM_TIER = NamespacedKey("hcu_items", "tier")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
package net.hareworks.hcu.items.domain.impl
|
||||||
|
|
||||||
|
import net.hareworks.hcu.items.domain.SpecialItem
|
||||||
|
import net.hareworks.hcu.items.domain.Tier
|
||||||
|
import net.kyori.adventure.text.Component
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor
|
||||||
|
import org.bukkit.Location
|
||||||
|
import org.bukkit.Material
|
||||||
|
import org.bukkit.event.player.PlayerFishEvent
|
||||||
|
import org.bukkit.inventory.ItemStack
|
||||||
|
|
||||||
|
class GrapplingItem : SpecialItem("grappling_hook") {
|
||||||
|
|
||||||
|
override fun buildItem(tier: Tier): ItemStack {
|
||||||
|
val item = ItemStack(Material.FISHING_ROD)
|
||||||
|
val meta = item.itemMeta ?: return item
|
||||||
|
|
||||||
|
meta.displayName(Component.text("Grappling Hook", tier.color))
|
||||||
|
meta.lore(listOf(
|
||||||
|
Component.text("Cast and reel in to pull yourself!", NamedTextColor.GRAY),
|
||||||
|
Component.text("Tier: ${tier.level}", tier.color)
|
||||||
|
))
|
||||||
|
|
||||||
|
// Optional: Make it unbreakable or have durability based on tier?
|
||||||
|
// meta.isUnbreakable = true
|
||||||
|
|
||||||
|
item.itemMeta = meta
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFish(event: PlayerFishEvent) {
|
||||||
|
val hook = event.hook
|
||||||
|
|
||||||
|
// Check if stuck via our custom PDC tag (meaning it hit a block and we anchored it)
|
||||||
|
val isStuck = hook.persistentDataContainer.has(KEY_HOOK_STUCK, org.bukkit.persistence.PersistentDataType.BYTE)
|
||||||
|
|
||||||
|
// If reeling in and it's stuck (anchored), pull the player
|
||||||
|
if (event.state == PlayerFishEvent.State.REEL_IN && isStuck) {
|
||||||
|
val player = event.player
|
||||||
|
val playerLoc = player.location
|
||||||
|
val hookLoc = hook.location
|
||||||
|
|
||||||
|
val vector = hookLoc.toVector().subtract(playerLoc.toVector())
|
||||||
|
|
||||||
|
val item = player.inventory.itemInMainHand
|
||||||
|
val tier = SpecialItem.getTier(item)
|
||||||
|
|
||||||
|
val speed = 1.0 + (tier.level * 0.4)
|
||||||
|
|
||||||
|
val velocity = vector.normalize().multiply(speed)
|
||||||
|
player.velocity = velocity
|
||||||
|
|
||||||
|
// "飛ぶ速度に比例して空腹になる"
|
||||||
|
// Cost based on speed (magnitude of velocity)
|
||||||
|
// Base cost multiplier
|
||||||
|
val hungerCostBase = 2.0
|
||||||
|
// Tier reduces cost? Or just proportional to speed?
|
||||||
|
// Let's make higher tiers slightly more efficient per unit of speed.
|
||||||
|
val efficiency = 1.0 + (tier.level * 0.1)
|
||||||
|
|
||||||
|
val hungerCost = (velocity.length() * hungerCostBase / efficiency).toInt().coerceAtLeast(1)
|
||||||
|
|
||||||
|
player.foodLevel = (player.foodLevel - hungerCost).coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup anchor when reeling in or if the hook is removed
|
||||||
|
if (event.state == PlayerFishEvent.State.REEL_IN ||
|
||||||
|
event.state == PlayerFishEvent.State.CAUGHT_ENTITY ||
|
||||||
|
event.state == PlayerFishEvent.State.BITE) { // BITE might be too early? usually REEL_IN is the end
|
||||||
|
|
||||||
|
val vehicle = hook.vehicle
|
||||||
|
if (vehicle is org.bukkit.entity.ArmorStand && vehicle.persistentDataContainer.has(KEY_ANCHOR_ID, org.bukkit.persistence.PersistentDataType.STRING)) {
|
||||||
|
vehicle.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProjectileLaunch(event: org.bukkit.event.entity.ProjectileLaunchEvent) {
|
||||||
|
val projectile = event.entity
|
||||||
|
val shooter = projectile.shooter
|
||||||
|
|
||||||
|
if (shooter is org.bukkit.entity.Player) {
|
||||||
|
// "浮を投げるとき投げたプレイヤーの速度が慣性に乗るようにしてください"
|
||||||
|
// Add player's velocity to the projectile
|
||||||
|
projectile.velocity = projectile.velocity.add(shooter.velocity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProjectileHit(event: org.bukkit.event.entity.ProjectileHitEvent) {
|
||||||
|
if ((event.hitBlock != null || event.hitEntity != null) && event.hitBlock?.isCollidable() == true) {
|
||||||
|
val hook = event.entity
|
||||||
|
if (hook is org.bukkit.entity.FishHook) {
|
||||||
|
// Determine spawn location slightly adjusted to avoid clipping into the wall too much?
|
||||||
|
// Or just exactly at the hook. Hook collision box is small.
|
||||||
|
val location = hook.location
|
||||||
|
|
||||||
|
// Spawn anchor
|
||||||
|
val anchor = location.world.spawn(location, org.bukkit.entity.ArmorStand::class.java) { stand ->
|
||||||
|
stand.isVisible = false
|
||||||
|
stand.isMarker = true
|
||||||
|
stand.setGravity(false)
|
||||||
|
stand.isSmall = true
|
||||||
|
stand.isInvulnerable = true
|
||||||
|
stand.persistentDataContainer.set(KEY_ANCHOR_ID, org.bukkit.persistence.PersistentDataType.STRING, "grapple")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount hook to anchor
|
||||||
|
if (anchor.addPassenger(hook)) {
|
||||||
|
hook.persistentDataContainer.set(KEY_HOOK_STUCK, org.bukkit.persistence.PersistentDataType.BYTE, 1)
|
||||||
|
} else {
|
||||||
|
anchor.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
|
||||||
|
// "手に持っているときは落下ダメージ半減"
|
||||||
|
// "受けたダメージ分だけ空腹になる"
|
||||||
|
// "もし空腹で受けきれなかった場合は普通にダメージを受ける"
|
||||||
|
// "Tireによって空腹度合いが変わる"
|
||||||
|
|
||||||
|
if (event.cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL) {
|
||||||
|
val player = event.entity
|
||||||
|
if (player is org.bukkit.entity.Player) {
|
||||||
|
val item = player.inventory.itemInMainHand
|
||||||
|
val tier = SpecialItem.getTier(item)
|
||||||
|
|
||||||
|
val originalDamage = event.damage
|
||||||
|
val reducedDamage = originalDamage / 2.0
|
||||||
|
val damageToAbsorb = originalDamage - reducedDamage
|
||||||
|
|
||||||
|
// Hunger cost:
|
||||||
|
// Base cost is equal to damage absorbed? Or some factor?
|
||||||
|
// "Tireによって空腹度合いが変わる" -> Higher tier = cheaper
|
||||||
|
// Example: Cost = Damage * (2.5 - (0.4 * Tier))
|
||||||
|
// Tier 1: 2.1x damage, Tier 5: 0.5x damage
|
||||||
|
val costFactor = (3.0 - (0.5 * tier.level)).coerceAtLeast(0.5)
|
||||||
|
val hungerCostPerDamage = costFactor
|
||||||
|
val totalHungerCost = (damageToAbsorb * hungerCostPerDamage).toInt()
|
||||||
|
|
||||||
|
if (player.foodLevel >= totalHungerCost) {
|
||||||
|
// "受けたダメージ分だけ空腹になる" (interpreted as paying the cost)
|
||||||
|
player.foodLevel = (player.foodLevel - totalHungerCost).coerceAtLeast(0)
|
||||||
|
event.damage = reducedDamage
|
||||||
|
} else {
|
||||||
|
// "空腹で受けきれなかった場合はそのままダメージを受けるのではなく空腹で受けられる分のダメージは受けて余ったダメージを直接受けるようにしてください"
|
||||||
|
// Calculate how much damage we can absorb with available food
|
||||||
|
val availableFood = player.foodLevel
|
||||||
|
val damageWeCanAbsorb = availableFood / hungerCostPerDamage
|
||||||
|
val damageWeCannotAbsorb = damageToAbsorb - damageWeCanAbsorb
|
||||||
|
|
||||||
|
// Consume all available food
|
||||||
|
player.foodLevel = 0
|
||||||
|
|
||||||
|
// Take reduced damage for what we could absorb, plus full damage for what we couldn't
|
||||||
|
event.damage = reducedDamage + damageWeCannotAbsorb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val KEY_HOOK_STUCK = org.bukkit.NamespacedKey("hcu_items", "hook_stuck")
|
||||||
|
val KEY_ANCHOR_ID = org.bukkit.NamespacedKey("hcu_items", "anchor_id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,4 +25,60 @@ class EventListener(private val plugin: App) : Listener {
|
||||||
specialItem?.onInteract(event)
|
specialItem?.onInteract(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onFish(event: org.bukkit.event.player.PlayerFishEvent) {
|
||||||
|
val player = event.player
|
||||||
|
val item = player.inventory.itemInMainHand // Assuming main hand usage primarily
|
||||||
|
|
||||||
|
if (SpecialItem.isSpecialItem(item)) {
|
||||||
|
val id = SpecialItem.getId(item) ?: return
|
||||||
|
val specialItem = ItemRegistry.get(id)
|
||||||
|
|
||||||
|
specialItem?.onFish(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onProjectileHit(event: org.bukkit.event.entity.ProjectileHitEvent) {
|
||||||
|
val projectile = event.entity
|
||||||
|
val shooter = projectile.shooter
|
||||||
|
|
||||||
|
if (shooter is org.bukkit.entity.Player) {
|
||||||
|
val item = shooter.inventory.itemInMainHand // Check main hand
|
||||||
|
if (SpecialItem.isSpecialItem(item)) {
|
||||||
|
val id = SpecialItem.getId(item) ?: return
|
||||||
|
val specialItem = ItemRegistry.get(id)
|
||||||
|
specialItem?.onProjectileHit(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onProjectileLaunch(event: org.bukkit.event.entity.ProjectileLaunchEvent) {
|
||||||
|
val projectile = event.entity
|
||||||
|
val shooter = projectile.shooter
|
||||||
|
|
||||||
|
if (shooter is org.bukkit.entity.Player) {
|
||||||
|
val item = shooter.inventory.itemInMainHand
|
||||||
|
if (SpecialItem.isSpecialItem(item)) {
|
||||||
|
val id = SpecialItem.getId(item) ?: return
|
||||||
|
val specialItem = ItemRegistry.get(id)
|
||||||
|
specialItem?.onProjectileLaunch(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
|
||||||
|
val entity = event.entity
|
||||||
|
if (entity is org.bukkit.entity.Player) {
|
||||||
|
val item = entity.inventory.itemInMainHand
|
||||||
|
if (SpecialItem.isSpecialItem(item)) {
|
||||||
|
val id = SpecialItem.getId(item) ?: return
|
||||||
|
val specialItem = ItemRegistry.get(id)
|
||||||
|
specialItem?.onEntityDamage(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user