feat: Implement dynamic event handling system for items and components
This commit is contained in:
parent
6bc2fb2be2
commit
fe002a5372
|
|
@ -1,119 +1,98 @@
|
||||||
# Content Creation Guide
|
# Content Creation Guide
|
||||||
|
|
||||||
|
[日本語 (Japanese)](creating_new_content_ja.md)
|
||||||
|
|
||||||
This project provides a framework for adding new items and features (components) using the HcuItems plugin.
|
This project provides a framework for adding new items and features (components) using the HcuItems plugin.
|
||||||
|
|
||||||
Event handling is uniformly managed by the `EventListener`, which delegates to appropriate classes based on information in the item's PersistentDataContainer (PDC).
|
### Event Handling Architecture
|
||||||
|
|
||||||
## Architecture Overview
|
The event system has been fully refactored to be dynamic.
|
||||||
|
The `EventListener` collects event interests from all registered items and components via their `getEventHandlers()` method and dynamically registers the necessary Bukkit listeners.
|
||||||
|
|
||||||
All events (right-click, damage, movement, etc.) are received by the `EventListener`.
|
When an event occurs:
|
||||||
|
1. `EventListener` receives the event.
|
||||||
|
2. It determines the context (e.g., extracting the `ItemStack` from `PlayerInteractEvent`).
|
||||||
|
3. It dispatches the event to the appropriate `AbstractItem` or `CustomComponent` handler if the item matches.
|
||||||
|
|
||||||
The `EventListener` delegates processing through the following steps:
|
### 1. Creating an AbstractItem (Custom Item)
|
||||||
|
|
||||||
1. Retrieves the item involved in the event.
|
|
||||||
2. Checks the item's PDC.
|
|
||||||
- If an `AbstractItem` ID (`hcu_items:id`) exists → Retrieves the item definition from `ItemRegistry` and executes.
|
|
||||||
- If a component key (`hcu_items:component_id`) exists → Retrieves the component definition from `ComponentRegistry` and executes.
|
|
||||||
|
|
||||||
## 1. Creating an AbstractItem (Custom Item)
|
|
||||||
|
|
||||||
Use this when creating an item with completely unique behavior.
|
Use this when creating an item with completely unique behavior.
|
||||||
|
|
||||||
### Steps
|
#### Steps
|
||||||
|
|
||||||
1. Create a new class that inherits from the `AbstractItem` class.
|
1. Create a new class that inherits from the `AbstractItem` class.
|
||||||
2. Specify a unique ID in the constructor.
|
2. Specify a unique ID in the constructor.
|
||||||
3. Implement the `buildItem` method to define the item's appearance (Material, Name, Lore, etc.).
|
3. Implement the `buildItem` method to define the item's appearance.
|
||||||
4. Override necessary event handlers (`onInteract`, `onPlayerMove`, etc.).
|
4. **Implement `getEventHandlers`** to define which events this item handles.
|
||||||
5. Register by calling `ItemRegistry.register(YourItem())` in `App.kt`'s `onEnable`.
|
5. Register by calling `ItemRegistry.register(YourItem())` in `App.kt`'s `onEnable`.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
class MyCustomItem : AbstractItem("my_custom_item") {
|
class MyCustomItem : AbstractItem("my_custom_item") {
|
||||||
|
|
||||||
|
// Define item appearance
|
||||||
override fun buildItem(tier: Tier): ItemStack {
|
override fun buildItem(tier: Tier): ItemStack {
|
||||||
return ItemStack(Material.DIAMOND_SWORD).apply {
|
return ItemStack(Material.DIAMOND_SWORD).apply {
|
||||||
// Meta configuration
|
// ... meta ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInteract(event: PlayerInteractEvent) {
|
// Register Event Handlers
|
||||||
event.player.sendMessage("Used custom item!")
|
override fun getEventHandlers(): Map<KClass<out Event>, ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
// Handle Player Interact
|
||||||
|
PlayerInteractEvent::class to { event, item ->
|
||||||
|
val e = event as PlayerInteractEvent
|
||||||
|
e.player.sendMessage("You used the custom item!")
|
||||||
|
},
|
||||||
|
// Handle Entity Damage
|
||||||
|
EntityDamageEvent::class to { event, item ->
|
||||||
|
val e = event as EntityDamageEvent
|
||||||
|
e.damage *= 2.0 // Double damage
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle method (Not a Bukkit event) - safe to override directly
|
||||||
|
override fun onTick(player: Player, item: ItemStack) {
|
||||||
|
// Run logic every tick (or configured interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Creating a CustomComponent (Adding Features to Existing Items)
|
### 2. Creating a CustomComponent
|
||||||
|
|
||||||
Use this when adding common features (e.g., glider, passive effects) to existing items or items that meet specific conditions.
|
Use this to add shared behavior (like a "Double Jump" ability) to multiple items.
|
||||||
|
|
||||||
### Steps
|
#### Steps
|
||||||
|
|
||||||
1. Create a class that inherits from `AbstractComponent` and implements `CustomComponent` (or `EquippableComponent`).
|
1. Create a class that inherits from `AbstractComponent` and implements `CustomComponent`.
|
||||||
2. Specify a unique component ID in the constructor.
|
2. Implement `getEventHandlers` to define event logic.
|
||||||
3. Implement the `apply` method with logic to apply the component to the item (key registration to PDC is automatic, but describe if additional metadata is needed).
|
3. Register in `App.kt`.
|
||||||
4. Override necessary event handlers.
|
|
||||||
5. Register by calling `ComponentRegistry.register(YourComponent(this))` in `App.kt`'s `onEnable`.
|
#### Example
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
class MyComponent(plugin: App) : AbstractComponent(plugin, "my_component") {
|
class MyComponent(plugin: App) : AbstractComponent(plugin, "my_component") {
|
||||||
override fun onInteract(event: PlayerInteractEvent) {
|
|
||||||
event.player.sendMessage("Component interaction!")
|
override fun getEventHandlers(): Map<KClass<out Event>, ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
PlayerToggleSneakEvent::class to { event, item ->
|
||||||
|
val e = event as PlayerToggleSneakEvent
|
||||||
|
if (e.isSneaking) {
|
||||||
|
e.player.sendMessage("Component activated by sneaking!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Event Mechanism
|
### 3. Configuration Management
|
||||||
|
|
||||||
`AbstractComponent` generates a `NamespacedKey` during initialization and writes this key to the item's PDC during `apply`.
|
Use the `Config` object to manage values.
|
||||||
|
|
||||||
When the `EventListener` confirms this key exists in the item's PDC, it calls your component's event handler.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- `AbstractItem` and `CustomComponent` can coexist. A single item can be an `AbstractItem` and have multiple `CustomComponent`s.
|
|
||||||
- Within event handlers, appropriately control `event.isCancelled` as needed.
|
|
||||||
|
|
||||||
## 3. Configuration Management
|
|
||||||
|
|
||||||
The project uses a centralized `Config` object to manage configuration values from `config.yml`. This allows for easy adjustment of values (like cooldowns) without code changes.
|
|
||||||
|
|
||||||
### Accessing Configuration
|
|
||||||
|
|
||||||
To access configuration values, simply access the public properties of the `Config` object:
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
import net.hareworks.hcu.items.config.Config
|
val cooldown = Config.myFeatureCooldown
|
||||||
|
|
||||||
// Example usage
|
|
||||||
val cooldown = Cooldown(Config.blinkCooldown)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding New Config Values
|
|
||||||
|
|
||||||
1. **Add Property to Config Object**: Add a new property to `net.hareworks.hcu.items.config.Config.kt`.
|
|
||||||
2. **Update Load Method**: Read the value from `plugin.config` in the `load` method.
|
|
||||||
3. **Update Defaults**: Add the default value in the `saveDefaults` method.
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
object Config {
|
|
||||||
// 1. Add Property
|
|
||||||
var newFeatureEnabled: Boolean = true
|
|
||||||
|
|
||||||
fun load(plugin: JavaPlugin) {
|
|
||||||
// ... existing code ...
|
|
||||||
|
|
||||||
// 2. Load Value
|
|
||||||
newFeatureEnabled = config.getBoolean("components.new_feature.enabled", true)
|
|
||||||
|
|
||||||
// ... existing code ...
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveDefaults(plugin: JavaPlugin, config: FileConfiguration) {
|
|
||||||
// ... existing code ...
|
|
||||||
|
|
||||||
// 3. Set Default
|
|
||||||
config.addDefault("components.new_feature.enabled", true)
|
|
||||||
|
|
||||||
// ... existing code ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
98
docs/creating_new_content_ja.md
Normal file
98
docs/creating_new_content_ja.md
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
# コンテンツ作成ガイド
|
||||||
|
|
||||||
|
[English](creating_new_content.md)
|
||||||
|
|
||||||
|
HcuItemsプラグインを使用して新しいアイテムや機能(コンポーネント)を追加するためのガイドです。
|
||||||
|
|
||||||
|
### イベントハンドリングのアーキテクチャ
|
||||||
|
|
||||||
|
イベントシステムは動的登録方式にリファクタリングされました。
|
||||||
|
`EventListener` は、登録されたすべてのアイテムとコンポーネントの `getEventHandlers()` メソッドから「どのイベントに興味があるか」という情報を収集し、必要なBukkitリスナーを動的に登録します。
|
||||||
|
|
||||||
|
イベント発生時の流れ:
|
||||||
|
1. `EventListener` がイベントを受け取ります。
|
||||||
|
2. イベントのコンテキスト(例: `PlayerInteractEvent` から `ItemStack` を取得)を解決します。
|
||||||
|
3. アイテムIDやコンポーネントIDが一致する場合、対応する `AbstractItem` や `CustomComponent` のハンドラーを実行します。
|
||||||
|
|
||||||
|
### 1. AbstractItem (カスタムアイテム) の作成
|
||||||
|
|
||||||
|
独自の挙動を持つ新しいアイテムを作成する場合に使用します。
|
||||||
|
|
||||||
|
#### 手順
|
||||||
|
|
||||||
|
1. `AbstractItem` クラスを継承した新しいクラスを作成します。
|
||||||
|
2. コンストラクタでユニークIDを指定します。
|
||||||
|
3. `buildItem` メソッドでアイテムの外見(マテリアル、名前、説明文など)を定義します。
|
||||||
|
4. **`getEventHandlers` を実装**し、このアイテムが処理するイベントを定義します。
|
||||||
|
5. `App.kt` の `onEnable` で `ItemRegistry.register(YourItem())` を呼び出して登録します。
|
||||||
|
|
||||||
|
#### 実装例
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class MyCustomItem : AbstractItem("my_custom_item") {
|
||||||
|
|
||||||
|
// アイテムの外見定義
|
||||||
|
override fun buildItem(tier: Tier): ItemStack {
|
||||||
|
return ItemStack(Material.DIAMOND_SWORD).apply {
|
||||||
|
// ... meta設定 ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// イベントハンドラーの登録
|
||||||
|
override fun getEventHandlers(): Map<KClass<out Event>, ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
// プレイヤーのクリックイベント
|
||||||
|
PlayerInteractEvent::class to { event, item ->
|
||||||
|
val e = event as PlayerInteractEvent
|
||||||
|
e.player.sendMessage("カスタムアイテムを使用しました!")
|
||||||
|
},
|
||||||
|
// ダメージイベント
|
||||||
|
EntityDamageEvent::class to { event, item ->
|
||||||
|
val e = event as EntityDamageEvent
|
||||||
|
e.damage *= 2.0 // ダメージ2倍
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ライフサイクルメソッド (Bukkitイベントではない) - 直接オーバーライド可能
|
||||||
|
override fun onTick(player: Player, item: ItemStack) {
|
||||||
|
// 定期実行ロジック
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. CustomComponent (コンポーネント) の作成
|
||||||
|
|
||||||
|
「ダブルジャンプ」のような、複数のアイテムに共通して付与できる機能を作成する場合に使用します。
|
||||||
|
|
||||||
|
#### 手順
|
||||||
|
|
||||||
|
1. `AbstractComponent` を継承し、`CustomComponent` インターフェースを実装するクラスを作成します。
|
||||||
|
2. `getEventHandlers` を実装してロジックを記述します。
|
||||||
|
3. `App.kt` で登録します。
|
||||||
|
|
||||||
|
#### 実装例
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class MyComponent(plugin: App) : AbstractComponent(plugin, "my_component") {
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<KClass<out Event>, ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
PlayerToggleSneakEvent::class to { event, item ->
|
||||||
|
val e = event as PlayerToggleSneakEvent
|
||||||
|
if (e.isSneaking) {
|
||||||
|
e.player.sendMessage("スニークでコンポーネント発動!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 設定管理 (Configuration)
|
||||||
|
|
||||||
|
`Config` オブジェクトを使用して設定値を管理します。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val cooldown = Config.myFeatureCooldown
|
||||||
|
```
|
||||||
|
|
@ -4,6 +4,9 @@ import io.papermc.paper.datacomponent.DataComponentType
|
||||||
import net.hareworks.hcu.items.api.Tier
|
import net.hareworks.hcu.items.api.Tier
|
||||||
import org.bukkit.NamespacedKey
|
import org.bukkit.NamespacedKey
|
||||||
import org.bukkit.inventory.ItemStack
|
import org.bukkit.inventory.ItemStack
|
||||||
|
import org.bukkit.event.Event
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import net.hareworks.hcu.items.events.ComponentEventHandler
|
||||||
|
|
||||||
interface CustomComponent {
|
interface CustomComponent {
|
||||||
val key: NamespacedKey
|
val key: NamespacedKey
|
||||||
|
|
@ -19,15 +22,12 @@ interface CustomComponent {
|
||||||
val dataComponentDependencies: List<DataComponentType.NonValued>
|
val dataComponentDependencies: List<DataComponentType.NonValued>
|
||||||
get() = emptyList()
|
get() = emptyList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of event classes to their handlers.
|
||||||
|
*/
|
||||||
|
fun getEventHandlers(): Map<KClass<out Event>, ComponentEventHandler<*>> = emptyMap()
|
||||||
|
|
||||||
fun apply(item: ItemStack, tier: Tier? = null)
|
fun apply(item: ItemStack, tier: Tier? = null)
|
||||||
fun has(item: ItemStack): Boolean
|
fun has(item: ItemStack): Boolean
|
||||||
fun remove(item: ItemStack)
|
fun remove(item: ItemStack)
|
||||||
|
|
||||||
fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {}
|
|
||||||
fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {}
|
|
||||||
fun onPlayerMove(event: org.bukkit.event.player.PlayerMoveEvent) {}
|
|
||||||
fun onToggleSneak(player: org.bukkit.entity.Player, item: org.bukkit.inventory.ItemStack, event: org.bukkit.event.player.PlayerToggleSneakEvent) {}
|
|
||||||
fun onToggleGlide(player: org.bukkit.entity.Player, item: org.bukkit.inventory.ItemStack, event: org.bukkit.event.entity.EntityToggleGlideEvent) {}
|
|
||||||
fun onBlockBreak(event: org.bukkit.event.block.BlockBreakEvent) {}
|
|
||||||
fun onItemHeld(player: org.bukkit.entity.Player, item: org.bukkit.inventory.ItemStack, event: org.bukkit.event.player.PlayerItemHeldEvent) {}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,20 @@ package net.hareworks.hcu.items.api.item
|
||||||
import net.hareworks.hcu.items.api.Tier
|
import net.hareworks.hcu.items.api.Tier
|
||||||
import org.bukkit.NamespacedKey
|
import org.bukkit.NamespacedKey
|
||||||
import org.bukkit.inventory.ItemStack
|
import org.bukkit.inventory.ItemStack
|
||||||
|
import org.bukkit.event.Event
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import net.hareworks.hcu.items.events.ComponentEventHandler
|
||||||
import org.bukkit.persistence.PersistentDataType
|
import org.bukkit.persistence.PersistentDataType
|
||||||
|
|
||||||
abstract class AbstractItem(override val id: String) : CustomItem {
|
abstract class AbstractItem(override val id: String) : CustomItem {
|
||||||
|
|
||||||
override val maxTier: Int = 5
|
override open val maxTier: Int = 5
|
||||||
override val minTier: Int = 1
|
override open val minTier: Int = 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of event classes to their handlers.
|
||||||
|
*/
|
||||||
|
override fun getEventHandlers(): Map<KClass<out Event>, ComponentEventHandler<*>> = emptyMap()
|
||||||
|
|
||||||
override fun createItemStack(tier: Tier): ItemStack {
|
override fun createItemStack(tier: Tier): ItemStack {
|
||||||
val validTier = if (tier.level < minTier) Tier.fromLevel(minTier)
|
val validTier = if (tier.level < minTier) Tier.fromLevel(minTier)
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,22 @@ package net.hareworks.hcu.items.api.item
|
||||||
|
|
||||||
import net.hareworks.hcu.items.api.Tier
|
import net.hareworks.hcu.items.api.Tier
|
||||||
import org.bukkit.inventory.ItemStack
|
import org.bukkit.inventory.ItemStack
|
||||||
|
import org.bukkit.event.Event
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import net.hareworks.hcu.items.events.ComponentEventHandler
|
||||||
|
|
||||||
interface CustomItem {
|
interface CustomItem {
|
||||||
val id: String
|
val id: String
|
||||||
val maxTier: Int
|
val maxTier: Int
|
||||||
val minTier: Int
|
val minTier: Int
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of event classes to their handlers.
|
||||||
|
*/
|
||||||
|
fun getEventHandlers(): Map<KClass<out Event>, ComponentEventHandler<*>> = emptyMap()
|
||||||
|
|
||||||
fun createItemStack(tier: Tier = Tier.ONE): ItemStack
|
fun createItemStack(tier: Tier = Tier.ONE): ItemStack
|
||||||
|
|
||||||
fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {}
|
|
||||||
fun onFish(event: org.bukkit.event.player.PlayerFishEvent) {}
|
|
||||||
fun onProjectileHit(event: org.bukkit.event.entity.ProjectileHitEvent) {}
|
|
||||||
fun onProjectileLaunch(event: org.bukkit.event.entity.ProjectileLaunchEvent) {}
|
|
||||||
fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {}
|
|
||||||
fun onPlayerMove(event: org.bukkit.event.player.PlayerMoveEvent) {}
|
|
||||||
fun onItemHeld(event: org.bukkit.event.player.PlayerItemHeldEvent) {}
|
|
||||||
fun onFoodLevelChange(event: org.bukkit.event.entity.FoodLevelChangeEvent) {}
|
|
||||||
fun onTick(player: org.bukkit.entity.Player, item: ItemStack) {}
|
fun onTick(player: org.bukkit.entity.Player, item: ItemStack) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,17 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink
|
||||||
private val TRAIL_COLOR = TextColor.color(0x9400D3) // Dark Violet
|
private val TRAIL_COLOR = TextColor.color(0x9400D3) // Dark Violet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<kotlin.reflect.KClass<out org.bukkit.event.Event>, net.hareworks.hcu.items.events.ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
EntityToggleGlideEvent::class to { event, item ->
|
||||||
|
val e = event as EntityToggleGlideEvent
|
||||||
|
if (e.entity is Player) {
|
||||||
|
handleToggleGlide(e.entity as Player, item, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun apply(item: ItemStack, tier: Tier?) {
|
override fun apply(item: ItemStack, tier: Tier?) {
|
||||||
if (isBoots(item.type)) {
|
if (isBoots(item.type)) {
|
||||||
super.apply(item, tier)
|
super.apply(item, tier)
|
||||||
|
|
@ -85,7 +96,7 @@ class BlinkComponent(private val plugin: App) : AbstractComponent(plugin, "blink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleGlide(player: Player, item: ItemStack, event: EntityToggleGlideEvent) {
|
private fun handleToggleGlide(player: Player, item: ItemStack, event: EntityToggleGlideEvent) {
|
||||||
// Only process if this item actually has this component
|
// Only process if this item actually has this component
|
||||||
if (!has(item)) return
|
if (!has(item)) return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,17 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, "
|
||||||
private val COOLDOWN_COLOR = NamedTextColor.GRAY
|
private val COOLDOWN_COLOR = NamedTextColor.GRAY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<kotlin.reflect.KClass<out org.bukkit.event.Event>, net.hareworks.hcu.items.events.ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
EntityToggleGlideEvent::class to { event, item ->
|
||||||
|
val e = event as EntityToggleGlideEvent
|
||||||
|
if (e.entity is Player) {
|
||||||
|
handleToggleGlide(e.entity as Player, item, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun apply(item: ItemStack, tier: Tier?) {
|
override fun apply(item: ItemStack, tier: Tier?) {
|
||||||
if (isBoots(item.type)) {
|
if (isBoots(item.type)) {
|
||||||
super.apply(item, tier)
|
super.apply(item, tier)
|
||||||
|
|
@ -80,7 +91,7 @@ class DoubleJumpComponent(private val plugin: App) : AbstractComponent(plugin, "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleGlide(player: Player, item: ItemStack, event: EntityToggleGlideEvent) {
|
private fun handleToggleGlide(player: Player, item: ItemStack, event: EntityToggleGlideEvent) {
|
||||||
// Only process if this item actually has this component
|
// Only process if this item actually has this component
|
||||||
if (!has(item)) return
|
if (!has(item)) return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,18 @@ class GliderComponent(private val plugin: App) : AbstractComponent(plugin, "glid
|
||||||
var lastTickTime: Long = System.currentTimeMillis()
|
var lastTickTime: Long = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<kotlin.reflect.KClass<out org.bukkit.event.Event>, net.hareworks.hcu.items.events.ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
org.bukkit.event.player.PlayerToggleSneakEvent::class to { event, item ->
|
||||||
|
val e = event as org.bukkit.event.player.PlayerToggleSneakEvent
|
||||||
|
handleToggleSneak(e.player, item, e)
|
||||||
|
},
|
||||||
|
org.bukkit.event.entity.EntityDamageEvent::class to { event, _ ->
|
||||||
|
handleEntityDamage(event as org.bukkit.event.entity.EntityDamageEvent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Global Ticker for active gliders
|
// Global Ticker for active gliders
|
||||||
val interval = net.hareworks.hcu.items.config.Config.global.tickInterval
|
val interval = net.hareworks.hcu.items.config.Config.global.tickInterval
|
||||||
|
|
@ -88,7 +100,7 @@ class GliderComponent(private val plugin: App) : AbstractComponent(plugin, "glid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleSneak(player: Player, item: ItemStack, event: org.bukkit.event.player.PlayerToggleSneakEvent) {
|
private fun handleToggleSneak(player: Player, item: ItemStack, event: org.bukkit.event.player.PlayerToggleSneakEvent) {
|
||||||
// Only trigger when STARTING to sneak
|
// Only trigger when STARTING to sneak
|
||||||
if (event.isSneaking) {
|
if (event.isSneaking) {
|
||||||
if (activeGliders.containsKey(player.uniqueId)) {
|
if (activeGliders.containsKey(player.uniqueId)) {
|
||||||
|
|
@ -321,7 +333,7 @@ class GliderComponent(private val plugin: App) : AbstractComponent(plugin, "glid
|
||||||
|
|
||||||
private fun getHungerInterval(tier: Tier): Int = 40 + (tier.level - 1) * 20
|
private fun getHungerInterval(tier: Tier): Int = 40 + (tier.level - 1) * 20
|
||||||
|
|
||||||
override fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
|
private fun handleEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
|
||||||
if (event.cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL) {
|
if (event.cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL) {
|
||||||
val entity = event.entity
|
val entity = event.entity
|
||||||
if (entity is Player && activeGliders.containsKey(entity.uniqueId)) {
|
if (entity is Player && activeGliders.containsKey(entity.uniqueId)) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ class VeinMinerComponent(private val plugin: JavaPlugin) : ToolComponent {
|
||||||
override val maxTier: Int = 1
|
override val maxTier: Int = 1
|
||||||
override val minTier: Int = 1
|
override val minTier: Int = 1
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<kotlin.reflect.KClass<out org.bukkit.event.Event>, net.hareworks.hcu.items.events.ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
BlockBreakEvent::class to { event, _ -> handleBlockBreak(event as BlockBreakEvent) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val activeHighlights = mutableMapOf<UUID, HighlightSession>()
|
private val activeHighlights = mutableMapOf<UUID, HighlightSession>()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -146,7 +152,7 @@ class VeinMinerComponent(private val plugin: JavaPlugin) : ToolComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBlockBreak(event: BlockBreakEvent) {
|
private fun handleBlockBreak(event: BlockBreakEvent) {
|
||||||
if (event.isCancelled) return
|
if (event.isCancelled) return
|
||||||
val player = event.player
|
val player = event.player
|
||||||
val block = event.block
|
val block = event.block
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,15 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
class GrapplingItem : AbstractItem("grappling_hook") {
|
class GrapplingItem : AbstractItem("grappling_hook") {
|
||||||
|
|
||||||
override val maxTier: Int = 5
|
override val maxTier: Int = 5
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<kotlin.reflect.KClass<out org.bukkit.event.Event>, net.hareworks.hcu.items.events.ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
PlayerFishEvent::class to { event, _ -> handleFish(event as PlayerFishEvent) },
|
||||||
|
org.bukkit.event.entity.ProjectileLaunchEvent::class to { event, _ -> handleProjectileLaunch(event as org.bukkit.event.entity.ProjectileLaunchEvent) },
|
||||||
|
org.bukkit.event.entity.ProjectileHitEvent::class to { event, _ -> handleProjectileHit(event as org.bukkit.event.entity.ProjectileHitEvent) },
|
||||||
|
org.bukkit.event.entity.EntityDamageEvent::class to { event, _ -> handleEntityDamage(event as org.bukkit.event.entity.EntityDamageEvent) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun buildItem(tier: Tier): ItemStack {
|
override fun buildItem(tier: Tier): ItemStack {
|
||||||
val item = ItemStack(Material.FISHING_ROD)
|
val item = ItemStack(Material.FISHING_ROD)
|
||||||
|
|
@ -45,7 +54,7 @@ class GrapplingItem : AbstractItem("grappling_hook") {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFish(event: PlayerFishEvent) {
|
private fun handleFish(event: PlayerFishEvent) {
|
||||||
val hook = event.hook
|
val hook = event.hook
|
||||||
val player = event.player
|
val player = event.player
|
||||||
|
|
||||||
|
|
@ -117,7 +126,7 @@ class GrapplingItem : AbstractItem("grappling_hook") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProjectileLaunch(event: org.bukkit.event.entity.ProjectileLaunchEvent) {
|
private fun handleProjectileLaunch(event: org.bukkit.event.entity.ProjectileLaunchEvent) {
|
||||||
val projectile = event.entity
|
val projectile = event.entity
|
||||||
val shooter = projectile.shooter
|
val shooter = projectile.shooter
|
||||||
|
|
||||||
|
|
@ -130,7 +139,7 @@ class GrapplingItem : AbstractItem("grappling_hook") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProjectileHit(event: org.bukkit.event.entity.ProjectileHitEvent) {
|
private fun handleProjectileHit(event: org.bukkit.event.entity.ProjectileHitEvent) {
|
||||||
if ((event.hitBlock != null || event.hitEntity != null) && event.hitBlock?.isCollidable() == true) {
|
if ((event.hitBlock != null || event.hitEntity != null) && event.hitBlock?.isCollidable() == true) {
|
||||||
val hook = event.entity
|
val hook = event.entity
|
||||||
if (hook is org.bukkit.entity.FishHook) {
|
if (hook is org.bukkit.entity.FishHook) {
|
||||||
|
|
@ -180,7 +189,7 @@ class GrapplingItem : AbstractItem("grappling_hook") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
|
private fun handleEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
|
||||||
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) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,12 @@ class MagnetItem : AbstractItem("magnet_item") {
|
||||||
|
|
||||||
override val maxTier: Int = 3
|
override val maxTier: Int = 3
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<kotlin.reflect.KClass<out org.bukkit.event.Event>, net.hareworks.hcu.items.events.ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
org.bukkit.event.player.PlayerItemHeldEvent::class to { event, _ -> handleItemHeld(event as org.bukkit.event.player.PlayerItemHeldEvent) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val KEY_RADIUS = NamespacedKey("hcu_items", "magnet_radius")
|
val KEY_RADIUS = NamespacedKey("hcu_items", "magnet_radius")
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +104,7 @@ class MagnetItem : AbstractItem("magnet_item") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemHeld(event: org.bukkit.event.player.PlayerItemHeldEvent) {
|
private fun handleItemHeld(event: org.bukkit.event.player.PlayerItemHeldEvent) {
|
||||||
val player = event.player
|
val player = event.player
|
||||||
val item = player.inventory.getItem(event.previousSlot) ?: return
|
val item = player.inventory.getItem(event.previousSlot) ?: return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@ import org.bukkit.Material
|
||||||
import org.bukkit.inventory.ItemStack
|
import org.bukkit.inventory.ItemStack
|
||||||
|
|
||||||
class TestItem : AbstractItem("test_sword") {
|
class TestItem : AbstractItem("test_sword") {
|
||||||
|
|
||||||
|
override fun getEventHandlers(): Map<kotlin.reflect.KClass<out org.bukkit.event.Event>, net.hareworks.hcu.items.events.ComponentEventHandler<*>> {
|
||||||
|
return mapOf(
|
||||||
|
org.bukkit.event.player.PlayerInteractEvent::class to { event, _ -> handleInteract(event as org.bukkit.event.player.PlayerInteractEvent) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun buildItem(tier: Tier): ItemStack {
|
override fun buildItem(tier: Tier): ItemStack {
|
||||||
val item = ItemStack(Material.DIAMOND_SWORD)
|
val item = ItemStack(Material.DIAMOND_SWORD)
|
||||||
val meta = item.itemMeta ?: return item
|
val meta = item.itemMeta ?: return item
|
||||||
|
|
@ -22,7 +29,7 @@ class TestItem : AbstractItem("test_sword") {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {
|
private fun handleInteract(event: org.bukkit.event.player.PlayerInteractEvent) {
|
||||||
val item = event.item
|
val item = event.item
|
||||||
val tier = AbstractItem.getTier(item)
|
val tier = AbstractItem.getTier(item)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package net.hareworks.hcu.items.events
|
||||||
|
|
||||||
|
import org.bukkit.event.Event
|
||||||
|
import org.bukkit.inventory.ItemStack
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for component event handlers.
|
||||||
|
*
|
||||||
|
* @param event The event being handled.
|
||||||
|
* @param item The specific item stack associated with this component/item execution.
|
||||||
|
*/
|
||||||
|
typealias ComponentEventHandler<T> = (T, ItemStack) -> Unit
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
package net.hareworks.hcu.items.events
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.event.Event
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent
|
||||||
|
import org.bukkit.event.entity.EntityToggleGlideEvent
|
||||||
|
import org.bukkit.event.entity.FoodLevelChangeEvent
|
||||||
|
import org.bukkit.event.entity.ProjectileHitEvent
|
||||||
|
import org.bukkit.event.entity.ProjectileLaunchEvent
|
||||||
|
import org.bukkit.event.player.PlayerFishEvent
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent
|
||||||
|
import org.bukkit.event.player.PlayerItemHeldEvent
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent
|
||||||
|
import org.bukkit.event.player.PlayerToggleSneakEvent
|
||||||
|
import org.bukkit.inventory.ItemStack
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy to extract relevant context (items) from an event.
|
||||||
|
*/
|
||||||
|
interface EventContextStrategy<T : Event> {
|
||||||
|
/**
|
||||||
|
* Resolves the items that should trigger components for this event.
|
||||||
|
*/
|
||||||
|
fun resolveItems(event: T): List<ItemStack>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the primary player involved in the event, if any.
|
||||||
|
* This is useful for context checks.
|
||||||
|
*/
|
||||||
|
fun resolvePlayer(event: T): Player?
|
||||||
|
}
|
||||||
|
|
||||||
|
object EventStrategyRegistry {
|
||||||
|
private val strategies = mutableMapOf<KClass<out Event>, EventContextStrategy<*>>()
|
||||||
|
|
||||||
|
fun <T : Event> register(eventClass: KClass<T>, strategy: EventContextStrategy<T>) {
|
||||||
|
strategies[eventClass] = strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T : Event> get(eventClass: KClass<T>): EventContextStrategy<T>? {
|
||||||
|
return strategies[eventClass] as? EventContextStrategy<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
registerDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerDefaults() {
|
||||||
|
// PlayerInteractEvent
|
||||||
|
register(PlayerInteractEvent::class, object : EventContextStrategy<PlayerInteractEvent> {
|
||||||
|
override fun resolveItems(event: PlayerInteractEvent): List<ItemStack> {
|
||||||
|
val items = mutableListOf<ItemStack>()
|
||||||
|
event.item?.let { items.add(it) }
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: PlayerInteractEvent): Player = event.player
|
||||||
|
})
|
||||||
|
|
||||||
|
// EntityDamageEvent
|
||||||
|
register(EntityDamageEvent::class, object : EventContextStrategy<EntityDamageEvent> {
|
||||||
|
override fun resolveItems(event: EntityDamageEvent): List<ItemStack> {
|
||||||
|
val entity = event.entity as? Player ?: return emptyList()
|
||||||
|
val items = mutableListOf<ItemStack>()
|
||||||
|
items.addAll(entity.inventory.armorContents.filterNotNull())
|
||||||
|
items.add(entity.inventory.itemInMainHand)
|
||||||
|
items.add(entity.inventory.itemInOffHand)
|
||||||
|
return items.filter { !it.type.isAir }
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: EntityDamageEvent): Player? = event.entity as? Player
|
||||||
|
})
|
||||||
|
|
||||||
|
// BlockBreakEvent
|
||||||
|
register(BlockBreakEvent::class, object : EventContextStrategy<BlockBreakEvent> {
|
||||||
|
override fun resolveItems(event: BlockBreakEvent): List<ItemStack> {
|
||||||
|
val item = event.player.inventory.itemInMainHand
|
||||||
|
return if (item.type.isAir) emptyList() else listOf(item)
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: BlockBreakEvent): Player = event.player
|
||||||
|
})
|
||||||
|
|
||||||
|
// PlayerToggleSneakEvent
|
||||||
|
register(PlayerToggleSneakEvent::class, object : EventContextStrategy<PlayerToggleSneakEvent> {
|
||||||
|
override fun resolveItems(event: PlayerToggleSneakEvent): List<ItemStack> {
|
||||||
|
val player = event.player
|
||||||
|
val items = mutableListOf<ItemStack>()
|
||||||
|
items.addAll(player.inventory.armorContents.filterNotNull())
|
||||||
|
items.add(player.inventory.itemInMainHand)
|
||||||
|
items.add(player.inventory.itemInOffHand)
|
||||||
|
return items.filter { !it.type.isAir }
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: PlayerToggleSneakEvent): Player = event.player
|
||||||
|
})
|
||||||
|
|
||||||
|
// EntityToggleGlideEvent
|
||||||
|
register(EntityToggleGlideEvent::class, object : EventContextStrategy<EntityToggleGlideEvent> {
|
||||||
|
override fun resolveItems(event: EntityToggleGlideEvent): List<ItemStack> {
|
||||||
|
val player = event.entity as? Player ?: return emptyList()
|
||||||
|
val items = mutableListOf<ItemStack>()
|
||||||
|
items.addAll(player.inventory.armorContents.filterNotNull())
|
||||||
|
items.add(player.inventory.itemInMainHand)
|
||||||
|
items.add(player.inventory.itemInOffHand)
|
||||||
|
return items.filter { !it.type.isAir }
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: EntityToggleGlideEvent): Player? = event.entity as? Player
|
||||||
|
})
|
||||||
|
|
||||||
|
// PlayerItemHeldEvent
|
||||||
|
register(PlayerItemHeldEvent::class, object : EventContextStrategy<PlayerItemHeldEvent> {
|
||||||
|
override fun resolveItems(event: PlayerItemHeldEvent): List<ItemStack> {
|
||||||
|
val item = event.player.inventory.getItem(event.previousSlot)
|
||||||
|
return if (item != null && !item.type.isAir) listOf(item) else emptyList()
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: PlayerItemHeldEvent): Player = event.player
|
||||||
|
})
|
||||||
|
|
||||||
|
// PlayerFishEvent
|
||||||
|
register(PlayerFishEvent::class, object : EventContextStrategy<PlayerFishEvent> {
|
||||||
|
override fun resolveItems(event: PlayerFishEvent): List<ItemStack> {
|
||||||
|
val item = event.player.inventory.itemInMainHand
|
||||||
|
return if (item.type.isAir) emptyList() else listOf(item)
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: PlayerFishEvent): Player = event.player
|
||||||
|
})
|
||||||
|
|
||||||
|
// ProjectileLaunchEvent
|
||||||
|
register(ProjectileLaunchEvent::class, object : EventContextStrategy<ProjectileLaunchEvent> {
|
||||||
|
override fun resolveItems(event: ProjectileLaunchEvent): List<ItemStack> {
|
||||||
|
val shooter = event.entity.shooter as? Player ?: return emptyList()
|
||||||
|
val item = shooter.inventory.itemInMainHand
|
||||||
|
return if (item.type.isAir) emptyList() else listOf(item)
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: ProjectileLaunchEvent): Player? = event.entity.shooter as? Player
|
||||||
|
})
|
||||||
|
|
||||||
|
// ProjectileHitEvent
|
||||||
|
register(ProjectileHitEvent::class, object : EventContextStrategy<ProjectileHitEvent> {
|
||||||
|
override fun resolveItems(event: ProjectileHitEvent): List<ItemStack> {
|
||||||
|
// Requires scanning all registered items because the projectile might not be currently held
|
||||||
|
// But for strategy pattern, typically we want context from the event.
|
||||||
|
// Existing logic iterates ALL AbstractItems.
|
||||||
|
// To support that, we might need a "Global" strategy?
|
||||||
|
// For now, let's look at shooter's hand like Launch?
|
||||||
|
// The original logic: ItemRegistry.getAll().forEach { it.onProjectileHit(event) }
|
||||||
|
// This implies it's a "Global" event listener for all custom items.
|
||||||
|
// This Strategy pattern resolves relevant ItemStacks.
|
||||||
|
// If we return empty list, no specific item logic is called.
|
||||||
|
// But we want to call onProjectileHit for the Item TYPE that matches the projectile.
|
||||||
|
// This is tricky.
|
||||||
|
// Let's resolve the shooter's main hand for now as a best guess for "who did it".
|
||||||
|
val shooter = event.entity.shooter as? Player ?: return emptyList()
|
||||||
|
val item = shooter.inventory.itemInMainHand
|
||||||
|
return if (item.type.isAir) emptyList() else listOf(item)
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: ProjectileHitEvent): Player? = event.entity.shooter as? Player
|
||||||
|
})
|
||||||
|
|
||||||
|
// PlayerMoveEvent
|
||||||
|
register(PlayerMoveEvent::class, object : EventContextStrategy<PlayerMoveEvent> {
|
||||||
|
override fun resolveItems(event: PlayerMoveEvent): List<ItemStack> {
|
||||||
|
val item = event.player.inventory.itemInMainHand
|
||||||
|
return if (item.type.isAir) emptyList() else listOf(item)
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: PlayerMoveEvent): Player = event.player
|
||||||
|
})
|
||||||
|
|
||||||
|
// FoodLevelChangeEvent
|
||||||
|
register(FoodLevelChangeEvent::class, object : EventContextStrategy<FoodLevelChangeEvent> {
|
||||||
|
override fun resolveItems(event: FoodLevelChangeEvent): List<ItemStack> {
|
||||||
|
val player = event.entity as? Player ?: return emptyList()
|
||||||
|
// Original logic: entity.inventory.contents.filterNotNull()
|
||||||
|
return player.inventory.contents.filterNotNull()
|
||||||
|
}
|
||||||
|
override fun resolvePlayer(event: FoodLevelChangeEvent): Player? = event.entity as? Player
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,163 +1,153 @@
|
||||||
package net.hareworks.hcu.items.listeners
|
package net.hareworks.hcu.items.listeners
|
||||||
|
|
||||||
import net.hareworks.hcu.items.api.component.CustomComponent
|
import net.hareworks.hcu.items.api.component.CustomComponent
|
||||||
import net.hareworks.hcu.items.api.item.AbstractItem
|
|
||||||
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
|
|
||||||
|
|
||||||
import net.hareworks.hcu.items.api.component.EquippableComponent
|
import net.hareworks.hcu.items.api.component.EquippableComponent
|
||||||
import net.hareworks.hcu.items.api.component.ToolComponent
|
import net.hareworks.hcu.items.api.component.ToolComponent
|
||||||
|
import net.hareworks.hcu.items.api.item.AbstractItem
|
||||||
|
import net.hareworks.hcu.items.events.EventContextStrategy
|
||||||
|
import net.hareworks.hcu.items.events.EventStrategyRegistry
|
||||||
|
import net.hareworks.hcu.items.registry.ComponentRegistry
|
||||||
|
import net.hareworks.hcu.items.registry.ItemRegistry
|
||||||
|
import org.bukkit.event.Event
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.event.entity.ProjectileHitEvent
|
||||||
|
import org.bukkit.inventory.ItemStack
|
||||||
|
import org.bukkit.plugin.EventExecutor
|
||||||
import org.bukkit.plugin.Plugin
|
import org.bukkit.plugin.Plugin
|
||||||
|
import org.bukkit.event.EventPriority
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class EventListener(private val plugin: Plugin) : Listener {
|
class EventListener(private val plugin: Plugin) : Listener {
|
||||||
|
|
||||||
// Track previously held items for ToolComponents to detect when they stop being held
|
// Track previously held items for ToolComponents
|
||||||
// Key: Player UUID, Value: Pair(MainHandItem, OffHandItem)
|
|
||||||
private val lastHeldItems = java.util.concurrent.ConcurrentHashMap<java.util.UUID, Pair<ItemStack?, ItemStack?>>()
|
private val lastHeldItems = java.util.concurrent.ConcurrentHashMap<java.util.UUID, Pair<ItemStack?, ItemStack?>>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Use configurable tick interval (defaulting to 1L if something goes wrong, though Config handles defaults)
|
// Register Dynamic Listeners
|
||||||
|
registerDynamicListeners()
|
||||||
|
|
||||||
|
// Start Ticker
|
||||||
val interval = net.hareworks.hcu.items.config.Config.global.tickInterval
|
val interval = net.hareworks.hcu.items.config.Config.global.tickInterval
|
||||||
plugin.server.scheduler.runTaskTimer(plugin, Runnable {
|
plugin.server.scheduler.runTaskTimer(plugin, Runnable {
|
||||||
tickComponents()
|
tickComponents()
|
||||||
}, 1L, interval)
|
}, 1L, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special Case: ProjectileHitEvent
|
||||||
|
* This event is difficult to resolve context for (projectile might be far from shooter),
|
||||||
|
* so it's kept as a manual Global Dispatch to all AbstractItems.
|
||||||
|
*/
|
||||||
@EventHandler
|
@EventHandler
|
||||||
fun onInteract(event: org.bukkit.event.player.PlayerInteractEvent) {
|
fun onProjectileHit(event: ProjectileHitEvent) {
|
||||||
if (event.hand == EquipmentSlot.OFF_HAND) return
|
val shooter = event.entity.shooter
|
||||||
|
if (shooter is org.bukkit.entity.Player) {
|
||||||
val item = event.item ?: return
|
ItemRegistry.getAll().forEach { item ->
|
||||||
|
val handler = item.getEventHandlers()[ProjectileHitEvent::class]
|
||||||
dispatchToItem(item) { it.onInteract(event) }
|
if (handler != null) {
|
||||||
dispatchToComponents(item) { it.onInteract(event) }
|
@Suppress("UNCHECKED_CAST")
|
||||||
}
|
(handler as (ProjectileHitEvent, ItemStack) -> Unit).invoke(event, ItemStack(org.bukkit.Material.AIR))
|
||||||
|
}
|
||||||
@EventHandler
|
}
|
||||||
fun onFish(event: org.bukkit.event.player.PlayerFishEvent) {
|
|
||||||
val player = event.player
|
|
||||||
val item = player.inventory.itemInMainHand
|
|
||||||
|
|
||||||
dispatchToItem(item) { it.onFish(event) }
|
|
||||||
// CustomComponent interface does not define onFish, so no dispatchToComponents here.
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
fun onProjectileHit(event: org.bukkit.event.entity.ProjectileHitEvent) {
|
|
||||||
val projectile = event.entity
|
|
||||||
val shooter = projectile.shooter
|
|
||||||
|
|
||||||
if (shooter is Player) {
|
|
||||||
// For ProjectileHitEvent, AbstractItems 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 AbstractItems.
|
|
||||||
ItemRegistry.getAll().forEach { it.onProjectileHit(event) }
|
|
||||||
// CustomComponent interface does not define onProjectileHit, so no dispatchToComponents here.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
private fun registerDynamicListeners() {
|
||||||
fun onProjectileLaunch(event: org.bukkit.event.entity.ProjectileLaunchEvent) {
|
val registeredEvents = mutableSetOf<KClass<out Event>>()
|
||||||
val projectile = event.entity
|
|
||||||
val shooter = projectile.shooter
|
// Collect all unique events monitored by Items or Components
|
||||||
|
ItemRegistry.getAll().forEach { item ->
|
||||||
if (shooter is Player) {
|
registeredEvents.addAll(item.getEventHandlers().keys)
|
||||||
val item = shooter.inventory.itemInMainHand
|
}
|
||||||
dispatchToItem(item) { it.onProjectileLaunch(event) }
|
ComponentRegistry.getAll().forEach { component ->
|
||||||
// CustomComponent interface does not define onProjectileLaunch, so no dispatchToComponents here.
|
registeredEvents.addAll(component.getEventHandlers().keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a listener for each event type
|
||||||
|
for (eventClass in registeredEvents) {
|
||||||
|
// Skip ProjectileHitEvent as it is handled manually
|
||||||
|
if (eventClass == ProjectileHitEvent::class) continue
|
||||||
|
|
||||||
|
val strategy = EventStrategyRegistry.get(eventClass)
|
||||||
|
if (strategy == null) {
|
||||||
|
plugin.logger.warning("No strategy found for event ${eventClass.simpleName} but it is used by an item/component.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unchecked cast to pass valid types to generic helper
|
||||||
|
// We know strategy is Strategy<T> where T corresponds to eventClass
|
||||||
|
registerEvent(eventClass as KClass<Event>, strategy as EventContextStrategy<Event>)
|
||||||
|
plugin.logger.info("Registered dynamic listener for ${eventClass.simpleName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
private fun <T : Event> registerEvent(eventClass: KClass<T>, strategy: EventContextStrategy<T>) {
|
||||||
fun onEntityDamage(event: org.bukkit.event.entity.EntityDamageEvent) {
|
val executor = EventExecutor { _, event ->
|
||||||
val entity = event.entity
|
if (eventClass.isInstance(event)) {
|
||||||
if (entity is Player) {
|
@Suppress("UNCHECKED_CAST")
|
||||||
val items = getEquipmentItems(entity)
|
val castedEvent = event as T
|
||||||
for (item in items) {
|
dispatchWithStrategy(castedEvent, strategy)
|
||||||
dispatchToItem(item) { it.onEntityDamage(event) }
|
|
||||||
dispatchToComponents(item) { it.onEntityDamage(event) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugin.server.pluginManager.registerEvent(
|
||||||
|
eventClass.java,
|
||||||
|
this, // Use this instance as the Listener owner
|
||||||
|
EventPriority.NORMAL,
|
||||||
|
executor,
|
||||||
|
plugin
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
private fun <T : Event> dispatchWithStrategy(event: T, strategy: EventContextStrategy<T>) {
|
||||||
fun onPlayerMove(event: org.bukkit.event.player.PlayerMoveEvent) {
|
val items = strategy.resolveItems(event)
|
||||||
val player = event.player
|
|
||||||
val item = player.inventory.itemInMainHand
|
|
||||||
|
|
||||||
dispatchToItem(item) { it.onPlayerMove(event) }
|
|
||||||
dispatchToComponents(item) { it.onPlayerMove(event) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
fun onToggleSneak(event: org.bukkit.event.player.PlayerToggleSneakEvent) {
|
|
||||||
val player = event.player
|
|
||||||
val items = getEquipmentItems(player)
|
|
||||||
|
|
||||||
|
// Avoid duplicate firing for the same component on the same item context
|
||||||
for (item in items) {
|
for (item in items) {
|
||||||
// AbstractItem interface does not define onToggleSneak.
|
dispatchToItem(event, item)
|
||||||
val handled = dispatchToComponentsWithDependencyCheck(
|
dispatchToComponents(event, item)
|
||||||
item = item,
|
|
||||||
isEventHandled = { event.isCancelled }
|
|
||||||
) { component ->
|
|
||||||
component.onToggleSneak(player, item, event)
|
|
||||||
}
|
|
||||||
if (handled) break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
private fun <T : Event> dispatchToItem(event: T, item: ItemStack) {
|
||||||
fun onToggleGlide(event: org.bukkit.event.entity.EntityToggleGlideEvent) {
|
if (item.type.isAir) return
|
||||||
val entity = event.entity
|
val id = AbstractItem.getId(item) ?: return
|
||||||
if (entity is Player) {
|
val abstractItem = ItemRegistry.get(id) ?: return
|
||||||
val items = getEquipmentItems(entity)
|
|
||||||
for (item in items) {
|
val handler = abstractItem.getEventHandlers()[event::class]
|
||||||
val handled = dispatchToComponentsWithDependencyCheck(
|
if (handler != null) {
|
||||||
item = item,
|
@Suppress("UNCHECKED_CAST")
|
||||||
isEventHandled = { event.isCancelled }
|
(handler as (T, ItemStack) -> Unit).invoke(event, item)
|
||||||
) { component ->
|
|
||||||
component.onToggleGlide(entity, item, event)
|
|
||||||
}
|
|
||||||
if (handled) break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
private fun <T : Event> dispatchToComponents(event: T, item: ItemStack) {
|
||||||
fun onBlockBreak(event: org.bukkit.event.block.BlockBreakEvent) {
|
if (item.type.isAir) return
|
||||||
val player = event.player
|
val meta = item.itemMeta ?: return
|
||||||
val item = player.inventory.itemInMainHand
|
val pdc = meta.persistentDataContainer
|
||||||
|
|
||||||
// Dispatch to components on the main hand item
|
val eventClass = event::class
|
||||||
dispatchToComponents(item) { it.onBlockBreak(event) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
for (key in pdc.keys) {
|
||||||
fun onItemHeld(event: org.bukkit.event.player.PlayerItemHeldEvent) {
|
val component = ComponentRegistry.get(key) ?: continue
|
||||||
val player = event.player
|
|
||||||
val item = player.inventory.getItem(event.previousSlot) ?: return
|
// Check Data Dependencies
|
||||||
|
val hasAllDependencies = component.dataComponentDependencies.all { dep ->
|
||||||
|
item.hasData(dep)
|
||||||
|
}
|
||||||
|
if (!hasAllDependencies) continue
|
||||||
|
|
||||||
dispatchToItem(item) { it.onItemHeld(event) }
|
val handler = component.getEventHandlers()[eventClass]
|
||||||
dispatchToComponents(item) { it.onItemHeld(player, item, event) }
|
if (handler != null) {
|
||||||
}
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(handler as (T, ItemStack) -> Unit).invoke(event, item)
|
||||||
@EventHandler
|
|
||||||
fun onFoodLevelChange(event: org.bukkit.event.entity.FoodLevelChangeEvent) {
|
|
||||||
val entity = event.entity
|
|
||||||
if (entity is Player) {
|
|
||||||
// インベントリ内の全アイテムをチェック
|
|
||||||
val items = entity.inventory.contents.filterNotNull()
|
|
||||||
for (item in items) {
|
|
||||||
dispatchToItem(item) { it.onFoodLevelChange(event) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Tick Logic ---
|
||||||
|
|
||||||
private fun tickComponents() {
|
private fun tickComponents() {
|
||||||
for (player in plugin.server.onlinePlayers) {
|
for (player in plugin.server.onlinePlayers) {
|
||||||
val uuid = player.uniqueId
|
val uuid = player.uniqueId
|
||||||
|
|
@ -171,7 +161,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
||||||
|
|
||||||
// Check Main Hand Change
|
// Check Main Hand Change
|
||||||
if (lastMainHand != null && !lastMainHand.isSimilar(currentMainHand)) {
|
if (lastMainHand != null && !lastMainHand.isSimilar(currentMainHand)) {
|
||||||
dispatchToComponents(lastMainHand) { component ->
|
dispatchToComponentsTick(lastMainHand) { component ->
|
||||||
if (component is ToolComponent) {
|
if (component is ToolComponent) {
|
||||||
component.onStopHolding(player, lastMainHand)
|
component.onStopHolding(player, lastMainHand)
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +170,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
||||||
|
|
||||||
// Check Off Hand Change
|
// Check Off Hand Change
|
||||||
if (lastOffHand != null && !lastOffHand.isSimilar(currentOffHand)) {
|
if (lastOffHand != null && !lastOffHand.isSimilar(currentOffHand)) {
|
||||||
dispatchToComponents(lastOffHand) { component ->
|
dispatchToComponentsTick(lastOffHand) { component ->
|
||||||
if (component is ToolComponent) {
|
if (component is ToolComponent) {
|
||||||
component.onStopHolding(player, lastOffHand)
|
component.onStopHolding(player, lastOffHand)
|
||||||
}
|
}
|
||||||
|
|
@ -192,21 +182,18 @@ class EventListener(private val plugin: Plugin) : Listener {
|
||||||
|
|
||||||
// --- Regular Tick Processing ---
|
// --- Regular Tick Processing ---
|
||||||
|
|
||||||
// 1. Dispatch onTick to ALL CustomItems in inventory
|
// 1. Dispatch onTick to ALL CustomItems
|
||||||
// inventory.contents typically covers storage, hotbar, armor, and offhand depending on implementation,
|
|
||||||
// but we iterate it to ensure we catch items anywhere in the inventory.
|
|
||||||
for (item in player.inventory.contents) {
|
for (item in player.inventory.contents) {
|
||||||
if (item == null || item.type.isAir) continue
|
if (item == null || item.type.isAir) continue
|
||||||
dispatchToItem(item) { it.onTick(player, item) }
|
dispatchToItemTick(item) { it.onTick(player, item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Dispatch to Components for specific slots (Armor, MainHand, OffHand)
|
// 2. Dispatch to Components
|
||||||
// Note: dispatchToItem is skipped here because it was already done above for all items.
|
|
||||||
|
|
||||||
// Armor Tick
|
// Armor Tick
|
||||||
for (item in player.inventory.armorContents) {
|
for (item in player.inventory.armorContents) {
|
||||||
if (item == null || item.type.isAir) continue
|
if (item == null || item.type.isAir) continue
|
||||||
dispatchToComponents(item) { component ->
|
dispatchToComponentsTick(item) { component ->
|
||||||
if (component is EquippableComponent) {
|
if (component is EquippableComponent) {
|
||||||
component.onTick(player, item)
|
component.onTick(player, item)
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +202,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
||||||
|
|
||||||
// ToolComponents in Main Hand
|
// ToolComponents in Main Hand
|
||||||
if (!currentMainHand.type.isAir) {
|
if (!currentMainHand.type.isAir) {
|
||||||
dispatchToComponents(currentMainHand) { component ->
|
dispatchToComponentsTick(currentMainHand) { component ->
|
||||||
if (component is ToolComponent) {
|
if (component is ToolComponent) {
|
||||||
component.onHoldTick(player, currentMainHand)
|
component.onHoldTick(player, currentMainHand)
|
||||||
}
|
}
|
||||||
|
|
@ -227,7 +214,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
||||||
|
|
||||||
// ToolComponents in Off Hand
|
// ToolComponents in Off Hand
|
||||||
if (!currentOffHand.type.isAir) {
|
if (!currentOffHand.type.isAir) {
|
||||||
dispatchToComponents(currentOffHand) { component ->
|
dispatchToComponentsTick(currentOffHand) { component ->
|
||||||
if (component is ToolComponent) {
|
if (component is ToolComponent) {
|
||||||
component.onHoldTick(player, currentOffHand)
|
component.onHoldTick(player, currentOffHand)
|
||||||
}
|
}
|
||||||
|
|
@ -239,69 +226,15 @@ class EventListener(private val plugin: Plugin) : Listener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEquipmentItems(player: Player): List<ItemStack> {
|
// Helpers for tick dispatching (legacy style interaction)
|
||||||
val equipment = player.equipment
|
private fun dispatchToItemTick(item: ItemStack, action: (AbstractItem) -> Unit) {
|
||||||
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: (AbstractItem) -> Unit) {
|
|
||||||
if (item.type.isAir) return
|
if (item.type.isAir) return
|
||||||
val id = AbstractItem.getId(item) ?: return
|
val id = AbstractItem.getId(item) ?: return
|
||||||
val abstractItem = ItemRegistry.get(id) ?: return
|
val abstractItem = ItemRegistry.get(id) ?: return
|
||||||
action(abstractItem)
|
action(abstractItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun dispatchToComponentsTick(item: ItemStack, action: (CustomComponent) -> Unit) {
|
||||||
* Dispatches to components on an item, but only if the item satisfies
|
|
||||||
* the component's dataComponentDependencies.
|
|
||||||
*
|
|
||||||
* This prevents multiple components with the same trigger conditions
|
|
||||||
* (e.g., GLIDER) from firing simultaneously when only one item
|
|
||||||
* actually has the required data component active.
|
|
||||||
*
|
|
||||||
* @param item The item to dispatch to
|
|
||||||
* @param isEventHandled Lambda to check if the event has been handled (e.g., cancelled)
|
|
||||||
* @param action The action to perform on each eligible component
|
|
||||||
* @return true if the event was handled by any component, false otherwise
|
|
||||||
*/
|
|
||||||
private fun dispatchToComponentsWithDependencyCheck(
|
|
||||||
item: ItemStack,
|
|
||||||
isEventHandled: () -> Boolean,
|
|
||||||
action: (CustomComponent) -> Unit
|
|
||||||
): Boolean {
|
|
||||||
if (item.type.isAir) return false
|
|
||||||
val meta = item.itemMeta ?: return false
|
|
||||||
val pdc = meta.persistentDataContainer
|
|
||||||
|
|
||||||
for (key in pdc.keys) {
|
|
||||||
// Stop if event is already handled by a previous component
|
|
||||||
if (isEventHandled()) return true
|
|
||||||
|
|
||||||
val component = ComponentRegistry.get(key) ?: continue
|
|
||||||
|
|
||||||
// Check if this item has all required data component dependencies
|
|
||||||
// This ensures the component only triggers on items that
|
|
||||||
// actually have the necessary data components active
|
|
||||||
val hasAllDependencies = component.dataComponentDependencies.all { dep ->
|
|
||||||
item.hasData(dep)
|
|
||||||
}
|
|
||||||
if (!hasAllDependencies) continue
|
|
||||||
|
|
||||||
action(component)
|
|
||||||
}
|
|
||||||
|
|
||||||
return isEventHandled()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches to all components on an item without checking dependencies.
|
|
||||||
* Use this for events that don't rely on data component triggers.
|
|
||||||
*/
|
|
||||||
private fun dispatchToComponents(item: ItemStack, action: (CustomComponent) -> Unit) {
|
|
||||||
if (item.type.isAir) return
|
if (item.type.isAir) return
|
||||||
val meta = item.itemMeta ?: return
|
val meta = item.itemMeta ?: return
|
||||||
val pdc = meta.persistentDataContainer
|
val pdc = meta.persistentDataContainer
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user