From 82e593f219d4a24ea3a94aa86e3ccdc1fe7d2d57 Mon Sep 17 00:00:00 2001 From: acrylic-style Date: Wed, 31 Jan 2024 22:39:14 +0900 Subject: [PATCH] feat: custom block with facing --- build.gradle.kts | 2 +- .../lifecore/block/CommandCustomBlock.kt | 6 +++ .../mori01231/lifecore/block/CustomBlock.kt | 30 ++++++++++----- .../lifecore/block/CustomBlockManager.kt | 31 +++++++++++++-- .../lifecore/block/CustomBlockState.kt | 6 ++- .../lifecore/listener/CustomBlockListener.kt | 27 ++++++++++--- .../github/mori01231/lifecore/util/AxisX.java | 38 +++++++++++++++++++ src/main/resources/config.yml | 1 + 8 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/github/mori01231/lifecore/util/AxisX.java diff --git a/build.gradle.kts b/build.gradle.kts index b36eeba..bb63898 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "net.azisaba" -version = "6.9.0" +version = "6.9.1" java { toolchain.languageVersion.set(JavaLanguageVersion.of(8)) diff --git a/src/main/java/com/github/mori01231/lifecore/block/CommandCustomBlock.kt b/src/main/java/com/github/mori01231/lifecore/block/CommandCustomBlock.kt index 9fd0891..e99a25a 100644 --- a/src/main/java/com/github/mori01231/lifecore/block/CommandCustomBlock.kt +++ b/src/main/java/com/github/mori01231/lifecore/block/CommandCustomBlock.kt @@ -6,15 +6,21 @@ import org.bukkit.inventory.EquipmentSlot class CommandCustomBlock( override val name: String, + override val lockFacing: Boolean, + override val axisShift: Int, + override val backgroundBlock: Material, material: Material, displayName: String? = null, lore: List? = null, private val commands: List, private val consoleCommands: List, + private val destroyWithoutWrench: Boolean, ) : CustomBlock(material, displayName, lore) { override fun onInteract(e: PlayerInteractEvent, state: CustomBlockState) { if (e.hand != EquipmentSlot.HAND) return commands.forEach { e.player.performCommand(it) } consoleCommands.forEach { e.player.server.dispatchCommand(e.player.server.consoleSender, it.replace("", e.player.name)) } } + + override fun canDestroy(state: CustomBlockState, wrench: Boolean): Boolean = wrench || destroyWithoutWrench } diff --git a/src/main/java/com/github/mori01231/lifecore/block/CustomBlock.kt b/src/main/java/com/github/mori01231/lifecore/block/CustomBlock.kt index 4b8761e..eb8dd7d 100644 --- a/src/main/java/com/github/mori01231/lifecore/block/CustomBlock.kt +++ b/src/main/java/com/github/mori01231/lifecore/block/CustomBlock.kt @@ -1,12 +1,12 @@ package com.github.mori01231.lifecore.block +import com.github.mori01231.lifecore.util.AxisX import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import net.minecraft.server.v1_15_R1.NBTTagCompound import org.bukkit.Material import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack -import org.bukkit.event.Event import org.bukkit.event.Listener import org.bukkit.event.block.BlockPlaceEvent import org.bukkit.event.player.PlayerInteractEvent @@ -18,22 +18,29 @@ abstract class CustomBlock( private val lore: List? = null, ) : Listener { open val name: String = javaClass.simpleName + open val lockFacing: Boolean = false + open val axisShift: Int = 0 + open val backgroundBlock: Material = Material.BARRIER var customModelData = 0 internal fun handleInteract(e: PlayerInteractEvent, state: CustomBlockState) { - //e.isCancelled = true - e.setUseItemInHand(Event.Result.ALLOW) - e.setUseInteractedBlock(Event.Result.ALLOW) - if (state.blockName != name) { - return - } + if (state.blockName != name) error("Block name mismatch: state ${state.blockName} != block $name") + //e.isCancelled = true // player wouldn't be able to place blocks on top of this block onInteract(e, state) } open fun onInteract(e: PlayerInteractEvent, state: CustomBlockState) { } - open fun canDestroy(state: CustomBlockState) = true + open fun canDestroy(state: CustomBlockState, wrench: Boolean) = wrench + + /** + * Executes the pre-destroy process, and returns whether the block can be dropped. + * @return Whether the block can be dropped. `true` if the block can be dropped. `false` if the block drop will be cancelled. + */ + open fun preDestroy(state: CustomBlockState): Boolean { + return true + } open fun onPlace(e: BlockPlaceEvent): CustomBlockState { val nms = CraftItemStack.asNMSCopy(e.itemInHand) @@ -43,10 +50,13 @@ abstract class CustomBlock( "" } if (tagString.isBlank()) { - return CustomBlockState(this) + return CustomBlockState(this, AxisX.valueOf(e.player.facing.name)) } val tag = Json.decodeFromString>(tagString) - return CustomBlockState(this, tag) + return CustomBlockState(this, AxisX.valueOf(e.player.facing.name), tag) + } + + open fun postPlace(e: BlockPlaceEvent, state: CustomBlockState) { } open fun getItemStack(state: CustomBlockState?): ItemStack { diff --git a/src/main/java/com/github/mori01231/lifecore/block/CustomBlockManager.kt b/src/main/java/com/github/mori01231/lifecore/block/CustomBlockManager.kt index ef5e45a..be0b75f 100644 --- a/src/main/java/com/github/mori01231/lifecore/block/CustomBlockManager.kt +++ b/src/main/java/com/github/mori01231/lifecore/block/CustomBlockManager.kt @@ -2,12 +2,16 @@ package com.github.mori01231.lifecore.block import com.github.mori01231.lifecore.LifeCore import com.github.mori01231.lifecore.listener.CustomBlockListener +import com.github.mori01231.lifecore.util.AxisX import com.github.mori01231.lifecore.util.LRUCache import kotlinx.serialization.json.Json import org.bukkit.ChatColor import org.bukkit.Location import org.bukkit.Material import org.bukkit.NamespacedKey +import org.bukkit.block.data.Directional +import org.bukkit.block.data.Orientable +import org.bukkit.block.data.type.Leaves import org.bukkit.entity.ArmorStand import org.bukkit.event.HandlerList import org.bukkit.inventory.ItemStack @@ -56,7 +60,7 @@ class CustomBlockManager(val plugin: LifeCore) { findArmorStand(location)?.remove() location.block.type = Material.AIR } else { - spawnBlock(location, state.getBlock()) + spawnBlock(location, state.getBlock(), state.axis) } region.setState(location.blockX, location.blockY, location.blockZ, state) region.save() @@ -106,17 +110,25 @@ class CustomBlockManager(val plugin: LifeCore) { blocks.forEach { block -> HandlerList.unregisterAll(block) } + blocks.clear() // add blocks plugin.config.getMapList("custom-blocks").forEach { map -> val blockName = map["name"]?.toString() ?: return plugin.logger.warning("name is required") + val axisShift = map["axisShift"]?.toString()?.toIntOrNull() ?: 0 + val lockFacing = map["lockFacing"]?.toString()?.toBoolean() ?: false + val backgroundBlock = Material.valueOf(map["backgroundBlock"]?.toString()?.uppercase() ?: "BARRIER") + if (backgroundBlock.isAir) { + return plugin.logger.warning("$blockName: backgroundBlock must not be air") + } val material = Material.valueOf(map["material"]?.toString()?.uppercase() ?: return plugin.logger.warning("material is required")) val displayName = map["displayName"]?.toString()?.let { ChatColor.translateAlternateColorCodes('&', it) } val lore = map["lore"]?.toString()?.let { ChatColor.translateAlternateColorCodes('&', it) }?.split("\n") val customModelData = map["customModelData"]?.toString()?.toIntOrNull() ?: 0 val commands = map["commands"] as List? val consoleCommands = map["consoleCommands"] as List? - val block = CommandCustomBlock(blockName, material, displayName, lore, commands ?: emptyList(), consoleCommands ?: emptyList()) + val destroyWithoutWrench = map["destroyWithoutWrench"]?.toString()?.toBoolean() ?: false + val block = CommandCustomBlock(blockName, lockFacing, axisShift, backgroundBlock, material, displayName, lore, commands ?: emptyList(), consoleCommands ?: emptyList(), destroyWithoutWrench) block.customModelData = customModelData registerCustomBlock(block) } @@ -131,9 +143,19 @@ class CustomBlockManager(val plugin: LifeCore) { .firstOrNull { it.customName == "custom_block" && !it.isVisible && it.isInvulnerable && it.isSmall } } - private fun spawnBlock(location: Location, block: CustomBlock): ArmorStand { + private fun spawnBlock(location: Location, block: CustomBlock, axis: AxisX): ArmorStand { findArmorStand(location)?.remove() - location.block.type = Material.BARRIER + location.block.type = block.backgroundBlock + location.block.blockData.let { + if (!block.lockFacing && it is Directional && axis.blockFace in it.faces) { + it.facing = axis.blockFace + location.block.blockData = it + } + if (it is Leaves) { + it.isPersistent = true + location.block.blockData = it + } + } val x = location.blockX + 0.5 val y = location.blockY.toDouble() val z = location.blockZ + 0.5 @@ -145,6 +167,7 @@ class CustomBlockManager(val plugin: LifeCore) { it.customName = "custom_block" it.isCustomNameVisible = false it.isSilent = true + it.setRotation(((axis.yaw + block.axisShift) % 360).toFloat(), 0f) it.equipment?.helmet = ItemStack(block.material).apply { itemMeta = itemMeta?.apply { setCustomModelData(block.customModelData) diff --git a/src/main/java/com/github/mori01231/lifecore/block/CustomBlockState.kt b/src/main/java/com/github/mori01231/lifecore/block/CustomBlockState.kt index a4b7a26..d0dfd3e 100644 --- a/src/main/java/com/github/mori01231/lifecore/block/CustomBlockState.kt +++ b/src/main/java/com/github/mori01231/lifecore/block/CustomBlockState.kt @@ -1,6 +1,7 @@ package com.github.mori01231.lifecore.block import com.github.mori01231.lifecore.LifeCore +import com.github.mori01231.lifecore.util.AxisX import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import org.bukkit.plugin.java.JavaPlugin @@ -8,10 +9,11 @@ import org.bukkit.plugin.java.JavaPlugin @Serializable data class CustomBlockState( val blockName: String, + val axis: AxisX = AxisX.SOUTH, val tag: MutableMap = mutableMapOf(), ) { - constructor(block: CustomBlock, tag: MutableMap = mutableMapOf()) : - this(block.name, tag) + constructor(block: CustomBlock, axis: AxisX = AxisX.SOUTH, tag: MutableMap = mutableMapOf()) : + this(block.name, axis, tag) init { getBlock() diff --git a/src/main/java/com/github/mori01231/lifecore/listener/CustomBlockListener.kt b/src/main/java/com/github/mori01231/lifecore/listener/CustomBlockListener.kt index 11461c3..2e3c5d2 100644 --- a/src/main/java/com/github/mori01231/lifecore/listener/CustomBlockListener.kt +++ b/src/main/java/com/github/mori01231/lifecore/listener/CustomBlockListener.kt @@ -21,14 +21,21 @@ class CustomBlockListener(val plugin: LifeCore) : Listener { val state = plugin.customBlockManager.getState(e.clickedBlock!!.location) ?: return if (wrench) { e.isCancelled = true + if (!state.getBlock().canDestroy(state, true)) return if (!e.player.hasPermission("lifecore.customblock.destroy.${state.blockName}")) { e.player.sendActionBar("このブロックを破壊する権限がありません。") return } + val drop = state.getBlock().preDestroy(state) plugin.customBlockManager.setState(e.clickedBlock!!.location, null) - e.player.playSound(e.player.location, Sound.ENTITY_ITEM_PICKUP, 1f, 1f) - e.player.inventory.addItem(state.getBlock().getItemStack(state)).forEach { (_, item) -> - e.player.world.dropItem(e.player.location, item) + if (drop) { + e.player.playSound(e.player.location, Sound.ENTITY_ITEM_PICKUP, 1f, 1f) + e.player.inventory.addItem(state.getBlock().getItemStack(state)).forEach { (_, item) -> + e.player.world.dropItemNaturally( + e.clickedBlock!!.location.clone().apply { add(0.5, 0.0, 0.5) }, + item + ) + } } } else { if (!e.player.hasPermission("lifecore.customblock.interact.${state.blockName}")) { @@ -57,18 +64,28 @@ class CustomBlockListener(val plugin: LifeCore) : Listener { } val state = block.onPlace(e) plugin.customBlockManager.setState(e.block.location, state) + block.postPlace(e, state) } - @EventHandler + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) fun onBlockBreak(e: BlockBreakEvent) { - if (e.player.gameMode != GameMode.CREATIVE) return val state = plugin.customBlockManager.getState(e.block.location) ?: return e.isCancelled = true if (!e.player.hasPermission("lifecore.customblock.destroy.${state.blockName}")) { e.player.sendActionBar("このブロックを破壊する権限がありません。") return } + if (e.player.gameMode != GameMode.CREATIVE && !state.getBlock().canDestroy(state, false)) { + return + } + val drop = state.getBlock().preDestroy(state) plugin.customBlockManager.setState(e.block.location, null) + if (drop && e.player.gameMode != GameMode.CREATIVE) { + e.player.world.dropItemNaturally( + e.block.location.clone().apply { add(0.5, 0.0, 0.5) }, + state.getBlock().getItemStack(state) + ) + } } @EventHandler diff --git a/src/main/java/com/github/mori01231/lifecore/util/AxisX.java b/src/main/java/com/github/mori01231/lifecore/util/AxisX.java new file mode 100644 index 0000000..9b7b9eb --- /dev/null +++ b/src/main/java/com/github/mori01231/lifecore/util/AxisX.java @@ -0,0 +1,38 @@ +package com.github.mori01231.lifecore.util; + +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +public enum AxisX { + SOUTH(270), + WEST(0), + NORTH(90), + EAST(180); + + private final int yaw; + + AxisX(int yaw) { + this.yaw = yaw; + } + + public int getYaw() { + return yaw; + } + + @Contract(pure = true) + public @NotNull BlockFace getBlockFace() { + switch (this) { + case SOUTH: + return BlockFace.SOUTH; + case WEST: + return BlockFace.WEST; + case NORTH: + return BlockFace.NORTH; + case EAST: + return BlockFace.EAST; + default: + throw new AssertionError(); + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 8e3bcbd..2ad0393 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -339,6 +339,7 @@ custom-model-data: custom-blocks: - name: reinforced_glass + backgroundBlock: barrier material: glass displayName: "&fReinforced Glass" customModelData: 1