Skip to content
Merged
Show file tree
Hide file tree
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 @@ -75,14 +75,12 @@ object PylonCore : JavaPlugin(), PylonAddon {
packetEvents.eventManager.registerListener(ArmorTextureEngine, PacketListenerPriority.HIGHEST)

val entityLibPlatform = SpigotEntityLibPlatform(this)
val entityLibSettings = APIConfig(packetEvents).tickTickables()
EntityLib.init(entityLibPlatform, entityLibSettings)
entityLibPlatform.entityIdProvider = EntityIdProvider { uuid, type ->
@Suppress("DEPRECATION")
Bukkit.getUnsafe().nextEntityId()
}
val entityLibSettings = APIConfig(packetEvents)
.tickTickables()
.trackPlatformEntities()
EntityLib.init(entityLibPlatform, entityLibSettings)

saveDefaultConfig()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ object BlockStorage : Listener {
blocksByKey[block.key]!!.add(phantomBlock)
blocksByChunk[block.block.chunk.position]!!.remove(block)
blocksByChunk[block.block.chunk.position]!!.add(phantomBlock)
BlockTextureEngine.remove(block)
}

@JvmSynthetic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.github.pylonmc.pylon.core.event.PylonBlockPlaceEvent
import io.github.pylonmc.pylon.core.event.PylonChunkBlocksLoadEvent
import io.github.pylonmc.pylon.core.event.PylonChunkBlocksUnloadEvent
import io.github.pylonmc.pylon.core.event.PylonMultiblockFormEvent
import io.github.pylonmc.pylon.core.event.PylonMultiblockRefreshEvent
import io.github.pylonmc.pylon.core.event.PylonMultiblockUnformEvent
import io.github.pylonmc.pylon.core.util.position.BlockPosition
import io.github.pylonmc.pylon.core.util.position.ChunkPosition
Expand Down Expand Up @@ -65,20 +66,26 @@ internal object MultiblockCache : Listener {
override fun run() {
for (multiblockPosition in dirtyMultiblocks) {
// For a multiblock to be formed, it must be fully loaded
val multiblock = BlockStorage.getAs<PylonMultiblock>(multiblockPosition)
if (multiblockPosition !in fullyLoadedMultiblocks) {
formedMultiblocks.remove(multiblockPosition)
if (formedMultiblocks.remove(multiblockPosition) && multiblock != null) {
multiblock.onMultiblockUnformed(true)
PylonMultiblockUnformEvent(multiblockPosition.block, multiblock as PylonBlock).callEvent()
}
continue
}

val multiblock = BlockStorage.getAs<PylonMultiblock>(multiblockPosition)
if (multiblock != null && multiblock.checkFormed()) {
if (formedMultiblocks.add(multiblockPosition)) {
multiblock.onMultiblockFormed()
PylonMultiblockFormEvent(multiblockPosition.block, multiblock as PylonBlock).callEvent()
} else {
multiblock.onMultiblockRefreshed()
PylonMultiblockRefreshEvent(multiblockPosition.block, multiblock as PylonBlock).callEvent()
}
} else {
if (formedMultiblocks.remove(multiblockPosition) && multiblock != null) {
multiblock.onMultiblockUnformed()
multiblock.onMultiblockUnformed(false)
PylonMultiblockUnformEvent(multiblockPosition.block, multiblock as PylonBlock).callEvent()
}
}
Expand All @@ -95,13 +102,17 @@ internal object MultiblockCache : Listener {
= dirtyMultiblocks.add(multiblock.block.position)

private fun refreshFullyLoaded(multiblock: PylonMultiblock) {
val multiblockPosition = multiblock.block.position
if (multiblock.chunksOccupied.all { it.isLoaded }) {
fullyLoadedMultiblocks.add(multiblock.block.position)
fullyLoadedMultiblocks.add(multiblockPosition)
markDirty(multiblock)
} else {
formedMultiblocks.remove(multiblock.block.position)
fullyLoadedMultiblocks.remove(multiblock.block.position)
dirtyMultiblocks.remove(multiblock.block.position)
if (formedMultiblocks.remove(multiblockPosition)) {
multiblock.onMultiblockUnformed(true)
PylonMultiblockUnformEvent(multiblock.block, multiblock as PylonBlock).callEvent()
}
fullyLoadedMultiblocks.remove(multiblockPosition)
dirtyMultiblocks.remove(multiblockPosition)
}
}

Expand All @@ -123,8 +134,8 @@ internal object MultiblockCache : Listener {
}
}

fullyLoadedMultiblocks.remove(multiblockPosition)
formedMultiblocks.remove(multiblockPosition)
fullyLoadedMultiblocks.remove(multiblockPosition)
dirtyMultiblocks.remove(multiblockPosition)
}

Expand Down Expand Up @@ -158,19 +169,23 @@ internal object MultiblockCache : Listener {

@EventHandler
private fun handle(event: PylonChunkBlocksUnloadEvent) {
// Mark existing multiblocks with components as not formed and not fully loaded
for (multiblockPosition in loadedMultiblocksWithComponentsInChunk(event.chunk.position)) {
formedMultiblocks.remove(multiblockPosition)
fullyLoadedMultiblocks.remove(multiblockPosition)
dirtyMultiblocks.remove(multiblockPosition)
}

// Remove multiblocks that were just unloaded
for (pylonBlock in event.pylonBlocks) {
if (pylonBlock is PylonMultiblock) {
onMultiblockRemoved(pylonBlock)
}
}

// Mark existing multiblocks with components as not formed and not fully loaded
for (multiblockPosition in loadedMultiblocksWithComponentsInChunk(event.chunk.position)) {
val multiblock = BlockStorage.getAs<PylonMultiblock>(multiblockPosition)
if (formedMultiblocks.remove(multiblockPosition) && multiblock != null) {
multiblock.onMultiblockUnformed(true)
PylonMultiblockUnformEvent(multiblockPosition.block, multiblock as PylonBlock).callEvent()
}
fullyLoadedMultiblocks.remove(multiblockPosition)
dirtyMultiblocks.remove(multiblockPosition)
}
}

@EventHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import io.github.pylonmc.pylon.core.util.position.BlockPosition
import io.github.pylonmc.pylon.core.util.position.position
import io.github.pylonmc.pylon.core.util.pylonKey
import io.github.retrooper.packetevents.util.SpigotConversionUtil
import io.papermc.paper.datacomponent.DataComponentTypes
import me.tofaa.entitylib.meta.display.ItemDisplayMeta
import me.tofaa.entitylib.wrapper.WrapperEntity
import org.bukkit.Axis
import net.kyori.adventure.key.Key
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.World
Expand Down Expand Up @@ -149,7 +151,7 @@ open class PylonBlock internal constructor(val block: Block) {
entity.spawn(Location(block.x + 0.5, block.y + 0.5, block.z + 0.5, 0f, 0f))

val item = getBlockTextureItem() ?: ItemStack(Material.BARRIER)
item.editMeta { itemMeta -> itemMeta.itemModel = NamespacedKey.minecraft("air") }
item.setData(DataComponentTypes.ITEM_MODEL, Key.key("air"))
meta.item = SpigotConversionUtil.fromBukkitItemStack(item)
meta.brightnessOverride = 15 shl 4 or 15 shl 20;
meta.scale = Vector3f(1.0009f, 1.0009f, 1.0009f)
Expand Down Expand Up @@ -183,15 +185,23 @@ open class PylonBlock internal constructor(val block: Block) {
/**
* Use this method to make any changes to the block texture entity, such as changing its item,
* transformation, etc, after initialization. (see [setupBlockTexture])
*
* If you want to make changes to the entity outside of this method, make sure to call
* [WrapperEntity.refresh] afterwards so that the changes are sent to the client.
*/
protected fun updateBlockTexture(updater: (WrapperEntity, ItemDisplayMeta) -> Unit) {
blockTextureEntity?.let {
val meta = it.getEntityMeta(ItemDisplayMeta::class.java)
updater(it, meta)
it.refresh()
}
}

/**
* Call this method to refresh the block texture entity's item to be the result of
* [getBlockTextureItem], or a barrier if that returns null.
*/
protected fun refreshBlockTextureItem() {
updateBlockTexture { _, meta ->
val item = getBlockTextureItem() ?: ItemStack(Material.BARRIER)
item.setData(DataComponentTypes.ITEM_MODEL, Key.key("air"))
meta.item = SpigotConversionUtil.fromBukkitItemStack(item)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package io.github.pylonmc.pylon.core.block.base

import io.github.pylonmc.pylon.core.block.BlockStorage
import io.github.pylonmc.pylon.core.block.PhantomBlock
import io.github.pylonmc.pylon.core.datatypes.PylonSerializers
import io.github.pylonmc.pylon.core.entity.EntityStorage
import io.github.pylonmc.pylon.core.entity.PylonEntity
import io.github.pylonmc.pylon.core.event.PylonBlockBreakEvent
import io.github.pylonmc.pylon.core.event.PylonBlockDeserializeEvent
import io.github.pylonmc.pylon.core.event.PylonBlockSerializeEvent
import io.github.pylonmc.pylon.core.event.PylonBlockUnloadEvent
import io.github.pylonmc.pylon.core.util.position.position
import io.github.pylonmc.pylon.core.util.pylonKey
import org.bukkit.Bukkit
import org.bukkit.Keyed
import org.bukkit.entity.BlockDisplay
import org.bukkit.block.Block
import org.bukkit.entity.Entity
import org.bukkit.entity.ItemDisplay
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.entity.EntityRemoveEvent
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.MustBeInvokedByOverriders
import java.util.IdentityHashMap
import java.util.UUID

Expand All @@ -26,14 +28,16 @@ import java.util.UUID
*
* Note that the Pylon entities may not be loaded at the same time that the block is loaded.
*/
interface PylonEntityHolderBlock : PylonBreakHandler {
interface PylonEntityHolderBlock {
val block: Block

@get:ApiStatus.NonExtendable
val heldEntities: MutableMap<String, UUID>
get() = holders.getOrPut(this) { mutableMapOf() }

fun addEntity(name: String, entity: Entity) {
heldEntities[name] = entity.uniqueId
entity.persistentDataContainer.set(blockKey, PylonSerializers.BLOCK_POSITION, block.position)
}

fun addEntity(name: String, entity: PylonEntity<*>)
Expand Down Expand Up @@ -92,17 +96,10 @@ interface PylonEntityHolderBlock : PylonBreakHandler {
@ApiStatus.NonExtendable
fun areAllHeldEntitiesLoaded() = heldEntities.keys.all { isHeldEntityPresent(it) }

@MustBeInvokedByOverriders
override fun postBreak() {
// Best-effort removal; unlikely to cause issues
for (name in heldEntities.keys) {
getHeldEntity(name)?.remove()
}
}

@ApiStatus.Internal
companion object : Listener {
private val entityKey = pylonKey("entity_holder_entity_uuids")
private val blockKey = pylonKey("entity_holder_block")
private val entityType = PylonSerializers.MAP.mapTypeFrom(PylonSerializers.STRING, PylonSerializers.UUID)

private val holders = IdentityHashMap<PylonEntityHolderBlock, MutableMap<String, UUID>>()
Expand All @@ -128,5 +125,29 @@ interface PylonEntityHolderBlock : PylonBreakHandler {
if (block !is PylonEntityHolderBlock) return
holders.remove(block)
}

@EventHandler
private fun onBreak(event: PylonBlockBreakEvent) {
val block = event.pylonBlock
if (block is PylonEntityHolderBlock) {
// Best-effort removal; unlikely to cause issues
block.heldEntities.values.forEach {
Bukkit.getEntity(it)?.let { if (it.isValid) it.remove() }
}
holders.remove(block)
} else if (block is PhantomBlock) {
block.pdc.get(entityKey, entityType)?.values?.forEach {
Bukkit.getEntity(it)?.let { if (it.isValid) it.remove() }
}
}
}

@EventHandler
private fun onEntityRemove(event: EntityRemoveEvent) {
if (event.cause == EntityRemoveEvent.Cause.UNLOAD || event.cause == EntityRemoveEvent.Cause.PLAYER_QUIT) return
val blockPos = event.entity.persistentDataContainer.get(blockKey, PylonSerializers.BLOCK_POSITION) ?: return
val block = BlockStorage.get(blockPos) as? PylonEntityHolderBlock ?: return
holders[block]?.entries?.removeIf { it.value == event.entity.uniqueId }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import io.github.pylonmc.pylon.core.block.MultiblockCache
import io.github.pylonmc.pylon.core.util.position.ChunkPosition
import org.bukkit.block.Block
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.MustBeInvokedByOverriders


/**
Expand Down Expand Up @@ -61,9 +60,21 @@ interface PylonMultiblock {
*/
fun onMultiblockFormed() {}

/**
* Called when the multiblock is refreshed (i.e., was formed before, and still is).
* This happens when something causes the Multiblock to re-check if it is formed,
* and it still is.
*/
fun onMultiblockRefreshed() {}

/**
* Called when the multiblock is unformed (i.e., was formed before, but now is not).
* This includes when a part of the multiblock is unloaded, and the multiblock becomes unformed because of it.
* This includes when a part of the multiblock is unloaded and the multiblock becomes unformed because of it,
* or when part of the multiblock is broken/changed, and it becomes unformed because of it.
*
* This is **not** called when the multiblock itself is unloaded, use [PylonUnloadBlock] for that.
*
* [partUnloaded] is true if the multiblock became unformed because part of it was unloaded, false otherwise.
*/
fun onMultiblockUnformed() {}
fun onMultiblockUnformed(partUnloaded: Boolean) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import io.github.pylonmc.pylon.core.datatypes.PylonSerializers
import io.github.pylonmc.pylon.core.entity.EntityStorage
import io.github.pylonmc.pylon.core.entity.PylonEntity
import io.github.pylonmc.pylon.core.entity.base.PylonInteractEntity
import io.github.pylonmc.pylon.core.entity.display.BlockDisplayBuilder
import io.github.pylonmc.pylon.core.entity.display.ItemDisplayBuilder
import io.github.pylonmc.pylon.core.entity.display.transform.TransformBuilder
import io.github.pylonmc.pylon.core.event.PylonBlockDeserializeEvent
Expand All @@ -28,7 +27,6 @@ import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.block.Block
import org.bukkit.block.BlockFace
import org.bukkit.entity.BlockDisplay
import org.bukkit.entity.ItemDisplay
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
Expand All @@ -37,6 +35,7 @@ import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.util.Vector
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.MustBeInvokedByOverriders
import org.joml.Vector3i
import java.util.IdentityHashMap
import java.util.UUID
Expand Down Expand Up @@ -219,8 +218,10 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon
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}"
val ghostBlock = component.spawnGhostBlock((block.position + offset).block)
heldEntities[key] = ghostBlock
if (!isHeldEntityPresent(key)) {
val ghostBlock = component.spawnGhostBlock((block.position + offset).block)
heldEntities[key] = ghostBlock
}
}
updateGhostBlockColors()
}
Expand Down Expand Up @@ -274,20 +275,27 @@ interface PylonSimpleMultiblock : PylonMultiblock, PylonEntityHolderBlock, Pylon
}
}

// Remove ghosts if fully formed
if (formed) {
val toRemove = heldEntities.keys.filter { it.startsWith("multiblock_ghost_block_") }
for (key in toRemove) {
EntityStorage.get(heldEntities[key]!!)!!.entity.remove()
heldEntities.remove(key)
}
}

updateGhostBlockColors()

return formed
}

@MustBeInvokedByOverriders
override fun onMultiblockFormed() {
val toRemove = heldEntities.keys.filter { it.startsWith("multiblock_ghost_block_") }
for (key in toRemove) {
EntityStorage.get(heldEntities[key]!!)!!.entity.remove()
heldEntities.remove(key)
}
}

@MustBeInvokedByOverriders
override fun onMultiblockUnformed(partUnloaded: Boolean) {
if (!partUnloaded) {
spawnGhostBlocks()
}
}

override fun isPartOfMultiblock(otherBlock: Block): Boolean = validStructures().any {
it.contains((otherBlock.position - block.position).vector3i)
}
Expand Down
Loading