diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonSimpleMultiblock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonSimpleMultiblock.kt index 6d070044c..e1b4cbdb4 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonSimpleMultiblock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonSimpleMultiblock.kt @@ -5,6 +5,7 @@ import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.block.BlockStorage import io.github.pylonmc.pylon.core.block.MultiblockCache import io.github.pylonmc.pylon.core.block.PylonBlock +import io.github.pylonmc.pylon.core.block.PylonBlockSchema import io.github.pylonmc.pylon.core.datatypes.PylonSerializers import io.github.pylonmc.pylon.core.entity.EntityStorage import io.github.pylonmc.pylon.core.entity.PylonEntity @@ -23,18 +24,19 @@ import io.github.pylonmc.pylon.core.util.position.position import io.github.pylonmc.pylon.core.util.pylonKey import io.github.pylonmc.pylon.core.util.rotateVectorToFace import kotlinx.coroutines.delay +import org.bukkit.Bukkit import org.bukkit.Color import org.bukkit.Material import org.bukkit.NamespacedKey import org.bukkit.block.Block import org.bukkit.block.BlockFace import org.bukkit.block.data.BlockData +import org.bukkit.entity.BlockDisplay import org.bukkit.entity.Display import org.bukkit.entity.ItemDisplay import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.player.PlayerInteractEntityEvent -import org.bukkit.inventory.ItemStack import org.bukkit.persistence.PersistentDataContainer import org.bukkit.util.Vector import org.jetbrains.annotations.ApiStatus @@ -57,17 +59,20 @@ import kotlin.time.Duration.Companion.seconds */ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, PylonDirectionalBlock { + interface SingleGhostBlock { + fun spawnGhostBlock(block: Block): UUID + } + + interface MultipleGhostBlocks { + fun spawnGhostBlocks(block: Block): List + } + /** * Represents a single block of a multiblock. */ interface MultiblockComponent { fun matches(block: Block): Boolean - /** - * Creates a 'ghost block' entity that represents this block. - */ - fun spawnGhostBlock(block: Block): UUID - companion object { @JvmStatic @@ -78,6 +83,10 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon } } + interface MultiblockComponentBlockDisplay { + fun blockDataList() : List + } + /** * A block display that represents this block, showing the player what block * needs to be placed in a specific location. @@ -109,7 +118,7 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon * the given materials in order. */ @JvmRecord - data class VanillaMultiblockComponent(val materials: List) : MultiblockComponent { + data class VanillaMultiblockComponent(val materials: List) : SingleGhostBlock, MultiblockComponent, MultiblockComponentBlockDisplay { constructor(first: Material, vararg materials: Material) : this(listOf(first) + materials) @@ -120,7 +129,7 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon override fun matches(block: Block): Boolean = !BlockStorage.isPylonBlock(block) && block.type in materials override fun spawnGhostBlock(block: Block): UUID { - val blockDataList = materials.map { it.createBlockData() } + val blockDataList = blockDataList() val display = BlockDisplayBuilder() .material(materials.first()) .glow(Color.WHITE) @@ -142,6 +151,8 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon return display.uniqueId } + + override fun blockDataList(): List = materials.map { it.createBlockData() } } /** @@ -165,7 +176,7 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon * */ @JvmRecord - data class VanillaBlockdataMultiblockComponent(val blockDatas: List) : MultiblockComponent { + data class VanillaBlockdataMultiblockComponent(val blockDatas: List) : SingleGhostBlock, MultiblockComponent, MultiblockComponentBlockDisplay { constructor(first: BlockData, vararg materials: BlockData) : this(listOf(first) + materials) @@ -206,18 +217,119 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon return display.uniqueId } + + override fun blockDataList(): List = blockDatas + } + + class MixedMultiblockComponent : MultiblockComponent, MultipleGhostBlocks { + val multiblockComponents: Collection + + constructor(multiblockComponents: Collection) { + this.multiblockComponents = multiblockComponents + } + + constructor(vararg validators: MultiblockComponent) : this(validators.toList()) + + override fun matches(block: Block): Boolean { + for (validator in multiblockComponents) { + if (validator.matches(block)) { + return true + } + } + + return false + } + + override fun spawnGhostBlocks(block: Block): List { + var blockDisplay: BlockDisplay? = null + var itemDisplay: ItemDisplay? = null + + val displayUpdates: MutableList = mutableListOf() + var name = "" + for (component in multiblockComponents) { + if (component is MultiblockComponentBlockDisplay) { + val blockDatas = component.blockDataList() + if (blockDisplay == null) { + blockDisplay = BlockDisplayBuilder() + .material(blockDatas.first().material) + .glow(Color.WHITE) + .transformation(TransformBuilder().scale(0.5)) + .build(block.location.toCenterLocation()) + } + + for (blockData in blockDatas) { + displayUpdates.add { + blockDisplay.isVisibleByDefault = true + itemDisplay?.isVisibleByDefault = false + blockDisplay.block = blockData + } + + name += blockData.getAsString(true) + ", " + } + } else if (component is PylonMultiblockComponent) { + val key = component.key + val schema = component.schema() + val itemBuilder = ItemStackBuilder.of(schema.material).addCustomModelDataString(key.toString()) + if (itemDisplay == null) { + itemDisplay = ItemDisplayBuilder() + .itemStack(itemBuilder) + .glow(Color.WHITE) + .transformation(TransformBuilder().scale(0.5)) + .build(block.location.toCenterLocation()) + } + + displayUpdates.add { + itemDisplay.isVisibleByDefault = true + blockDisplay?.isVisibleByDefault = false + itemDisplay.setItemStack( + itemBuilder.build() + ) + } + + name += "$key, " + } + } + + blockDisplay?.let { + EntityStorage.add(MultiblockGhostBlock(it, name)) + } + + itemDisplay?.let { + EntityStorage.add(MultiblockGhostBlock(it, name)) + } + + if (displayUpdates.size > 1) { + PylonCore.launch { + var i = 0 + while (itemDisplay?.isValid ?: true && blockDisplay?.isValid ?: true) { + displayUpdates[i].run() + i++ + i %= displayUpdates.size + delay(1.seconds) + } + } + } + + val mutableList = mutableListOf() + blockDisplay?.let { mutableList.add(it.uniqueId) } + itemDisplay?.let { mutableList.add(it.uniqueId) } + + return mutableList + } } /** * Represents a Pylon block component of a multiblock. */ @JvmRecord - data class PylonMultiblockComponent(val key: NamespacedKey) : MultiblockComponent { + data class PylonMultiblockComponent(val key: NamespacedKey) : SingleGhostBlock, MultiblockComponent { + fun schema() : PylonBlockSchema = PylonRegistry.BLOCKS[key] + ?: throw IllegalArgumentException("Block schema $key does not exist") + override fun matches(block: Block): Boolean = BlockStorage.get(block)?.schema?.key == key override fun spawnGhostBlock(block: Block): UUID { - val schema = PylonRegistry.BLOCKS[key] - ?: throw IllegalArgumentException("Block schema $key does not exist") + val schema = schema() val display = ItemDisplayBuilder() .itemStack(ItemStackBuilder.of(schema.material).addCustomModelDataString(key.toString())) .glow(Color.WHITE) @@ -284,10 +396,24 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon val facing = simpleMultiblockData.facing val rotatedComponents = if (facing == null) components else rotateComponentsToFace(components, facing) for ((offset, component) in rotatedComponents) { - val key = "multiblock_ghost_block_${offset.x}_${offset.y}_${offset.z}" - if (!isHeldEntityPresent(key)) { - val ghostBlock = component.spawnGhostBlock((block.position + offset).block) - heldEntities[key] = ghostBlock + val startSection = "multiblock_ghost_block_${offset.x}_${offset.y}_${offset.z}" + + if (component is SingleGhostBlock) { + if (!isHeldEntityPresent(startSection)) { + val ghostBlock = component.spawnGhostBlock((block.position + offset).block) + heldEntities[startSection] = ghostBlock + } + } else if (component is MultipleGhostBlocks) { + if (!heldEntities.keys.any { it.startsWith(startSection) }) { + val ghostBlocks = component.spawnGhostBlocks((block.position + offset).block) + + var i = 0 + ghostBlocks.forEach { ghostBlock -> + val key = "${startSection}_$i" + i++ + heldEntities[key] = ghostBlock + } + } } } updateGhostBlockColors() @@ -380,6 +506,7 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon val facing = simpleMultiblockData.facing val rotatedComponents = if (facing == null) components else rotateComponentsToFace(components, facing) for ((offset, component) in rotatedComponents) { + val mainKey = "multiblock_ghost_block_${offset.x}_${offset.y}_${offset.z}" val entity = getHeldPylonEntity( MultiblockGhostBlock::class.java, "multiblock_ghost_block_${offset.x}_${offset.y}_${offset.z}" @@ -390,6 +517,23 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon } else { Color.RED } + } else { + for (entry in heldEntities.entries) { + if (!entry.key.startsWith(mainKey)) continue + + val entityEntry = getHeldPylonEntity( + MultiblockGhostBlock::class.java, + entry.key + ) + + if (entityEntry == null) continue + + entityEntry.entity.glowColorOverride = if (component.matches((block.position + offset).block)) { + Color.GREEN + } else { + Color.RED + } + } } } }