Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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<UUID>
}

/**
* 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
Expand All @@ -78,6 +83,10 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon
}
}

interface MultiblockComponentBlockDisplay {
fun blockDataList() : List<BlockData>
}

/**
* A block display that represents this block, showing the player what block
* needs to be placed in a specific location.
Expand Down Expand Up @@ -109,7 +118,7 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon
* the given materials in order.
*/
@JvmRecord
data class VanillaMultiblockComponent(val materials: List<Material>) : MultiblockComponent {
data class VanillaMultiblockComponent(val materials: List<Material>) : SingleGhostBlock, MultiblockComponent, MultiblockComponentBlockDisplay {

constructor(first: Material, vararg materials: Material) : this(listOf(first) + materials)

Expand All @@ -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)
Expand All @@ -142,6 +151,8 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon

return display.uniqueId
}

override fun blockDataList(): List<BlockData> = materials.map { it.createBlockData() }
}

/**
Expand All @@ -165,7 +176,7 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon
*
*/
@JvmRecord
data class VanillaBlockdataMultiblockComponent(val blockDatas: List<BlockData>) : MultiblockComponent {
data class VanillaBlockdataMultiblockComponent(val blockDatas: List<BlockData>) : SingleGhostBlock, MultiblockComponent, MultiblockComponentBlockDisplay {

constructor(first: BlockData, vararg materials: BlockData) : this(listOf(first) + materials)

Expand Down Expand Up @@ -206,18 +217,119 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon

return display.uniqueId
}

override fun blockDataList(): List<BlockData> = blockDatas
}

class MixedMultiblockComponent : MultiblockComponent, MultipleGhostBlocks {
val multiblockComponents: Collection<MultiblockComponent>

constructor(multiblockComponents: Collection<MultiblockComponent>) {
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<UUID> {
var blockDisplay: BlockDisplay? = null
var itemDisplay: ItemDisplay? = null

val displayUpdates: MutableList<Runnable> = 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<UUID>()
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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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}"
Expand All @@ -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
}
}
}
}
}
Expand Down