refactor: Extract common item scroll adjustment logic into ItemActionUtil and manage scroll states for items.

This commit is contained in:
Kariya 2025-12-19 09:28:24 +00:00
parent 3b62b26273
commit d4c8cd84fb
5 changed files with 164 additions and 59 deletions

View File

@ -18,6 +18,7 @@ import net.hareworks.hcu.items.content.components.AreaTillerComponent
import org.bukkit.permissions.PermissionDefault import org.bukkit.permissions.PermissionDefault
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import net.hareworks.hcu.items.util.ItemActionUtil
public class App : JavaPlugin() { public class App : JavaPlugin() {
@ -53,4 +54,9 @@ public class App : JavaPlugin() {
server.pluginManager.registerEvents(net.hareworks.hcu.items.listeners.EventListener(this), this) server.pluginManager.registerEvents(net.hareworks.hcu.items.listeners.EventListener(this), this)
} }
override fun onDisable() {
ItemActionUtil.clearAllScrollStates()
logger.info("Items plugin disabled!")
}
} }

View File

@ -37,6 +37,7 @@ import java.util.UUID
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.reflect.KClass import kotlin.reflect.KClass
import net.hareworks.hcu.items.util.ItemActionUtil
/** /**
* AreaTillerComponent - 広範囲一括耕地化コンポーネント * AreaTillerComponent - 広範囲一括耕地化コンポーネント
@ -302,39 +303,27 @@ class AreaTillerComponent(private val plugin: JavaPlugin) : ToolComponent {
*/ */
private fun handleItemHeld(event: PlayerItemHeldEvent, item: ItemStack) { private fun handleItemHeld(event: PlayerItemHeldEvent, item: ItemStack) {
val player = event.player val player = event.player
val session = editSessions.getOrPut(player.uniqueId) { EditSession() }
// スニーク中のみ調整
if (!player.isSneaking) return if (!player.isSneaking) return
val session = editSessions.getOrPut(player.uniqueId) { EditSession() } ItemActionUtil.handleScrollAdjustment(event) { delta ->
val tier = getTier(item) val tier = getTier(item)
val maxRange = getMaxRangeForTier(tier) val maxRange = getMaxRangeForTier(tier)
val now = System.currentTimeMillis() // プレイヤーの向きから、どの方角の範囲を伸長するか決定
if (now - session.lastScrollTime < 50) return val facing = getDirectionFacing(player.location.yaw)
session.lastScrollTime = now 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)
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
} }
/** /**

View File

@ -13,6 +13,7 @@ import org.bukkit.entity.Item
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataType import org.bukkit.persistence.PersistentDataType
import net.hareworks.hcu.items.util.ItemActionUtil
class MagnetItem : AbstractItem("magnet_item") { class MagnetItem : AbstractItem("magnet_item") {
@ -109,42 +110,37 @@ class MagnetItem : AbstractItem("magnet_item") {
val item = player.inventory.getItem(event.previousSlot) ?: return val item = player.inventory.getItem(event.previousSlot) ?: return
if (!Config.magnet.allowScrollChange) return if (!Config.magnet.allowScrollChange) return
if (!player.isSneaking) return if (!player.isSneaking) return
// 動いていない時のみ調整可能 ItemActionUtil.handleScrollAdjustment(event) { delta ->
if (player.velocity.length() > 0.08) return // 完全な0は難しいため閾値を設定 val configInfo = Config.magnet
val tier = getTier(item)
val maxRadius = configInfo.radiusBase + (tier.level * configInfo.radiusPerTier)
val configInfo = Config.magnet // Get current radius
val tier = getTier(item) var currentRadius = item.itemMeta?.persistentDataContainer?.get(KEY_RADIUS, PersistentDataType.DOUBLE) ?: maxRadius
val maxRadius = configInfo.radiusBase + (tier.level * configInfo.radiusPerTier)
val diff = event.newSlot - event.previousSlot currentRadius += delta.toDouble()
val scrollUp = (diff == -1 || diff == 8) currentRadius = currentRadius.coerceIn(1.0, maxRadius)
val change = if (scrollUp) 1.0 else -1.0
// Get current radius // Save to PDC
var currentRadius = item.itemMeta?.persistentDataContainer?.get(KEY_RADIUS, PersistentDataType.DOUBLE) ?: maxRadius val meta = item.itemMeta
meta.persistentDataContainer.set(KEY_RADIUS, PersistentDataType.DOUBLE, currentRadius)
item.itemMeta = meta
currentRadius += change // Feedback
currentRadius = currentRadius.coerceIn(1.0, maxRadius) player.sendActionBar(Component.text("回収範囲: ", NamedTextColor.AQUA)
.append(Component.text(String.format("%.1f", currentRadius) + "m", NamedTextColor.YELLOW))
.append(Component.text(" | ", NamedTextColor.DARK_GRAY))
.append(Component.text("最大: ", NamedTextColor.GRAY))
.append(Component.text(String.format("%.1f", maxRadius) + "m", NamedTextColor.WHITE)))
// Save to PDC // 視覚的なフィードバック (BlockDisplayによるリング表示)
val meta = item.itemMeta updateRadiusRing(player, currentRadius, tier)
meta.persistentDataContainer.set(KEY_RADIUS, PersistentDataType.DOUBLE, currentRadius)
item.itemMeta = meta
// Feedback player.playSound(player.location, Sound.UI_BUTTON_CLICK, 0.3f, 1.2f)
player.sendActionBar(Component.text("回収範囲: ", NamedTextColor.AQUA) }
.append(Component.text(String.format("%.1f", currentRadius) + "m", NamedTextColor.YELLOW))
.append(Component.text(" | ", NamedTextColor.DARK_GRAY))
.append(Component.text("最大: ", NamedTextColor.GRAY))
.append(Component.text(String.format("%.1f", maxRadius) + "m", NamedTextColor.WHITE)))
// 視覚的なフィードバック (BlockDisplayによるリング表示)
updateRadiusRing(player, currentRadius, tier)
// Cancel event to prevent slot switch
event.isCancelled = true
} }
private val activeRings = java.util.concurrent.ConcurrentHashMap<java.util.UUID, RingSession>() private val activeRings = java.util.concurrent.ConcurrentHashMap<java.util.UUID, RingSession>()

View File

@ -15,7 +15,9 @@ import org.bukkit.event.entity.ProjectileHitEvent
import org.bukkit.event.entity.EntityExplodeEvent import org.bukkit.event.entity.EntityExplodeEvent
import org.bukkit.event.entity.EntityInteractEvent import org.bukkit.event.entity.EntityInteractEvent
import org.bukkit.event.block.* import org.bukkit.event.block.*
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import net.hareworks.hcu.items.util.ItemActionUtil
import org.bukkit.plugin.EventExecutor import org.bukkit.plugin.EventExecutor
import org.bukkit.plugin.Plugin import org.bukkit.plugin.Plugin
import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority
@ -120,6 +122,11 @@ class EventListener(private val plugin: Plugin) : Listener {
dispatchGlobalToComponents(event) dispatchGlobalToComponents(event)
} }
@EventHandler
fun onPlayerQuit(event: PlayerQuitEvent) {
ItemActionUtil.clearScrollState(event.player)
}
private fun <T : Event> dispatchGlobalToComponents(event: T) { private fun <T : Event> dispatchGlobalToComponents(event: T) {
val eventClass = event.javaClass.kotlin val eventClass = event.javaClass.kotlin
ComponentRegistry.getAll().forEach { component -> ComponentRegistry.getAll().forEach { component ->

View File

@ -0,0 +1,107 @@
package net.hareworks.hcu.items.util
import org.bukkit.entity.Player
import org.bukkit.event.player.PlayerItemHeldEvent
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
/**
* アイテム操作に関する共通ユーティリティ
*/
object ItemActionUtil {
// プレイヤーごとのスクロール状態を管理
private data class ScrollState(
var lastScrollTime: Long = 0L,
var accumulatedDelta: Double = 0.0,
var lastProcessedTime: Long = 0L
)
private val scrollStates = ConcurrentHashMap<UUID, ScrollState>()
// 設定値
private const val SCROLL_COOLDOWN_MS = 50L // スクロール間隔(ミリ秒)
private const val ACCELERATION_THRESHOLD = 150L // 加速判定の時間閾値
private const val DELTA_THRESHOLD = 0.8 // 累積値の閾値
private const val MAX_ACCUMULATED_DELTA = 5.0 // 最大累積値
/**
* マウスホイールスロット変更による設定値の調整ロジックを処理します
* スクロールの速度に応じて滑らかにかつ連続スクロール時には加速して調整します
*
* @param event スロット変更イベント
* @param onAdjust 調整が有効な場合に呼び出されるコールバック (delta: Int)
*/
fun handleScrollAdjustment(event: PlayerItemHeldEvent, onAdjust: (Int) -> Unit) {
val player = event.player
// 2. プレイヤーがほぼ静止状態のみ有効
if (player.velocity.length() > 0.08) return
// 3. スクロール方向を判定
val diff = event.newSlot - event.previousSlot
val scrollUp = (diff == -1 || diff == 8)
val rawDelta = if (scrollUp) 1.0 else -1.0
// 4. 滑らかなスクロール処理
val state = scrollStates.computeIfAbsent(player.uniqueId) { ScrollState() }
val currentTime = System.currentTimeMillis()
val timeSinceLastScroll = currentTime - state.lastScrollTime
// クールダウンチェック(連続スクロールの検出)
if (timeSinceLastScroll < SCROLL_COOLDOWN_MS) {
event.isCancelled = true
return
}
// 加速度の計算(素早くスクロールすると累積値が増える)
val accelerationFactor = if (timeSinceLastScroll < ACCELERATION_THRESHOLD) {
1.5 // 素早いスクロールで加速
} else {
1.0
}
// 累積デルタ値の更新
state.accumulatedDelta += rawDelta * accelerationFactor
state.lastScrollTime = currentTime
// 累積値の制限
state.accumulatedDelta = state.accumulatedDelta.coerceIn(
-MAX_ACCUMULATED_DELTA,
MAX_ACCUMULATED_DELTA
)
// 閾値を超えたら実際に値を変更
val timeSinceLastProcess = currentTime - state.lastProcessedTime
if (kotlin.math.abs(state.accumulatedDelta) >= DELTA_THRESHOLD ||
timeSinceLastProcess > 200L) { // または一定時間経過
val deltaToApply = state.accumulatedDelta.toInt()
if (deltaToApply != 0) {
onAdjust(deltaToApply)
state.lastProcessedTime = currentTime
state.accumulatedDelta = 0.0
}
}
// 5. イベントをキャンセルしてスロット切り替えを防ぐ
event.isCancelled = true
}
/**
* プレイヤーのスクロール状態をクリアします
* プレイヤーがログアウトした際などに呼び出してください
*
* @param player 対象プレイヤー
*/
fun clearScrollState(player: Player) {
scrollStates.remove(player.uniqueId)
}
/**
* 全てのスクロール状態をクリアします
* プラグインの無効化時などに呼び出してください
*/
fun clearAllScrollStates() {
scrollStates.clear()
}
}