From 3b62b26273306d46207c77d3bc7d4ef04461f56d Mon Sep 17 00:00:00 2001 From: Kariya Date: Fri, 19 Dec 2025 09:17:14 +0000 Subject: [PATCH] feat: Add Area Tiller component with configurable range and global block event handling. --- .../kotlin/net/hareworks/hcu/items/App.kt | 2 + .../net/hareworks/hcu/items/config/Config.kt | 44 ++ .../content/components/AreaTillerComponent.kt | 599 ++++++++++++++++++ .../hcu/items/listeners/EventListener.kt | 95 ++- src/main/resources/config.yml | 7 + 5 files changed, 744 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/net/hareworks/hcu/items/content/components/AreaTillerComponent.kt diff --git a/src/main/kotlin/net/hareworks/hcu/items/App.kt b/src/main/kotlin/net/hareworks/hcu/items/App.kt index dad0678..19b0968 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/App.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/App.kt @@ -14,6 +14,7 @@ import net.hareworks.hcu.items.content.components.GliderComponent import net.hareworks.hcu.items.content.components.DoubleJumpComponent import net.hareworks.hcu.items.content.components.BlinkComponent import net.hareworks.hcu.items.content.components.VeinMinerComponent +import net.hareworks.hcu.items.content.components.AreaTillerComponent import org.bukkit.permissions.PermissionDefault import org.bukkit.plugin.java.JavaPlugin @@ -48,6 +49,7 @@ public class App : JavaPlugin() { ComponentRegistry.register(DoubleJumpComponent(this)) ComponentRegistry.register(BlinkComponent(this)) ComponentRegistry.register(VeinMinerComponent(this)) + ComponentRegistry.register(AreaTillerComponent(this)) server.pluginManager.registerEvents(net.hareworks.hcu.items.listeners.EventListener(this), this) } diff --git a/src/main/kotlin/net/hareworks/hcu/items/config/Config.kt b/src/main/kotlin/net/hareworks/hcu/items/config/Config.kt index 9695d5d..7fb8386 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/config/Config.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/config/Config.kt @@ -20,6 +20,7 @@ object Config { var blink = BlinkSettings() var doubleJump = DoubleJumpSettings() var veinMiner = VeinMinerSettings() + var areaTiller = AreaTillerSettings() var magnet = MagnetSettings() var autoFeeder = AutoFeederSettings() @@ -62,6 +63,14 @@ object Config { var hungerThreshold: Int = 10 ) + /** + * AreaTillerコンポーネント設定 + */ + data class AreaTillerSettings( + var maxRangePerTier: Map = mapOf(1 to 4, 2 to 5, 3 to 4, 4 to 5), + var defaultRange: Int = 3 + ) + /** * VeinMinerコンポーネント設定 */ @@ -108,6 +117,7 @@ object Config { loadDoubleJumpSettings(config) loadVeinMinerSettings(plugin, config) loadMagnetSettings(config) + loadAreaTillerSettings(config) loadAutoFeederSettings(config) saveDefaults(plugin, config) @@ -187,6 +197,33 @@ object Config { ) } + /** + * AreaTiller設定を読み込む + */ + private fun loadAreaTillerSettings(config: FileConfiguration) { + val rangeMap = mutableMapOf() + val section = config.getConfigurationSection("components.area_tiller.max_range_per_tier") + if (section != null) { + for (key in section.getKeys(false)) { + key.toIntOrNull()?.let { tier -> + rangeMap[tier] = section.getInt(key) + } + } + } + // Fallback defaults if empty + if (rangeMap.isEmpty()) { + rangeMap[1] = 4 + rangeMap[2] = 5 + rangeMap[3] = 4 + rangeMap[4] = 5 + } + + areaTiller = AreaTillerSettings( + maxRangePerTier = rangeMap, + defaultRange = config.getInt("components.area_tiller.default_range", 3) + ) + } + /** * デフォルト設定を保存する */ @@ -201,6 +238,13 @@ object Config { config.addDefault("components.magnet.radius_base", 5.0) config.addDefault("components.magnet.radius_per_tier", 5.0) config.addDefault("components.magnet.allow_scroll_change", true) + + config.addDefault("components.area_tiller.default_range", 3) + config.addDefault("components.area_tiller.max_range_per_tier.1", 4) + config.addDefault("components.area_tiller.max_range_per_tier.2", 5) + config.addDefault("components.area_tiller.max_range_per_tier.3", 4) + config.addDefault("components.area_tiller.max_range_per_tier.4", 5) + config.addDefault("items.auto_feeder.hunger_threshold", 10) config.options().copyDefaults(true) diff --git a/src/main/kotlin/net/hareworks/hcu/items/content/components/AreaTillerComponent.kt b/src/main/kotlin/net/hareworks/hcu/items/content/components/AreaTillerComponent.kt new file mode 100644 index 0000000..36ac30c --- /dev/null +++ b/src/main/kotlin/net/hareworks/hcu/items/content/components/AreaTillerComponent.kt @@ -0,0 +1,599 @@ +package net.hareworks.hcu.items.content.components + +import net.hareworks.hcu.items.api.component.ToolComponent +import net.hareworks.hcu.items.api.Tier +import net.hareworks.hcu.items.config.Config +import net.hareworks.hcu.items.events.ComponentEventHandler +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.block.Block +import org.bukkit.block.BlockFace +import org.bukkit.block.data.type.Farmland +import org.bukkit.entity.Player +import org.bukkit.entity.BlockDisplay +import org.bukkit.entity.Display +import org.bukkit.event.Event +import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.event.entity.EntityInteractEvent +import org.bukkit.event.block.* +import org.bukkit.event.player.PlayerItemHeldEvent +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.inventory.EquipmentSlot +import org.bukkit.inventory.ItemStack +import org.bukkit.persistence.PersistentDataType +import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.util.Transformation +import org.bukkit.Color +import org.bukkit.Particle +import org.bukkit.Sound +import org.bukkit.Tag +import org.bukkit.GameMode +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.block.MoistureChangeEvent +import org.joml.Vector3f +import org.joml.AxisAngle4f +import java.util.UUID +import kotlin.math.max +import kotlin.math.min +import kotlin.reflect.KClass + +/** + * AreaTillerComponent - 広範囲一括耕地化コンポーネント + * + * クワを使用時に広範囲の土ブロックを一度に耕地化する。 + * Tier3以上では、水なしでも湿った状態を保つ特殊な耕地を生成する。 + */ +class AreaTillerComponent(private val plugin: JavaPlugin) : ToolComponent { + + override val key: NamespacedKey = NamespacedKey(plugin, "area_tiller") + override val displayName: String = "Area Tiller" + override val maxTier: Int = 4 + override val minTier: Int = 1 + + // 耕地化対象ブロック + private val tillableBlocks = setOf( + Material.DIRT, + Material.GRASS_BLOCK, + Material.DIRT_PATH, + Material.COARSE_DIRT, + Material.ROOTED_DIRT + ) + + // クワのマテリアル + private val hoeTag = Tag.ITEMS_HOES + + // 編集モードセッション管理 + private val editSessions = mutableMapOf() + + // ハイライトセッション管理 + private val activeHighlights = mutableMapOf() + + // 再帰防止フラグ(広範囲耕地化中のプレイヤーを追跡) + private val processingPlayers = mutableSetOf() + + // 永続的な湿った耕地のためのキー + private val moistKey = NamespacedKey(plugin, "eternally_moist") + + companion object { + var supportsBlockDisplay: Boolean = false + init { + try { + Class.forName("org.bukkit.entity.BlockDisplay") + supportsBlockDisplay = true + } catch (e: ClassNotFoundException) { + supportsBlockDisplay = false + } + } + } + + /** + * 編集セッションデータ (4方向個別の範囲) + */ + data class EditSession( + var north: Int = 1, + var south: Int = 1, + var east: Int = 1, + var west: Int = 1, + var lastScrollTime: Long = 0 + ) + + /** + * ハイライトセッションデータ + */ + data class HighlightSession( + val centerBlock: Block, + val entities: List, + val lastUpdate: Long, + val north: Int, + val south: Int, + val east: Int, + val west: Int + ) + + override fun getEventHandlers(): Map, ComponentEventHandler<*>> { + return mapOf( + PlayerInteractEvent::class to { event, item -> + handleInteract(event as PlayerInteractEvent, item) + }, + PlayerItemHeldEvent::class to { event, item -> + handleItemHeld(event as PlayerItemHeldEvent, item) + }, + MoistureChangeEvent::class to { event, _ -> + handleMoistureChange(event as MoistureChangeEvent) + }, + BlockBreakEvent::class to { event, _ -> + handleGenericBlockCleanup(event as BlockBreakEvent) + }, + BlockExplodeEvent::class to { event, _ -> + handleBlockExplodeCleanup(event as BlockExplodeEvent) + }, + EntityExplodeEvent::class to { event, _ -> + handleEntityExplodeCleanup(event as EntityExplodeEvent) + }, + EntityInteractEvent::class to { event, _ -> + handleEntityInteractCleanup(event as EntityInteractEvent) + }, + BlockFadeEvent::class to { event, _ -> + handleGenericBlockCleanup(event as BlockFadeEvent) + }, + BlockFromToEvent::class to { event, _ -> + handleBlockFromToCleanup(event as BlockFromToEvent) + }, + BlockPistonExtendEvent::class to { event, _ -> + handlePistonExtendCleanup(event as BlockPistonExtendEvent) + }, + BlockPistonRetractEvent::class to { event, _ -> + handlePistonRetractCleanup(event as BlockPistonRetractEvent) + }, + BlockPlaceEvent::class to { event, _ -> + handleGenericBlockCleanup(event as BlockPlaceEvent) + }, + BlockBurnEvent::class to { event, _ -> + handleGenericBlockCleanup(event as BlockBurnEvent) + }, + BlockPhysicsEvent::class to { event, _ -> + handleBlockPhysicsCleanup(event as BlockPhysicsEvent) + } + ) + } + + override fun apply(item: ItemStack, tier: Tier?) { + val meta = item.itemMeta ?: return + val tierValue = tier?.level ?: 1 + meta.persistentDataContainer.set(key, PersistentDataType.INTEGER, tierValue) + item.itemMeta = meta + } + + override fun has(item: ItemStack): Boolean { + if (item.type.isAir) return false + return item.itemMeta?.persistentDataContainer?.has(key, PersistentDataType.INTEGER) == true + } + + override fun remove(item: ItemStack) { + val meta = item.itemMeta ?: return + meta.persistentDataContainer.remove(key) + item.itemMeta = meta + } + + /** + * アイテムからティア値を取得 + */ + private fun getTier(item: ItemStack): Int { + return item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.INTEGER) ?: 1 + } + + /** + * ティアに応じた最大範囲を取得 + */ + private fun getMaxRangeForTier(tier: Int): Int { + return Config.areaTiller.maxRangePerTier[tier] ?: Config.areaTiller.defaultRange + } + + override fun onHoldTick(player: Player, item: ItemStack) { + val targetBlock = player.getTargetBlockExact(5) + + // ターゲットブロックがない、または耕地化不可の場合はハイライト消去 + if (targetBlock == null || !tillableBlocks.contains(targetBlock.type)) { + clearHighlight(player) + return + } + + val session = editSessions.getOrPut(player.uniqueId) { EditSession() } + val tier = getTier(item) + val maxRange = getMaxRangeForTier(tier) + + // 範囲を制限内に収める + val n = session.north.coerceIn(0, maxRange) + val s = session.south.coerceIn(0, maxRange) + val e = session.east.coerceIn(0, maxRange) + val w = session.west.coerceIn(0, maxRange) + + // 編集モードチェック(スニーク中) + val isEditMode = player.isSneaking + + // 編集モード中はどの方角を調整しているか特定 + val facing = if (isEditMode) getDirectionFacing(player.location.yaw) else null + + // 編集モード中は軸情報をアクションバーに表示 + if (isEditMode && facing != null) { + player.sendActionBar( + net.kyori.adventure.text.Component.text() + .append(net.kyori.adventure.text.Component.text("範囲: ", net.kyori.adventure.text.format.NamedTextColor.GRAY)) + .append(net.kyori.adventure.text.Component.text("北:$n ", if(facing == BlockFace.NORTH) net.kyori.adventure.text.format.NamedTextColor.YELLOW else net.kyori.adventure.text.format.NamedTextColor.WHITE)) + .append(net.kyori.adventure.text.Component.text("南:$s ", if(facing == BlockFace.SOUTH) net.kyori.adventure.text.format.NamedTextColor.YELLOW else net.kyori.adventure.text.format.NamedTextColor.WHITE)) + .append(net.kyori.adventure.text.Component.text("東:$e ", if(facing == BlockFace.EAST) net.kyori.adventure.text.format.NamedTextColor.YELLOW else net.kyori.adventure.text.format.NamedTextColor.WHITE)) + .append(net.kyori.adventure.text.Component.text("西:$w ", if(facing == BlockFace.WEST) net.kyori.adventure.text.format.NamedTextColor.YELLOW else net.kyori.adventure.text.format.NamedTextColor.WHITE)) + .append(net.kyori.adventure.text.Component.text("(方向を向いてスクロールで調整)", net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY)) + .build() + ) + } + + // ハイライト更新チェック + val highlight = activeHighlights[player.uniqueId] + if (highlight != null && + highlight.centerBlock == targetBlock && + highlight.north == n && highlight.south == s && + highlight.east == e && highlight.west == w) { + return + } + + // ハイライト再生成 + clearHighlight(player) + + val blocksToTill = calculateTillableBlocks(targetBlock, n, s, e, w) + if (blocksToTill.isEmpty()) return + + val entities = createHighlightEntities(player, blocksToTill, tier >= 3) + activeHighlights[player.uniqueId] = HighlightSession( + targetBlock, entities, System.currentTimeMillis(), n, s, e, w + ) + } + + override fun onStopHolding(player: Player, item: ItemStack) { + clearHighlight(player) + } + + /** + * インタラクトイベント処理 + */ + private fun handleInteract(event: PlayerInteractEvent, item: ItemStack) { + // 再帰防止チェック + if (processingPlayers.contains(event.player.uniqueId)) return + + // 右クリックで耕地化 + if (event.action != Action.RIGHT_CLICK_BLOCK) return + if (event.hand != EquipmentSlot.HAND) return + + val block = event.clickedBlock ?: return + if (!tillableBlocks.contains(block.type)) return + + // スニーク中は何もしない(ホイール調整のため) + if (event.player.isSneaking) { + return + } + + val player = event.player + val tier = getTier(item) + val session = editSessions[player.uniqueId] ?: EditSession() + val maxRange = getMaxRangeForTier(tier) + + val n = session.north.coerceIn(0, maxRange) + val s = session.south.coerceIn(0, maxRange) + val e = session.east.coerceIn(0, maxRange) + val w = session.west.coerceIn(0, maxRange) + + val blocksToTill = calculateTillableBlocks(block, n, s, e, w) + if (blocksToTill.isEmpty()) return + + // 元のイベントをキャンセル(単一ブロックの耕地化を防ぐ) + event.isCancelled = true + + // 広範囲耕地化実行 + performAreaTill(player, blocksToTill, item, tier) + + // ハイライトクリア + clearHighlight(player) + } + + /** + * マウスホイール(スロット変更)によるサイズ調整の処理 + * プレイヤーが向いている方向の範囲を個別に調整する(Blenderの面移動に近い感覚) + */ + private fun handleItemHeld(event: PlayerItemHeldEvent, item: ItemStack) { + val player = event.player + + // スニーク中のみ調整 + if (!player.isSneaking) return + + val session = editSessions.getOrPut(player.uniqueId) { EditSession() } + val tier = getTier(item) + val maxRange = getMaxRangeForTier(tier) + + val now = System.currentTimeMillis() + if (now - session.lastScrollTime < 50) return + + session.lastScrollTime = now + + // スクロール方向を判定 + val diff = event.newSlot - event.previousSlot + val scrollUp = (diff == -1 || diff == 8) + val delta = if (scrollUp) 1 else -1 + + // プレイヤーの向きから、どの方角の範囲を伸長するか決定 + val facing = getDirectionFacing(player.location.yaw) + + when (facing) { + BlockFace.NORTH -> session.north = (session.north + delta).coerceIn(0, maxRange) + BlockFace.SOUTH -> session.south = (session.south + delta).coerceIn(0, maxRange) + BlockFace.EAST -> session.east = (session.east + delta).coerceIn(0, maxRange) + BlockFace.WEST -> session.west = (session.west + delta).coerceIn(0, maxRange) + else -> {} + } + + player.playSound(player.location, Sound.UI_BUTTON_CLICK, 0.3f, 1.2f) + + // イベントをキャンセルしてスロット切り替えを防ぐ + event.isCancelled = true + } + + /** + * プレイヤーのYawから、N/S/E/Wのどの方角を向いているか取得 + */ + private fun getDirectionFacing(yaw: Float): BlockFace { + val normalizedYaw = ((yaw % 360) + 360) % 360 + return when (normalizedYaw) { + in 45f..135f -> BlockFace.WEST + in 135f..225f -> BlockFace.NORTH + in 225f..315f -> BlockFace.EAST + else -> BlockFace.SOUTH + } + } + + /** + * 耕地化対象ブロックを計算 (4方向の範囲を使用) + */ + private fun calculateTillableBlocks(centerBlock: Block, north: Int, south: Int, east: Int, west: Int): Set { + val blocks = mutableSetOf() + + // North は -Z, South は +Z, East は +X, West は -X + for (x in -west..east) { + for (z in -north..south) { + val block = centerBlock.getRelative(x, 0, z) + if (tillableBlocks.contains(block.type)) { + // 上にブロックがないことを確認 + val above = block.getRelative(BlockFace.UP) + if (above.type.isAir || !above.type.isSolid) { + blocks.add(block) + } + } + } + } + + return blocks + } + + /** + * 広範囲耕地化を実行 + */ + private fun performAreaTill(player: Player, blocks: Set, item: ItemStack, tier: Int) { + // 再帰防止フラグをセット + processingPlayers.add(player.uniqueId) + + try { + val isEternallyMoist = tier >= 3 + var tilledCount = 0 + + for (block in blocks) { + // PlayerInteractEventを発火して他のプラグインとの互換性を保つ + val fakeEvent = PlayerInteractEvent( + player, + Action.RIGHT_CLICK_BLOCK, + item, + block, + BlockFace.UP, + EquipmentSlot.HAND + ) + plugin.server.pluginManager.callEvent(fakeEvent) + + if (fakeEvent.isCancelled) continue + + // 耕地化 + block.type = Material.FARMLAND + + // Tier3以上: 永続的な湿った状態 + if (isEternallyMoist) { + val farmlandData = block.blockData as? Farmland + farmlandData?.let { + it.moisture = it.maximumMoisture + block.blockData = it + } + // 永続的な湿り状態をマーク(別途Tickで維持) + markEternallyMoist(block) + } + + tilledCount++ + } + + // ツールに耐久ダメージ + if (tilledCount > 0) { + if(player.gameMode != GameMode.CREATIVE && player.gameMode != GameMode.SPECTATOR) + player.damageItemStack(item, tilledCount) + // 効果音 + player.playSound( + player.location, + Sound.ITEM_HOE_TILL, + 1.0f, + 0.8f + (tier * 0.1f) + ) + + // Tier3以上の特殊エフェクト + if (isEternallyMoist) { + blocks.forEach { block -> + player.spawnParticle( + Particle.DRIPPING_WATER, + block.location.add(0.5, 1.0, 0.5), + 3, + 0.3, 0.1, 0.3, + 0.0 + ) + } + } + } + } finally { + // 再帰防止フラグをクリア + processingPlayers.remove(player.uniqueId) + } + } + + /** + * 耕地の水分量変化(乾燥)を監視 + */ + private fun handleMoistureChange(event: MoistureChangeEvent) { + val block = event.block + + // 自己浄化: 耕地でなくなった場合、あるいは乾燥しようとしている場合 + if (block.type != Material.FARMLAND) { + removeMoistMark(block) + return + } + + // チャンクのPDCを確認して「永続的な湿り」マークがあるかチェック + if (isMarkedAsEternallyMoist(block)) { + // 水分が減るのを阻止(イベントをキャンセル) + event.isCancelled = true + + // 念のためnewStateも最大値に固定 + val data = event.newState + if (data is Farmland) { + data.moisture = data.maximumMoisture + } + } + } + + /** + * 汎用的なブロック消失クリーンアップ + */ + private fun handleGenericBlockCleanup(event: Event) { + val block = when (event) { + is BlockBreakEvent -> event.block + is BlockFadeEvent -> event.block + is BlockPlaceEvent -> event.block + is BlockBurnEvent -> event.block + else -> return + } + removeMoistMark(block) + } + + private fun handleBlockExplodeCleanup(event: BlockExplodeEvent) { + event.blockList().forEach { removeMoistMark(it) } + } + + private fun handleEntityExplodeCleanup(event: EntityExplodeEvent) { + event.blockList().forEach { removeMoistMark(it) } + } + + private fun handleEntityInteractCleanup(event: EntityInteractEvent) { + // 耕地を踏みつけた時など、耕地でなくなる可能性が高いイベント + removeMoistMark(event.block) + } + + private fun handleBlockFromToCleanup(event: BlockFromToEvent) { + // 水や溶岩が流れ込んで耕地が消える場合 + removeMoistMark(event.block) + } + + private fun handlePistonExtendCleanup(event: BlockPistonExtendEvent) { + event.blocks.forEach { removeMoistMark(it) } + } + + private fun handlePistonRetractCleanup(event: BlockPistonRetractEvent) { + event.blocks.forEach { removeMoistMark(it) } + } + + private fun handleBlockPhysicsCleanup(event: BlockPhysicsEvent) { + // 全ての物理更新時に、もし「以前耕地でマークされていたのに、今は耕地でない」なら削除 + val block = event.block + if (block.type != Material.FARMLAND) { + removeMoistMark(block) + } + } + + /** + * ブロックが永続的な湿り状態としてマークされているか確認 + */ + private fun isMarkedAsEternallyMoist(block: Block): Boolean { + val chunk = block.chunk + val pdc = chunk.persistentDataContainer + val key = NamespacedKey(plugin, "moist_${block.x}_${block.y}_${block.z}") + return pdc.has(key, PersistentDataType.BYTE) + } + + /** + * ブロックを永続的な湿り状態としてマーク + */ + private fun markEternallyMoist(block: Block) { + val chunk = block.chunk + val pdc = chunk.persistentDataContainer + val key = NamespacedKey(plugin, "moist_${block.x}_${block.y}_${block.z}") + pdc.set(key, PersistentDataType.BYTE, 1.toByte()) + } + + private fun removeMoistMark(block: Block) { + val chunk = block.chunk + val pdc = chunk.persistentDataContainer + val key = NamespacedKey(plugin, "moist_${block.x}_${block.y}_${block.z}") + if (pdc.has(key, PersistentDataType.BYTE)) { + pdc.remove(key) + } + } + + /** + * ハイライトエンティティを作成 + */ + private fun createHighlightEntities( + player: Player, + blocks: Set, + isEternallyMoist: Boolean + ): List { + if (!supportsBlockDisplay) return emptyList() + + val entities = mutableListOf() + val highlightColor = if (isEternallyMoist) Color.AQUA else Color.LIME + + for (block in blocks) { + val loc = block.location.add(0.5, 0.5, 0.5) + try { + val display = block.world.spawn(loc, BlockDisplay::class.java) { e -> + e.block = Material.FARMLAND.createBlockData() + e.transformation = Transformation( + Vector3f(-0.5005f, -0.5005f, -0.5005f), + AxisAngle4f(0f, 0f, 0f, 1f), + Vector3f(1.001f, 1.001f, 1.001f), + AxisAngle4f(0f, 0f, 0f, 1f) + ) + e.isGlowing = true + e.glowColorOverride = highlightColor + e.brightness = Display.Brightness(15, 15) + e.isVisibleByDefault = false + } + player.showEntity(plugin, display) + entities.add(display) + } catch (e: Exception) { + // BlockDisplay非対応バージョン + } + } + + return entities + } + + /** + * ハイライトをクリア + */ + private fun clearHighlight(player: Player) { + val session = activeHighlights.remove(player.uniqueId) ?: return + for (e in session.entities) { + e.remove() + } + } +} 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 2b57ecb..698c57c 100644 --- a/src/main/kotlin/net/hareworks/hcu/items/listeners/EventListener.kt +++ b/src/main/kotlin/net/hareworks/hcu/items/listeners/EventListener.kt @@ -12,6 +12,9 @@ import org.bukkit.event.Event import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.entity.ProjectileHitEvent +import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.event.entity.EntityInteractEvent +import org.bukkit.event.block.* import org.bukkit.inventory.ItemStack import org.bukkit.plugin.EventExecutor import org.bukkit.plugin.Plugin @@ -52,6 +55,81 @@ class EventListener(private val plugin: Plugin) : Listener { } } } + + /** + * MoistureChangeEvent - 耕地の水分変化(乾燥)を監視 + * これもアイテムに紐付かないグローバルなイベントとして処理する。 + */ + @EventHandler + fun onMoistureChange(event: MoistureChangeEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockBreakGlobal(event: BlockBreakEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockExplodeGlobal(event: BlockExplodeEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onEntityExplodeGlobal(event: EntityExplodeEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onEntityInteractGlobal(event: EntityInteractEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockFadeGlobal(event: BlockFadeEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockFromToGlobal(event: BlockFromToEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockPistonExtendGlobal(event: BlockPistonExtendEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockPistonRetractGlobal(event: BlockPistonRetractEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockPlaceGlobal(event: BlockPlaceEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockBurnGlobal(event: BlockBurnEvent) { + dispatchGlobalToComponents(event) + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onBlockPhysicsGlobal(event: BlockPhysicsEvent) { + dispatchGlobalToComponents(event) + } + + private fun dispatchGlobalToComponents(event: T) { + val eventClass = event.javaClass.kotlin + ComponentRegistry.getAll().forEach { component -> + val handler = component.getEventHandlers()[eventClass] + if (handler != null) { + @Suppress("UNCHECKED_CAST") + (handler as (T, ItemStack) -> Unit).invoke(event, ItemStack(org.bukkit.Material.AIR)) + } + } + } private fun registerDynamicListeners() { val registeredEvents = mutableSetOf>() @@ -66,9 +144,20 @@ class EventListener(private val plugin: Plugin) : Listener { // Register a listener for each event type for (eventClass in registeredEvents) { - // Skip ProjectileHitEvent as it is handled manually - if (eventClass == ProjectileHitEvent::class) continue - + // Skip events that are handled manually or globally + if (eventClass == ProjectileHitEvent::class || + eventClass == MoistureChangeEvent::class || + eventClass == EntityExplodeEvent::class || + eventClass == BlockExplodeEvent::class || + eventClass == EntityInteractEvent::class || + eventClass == BlockFadeEvent::class || + eventClass == BlockFromToEvent::class || + eventClass == BlockPistonExtendEvent::class || + eventClass == BlockPistonRetractEvent::class || + eventClass == BlockPlaceEvent::class || + eventClass == BlockBurnEvent::class || + eventClass == BlockPhysicsEvent::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.") diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index cfdf48b..74b12fa 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -70,6 +70,13 @@ components: - "minecraft:shroomlight" - "minecraft:hay_block" - "#minecraft:wart_blocks" + area_tiller: + default_range: 3 # デフォルトの耕地化範囲 + max_range_per_tier: + 1: 4 # Tier1: 4x4 + 2: 5 # Tier2: 5x5 + 3: 4 # Tier3: 4x4 (永続湿り耕地) + 4: 5 # Tier4: 5x5 (永続湿り耕地) items: auto_feeder: