feat: Add Magnet Item with configurable attraction radius and scroll adjustment.
This commit is contained in:
parent
0e72f24164
commit
fb7ae54875
|
|
@ -8,6 +8,7 @@ import net.hareworks.hcu.items.registry.ItemRegistry
|
|||
import net.hareworks.hcu.items.registry.ComponentRegistry
|
||||
import net.hareworks.hcu.items.content.items.TestItem
|
||||
import net.hareworks.hcu.items.content.items.GrapplingItem
|
||||
import net.hareworks.hcu.items.content.items.MagnetItem
|
||||
import net.hareworks.hcu.items.content.components.GliderComponent
|
||||
import net.hareworks.hcu.items.content.components.DoubleJumpComponent
|
||||
import net.hareworks.hcu.items.content.components.BlinkComponent
|
||||
|
|
@ -39,6 +40,7 @@ public class App : JavaPlugin() {
|
|||
// Register items
|
||||
ItemRegistry.register(TestItem())
|
||||
ItemRegistry.register(GrapplingItem())
|
||||
ItemRegistry.register(MagnetItem())
|
||||
|
||||
// Register Components
|
||||
ComponentRegistry.register(GliderComponent(this))
|
||||
|
|
|
|||
|
|
@ -29,4 +29,5 @@ interface CustomComponent {
|
|||
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) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,6 @@ interface CustomItem {
|
|||
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 onTick(player: org.bukkit.entity.Player, item: ItemStack) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ object Config {
|
|||
var blink = BlinkSettings()
|
||||
var doubleJump = DoubleJumpSettings()
|
||||
var veinMiner = VeinMinerSettings()
|
||||
var magnet = MagnetSettings()
|
||||
|
||||
/**
|
||||
* グローバル設定
|
||||
|
|
@ -44,6 +45,15 @@ object Config {
|
|||
var powerForward: Double = 0.3
|
||||
)
|
||||
|
||||
/**
|
||||
* Magnetコンポーネント設定
|
||||
*/
|
||||
data class MagnetSettings(
|
||||
var radiusBase: Double = 5.0,
|
||||
var radiusPerTier: Double = 5.0,
|
||||
var allowScrollChange: Boolean = true
|
||||
)
|
||||
|
||||
/**
|
||||
* VeinMinerコンポーネント設定
|
||||
*/
|
||||
|
|
@ -89,6 +99,7 @@ object Config {
|
|||
loadBlinkSettings(config)
|
||||
loadDoubleJumpSettings(config)
|
||||
loadVeinMinerSettings(plugin, config)
|
||||
loadMagnetSettings(config)
|
||||
|
||||
saveDefaults(plugin, config)
|
||||
}
|
||||
|
|
@ -147,6 +158,17 @@ object Config {
|
|||
loadToolCategories(config, veinMiner.toolCategories)
|
||||
}
|
||||
|
||||
/**
|
||||
* Magnet設定を読み込む
|
||||
*/
|
||||
private fun loadMagnetSettings(config: FileConfiguration) {
|
||||
magnet = MagnetSettings(
|
||||
radiusBase = config.getDouble("components.magnet.radius_base", 5.0),
|
||||
radiusPerTier = config.getDouble("components.magnet.radius_per_tier", 5.0),
|
||||
allowScrollChange = config.getBoolean("components.magnet.allow_scroll_change", true)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* デフォルト設定を保存する
|
||||
*/
|
||||
|
|
@ -158,6 +180,9 @@ object Config {
|
|||
config.addDefault("components.double_jump.power.forward", 0.3)
|
||||
config.addDefault("components.vein_miner.max_blocks", 64)
|
||||
config.addDefault("components.vein_miner.activation_mode", "SNEAK")
|
||||
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.options().copyDefaults(true)
|
||||
plugin.saveConfig()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,283 @@
|
|||
package net.hareworks.hcu.items.content.items
|
||||
|
||||
import net.hareworks.hcu.items.api.Tier
|
||||
import net.hareworks.hcu.items.api.item.AbstractItem
|
||||
import net.hareworks.hcu.items.config.Config
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.Sound
|
||||
import org.bukkit.entity.ExperienceOrb
|
||||
import org.bukkit.entity.Item
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
|
||||
class MagnetItem : AbstractItem("magnet_item") {
|
||||
|
||||
override val maxTier: Int = 3
|
||||
|
||||
companion object {
|
||||
val KEY_RADIUS = NamespacedKey("hcu_items", "magnet_radius")
|
||||
}
|
||||
|
||||
override fun buildItem(tier: Tier): ItemStack {
|
||||
return ItemStack(Material.IRON_NUGGET).apply {
|
||||
val meta = itemMeta ?: return@apply
|
||||
|
||||
meta.displayName(Component.text("マグネットグローブ", tier.color))
|
||||
|
||||
val configInfo = Config.magnet
|
||||
val maxRadius = configInfo.radiusBase + (tier.level * configInfo.radiusPerTier)
|
||||
|
||||
// 星(強さの表現)
|
||||
val powerStars = "★".repeat(tier.level) + "☆".repeat(3 - tier.level) // Tier上限3なので3つ
|
||||
|
||||
meta.lore(listOf(
|
||||
Component.empty(),
|
||||
Component.text("周囲のアイテムを強力に引き寄せる", NamedTextColor.GRAY),
|
||||
Component.text("インベントリにあるだけで効果発揮", NamedTextColor.GRAY),
|
||||
Component.empty(),
|
||||
Component.text("【性能】", NamedTextColor.WHITE),
|
||||
Component.text("ティア: ${tier.name}", tier.color),
|
||||
Component.text("吸引力: $powerStars", NamedTextColor.AQUA),
|
||||
Component.text("最大範囲: ${maxRadius}m", NamedTextColor.GREEN),
|
||||
Component.empty(),
|
||||
Component.text("メインハンドで[Shift]+[Scroll]", NamedTextColor.YELLOW),
|
||||
Component.text("回収範囲を調整", NamedTextColor.YELLOW)
|
||||
))
|
||||
itemMeta = meta
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTick(player: Player, item: ItemStack) {
|
||||
// オフハンドにある時のみ機能する
|
||||
if (!item.isSimilar(player.inventory.itemInOffHand)) return
|
||||
|
||||
val tier = getTier(item)
|
||||
val configInfo = Config.magnet
|
||||
|
||||
// Calculate max radius based on Tier
|
||||
val maxRadius = configInfo.radiusBase + (tier.level * configInfo.radiusPerTier)
|
||||
|
||||
// Get current set radius from PDC, default to max
|
||||
val currentRadius = item.itemMeta?.persistentDataContainer?.get(KEY_RADIUS, PersistentDataType.DOUBLE) ?: maxRadius
|
||||
val actualRadius = currentRadius.coerceAtMost(maxRadius)
|
||||
|
||||
// Pull implementation
|
||||
val location = player.location.add(0.0, 1.0, 0.0) // Check from player center
|
||||
val entities = location.world.getNearbyEntities(location, actualRadius, actualRadius, actualRadius)
|
||||
|
||||
var pulledAny = false
|
||||
|
||||
for (entity in entities) {
|
||||
if (entity is Item || entity is ExperienceOrb) {
|
||||
// Ignore items with pickup delay > 20 (newly dropped)
|
||||
if (entity is Item && entity.pickupDelay > 20) continue
|
||||
|
||||
// Direction to player
|
||||
val direction = location.clone().subtract(entity.location).toVector()
|
||||
val distance = direction.length()
|
||||
|
||||
|
||||
val force = direction.normalize().multiply(0.3)
|
||||
entity.velocity = entity.velocity.add(force)
|
||||
pulledAny = true
|
||||
|
||||
// 吸引エフェクト
|
||||
player.spawnParticle(
|
||||
org.bukkit.Particle.END_ROD,
|
||||
entity.location,
|
||||
1,
|
||||
0.0, 0.0, 0.0,
|
||||
0.5,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemHeld(event: org.bukkit.event.player.PlayerItemHeldEvent) {
|
||||
val player = event.player
|
||||
val item = player.inventory.getItem(event.previousSlot) ?: return
|
||||
|
||||
if (!Config.magnet.allowScrollChange) return
|
||||
if (!player.isSneaking) return
|
||||
|
||||
// 動いていない時のみ調整可能
|
||||
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 diff = event.newSlot - event.previousSlot
|
||||
val scrollUp = (diff == -1 || diff == 8)
|
||||
val change = if (scrollUp) 1.0 else -1.0
|
||||
|
||||
// Get current radius
|
||||
var currentRadius = item.itemMeta?.persistentDataContainer?.get(KEY_RADIUS, PersistentDataType.DOUBLE) ?: maxRadius
|
||||
|
||||
currentRadius += change
|
||||
currentRadius = currentRadius.coerceIn(1.0, maxRadius)
|
||||
|
||||
// Save to PDC
|
||||
val meta = item.itemMeta
|
||||
meta.persistentDataContainer.set(KEY_RADIUS, PersistentDataType.DOUBLE, currentRadius)
|
||||
item.itemMeta = meta
|
||||
|
||||
// Feedback
|
||||
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 data class RingSession(
|
||||
val entities: List<org.bukkit.entity.BlockDisplay>,
|
||||
var task: org.bukkit.scheduler.BukkitTask,
|
||||
var radius: Double,
|
||||
var tierMaterial: Material,
|
||||
var lastKeepAlive: Long
|
||||
)
|
||||
|
||||
private fun updateRadiusRing(player: Player, radius: Double, tier: Tier) {
|
||||
val tierMaterial = getTierGlassMaterial(tier)
|
||||
val session = activeRings[player.uniqueId]
|
||||
|
||||
if (session != null && session.entities.all { it.isValid }) {
|
||||
// セッション更新
|
||||
session.radius = radius
|
||||
session.tierMaterial = tierMaterial
|
||||
session.lastKeepAlive = System.currentTimeMillis()
|
||||
|
||||
// 色が変わった場合はブロック更新
|
||||
// ※Tierが変わるとMaterialが変わるため
|
||||
session.entities.forEach {
|
||||
it.block = org.bukkit.Bukkit.createBlockData(tierMaterial)
|
||||
}
|
||||
|
||||
// 即座に位置更新(スクロールの反応性を良くするため)
|
||||
updateRingTransformations(player, radius, session.entities)
|
||||
} else {
|
||||
// クリーンアップ(念のため)
|
||||
session?.entities?.forEach { it.remove() }
|
||||
session?.task?.cancel()
|
||||
|
||||
// 新規作成
|
||||
val entities = createRingEntities(player, radius, tierMaterial)
|
||||
val task = startRingTask(player)
|
||||
activeRings[player.uniqueId] = RingSession(entities, task, radius, tierMaterial, System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
|
||||
private fun startRingTask(player: Player): org.bukkit.scheduler.BukkitTask {
|
||||
return org.bukkit.Bukkit.getScheduler().runTaskTimer(net.hareworks.hcu.items.App.instance, Runnable {
|
||||
val session = activeRings[player.uniqueId]
|
||||
if (session == null || !player.isOnline || session.entities.any { !it.isValid }) {
|
||||
session?.entities?.forEach { it.remove() }
|
||||
activeRings.remove(player.uniqueId)
|
||||
return@Runnable
|
||||
}
|
||||
|
||||
// タイムアウトチェック (3秒)
|
||||
if (System.currentTimeMillis() - session.lastKeepAlive > 3000) {
|
||||
session.entities.forEach { it.remove() }
|
||||
session.task.cancel()
|
||||
activeRings.remove(player.uniqueId)
|
||||
return@Runnable
|
||||
}
|
||||
|
||||
// プレイヤーの位置に合わせて追従
|
||||
updateRingTransformations(player, session.radius, session.entities)
|
||||
|
||||
}, 0L, 1L) // 毎実行
|
||||
}
|
||||
|
||||
private fun createRingEntities(player: Player, radius: Double, material: Material): List<org.bukkit.entity.BlockDisplay> {
|
||||
val center = player.location
|
||||
val segments = 64 // 分割数を増やして滑らかに
|
||||
val entities = mutableListOf<org.bukkit.entity.BlockDisplay>()
|
||||
|
||||
for (i in 0 until segments) {
|
||||
val display = player.world.spawn(center, org.bukkit.entity.BlockDisplay::class.java) { e ->
|
||||
e.block = org.bukkit.Bukkit.createBlockData(material)
|
||||
e.brightness = org.bukkit.entity.Display.Brightness(15, 15)
|
||||
e.isPersistent = false // セーブしない
|
||||
e.setGravity(false)
|
||||
e.isVisibleByDefault = false // デフォルトでは見えない
|
||||
}
|
||||
player.showEntity(net.hareworks.hcu.items.App.instance, display) // プレイヤーにだけ見せる
|
||||
entities.add(display)
|
||||
}
|
||||
|
||||
// 初期配置
|
||||
updateRingTransformations(player, radius, entities)
|
||||
return entities
|
||||
}
|
||||
|
||||
private fun updateRingTransformations(player: Player, radius: Double, entities: List<org.bukkit.entity.BlockDisplay>) {
|
||||
val center = player.location.clone().add(0.0, 0.1, 0.0) // 足元少し上
|
||||
|
||||
val segments = entities.size
|
||||
val angleStep = 2.0 * Math.PI / segments
|
||||
|
||||
// 滑らかにつなぐための長さ計算(弦の長さ)
|
||||
val segmentLength = 2.0 * radius * Math.sin(Math.PI / segments)
|
||||
|
||||
// 線の太さ
|
||||
val thickness = 0.05f
|
||||
|
||||
for (i in 0 until segments) {
|
||||
val entity = entities[i]
|
||||
val angle = i * angleStep
|
||||
|
||||
// 配置位置
|
||||
val x = radius * Math.cos(angle)
|
||||
val z = radius * Math.sin(angle)
|
||||
val loc = center.clone().add(x, 0.0, z)
|
||||
|
||||
// 向きの設定
|
||||
val nextAngle = angle + angleStep
|
||||
val nextX = radius * Math.cos(nextAngle)
|
||||
val nextZ = radius * Math.sin(nextAngle)
|
||||
val dir = org.bukkit.util.Vector(nextX - x, 0.0, nextZ - z)
|
||||
loc.direction = dir
|
||||
|
||||
entity.teleport(loc)
|
||||
|
||||
// Transformation設定: Z軸方向(視線方向)に伸ばしてつなげる
|
||||
val scaleX = thickness
|
||||
val scaleY = thickness
|
||||
val scaleZ = segmentLength.toFloat() * 1.05f // 隙間防止のため少し長めに
|
||||
|
||||
// X, Yの中心を合わせて、Z方向(前方)へ伸ばす
|
||||
val transformation = org.joml.Matrix4f()
|
||||
.translate(-thickness / 2, -thickness / 2, 0f)
|
||||
.scale(scaleX, scaleY, scaleZ)
|
||||
|
||||
entity.setTransformationMatrix(transformation)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTierGlassMaterial(tier: Tier): Material {
|
||||
return when ((tier.level - 1) % 5 + 1) {
|
||||
1 -> Material.WHITE_STAINED_GLASS
|
||||
2 -> Material.LIME_STAINED_GLASS
|
||||
3 -> Material.LIGHT_BLUE_STAINED_GLASS
|
||||
4 -> Material.MAGENTA_STAINED_GLASS
|
||||
5 -> Material.YELLOW_STAINED_GLASS
|
||||
else -> Material.WHITE_STAINED_GLASS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -137,6 +137,15 @@ class EventListener(private val plugin: Plugin) : Listener {
|
|||
dispatchToComponents(item) { it.onBlockBreak(event) }
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onItemHeld(event: org.bukkit.event.player.PlayerItemHeldEvent) {
|
||||
val player = event.player
|
||||
val item = player.inventory.getItem(event.previousSlot) ?: return
|
||||
|
||||
dispatchToItem(item) { it.onItemHeld(event) }
|
||||
dispatchToComponents(item) { it.onItemHeld(player, item, event) }
|
||||
}
|
||||
|
||||
private fun tickComponents() {
|
||||
for (player in plugin.server.onlinePlayers) {
|
||||
val uuid = player.uniqueId
|
||||
|
|
@ -166,6 +175,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
|||
}
|
||||
}
|
||||
|
||||
// Update Cache
|
||||
// Update Cache
|
||||
lastHeldItems[uuid] = Pair(currentMainHand, currentOffHand)
|
||||
|
||||
|
|
@ -175,6 +185,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
|||
val armorItems = player.inventory.armorContents.filterNotNull()
|
||||
for (item in armorItems) {
|
||||
if (item.type.isAir) continue
|
||||
dispatchToItem(item) { it.onTick(player, item) }
|
||||
dispatchToComponents(item) { component ->
|
||||
if (component is EquippableComponent) {
|
||||
component.onTick(player, item)
|
||||
|
|
@ -184,6 +195,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
|||
|
||||
// ToolComponents in Main Hand
|
||||
if (!currentMainHand.type.isAir) {
|
||||
dispatchToItem(currentMainHand) { it.onTick(player, currentMainHand) }
|
||||
dispatchToComponents(currentMainHand) { component ->
|
||||
if (component is ToolComponent) {
|
||||
component.onHoldTick(player, currentMainHand)
|
||||
|
|
@ -196,6 +208,7 @@ class EventListener(private val plugin: Plugin) : Listener {
|
|||
|
||||
// ToolComponents in Off Hand
|
||||
if (!currentOffHand.type.isAir) {
|
||||
dispatchToItem(currentOffHand) { it.onTick(player, currentOffHand) }
|
||||
dispatchToComponents(currentOffHand) { component ->
|
||||
if (component is ToolComponent) {
|
||||
component.onHoldTick(player, currentOffHand)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ components:
|
|||
power:
|
||||
vertical: 0.5
|
||||
forward: 0.3
|
||||
magnet:
|
||||
radius_base: 5.0
|
||||
radius_per_tier: 5.0
|
||||
allow_scroll_change: true
|
||||
vein_miner:
|
||||
max_blocks: 64
|
||||
activation_mode: "SNEAK" # SNEAK, ALWAYS, STAND
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user