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 @@ -9,6 +9,7 @@ import com.github.shynixn.mccoroutine.bukkit.ticks
import io.github.pylonmc.pylon.core.addon.PylonAddon
import io.github.pylonmc.pylon.core.block.*
import io.github.pylonmc.pylon.core.block.base.*
import io.github.pylonmc.pylon.core.block.base.PylonFallingBlock.PylonFallingBlockEntity
import io.github.pylonmc.pylon.core.command.ROOT_COMMAND
import io.github.pylonmc.pylon.core.command.ROOT_COMMAND_PY_ALIAS
import io.github.pylonmc.pylon.core.config.Config
Expand Down Expand Up @@ -55,6 +56,7 @@ import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.entity.Display
import org.bukkit.entity.FallingBlock
import org.bukkit.entity.ItemDisplay
import org.bukkit.permissions.Permission
import org.bukkit.permissions.PermissionDefault
Expand Down Expand Up @@ -202,6 +204,8 @@ object PylonCore : JavaPlugin(), PylonAddon {
PylonEntity.register<ItemDisplay, FluidIntersectionDisplay>(FluidIntersectionDisplay.KEY)
PylonEntity.register<ItemDisplay, FluidPipeDisplay>(FluidPipeDisplay.KEY)

PylonEntity.register<FallingBlock, PylonFallingBlockEntity>(PylonFallingBlock.KEY)

PylonBlock.register<FluidSectionMarker>(FluidSectionMarker.KEY, Material.STRUCTURE_VOID)
PylonBlock.register<FluidIntersectionMarker>(FluidIntersectionMarker.KEY, Material.STRUCTURE_VOID)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import io.github.pylonmc.pylon.core.item.PylonItem
import io.github.pylonmc.pylon.core.item.research.Research.Companion.canUse
import io.github.pylonmc.pylon.core.util.damageItem
import io.github.pylonmc.pylon.core.util.isFakeEvent
import io.github.pylonmc.pylon.core.util.position.position
import io.papermc.paper.datacomponent.DataComponentTypes
import io.papermc.paper.event.block.*
import io.papermc.paper.event.entity.EntityCompostItemEvent
Expand All @@ -22,7 +23,7 @@ import org.bukkit.GameMode
import org.bukkit.Material
import org.bukkit.block.Container
import org.bukkit.block.Hopper
import org.bukkit.entity.minecart.HopperMinecart
import org.bukkit.entity.FallingBlock
import org.bukkit.event.Event
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
Expand All @@ -31,13 +32,11 @@ import org.bukkit.event.block.*
import org.bukkit.event.block.BellRingEvent
import org.bukkit.event.enchantment.EnchantItemEvent
import org.bukkit.event.enchantment.PrepareItemEnchantEvent
import org.bukkit.event.entity.EntityChangeBlockEvent
import org.bukkit.event.entity.EntityDropItemEvent
import org.bukkit.event.entity.EntityExplodeEvent
import org.bukkit.event.inventory.BrewingStandFuelEvent
import org.bukkit.event.inventory.FurnaceBurnEvent
import org.bukkit.event.inventory.FurnaceExtractEvent
import org.bukkit.event.inventory.InventoryMoveItemEvent
import org.bukkit.event.inventory.InventoryOpenEvent
import org.bukkit.event.inventory.InventoryPickupItemEvent
import org.bukkit.event.entity.EntityRemoveEvent
import org.bukkit.event.inventory.*
import org.bukkit.event.player.PlayerBucketEmptyEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerTakeLecternBookEvent
Expand Down Expand Up @@ -95,6 +94,68 @@ internal object BlockListener : Listener {
}
}

private val fallMap = HashMap<UUID, Pair<PylonFallingBlock, PylonFallingBlock.PylonFallingBlockEntity>>();

@EventHandler(ignoreCancelled = true)
private fun entityBlockChange(event: EntityChangeBlockEvent) {
val entity = event.entity

if (entity !is FallingBlock) return

val block = event.block
if (!entity.isInWorld) {
val pylonBlock = BlockStorage.get(block) ?: return
val pylonFallingBlock = pylonBlock as? PylonFallingBlock
if (pylonFallingBlock == null) {
event.isCancelled = true
return
}

val blockPdc = PylonBlock.serialize(pylonBlock, block.chunk.persistentDataContainer.adapterContext)
val fallingEntity = PylonFallingBlock.PylonFallingBlockEntity(pylonBlock.schema, blockPdc, block.position, entity)
pylonFallingBlock.onFallStart(event, fallingEntity)
if (!event.isCancelled) {
BlockStorage.deleteBlock(block.position)
EntityStorage.add(fallingEntity)
// save this here as the entity storage is going to nuke it if the item drops
fallMap[entity.uniqueId] = Pair(pylonFallingBlock, fallingEntity)
}
} else {
val pylonEntity = EntityStorage.get(entity) as? PylonFallingBlock.PylonFallingBlockEntity ?: return
val pylonBlock = BlockStorage.loadBlock(block.position, pylonEntity.blockSchema, pylonEntity.blockData) as PylonFallingBlock

pylonBlock.onFallStop(event, pylonEntity)
}
}

@EventHandler
private fun entityDespawn(event: EntityRemoveEvent) {
// DESPAWN = Fell and created block ; OUT_OF_WORLD = Fell and dropped item
if (event.cause != EntityRemoveEvent.Cause.DESPAWN) return
val entity = event.entity
if (entity !is FallingBlock) return
fallMap.remove(entity.uniqueId)
}

@EventHandler(ignoreCancelled = true)
private fun fallingBlockDrop(event: EntityDropItemEvent) {
val entity = event.entity

if (entity !is FallingBlock) return

val (pylonFallingBlock, pylonFallingEntity) = fallMap[entity.uniqueId] ?: return
fallMap.remove(entity.uniqueId)

val relativeItem = pylonFallingBlock.onItemDrop(event, pylonFallingEntity)
if (event.isCancelled) return
if (relativeItem == null) {
event.isCancelled = true
return
}

event.itemDrop.itemStack = relativeItem
}

@EventHandler(ignoreCancelled = true)
private fun blockRemove(event: BlockBreakEvent) {
if (BlockStorage.isPylonBlock(event.block)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import org.bukkit.event.Listener
import org.bukkit.event.world.ChunkLoadEvent
import org.bukkit.event.world.ChunkUnloadEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantReadWriteLock
Expand Down Expand Up @@ -61,7 +63,7 @@ import kotlin.random.Random
*/
object BlockStorage : Listener {

private val pylonBlocksKey = pylonKey("blocks")
val pylonBlocksKey = pylonKey("blocks")

// Access to blocks, blocksByChunk, blocksById fields must be synchronized
// to prevent them briefly going out of sync
Expand Down Expand Up @@ -262,6 +264,47 @@ object BlockStorage : Listener {
return block
}

/**
* Manually loads Pylon block data at a location from a persistent data container that contains the serialised block. Only call on the main thread.
*
* @return The block that was loaded, or null if the block loading was cancelled
*
* @throws IllegalArgumentException if the chunk of the given [blockPosition] is not
* loaded, the block already contains a Pylon block
*/
@JvmStatic
fun loadBlock(
blockPosition: BlockPosition,
schema: PylonBlockSchema,
pdcData: PersistentDataContainer
): PylonBlock? {
val context = BlockCreateContext.ManualLoading(blockPosition.block)
val block = blockPosition.block
pdcData.set(PylonBlock.pylonBlockPositionKey, PersistentDataType.LONG, blockPosition.asLong)

require(block.chunk.isLoaded) { "You can only place Pylon blocks in loaded chunks" }
require(!isPylonBlock(block)) { "You cannot place a new Pylon block in place of an existing Pylon blocks" }

if (!PrePylonBlockPlaceEvent(block, schema, context).callEvent()) return null
if (context.shouldSetType) {
block.type = schema.material
}

val pyBlock = PylonBlock.deserialize(block.world, pdcData)!!

lockBlockWrite {
check(blockPosition.chunk in blocksByChunk) { "Chunk '${blockPosition.chunk}' must be loaded" }
blocks[blockPosition] = pyBlock
blocksByKey.getOrPut(schema.key, ::mutableListOf).add(pyBlock)
blocksByChunk[blockPosition.chunk]!!.add(pyBlock)
}

BlockTextureEngine.insert(pyBlock)
PylonBlockPlaceEvent(block, pyBlock, context).callEvent()

return pyBlock
}

/**
* Creates a new Pylon block. Only call on the main thread.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,11 @@ open class PylonBlock internal constructor(val block: Block) {
@JvmStatic
val pylonBlockTextureEntityKey = pylonKey("pylon_block_texture_entity")

private val pylonBlockKeyKey = pylonKey("pylon_block_key")
private val pylonBlockPositionKey = pylonKey("position")
@JvmStatic
val pylonBlockKeyKey = pylonKey("pylon_block_key")

@JvmStatic
val pylonBlockPositionKey = pylonKey("position")

@get:JvmStatic
val Block.pylonBlock: PylonBlock?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,12 @@ interface PylonEntityHolderBlock {

@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)
@JvmStatic
val entityKey = pylonKey("entity_holder_entity_uuids")
@JvmStatic
val blockKey = pylonKey("entity_holder_block")
@JvmStatic
val entityType = PylonSerializers.MAP.mapTypeFrom(PylonSerializers.STRING, PylonSerializers.UUID)

@JvmSynthetic
internal val holders = IdentityHashMap<PylonEntityHolderBlock, MutableMap<String, UUID>>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.github.pylonmc.pylon.core.block.base

import io.github.pylonmc.pylon.core.PylonCore
import io.github.pylonmc.pylon.core.block.BlockStorage
import io.github.pylonmc.pylon.core.block.PylonBlock
import io.github.pylonmc.pylon.core.block.PylonBlockSchema
import io.github.pylonmc.pylon.core.datatypes.NamespacedKeyPersistentDataType
import io.github.pylonmc.pylon.core.datatypes.PylonSerializers
import io.github.pylonmc.pylon.core.entity.PylonEntity
import io.github.pylonmc.pylon.core.registry.PylonRegistry
import io.github.pylonmc.pylon.core.util.position.BlockPosition
import org.bukkit.NamespacedKey
import org.bukkit.entity.FallingBlock
import org.bukkit.event.entity.EntityChangeBlockEvent
import org.bukkit.event.entity.EntityDropItemEvent
import org.bukkit.persistence.PersistentDataContainer

/**
* Interface meant to be used for all pylon blocks affected by gravity, like sand, gravel etc.
*
* If you implement this interface, and the block can't fall, then the methods will never be called.
*
* Beware of how you modify the passed data in the entity, because of the order of serialization and deserialization
* some modification need to be applied directly to the PDC stored in the entity, or they will be lost.
*
* Also at some steps, the entity or the block might not exist yet in their relative storage,
* handle this interface with caution as it needs to handle state using internals most of the time.
*
* As a suggestion, don't make important blocks with lots of data affected by gravity,
* or it might become a nightmare to apply the correct changes.
*/
interface PylonFallingBlock {
/**
* When calling this, the entity doesn't exist yet in [io.github.pylonmc.pylon.core.entity.EntityStorage]
* Called after serialization
*/
fun onFallStart(event: EntityChangeBlockEvent, spawnedEntity: PylonFallingBlockEntity)

/**
* Called after deserialization
* Cancelling the event at this step does nothing, and the entity is about to be removed
*/
fun onFallStop(event: EntityChangeBlockEvent, entity: PylonFallingBlockEntity)

/**
* When called the block doesn't exist in the world and in [BlockStorage]
*/
fun onItemDrop(event: EntityDropItemEvent, entity: PylonFallingBlockEntity) = PylonRegistry.ITEMS[(this as PylonBlock).key]?.getItemStack()

class PylonFallingBlockEntity : PylonEntity<FallingBlock> {
val fallStartPosition: BlockPosition
val blockSchema: PylonBlockSchema
val blockData: PersistentDataContainer

constructor(blockSchema: PylonBlockSchema, blockData: PersistentDataContainer, fallingStart: BlockPosition, entity: FallingBlock) : super(KEY, entity) {
this.blockSchema = blockSchema
this.blockData = blockData
this.fallStartPosition = fallingStart
}

constructor(entity: FallingBlock) : super(entity) {
val pdc = entity.persistentDataContainer

val fallingBlockType = pdc.get(FALLING_BLOCK_TYPE, NamespacedKeyPersistentDataType)!!
this.blockSchema = PylonRegistry.BLOCKS[fallingBlockType]!!
this.blockData = pdc.get(FALLING_BLOCK_DATA, PylonSerializers.TAG_CONTAINER)!!
this.fallStartPosition = pdc.get(FALLING_BLOCK_START, PylonSerializers.BLOCK_POSITION)!!
}

override fun write(pdc: PersistentDataContainer) {
pdc.set(FALLING_BLOCK_TYPE, NamespacedKeyPersistentDataType, blockSchema.key)
pdc.set(FALLING_BLOCK_DATA, PylonSerializers.TAG_CONTAINER, blockData)
pdc.set(FALLING_BLOCK_START, PylonSerializers.BLOCK_POSITION, fallStartPosition)
}
}

companion object {
@JvmField
val KEY = NamespacedKey(PylonCore, "falling_pylon_block")

@JvmField
val FALLING_BLOCK_DATA = NamespacedKey(PylonCore, "falling_pylon_block_data")

@JvmField
val FALLING_BLOCK_TYPE = NamespacedKey(PylonCore, "falling_pylon_block_type")

@JvmField
val FALLING_BLOCK_START = NamespacedKey(PylonCore, "falling_pylon_block_start")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,11 @@ interface BlockCreateContext {
override val item: ItemStack? = null,
override val shouldSetType: Boolean = true
) : BlockCreateContext

@JvmRecord
data class ManualLoading @JvmOverloads constructor(
override val block: Block,
override val item: ItemStack? = null,
override val shouldSetType: Boolean = true
) : BlockCreateContext
}
Loading