refactor: Centralize event dispatching to special items and components, enhance item hand checks, and add new content creation documentation.

This commit is contained in:
Kariya 2025-12-10 13:12:03 +00:00
parent d4e84fea1b
commit 2f441e334d
5 changed files with 149 additions and 90 deletions

View File

@ -0,0 +1,68 @@
# コンテンツの作成ガイド
このプロジェクトでは、HcuItemsプラグインを使用して新しいアイテムや機能コンポーネントを追加するための仕組みを提供しています。
イベント処理は`EventListener`によって統一的に管理され、アイテムのPersistentDataContainer (PDC)の情報に基づいて適切なクラスに委譲されます。
## アーキテクチャ概要
全てのイベント(右クリック、ダメージ、移動など)は `EventListener` で受信されます。
`EventListener` は以下の手順で処理を委譲します:
1. イベントに関与したアイテムを取得します。
2. アイテムのPDCを確認します。
- `SpecialItem` ID (`hcu_items:id`) がある場合 -> `ItemRegistry` からアイテム定義を取得して実行。
- コンポーネント用のキー (`hcu_items:component_id`) がある場合 -> `ComponentRegistry` からコンポーネント定義を取得して実行。
## 1. SpecialItem (独自のアイテム) の作成
完全に独自の挙動を持つアイテムを作成する場合に使用します。
### 手順
1. `SpecialItem` クラスを継承した新しいクラスを作成します。
2. コンストラクタで一意のIDを指定します。
3. `buildItem` メソッドを実装し、アイテムの見た目Material, Name, Loreなどを定義します。
4. 必要なイベントハンドラ(`onInteract`, `onPlayerMove` など)をオーバーライドします。
5. `App.kt``onEnable``ItemRegistry.register(YourItem())` を呼び出して登録します。
```kotlin
class MyCustomItem : SpecialItem("my_custom_item") {
override fun buildItem(tier: Tier): ItemStack {
return ItemStack(Material.DIAMOND_SWORD).apply {
// Meta設定
}
}
override fun onInteract(event: PlayerInteractEvent) {
event.player.sendMessage("Used custom item!")
}
}
```
## 2. CustomComponent (既存アイテムへの機能追加) の作成
既存のアイテムや、特定の条件を満たすアイテムに共通の機能(例:グライダー、パッシブ効果)を付与する場合に使用します。
### 手順
1. `AbstractComponent` を継承し、`CustomComponent` (または `EquippableComponent`) を実装したクラスを作成します。
2. コンストラクタで一意のコンポーネントIDを指定します。
3. `apply` メソッドでアイテムにコンポーネントを適用するロジックPDCへのキー登録は自動で行われますが、追加のメタデータが必要な場合は記述を実装します。
4. 必要なイベントハンドラをオーバーライドします。
5. `App.kt``onEnable``ComponentRegistry.register(YourComponent(this))` を呼び出して登録します。
```kotlin
class MyComponent(plugin: App) : AbstractComponent(plugin, "my_component") {
override fun onInteract(event: PlayerInteractEvent) {
event.player.sendMessage("Component interaction!")
}
}
```
### イベントの仕組み
`AbstractComponent` は初期化時に `NamespacedKey` を生成し、`apply` 時にこのキーをアイテムのPDCに書き込みます。
`EventListener` はアイテムのPDCにこのキーが存在することを確認すると、あなたのコンポーネントのイベントハンドラを呼び出します。
## 注意事項
- `SpecialItem``CustomComponent` は共存可能です。1つのアイテムが `SpecialItem` であり、かつ複数の `CustomComponent` を持つことができます。
- イベントハンドラ内では、必要に応じて `event.isCancelled` などを適切に制御してください。

View File

@ -26,7 +26,7 @@ public class App : JavaPlugin() {
override fun onEnable() { override fun onEnable() {
instance = this instance = this
saveDefaultConfig() saveDefaultConfig()
server.pluginManager.registerEvents(net.hareworks.hcu.items.listeners.EventListener(this), this) server.pluginManager.registerEvents(net.hareworks.hcu.items.listeners.EventListener(), this)
server.pluginManager.registerEvents(net.hareworks.hcu.items.listeners.ComponentListener(this), this) server.pluginManager.registerEvents(net.hareworks.hcu.items.listeners.ComponentListener(this), this)
logger.info("Items plugin enabled!") logger.info("Items plugin enabled!")

View File

@ -174,8 +174,15 @@ class GrapplingItem : SpecialItem("grappling_hook") {
if (event.cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL) { if (event.cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL) {
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 // Check both hands for the hook
if (!SpecialItem.isSpecialItem(item) || SpecialItem.getId(item) != "grappling_hook") return val mainHand = player.inventory.itemInMainHand
val offHand = player.inventory.itemInOffHand
val item = when {
isGrapplingHook(mainHand) -> mainHand
isGrapplingHook(offHand) -> offHand
else -> return
}
val tier = SpecialItem.getTier(item) val tier = SpecialItem.getTier(item)
@ -220,6 +227,10 @@ class GrapplingItem : SpecialItem("grappling_hook") {
} }
} }
private fun isGrapplingHook(item: ItemStack): Boolean {
return SpecialItem.getId(item) == "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")

View File

@ -1,35 +1,25 @@
package net.hareworks.hcu.items.listeners package net.hareworks.hcu.items.listeners
import org.bukkit.event.Listener import net.hareworks.hcu.items.api.component.CustomComponent
import org.bukkit.event.EventHandler
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.EquipmentSlot
import net.hareworks.hcu.items.App
import net.hareworks.hcu.items.registry.ItemRegistry
import net.hareworks.hcu.items.api.item.SpecialItem import net.hareworks.hcu.items.api.item.SpecialItem
class EventListener(private val plugin: App) : Listener { import net.hareworks.hcu.items.registry.ComponentRegistry
import net.hareworks.hcu.items.registry.ItemRegistry
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.inventory.EquipmentSlot
import org.bukkit.inventory.ItemStack
class EventListener : Listener {
@EventHandler @EventHandler
fun onInteract(event: PlayerInteractEvent) { fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {
if (event.hand == EquipmentSlot.OFF_HAND) return if (event.hand == EquipmentSlot.OFF_HAND) return
val item = event.item ?: return val item = event.item ?: return
// Legacy SpecialItem logic dispatchToItem(item) { it.onInteract(event) }
if (SpecialItem.isSpecialItem(item)) { dispatchToComponents(item) { it.onInteract(event) }
val id = SpecialItem.getId(item) ?: return
val specialItem = ItemRegistry.get(id)
specialItem?.onInteract(event)
}
// Component Logic
for (component in net.hareworks.hcu.items.registry.ComponentRegistry.getAll()) {
if (component.has(item)) {
component.onInteract(event)
}
}
} }
@EventHandler @EventHandler
@ -37,11 +27,8 @@ class EventListener(private val plugin: App) : Listener {
val player = event.player val player = event.player
val item = player.inventory.itemInMainHand val item = player.inventory.itemInMainHand
if (SpecialItem.isSpecialItem(item)) { dispatchToItem(item) { it.onFish(event) }
val id = SpecialItem.getId(item) ?: return // CustomComponent interface does not define onFish, so no dispatchToComponents here.
val specialItem = ItemRegistry.get(id)
specialItem?.onFish(event)
}
} }
@EventHandler @EventHandler
@ -49,13 +36,12 @@ class EventListener(private val plugin: App) : Listener {
val projectile = event.entity val projectile = event.entity
val shooter = projectile.shooter val shooter = projectile.shooter
if (shooter is org.bukkit.entity.Player) { if (shooter is Player) {
val item = shooter.inventory.itemInMainHand // For ProjectileHitEvent, SpecialItems often need to check if the projectile belongs to them,
if (SpecialItem.isSpecialItem(item)) { // which might not be the item currently held by the player.
val id = SpecialItem.getId(item) ?: return // Therefore, we iterate through all registered SpecialItems.
val specialItem = ItemRegistry.get(id) ItemRegistry.getAll().forEach { it.onProjectileHit(event) }
specialItem?.onProjectileHit(event) // CustomComponent interface does not define onProjectileHit, so no dispatchToComponents here.
}
} }
} }
@ -64,36 +50,21 @@ class EventListener(private val plugin: App) : Listener {
val projectile = event.entity val projectile = event.entity
val shooter = projectile.shooter val shooter = projectile.shooter
if (shooter is org.bukkit.entity.Player) { if (shooter is Player) {
val item = shooter.inventory.itemInMainHand val item = shooter.inventory.itemInMainHand
if (SpecialItem.isSpecialItem(item)) { dispatchToItem(item) { it.onProjectileLaunch(event) }
val id = SpecialItem.getId(item) ?: return // CustomComponent interface does not define onProjectileLaunch, so no dispatchToComponents here.
val specialItem = ItemRegistry.get(id)
specialItem?.onProjectileLaunch(event)
}
} }
} }
@EventHandler @EventHandler
fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) { fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
val entity = event.entity val entity = event.entity
if (entity is org.bukkit.entity.Player) { if (entity is Player) {
val item = entity.inventory.itemInMainHand val items = getEquipmentItems(entity)
for (item in items) {
// Legacy Logic dispatchToItem(item) { it.onEntityDamage(event) }
if (SpecialItem.isSpecialItem(item)) { dispatchToComponents(item) { it.onEntityDamage(event) }
val id = SpecialItem.getId(item) ?: return
val specialItem = ItemRegistry.get(id)
specialItem?.onEntityDamage(event)
}
// Component Logic
if (!item.type.isAir) {
for (component in net.hareworks.hcu.items.registry.ComponentRegistry.getAll()) {
if (component.has(item)) {
component.onEntityDamage(event)
}
}
} }
} }
} }
@ -103,39 +74,46 @@ class EventListener(private val plugin: App) : Listener {
val player = event.player val player = event.player
val item = player.inventory.itemInMainHand val item = player.inventory.itemInMainHand
// Legacy Logic dispatchToItem(item) { it.onPlayerMove(event) }
if (SpecialItem.isSpecialItem(item)) { dispatchToComponents(item) { it.onPlayerMove(event) }
val id = SpecialItem.getId(item) ?: return
val specialItem = ItemRegistry.get(id)
specialItem?.onPlayerMove(event)
}
// Component Logic
if (!item.type.isAir) {
for (component in net.hareworks.hcu.items.registry.ComponentRegistry.getAll()) {
if (component.has(item)) {
component.onPlayerMove(event)
}
}
}
} }
@EventHandler @EventHandler
fun onToggleSneak(event: org.bukkit.event.player.PlayerToggleSneakEvent) { fun onToggleSneak(event: org.bukkit.event.player.PlayerToggleSneakEvent) {
val player = event.player val player = event.player
val equipment = player.equipment ?: return val items = getEquipmentItems(player)
val itemsToCheck = mutableListOf<org.bukkit.inventory.ItemStack>() for (item in items) {
itemsToCheck.addAll(equipment.armorContents.filterNotNull()) // SpecialItem interface does not define onToggleSneak.
itemsToCheck.add(equipment.itemInMainHand) dispatchToComponents(item) { it.onToggleSneak(player, item, event) }
itemsToCheck.add(equipment.itemInOffHand) }
}
private fun getEquipmentItems(player: Player): List<ItemStack> {
val equipment = player.equipment ?: return emptyList()
val items = mutableListOf<ItemStack>()
items.addAll(equipment.armorContents.filterNotNull())
items.add(equipment.itemInMainHand)
items.add(equipment.itemInOffHand)
return items.filter { !it.type.isAir }
}
private fun dispatchToItem(item: ItemStack, action: (SpecialItem) -> Unit) {
if (item.type.isAir) return
val id = SpecialItem.getId(item) ?: return
val specialItem = ItemRegistry.get(id) ?: return
action(specialItem)
}
private fun dispatchToComponents(item: ItemStack, action: (CustomComponent) -> Unit) {
if (item.type.isAir) return
val meta = item.itemMeta ?: return
val pdc = meta.persistentDataContainer
for (item in itemsToCheck) { for (key in pdc.keys) {
if (item.type.isAir) continue val component = ComponentRegistry.get(key)
for (component in net.hareworks.hcu.items.registry.ComponentRegistry.getAll()) { if (component != null) {
if (component.has(item)) { action(component)
component.onToggleSneak(player, item, event)
}
} }
} }
} }

View File

@ -4,15 +4,17 @@ import net.hareworks.hcu.items.api.component.CustomComponent
import net.hareworks.hcu.items.api.component.EquippableComponent import net.hareworks.hcu.items.api.component.EquippableComponent
object ComponentRegistry { object ComponentRegistry {
private val components = mutableListOf<CustomComponent>() private val components = mutableMapOf<org.bukkit.NamespacedKey, CustomComponent>()
fun register(component: CustomComponent) { fun register(component: CustomComponent) {
components.add(component) components[component.key] = component
} }
fun getAll(): List<CustomComponent> = components.toList() fun getAll(): List<CustomComponent> = components.values.toList()
fun get(key: org.bukkit.NamespacedKey): CustomComponent? = components[key]
fun getEquippableComponents(): List<EquippableComponent> { fun getEquippableComponents(): List<EquippableComponent> {
return components.filterIsInstance<EquippableComponent>() return components.values.filterIsInstance<EquippableComponent>()
} }
} }