diff --git a/docs/creating_new_content.md b/docs/creating_new_content.md new file mode 100644 index 0000000..09f38ff --- /dev/null +++ b/docs/creating_new_content.md @@ -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` などを適切に制御してください。 diff --git a/src/main/kotlin/net/hareworks/hcu/items/App.kt b/src/main/kotlin/net/hareworks/hcu/items/App.kt index 248d520..2b4d236 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/App.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/App.kt @@ -26,7 +26,7 @@ public class App : JavaPlugin() { override fun onEnable() { instance = this 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) logger.info("Items plugin enabled!") diff --git a/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt b/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt index 80d8b4c..1d5322d 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/content/items/GrapplingItem.kt @@ -174,8 +174,15 @@ class GrapplingItem : SpecialItem("grappling_hook") { 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 - if (!SpecialItem.isSpecialItem(item) || SpecialItem.getId(item) != "grappling_hook") return + // Check both hands for the hook + 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) @@ -220,6 +227,10 @@ class GrapplingItem : SpecialItem("grappling_hook") { } } + private fun isGrapplingHook(item: ItemStack): Boolean { + return SpecialItem.getId(item) == "grappling_hook" + } + companion object { val KEY_HOOK_STUCK = org.bukkit.NamespacedKey("hcu_items", "hook_stuck") val KEY_ANCHOR_ID = org.bukkit.NamespacedKey("hcu_items", "anchor_id") diff --git a/src/main/kotlin/net/hareworks/hcu/items/listeners/EventListener.kt b/src/main/kotlin/net/hareworks/hcu/items/listeners/EventListener.kt index 25b607a..8bc396e 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/listeners/EventListener.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/listeners/EventListener.kt @@ -1,35 +1,25 @@ package net.hareworks.hcu.items.listeners -import org.bukkit.event.Listener -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.component.CustomComponent 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 - fun onInteract(event: PlayerInteractEvent) { + fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) { if (event.hand == EquipmentSlot.OFF_HAND) return val item = event.item ?: return - // Legacy SpecialItem logic - if (SpecialItem.isSpecialItem(item)) { - 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) - } - } + dispatchToItem(item) { it.onInteract(event) } + dispatchToComponents(item) { it.onInteract(event) } } @EventHandler @@ -37,11 +27,8 @@ class EventListener(private val plugin: App) : Listener { val player = event.player val item = player.inventory.itemInMainHand - if (SpecialItem.isSpecialItem(item)) { - val id = SpecialItem.getId(item) ?: return - val specialItem = ItemRegistry.get(id) - specialItem?.onFish(event) - } + dispatchToItem(item) { it.onFish(event) } + // CustomComponent interface does not define onFish, so no dispatchToComponents here. } @EventHandler @@ -49,13 +36,12 @@ class EventListener(private val plugin: App) : Listener { 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?.onProjectileHit(event) - } + if (shooter is Player) { + // For ProjectileHitEvent, SpecialItems often need to check if the projectile belongs to them, + // which might not be the item currently held by the player. + // Therefore, we iterate through all registered SpecialItems. + ItemRegistry.getAll().forEach { it.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 shooter = projectile.shooter - if (shooter is org.bukkit.entity.Player) { + if (shooter is Player) { val item = shooter.inventory.itemInMainHand - if (SpecialItem.isSpecialItem(item)) { - val id = SpecialItem.getId(item) ?: return - val specialItem = ItemRegistry.get(id) - specialItem?.onProjectileLaunch(event) - } + dispatchToItem(item) { it.onProjectileLaunch(event) } + // CustomComponent interface does not define onProjectileLaunch, so no dispatchToComponents here. } } @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 - - // Legacy Logic - if (SpecialItem.isSpecialItem(item)) { - 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) - } - } + if (entity is Player) { + val items = getEquipmentItems(entity) + for (item in items) { + dispatchToItem(item) { it.onEntityDamage(event) } + dispatchToComponents(item) { it.onEntityDamage(event) } } } } @@ -103,39 +74,46 @@ class EventListener(private val plugin: App) : Listener { val player = event.player val item = player.inventory.itemInMainHand - // Legacy Logic - if (SpecialItem.isSpecialItem(item)) { - 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) - } - } - } + dispatchToItem(item) { it.onPlayerMove(event) } + dispatchToComponents(item) { it.onPlayerMove(event) } } @EventHandler fun onToggleSneak(event: org.bukkit.event.player.PlayerToggleSneakEvent) { val player = event.player - val equipment = player.equipment ?: return + val items = getEquipmentItems(player) - val itemsToCheck = mutableListOf() - itemsToCheck.addAll(equipment.armorContents.filterNotNull()) - itemsToCheck.add(equipment.itemInMainHand) - itemsToCheck.add(equipment.itemInOffHand) + for (item in items) { + // SpecialItem interface does not define onToggleSneak. + dispatchToComponents(item) { it.onToggleSneak(player, item, event) } + } + } + + private fun getEquipmentItems(player: Player): List { + val equipment = player.equipment ?: return emptyList() + val items = mutableListOf() + 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) { - if (item.type.isAir) continue - for (component in net.hareworks.hcu.items.registry.ComponentRegistry.getAll()) { - if (component.has(item)) { - component.onToggleSneak(player, item, event) - } + for (key in pdc.keys) { + val component = ComponentRegistry.get(key) + if (component != null) { + action(component) } } } diff --git a/src/main/kotlin/net/hareworks/hcu/items/registry/ComponentRegistry.kt b/src/main/kotlin/net/hareworks/hcu/items/registry/ComponentRegistry.kt index 9047b84..be370b0 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/registry/ComponentRegistry.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/registry/ComponentRegistry.kt @@ -4,15 +4,17 @@ import net.hareworks.hcu.items.api.component.CustomComponent import net.hareworks.hcu.items.api.component.EquippableComponent object ComponentRegistry { - private val components = mutableListOf() + private val components = mutableMapOf() fun register(component: CustomComponent) { - components.add(component) + components[component.key] = component } - fun getAll(): List = components.toList() + fun getAll(): List = components.values.toList() + + fun get(key: org.bukkit.NamespacedKey): CustomComponent? = components[key] fun getEquippableComponents(): List { - return components.filterIsInstance() + return components.values.filterIsInstance() } }