feat: Implement Vein Miner component with configurable multi-block breaking, compatible material groups, tool categories, and visual highlighting.
This commit is contained in:
parent
f1787b9b62
commit
f45947b9e9
|
|
@ -11,6 +11,7 @@ import net.hareworks.hcu.items.content.items.GrapplingItem
|
|||
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 org.bukkit.permissions.PermissionDefault
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
|
@ -43,6 +44,7 @@ public class App : JavaPlugin() {
|
|||
ComponentRegistry.register(GliderComponent(this))
|
||||
ComponentRegistry.register(DoubleJumpComponent(this))
|
||||
ComponentRegistry.register(BlinkComponent(this))
|
||||
ComponentRegistry.register(VeinMinerComponent(this))
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,4 +28,5 @@ interface CustomComponent {
|
|||
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) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ object Config {
|
|||
var doubleJumpPowerVertical: Double = 0.5
|
||||
var doubleJumpPowerForward: Double = 0.3
|
||||
|
||||
// Vein Miner
|
||||
data class ToolCategory(val toolPattern: String, val allowedBlockPatterns: List<String>)
|
||||
var veinMinerMaxBlocks: Int = 64
|
||||
var veinMinerActivationMode: String = "SNEAK" // "SNEAK", "ALWAYS", "STAND"
|
||||
val veinMinerCompatibleMaterials: MutableMap<org.bukkit.Material, Set<org.bukkit.Material>> = mutableMapOf()
|
||||
val veinMinerToolCategories: MutableList<ToolCategory> = mutableListOf()
|
||||
|
||||
fun load(plugin: JavaPlugin) {
|
||||
plugin.reloadConfig()
|
||||
val config = plugin.config
|
||||
|
|
@ -23,6 +30,12 @@ object Config {
|
|||
doubleJumpPowerVertical = config.getDouble("components.double_jump.power.vertical", 0.5)
|
||||
doubleJumpPowerForward = config.getDouble("components.double_jump.power.forward", 0.3)
|
||||
|
||||
// Vein Miner
|
||||
veinMinerMaxBlocks = config.getInt("components.vein_miner.max_blocks", 64)
|
||||
veinMinerActivationMode = config.getString("components.vein_miner.activation_mode", "SNEAK") ?: "SNEAK"
|
||||
loadCompatibleGroups(config)
|
||||
loadToolCategories(config)
|
||||
|
||||
// Save back to ensure defaults are written if missing
|
||||
saveDefaults(plugin, config)
|
||||
}
|
||||
|
|
@ -33,7 +46,45 @@ object Config {
|
|||
config.addDefault("components.double_jump.power.vertical", 0.5)
|
||||
config.addDefault("components.double_jump.power.forward", 0.3)
|
||||
|
||||
config.addDefault("components.vein_miner.max_blocks", 64)
|
||||
// Groups default is complex to add here, relying on config.yml resource or existing file
|
||||
|
||||
// Tool categories default is handled by config.yml resource
|
||||
|
||||
config.options().copyDefaults(true)
|
||||
plugin.saveConfig()
|
||||
}
|
||||
|
||||
private fun loadCompatibleGroups(config: FileConfiguration) {
|
||||
veinMinerCompatibleMaterials.clear()
|
||||
val groups = config.getList("components.vein_miner.compatible_groups") as? List<*> ?: return
|
||||
|
||||
for (groupObj in groups) {
|
||||
val group = groupObj as? List<*> ?: continue
|
||||
val materials = group.mapNotNull {
|
||||
val name = (it as? String) ?: return@mapNotNull null
|
||||
try {
|
||||
org.bukkit.Material.valueOf(name.uppercase().removePrefix("MINECRAFT:"))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}.toSet()
|
||||
|
||||
for (mat in materials) {
|
||||
val existing = veinMinerCompatibleMaterials.getOrDefault(mat, emptySet())
|
||||
veinMinerCompatibleMaterials[mat] = existing + materials
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadToolCategories(config: FileConfiguration) {
|
||||
veinMinerToolCategories.clear()
|
||||
val list = config.getMapList("components.vein_miner.tool_categories")
|
||||
|
||||
for (map in list) {
|
||||
val toolPattern = map["tool_pattern"] as? String ?: continue
|
||||
val allowedBlocks = (map["allowed_blocks"] as? List<*>)?.filterIsInstance<String>() ?: continue
|
||||
veinMinerToolCategories.add(ToolCategory(toolPattern, allowedBlocks))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,311 @@
|
|||
package net.hareworks.hcu.items.content.components
|
||||
|
||||
import net.hareworks.hcu.items.api.component.EquippableComponent
|
||||
import net.hareworks.hcu.items.api.Tier
|
||||
import net.hareworks.hcu.items.config.Config
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.block.Block
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.block.BlockBreakEvent
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
import org.bukkit.inventory.meta.Damageable
|
||||
import org.bukkit.enchantments.Enchantment
|
||||
import org.bukkit.entity.ExperienceOrb
|
||||
import org.bukkit.entity.BlockDisplay
|
||||
import org.bukkit.entity.Display
|
||||
import org.bukkit.util.Transformation
|
||||
import org.joml.Vector3f
|
||||
import org.bukkit.Color
|
||||
import org.bukkit.Bukkit
|
||||
import java.util.LinkedList
|
||||
import java.util.Queue
|
||||
import java.util.Random
|
||||
import java.util.UUID
|
||||
|
||||
class VeinMinerComponent(private val plugin: JavaPlugin) : EquippableComponent {
|
||||
|
||||
override val key: NamespacedKey = NamespacedKey(plugin, "vein_miner")
|
||||
override val displayName: String = "Vein Miner"
|
||||
override val maxTier: Int = 1
|
||||
override val minTier: Int = 1
|
||||
|
||||
private val activeHighlights = mutableMapOf<UUID, HighlightSession>()
|
||||
|
||||
data class HighlightSession(
|
||||
val centerBlock: Block,
|
||||
val entities: List<BlockDisplay>,
|
||||
val lastUpdate: Long
|
||||
)
|
||||
|
||||
override fun apply(item: ItemStack, tier: Tier?) {
|
||||
val meta = item.itemMeta ?: return
|
||||
meta.persistentDataContainer.set(key, PersistentDataType.BYTE, 1.toByte())
|
||||
item.itemMeta = meta
|
||||
}
|
||||
|
||||
override fun has(item: ItemStack): Boolean {
|
||||
if (item.type.isAir) return false
|
||||
return item.itemMeta?.persistentDataContainer?.has(key, PersistentDataType.BYTE) == true
|
||||
}
|
||||
|
||||
override fun remove(item: ItemStack) {
|
||||
val meta = item.itemMeta ?: return
|
||||
meta.persistentDataContainer.remove(key)
|
||||
item.itemMeta = meta
|
||||
}
|
||||
|
||||
override fun onTick(player: Player, item: ItemStack) {
|
||||
// ハイライト条件チェック (Sneakしながら)
|
||||
if (!player.isSneaking) {
|
||||
clearHighlight(player)
|
||||
return
|
||||
}
|
||||
|
||||
// ターゲットブロック取得
|
||||
val targetBlock = player.getTargetBlockExact(5)
|
||||
if (targetBlock == null || targetBlock.type == Material.AIR) {
|
||||
clearHighlight(player)
|
||||
return
|
||||
}
|
||||
|
||||
// 既存ハイライトの更新不要チェック
|
||||
val session = activeHighlights[player.uniqueId]
|
||||
if (session != null && session.centerBlock == targetBlock) {
|
||||
return
|
||||
}
|
||||
|
||||
// ハイライト再生成
|
||||
clearHighlight(player)
|
||||
|
||||
// 破壊対象計算
|
||||
val blocksToBreak = calculateBreakList(player, targetBlock, item)
|
||||
if (blocksToBreak.isEmpty()) return
|
||||
|
||||
// ハイライトエンティティ生成
|
||||
val entities = mutableListOf<BlockDisplay>()
|
||||
|
||||
// クライアントのパフォーマンスを考慮し、最大ハイライト数を制限しても良いが、
|
||||
// ユーザー要望通り全てハイライトする
|
||||
for (block in blocksToBreak) {
|
||||
val loc = block.location.add(0.5, 0.5, 0.5)
|
||||
try {
|
||||
// BlockDisplay生成
|
||||
val display = block.world.spawn(loc, BlockDisplay::class.java) { e ->
|
||||
e.block = block.blockData
|
||||
// 少し大きくして元のブロックを覆う
|
||||
e.transformation = Transformation(
|
||||
Vector3f(0f, 0f, 0f),
|
||||
org.joml.AxisAngle4f(0f, 0f, 0f, 0f),
|
||||
Vector3f(1.01f, 1.01f, 1.01f),
|
||||
org.joml.AxisAngle4f(0f, 0f, 0f, 0f)
|
||||
)
|
||||
e.isGlowing = true
|
||||
e.glowColorOverride = Color.AQUA // 鮮やかな色
|
||||
e.brightness = Display.Brightness(15, 15) // 最大輝度
|
||||
e.isVisibleByDefault = false
|
||||
}
|
||||
player.showEntity(plugin, display)
|
||||
entities.add(display)
|
||||
} catch (e: Exception) {
|
||||
// 1.19.4未満などのケース
|
||||
}
|
||||
}
|
||||
|
||||
activeHighlights[player.uniqueId] = HighlightSession(targetBlock, entities, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
// アイテム持ち替えやスニーク解除時にも消えるように
|
||||
override fun onUnequip(player: Player, item: ItemStack) {
|
||||
clearHighlight(player)
|
||||
}
|
||||
|
||||
private fun clearHighlight(player: Player) {
|
||||
val session = activeHighlights.remove(player.uniqueId) ?: return
|
||||
for (e in session.entities) {
|
||||
e.remove()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBlockBreak(event: BlockBreakEvent) {
|
||||
if (event.isCancelled) return
|
||||
val player = event.player
|
||||
val block = event.block
|
||||
val item = player.inventory.itemInMainHand
|
||||
|
||||
// 発動条件チェック
|
||||
if (!shouldActivate(player)) return
|
||||
|
||||
// 破壊対象リスト
|
||||
val blocksToBreak = calculateBreakList(player, block, item)
|
||||
|
||||
// 単一破壊(連鎖なし)なら何もしない(通常のイベントに任せる)
|
||||
if (blocksToBreak.size <= 1) return
|
||||
|
||||
// ハイライト消去
|
||||
clearHighlight(player)
|
||||
|
||||
val dropLocation = block.location.add(0.5, 0.5, 0.5)
|
||||
val hasSilkTouch = item.containsEnchantment(Enchantment.SILK_TOUCH)
|
||||
|
||||
var soundPlayed = false
|
||||
|
||||
for (target in blocksToBreak) {
|
||||
if (target == block) continue // 起点ブロックはイベントフローで破壊される
|
||||
|
||||
// 保護プラグインチェック (擬似イベント)
|
||||
val checkEvent = BlockBreakEvent(target, player)
|
||||
Bukkit.getPluginManager().callEvent(checkEvent)
|
||||
if (checkEvent.isCancelled) continue
|
||||
|
||||
// 演出 (最初の1回だけ、または確率で)
|
||||
if (!soundPlayed) {
|
||||
target.world.playSound(target.location, target.blockSoundGroup.breakSound, 1f, 1f)
|
||||
target.world.spawnParticle(org.bukkit.Particle.BLOCK, target.location.add(0.5,0.5,0.5), 10, 0.3, 0.3, 0.3, target.blockData)
|
||||
soundPlayed = true
|
||||
}
|
||||
|
||||
// ドロップ処理
|
||||
val drops = target.getDrops(item)
|
||||
for (drop in drops) {
|
||||
target.world.dropItem(dropLocation, drop)
|
||||
}
|
||||
|
||||
// 経験値
|
||||
if (!hasSilkTouch) {
|
||||
val xp = getExpFromBlock(target.type)
|
||||
if (xp > 0) {
|
||||
val orb = target.world.spawn(dropLocation, ExperienceOrb::class.java)
|
||||
orb.experience = xp
|
||||
}
|
||||
}
|
||||
|
||||
// ブロック消去
|
||||
target.type = Material.AIR
|
||||
|
||||
// 耐久消費
|
||||
damageItem(player, item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldActivate(player: Player): Boolean {
|
||||
return when (Config.veinMinerActivationMode) {
|
||||
"SNEAK" -> player.isSneaking
|
||||
"STAND" -> !player.isSneaking
|
||||
"ALWAYS" -> true
|
||||
else -> player.isSneaking
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateBreakList(player: Player, startBlock: Block, item: ItemStack): Set<Block> {
|
||||
val visited = mutableSetOf<Block>()
|
||||
|
||||
// ターゲット適正チェック
|
||||
if (!isValidTarget(startBlock, item)) return emptySet()
|
||||
|
||||
// 互換ブロックタイプ抽出
|
||||
val startType = startBlock.type
|
||||
val targetMaterials = Config.veinMinerCompatibleMaterials[startType] ?: setOf()
|
||||
val efficientTargets = if (targetMaterials.isNotEmpty()) targetMaterials else setOf(startType)
|
||||
|
||||
val queue: Queue<Block> = LinkedList()
|
||||
visited.add(startBlock)
|
||||
queue.add(startBlock)
|
||||
|
||||
val max = Config.veinMinerMaxBlocks
|
||||
// 見つかったブロックの実リスト
|
||||
val foundBlocks = mutableSetOf<Block>()
|
||||
foundBlocks.add(startBlock)
|
||||
|
||||
while (queue.isNotEmpty() && foundBlocks.size < max) {
|
||||
val current = queue.poll()
|
||||
|
||||
// 隣接チェック
|
||||
for (x in -1..1) {
|
||||
for (y in -1..1) {
|
||||
for (z in -1..1) {
|
||||
if (x == 0 && y == 0 && z == 0) continue
|
||||
val neighbor = current.getRelative(x, y, z)
|
||||
|
||||
if (visited.contains(neighbor)) continue
|
||||
|
||||
if (efficientTargets.contains(neighbor.type)) {
|
||||
visited.add(neighbor)
|
||||
|
||||
// まだ破壊できるならQueueに追加
|
||||
if (foundBlocks.size < max) {
|
||||
foundBlocks.add(neighbor)
|
||||
queue.add(neighbor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundBlocks
|
||||
}
|
||||
|
||||
private fun isValidTarget(block: Block, item: ItemStack): Boolean {
|
||||
if (block.getDrops(item).isEmpty()) return false
|
||||
|
||||
val itemName = item.type.key.toString()
|
||||
val blockName = block.type.key.toString()
|
||||
|
||||
var isAllowed = false
|
||||
for (category in Config.veinMinerToolCategories) {
|
||||
if (itemName.contains(category.toolPattern)) {
|
||||
for (pattern in category.allowedBlockPatterns) {
|
||||
if (blockName.contains(pattern)) {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAllowed) break
|
||||
}
|
||||
return isAllowed
|
||||
}
|
||||
|
||||
private fun damageItem(player: Player, item: ItemStack) {
|
||||
val meta = item.itemMeta as? Damageable ?: return
|
||||
val unbreakingLevel = item.getEnchantmentLevel(Enchantment.UNBREAKING)
|
||||
|
||||
if (unbreakingLevel > 0) {
|
||||
val random = Random()
|
||||
if (random.nextInt(unbreakingLevel + 1) > 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val maxDamage = item.type.maxDurability
|
||||
if (maxDamage > 0) {
|
||||
val newDamage = meta.damage + 1
|
||||
if (newDamage >= maxDamage) {
|
||||
item.amount = 0
|
||||
player.world.playSound(player.location, org.bukkit.Sound.ENTITY_ITEM_BREAK, 1f, 1f)
|
||||
} else {
|
||||
meta.damage = newDamage
|
||||
item.itemMeta = meta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExpFromBlock(type: Material): Int {
|
||||
val random = Random()
|
||||
return when (type) {
|
||||
Material.COAL_ORE, Material.DEEPSLATE_COAL_ORE -> random.nextInt(3)
|
||||
Material.DIAMOND_ORE, Material.DEEPSLATE_DIAMOND_ORE -> random.nextInt(5) + 3
|
||||
Material.EMERALD_ORE, Material.DEEPSLATE_EMERALD_ORE -> random.nextInt(5) + 3
|
||||
Material.LAPIS_ORE, Material.DEEPSLATE_LAPIS_ORE -> random.nextInt(4) + 2
|
||||
Material.NETHER_QUARTZ_ORE -> random.nextInt(4) + 2
|
||||
Material.REDSTONE_ORE, Material.DEEPSLATE_REDSTONE_ORE -> random.nextInt(5) + 1
|
||||
Material.NETHER_GOLD_ORE -> random.nextInt(2)
|
||||
Material.SCULK -> 1
|
||||
Material.SPAWNER -> random.nextInt(29) + 15
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -121,6 +121,15 @@ class EventListener(private val plugin: Plugin) : Listener {
|
|||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onBlockBreak(event: org.bukkit.event.block.BlockBreakEvent) {
|
||||
val player = event.player
|
||||
val item = player.inventory.itemInMainHand
|
||||
|
||||
// Dispatch to components on the main hand item
|
||||
dispatchToComponents(item) { it.onBlockBreak(event) }
|
||||
}
|
||||
|
||||
private fun tickComponents() {
|
||||
for (player in plugin.server.onlinePlayers) {
|
||||
val items = getEquipmentItems(player)
|
||||
|
|
|
|||
|
|
@ -6,3 +6,45 @@ components:
|
|||
power:
|
||||
vertical: 0.5
|
||||
forward: 0.3
|
||||
vein_miner:
|
||||
max_blocks: 64
|
||||
activation_mode: "SNEAK" # SNEAK, ALWAYS, STAND
|
||||
compatible_groups:
|
||||
- [ "minecraft:diamond_ore", "minecraft:deepslate_diamond_ore" ]
|
||||
- [ "minecraft:iron_ore", "minecraft:deepslate_iron_ore" ]
|
||||
- [ "minecraft:gold_ore", "minecraft:deepslate_gold_ore" ]
|
||||
- [ "minecraft:copper_ore", "minecraft:deepslate_copper_ore" ]
|
||||
- [ "minecraft:coal_ore", "minecraft:deepslate_coal_ore" ]
|
||||
- [ "minecraft:redstone_ore", "minecraft:deepslate_redstone_ore" ]
|
||||
- [ "minecraft:lapis_ore", "minecraft:deepslate_lapis_ore" ]
|
||||
- [ "minecraft:emerald_ore", "minecraft:deepslate_emerald_ore" ]
|
||||
tool_categories:
|
||||
- tool_pattern: "_pickaxe"
|
||||
allowed_blocks:
|
||||
- "_ore"
|
||||
- "minecraft:ancient_debris"
|
||||
- "minecraft:amethyst_block"
|
||||
- "minecraft:budding_amethyst"
|
||||
- "minecraft:obsidian"
|
||||
- tool_pattern: "_axe"
|
||||
allowed_blocks:
|
||||
- "_log"
|
||||
- "_stem"
|
||||
- "_hyphae"
|
||||
- "minecraft:mangrove_roots"
|
||||
- "minecraft:bamboo_block"
|
||||
- tool_pattern: "_shovel"
|
||||
allowed_blocks:
|
||||
- "minecraft:clay"
|
||||
- "minecraft:gravel"
|
||||
- "minecraft:soul_sand"
|
||||
- "minecraft:soul_soil"
|
||||
- "minecraft:mud"
|
||||
- "minecraft:snow"
|
||||
- tool_pattern: "_hoe"
|
||||
allowed_blocks:
|
||||
- "_leaves"
|
||||
- "minecraft:nether_wart_block"
|
||||
- "minecraft:shroomlight"
|
||||
- "minecraft:hay_block"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user