diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt index a190892ad..275921753 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt @@ -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() diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/BlockStorage.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/BlockStorage.kt index 9da5d4f7e..1a7cd7920 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/BlockStorage.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/BlockStorage.kt @@ -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 diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/MultiblockCache.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/MultiblockCache.kt index b0cab4fda..32216255a 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/MultiblockCache.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/MultiblockCache.kt @@ -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 @@ -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(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(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() } } @@ -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) } } @@ -123,8 +134,8 @@ internal object MultiblockCache : Listener { } } - fullyLoadedMultiblocks.remove(multiblockPosition) formedMultiblocks.remove(multiblockPosition) + fullyLoadedMultiblocks.remove(multiblockPosition) dirtyMultiblocks.remove(multiblockPosition) } @@ -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(multiblockPosition) + if (formedMultiblocks.remove(multiblockPosition) && multiblock != null) { + multiblock.onMultiblockUnformed(true) + PylonMultiblockUnformEvent(multiblockPosition.block, multiblock as PylonBlock).callEvent() + } + fullyLoadedMultiblocks.remove(multiblockPosition) + dirtyMultiblocks.remove(multiblockPosition) + } } @EventHandler diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt index 7400e1991..d9c449bb0 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt @@ -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 @@ -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) @@ -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) } } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonEntityHolderBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonEntityHolderBlock.kt index f2686a462..95d1ac7c6 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonEntityHolderBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonEntityHolderBlock.kt @@ -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 @@ -26,7 +28,8 @@ 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 @@ -34,6 +37,7 @@ interface PylonEntityHolderBlock : PylonBreakHandler { 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<*>) @@ -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>() @@ -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 } + } } } \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonMultiblock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonMultiblock.kt index 14a43746b..df35802fd 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonMultiblock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonMultiblock.kt @@ -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 /** @@ -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) {} } \ No newline at end of file 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 fcf398362..83959dbee 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 @@ -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 @@ -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 @@ -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 @@ -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() } @@ -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) } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonTickingBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonTickingBlock.kt index 658f60700..498b82d21 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonTickingBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonTickingBlock.kt @@ -2,6 +2,7 @@ package io.github.pylonmc.pylon.core.block.base import io.github.pylonmc.pylon.core.config.PylonConfig import io.github.pylonmc.pylon.core.datatypes.PylonSerializers +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 @@ -96,5 +97,13 @@ interface PylonTickingBlock { tickingBlocks.remove(block) } } + + @EventHandler + private fun onBreak(event: PylonBlockBreakEvent) { + val block = event.pylonBlock + if (block is PylonTickingBlock) { + tickingBlocks.remove(block) + } + } } } \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt index 7829b59a9..9357cf148 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt @@ -1,6 +1,7 @@ package io.github.pylonmc.pylon.core.content.fluid import io.github.pylonmc.pylon.core.block.PylonBlock +import io.github.pylonmc.pylon.core.block.base.PylonBreakHandler import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock import io.github.pylonmc.pylon.core.block.context.BlockBreakContext import io.github.pylonmc.pylon.core.block.context.BlockBreakContext.PlayerBreak @@ -22,7 +23,7 @@ import org.jetbrains.annotations.ApiStatus * on pipe corners/junctions. */ @ApiStatus.Internal -class FluidPipeConnector : PylonBlock, PylonEntityHolderBlock { +class FluidPipeConnector : PylonBlock, PylonEntityHolderBlock, PylonBreakHandler { @Suppress("unused") constructor(block: Block, context: BlockCreateContext) : super(block) { @@ -47,8 +48,6 @@ class FluidPipeConnector : PylonBlock, PylonEntityHolderBlock { // can be null if called from two different location (eg two different connection points removing the display) pipeDisplay?.delete(true, player, drops) } - - super.onBreak(drops, context) } override fun getWaila(player: Player): WailaConfig? diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/MultiblockFormEvent.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonMultiblockFormEvent.kt similarity index 100% rename from pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/MultiblockFormEvent.kt rename to pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonMultiblockFormEvent.kt diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonMultiblockRefreshEvent.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonMultiblockRefreshEvent.kt new file mode 100644 index 000000000..bf9069284 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonMultiblockRefreshEvent.kt @@ -0,0 +1,19 @@ +package io.github.pylonmc.pylon.core.event + +import io.github.pylonmc.pylon.core.block.PylonBlock +import org.bukkit.block.Block +import org.bukkit.event.Event +import org.bukkit.event.HandlerList + +class PylonMultiblockRefreshEvent( + val block: Block, + val pylonBlock: PylonBlock, +) : Event() { + override fun getHandlers(): HandlerList + = handlerList + + companion object { + @JvmStatic + val handlerList: HandlerList = HandlerList() + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/MultiblockUnformEvent.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonMultiblockUnformEvent.kt similarity index 100% rename from pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/MultiblockUnformEvent.kt rename to pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonMultiblockUnformEvent.kt diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt index 7064e4c6d..47b412f29 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt @@ -143,8 +143,7 @@ object BlockTextureEngine : Listener { while (true) { val player = Bukkit.getPlayer(uuid) if (player == null || !player.hasCustomBlockTextures) { - visible.forEach { it.blockTextureEntity?.removeViewer(uuid) } - visible.clear() + octrees.values.forEach { it.forEach { b -> b.blockTextureEntity?.removeViewer(uuid) } } jobs.remove(uuid) break } @@ -264,15 +263,6 @@ object BlockTextureEngine : Listener { occludingCache.remove(ChunkPosition(event.chunk)) } - @EventHandler(priority = EventPriority.MONITOR) - private fun onPlayerQuit(event: PlayerQuitEvent) { - jobs.remove(event.player.uniqueId)?.let { - if (it.isActive) { - it.cancel() - } - } - } - private data class ChunkData( var timestamp: Long, val occluding: LoadingCache = Caffeine.newBuilder() diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/Octree.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/Octree.kt index decf67f6a..af43302ab 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/Octree.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/Octree.kt @@ -11,7 +11,7 @@ open class Octree( private val bounds: BoundingBox, private val depth: Int, private val entryStrategy: (N) -> BoundingBox, -) { +) : Iterable { private var entries: MutableList = CopyOnWriteArrayList() private var children: Array>? = null @@ -35,7 +35,7 @@ open class Octree( } } - if (entries.size < DEFAULT_MAX_ENTRIES || depth >= DEFAULT_MAX_DEPTH) { + if (entries.size < maxEntries || depth >= maxDepth) { entries.add(entry) return true } @@ -127,9 +127,39 @@ open class Octree( return children?.maxOfOrNull { it.maxDepth() } ?: depth } + override fun iterator(): Iterator = OctreeIterator(this) + companion object { // LISTEN IF THESE ARE INSANE FIX THEM, THIS ISN'T MY STRONG SUIT - JustAHuman private const val DEFAULT_MAX_DEPTH = 2048 private const val DEFAULT_MAX_ENTRIES = 128 } + + private class OctreeIterator(root: Octree) : Iterator { + private val stack = ArrayDeque>() + private var currentEntries: Iterator? = null + + init { + stack.add(root) + } + + override fun hasNext(): Boolean { + while (true) { + if (currentEntries?.hasNext() == true) { + return true + } + if (stack.isEmpty()) { + return false + } + val node = stack.removeLast() + currentEntries = node.entries.iterator() + node.children?.let { stack.addAll(it) } + } + } + + override fun next(): N { + if (!hasNext()) throw NoSuchElementException() + return currentEntries!!.next() + } + } } \ No newline at end of file