feat: Add Area Tiller component with configurable range and global block event handling.

This commit is contained in:
Kariya 2025-12-19 09:17:14 +00:00
parent 483493bb87
commit 3b62b26273
5 changed files with 744 additions and 3 deletions

View File

@ -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)
}

View File

@ -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<Int, Int> = 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<Int, Int>()
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)

View File

@ -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<UUID, EditSession>()
// ハイライトセッション管理
private val activeHighlights = mutableMapOf<UUID, HighlightSession>()
// 再帰防止フラグ(広範囲耕地化中のプレイヤーを追跡)
private val processingPlayers = mutableSetOf<UUID>()
// 永続的な湿った耕地のためのキー
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<BlockDisplay>,
val lastUpdate: Long,
val north: Int,
val south: Int,
val east: Int,
val west: Int
)
override fun getEventHandlers(): Map<KClass<out Event>, 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<Block> {
val blocks = mutableSetOf<Block>()
// 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<Block>, 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<Block>,
isEternallyMoist: Boolean
): List<BlockDisplay> {
if (!supportsBlockDisplay) return emptyList()
val entities = mutableListOf<BlockDisplay>()
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()
}
}
}

View File

@ -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
@ -53,6 +56,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 <T : Event> 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<KClass<out Event>>()
@ -66,8 +144,19 @@ 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) {

View File

@ -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: