diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiBlockManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiBlockManager.kt index f24e81542cc..6175fb0bc53 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiBlockManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiBlockManager.kt @@ -3,14 +3,18 @@ package xyz.xenondevs.nova.api import org.bukkit.Location +import org.bukkit.entity.Entity import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.api.block.NovaBlockState import xyz.xenondevs.nova.api.material.NovaMaterial +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.block.state.NovaTileEntityState import xyz.xenondevs.nova.world.block.BlockManager -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import xyz.xenondevs.nova.world.pos +import java.util.* import xyz.xenondevs.nova.api.block.BlockManager as IBlockManager import xyz.xenondevs.nova.api.block.NovaBlock as INovaBlock @@ -28,9 +32,14 @@ internal object ApiBlockManager : IBlockManager { } override fun placeBlock(location: Location, block: INovaBlock, source: Any?, playSound: Boolean) { - require(block is ApiBlockWrapper) { "material must be ApiBlockWrapper" } - val ctx = BlockPlaceContext.forAPI(location, block, source) - BlockManager.placeBlockState(block.block, ctx, playSound) + require(block is ApiBlockWrapper) { "block must be ApiBlockWrapper" } + + val ctxBuilder = Context.intention(BlockPlace) + .param(ContextParamTypes.BLOCK_POS, location.pos) + .param(ContextParamTypes.BLOCK_TYPE_NOVA, block.block) + .param(ContextParamTypes.BLOCK_PLACE_EFFECTS, playSound) + setSourceParam(ctxBuilder, source) + BlockManager.placeBlockState(block.block, ctxBuilder.build()) } override fun placeBlock(location: Location, material: NovaMaterial, source: Any?, playSound: Boolean) { @@ -39,13 +48,31 @@ internal object ApiBlockManager : IBlockManager { } override fun getDrops(location: Location, source: Any?, tool: ItemStack?): List? { - val ctx = BlockBreakContext.forAPI(location, source, tool) - return BlockManager.getDrops(ctx) + val ctxBuilder = Context.intention(BlockBreak) + .param(ContextParamTypes.BLOCK_POS, location.pos) + .param(ContextParamTypes.TOOL_ITEM_STACK, tool) + setSourceParam(ctxBuilder, source) + return BlockManager.getDrops(ctxBuilder.build()) } override fun removeBlock(location: Location, source: Any?, breakEffects: Boolean): Boolean { - val ctx = BlockBreakContext.forAPI(location, source, null) - return BlockManager.removeBlockState(ctx, breakEffects) + val ctxBuilder = Context.intention(BlockBreak) + .param(ContextParamTypes.BLOCK_POS, location.pos) + .param(ContextParamTypes.BLOCK_BREAK_EFFECTS, breakEffects) + setSourceParam(ctxBuilder, source) + return BlockManager.removeBlockState(ctxBuilder.build()) + } + + private fun setSourceParam(builder: Context.Builder<*>, source: Any?) { + if (source == null) + return + + when (source) { + is Entity -> builder.param(ContextParamTypes.SOURCE_ENTITY, source) + is ApiTileEntityWrapper -> builder.param(ContextParamTypes.SOURCE_TILE_ENTITY, source.tileEntity) + is Location -> builder.param(ContextParamTypes.SOURCE_LOCATION, source) + is UUID -> builder.param(ContextParamTypes.SOURCE_UUID, source) + } } } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiTileEntityWrapper.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiTileEntityWrapper.kt index c48a5f28b0b..da56e2d1cfc 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiTileEntityWrapper.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/api/ApiTileEntityWrapper.kt @@ -10,7 +10,7 @@ import xyz.xenondevs.nova.tileentity.TileEntity import xyz.xenondevs.nova.api.tileentity.TileEntity as ITileEntity @Suppress("DEPRECATION") -internal class ApiTileEntityWrapper(private val tileEntity: TileEntity) : ITileEntity { +internal class ApiTileEntityWrapper(val tileEntity: TileEntity) : ITileEntity { @Deprecated("Use NovaBlock instead", replaceWith = ReplaceWith("block")) override fun getMaterial(): NovaMaterial = LegacyMaterialWrapper(Either.right(tileEntity.block)) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt index 212c3e9e1a7..a7fe4c58de5 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/command/impl/NovaCommand.kt @@ -25,6 +25,9 @@ import xyz.xenondevs.nova.command.requiresPlayerPermission import xyz.xenondevs.nova.command.sendFailure import xyz.xenondevs.nova.command.sendSuccess import xyz.xenondevs.nova.data.config.Configs +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.recipe.RecipeManager import xyz.xenondevs.nova.data.resources.ResourceGeneration import xyz.xenondevs.nova.data.resources.builder.ResourcePackBuilder @@ -53,7 +56,6 @@ import xyz.xenondevs.nova.util.item.takeUnlessEmpty import xyz.xenondevs.nova.util.runAsyncTask import xyz.xenondevs.nova.world.block.BlockManager import xyz.xenondevs.nova.world.block.backingstate.BackingStateManager -import xyz.xenondevs.nova.world.block.context.BlockBreakContext import xyz.xenondevs.nova.world.block.hitbox.HitboxManager import xyz.xenondevs.nova.world.chunkPos import xyz.xenondevs.nova.world.fakeentity.FakeEntityManager.MAX_RENDER_DISTANCE @@ -405,7 +407,12 @@ internal object NovaCommand : Command("nova") { val player = ctx.player val chunks = player.location.chunk.getSurroundingChunks(ctx["range"], true) val novaBlocks = chunks.flatMap { WorldDataManager.getBlockStates(it.pos).values.filterIsInstance() } - novaBlocks.forEach { BlockManager.removeBlockState(BlockBreakContext(it.pos)) } + novaBlocks.forEach { + val breakCtx = Context.intention(ContextIntentions.BlockBreak) + .param(ContextParamTypes.BLOCK_POS, it.pos) + .build() + BlockManager.removeBlockState(breakCtx) + } ctx.source.sendSuccess(Component.translatable( "command.nova.remove_tile_entities.success", diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/Context.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/Context.kt new file mode 100644 index 00000000000..bb5933eefef --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/Context.kt @@ -0,0 +1,204 @@ +@file:Suppress("UNCHECKED_CAST") + +package xyz.xenondevs.nova.data.context + +import xyz.xenondevs.commons.collections.filterValuesNotNullTo +import xyz.xenondevs.commons.reflection.call +import xyz.xenondevs.nova.data.context.intention.ContextIntention +import xyz.xenondevs.nova.data.context.param.ContextParamType +import xyz.xenondevs.nova.data.context.param.DefaultingContextParamType + +class Context private constructor( + private val intention: I, + private val explicitParams: Map, Any>, + private val resolvedParams: Map, Any>, +) { + + /** + * Returns the value of the given [paramType] or null if it is not present. + */ + operator fun get(paramType: ContextParamType): V? { + return getParam(paramType) + } + + /** + * Returns the value of the given [paramType]. + */ + operator fun get(paramType: DefaultingContextParamType): V { + return getParam(paramType) ?: paramType.defaultValue + } + + /** + * Returns the value of the given [paramType] or throws an exception if it is not present. + * + * @throws IllegalStateException If the given [paramType] is an optional parameter that is not present. + * @throws IllegalArgumentException If the given [paramType] is not allowed under this context's intention. + */ + fun getOrThrow(paramType: ContextParamType): V { + val value = getParam(paramType) + + if (value != null) + return value + + if (paramType is DefaultingContextParamType) + return paramType.defaultValue + + throwParamNotPresent(paramType) + } + + private fun getParam(paramType: ContextParamType): V? { + return (explicitParams[paramType] ?: resolvedParams[paramType]) as V? + } + + + /** + * Checks whether the given [paramType] is present in this context. + * + * @throws IllegalArgumentException When the given [paramType] is not allowed under this context's intention. + */ + fun has(paramType: ContextParamType<*>): Boolean { + if (paramType in explicitParams || paramType in resolvedParams) + return true + + if (paramType !in intention.all) + throw IllegalArgumentException("A context of intention $intention will never contain parameter ${paramType.id}") + + return false + } + + /** + * Checks whether the given [paramType] is explicitly specified in this context. + * + * @throws IllegalArgumentException When the given [paramType] is not allowed under this context's intention. + */ + fun hasExplicitly(paramType: ContextParamType<*>): Boolean { + if (paramType in explicitParams) + return true + + if (paramType !in intention.all) + throw IllegalArgumentException("A context of intention $intention will never contain parameter ${paramType.id}") + + return false + } + + private fun throwParamNotPresent(paramType: ContextParamType<*>): Nothing { + if (paramType in intention.all) + throw IllegalStateException("Context parameter ${paramType.id} is not present") + else throw IllegalArgumentException("Context parameter ${paramType.id} is not allowed") + } + + companion object { + + /** + * Creates a new context builder for the given [intention]. + */ + fun intention(intention: I): Builder { + return Builder(intention) + } + + } + + class Builder internal constructor(private val intention: I) { + + /** + * The parameters that are explicitly set. + */ + private val explicitParams = HashMap, Any>() + + /** + * The parameters that are loaded through autofillers. The value is null if the param could not be loaded + * through fallbacks. + */ + private val resolvedParams = HashMap, Any?>() + + /** + * Sets the given [paramType] to the given [value]. + */ + fun param(paramType: ContextParamType, value: V?): Builder { + if (paramType !in intention.all) + throw IllegalArgumentException("Context parameter ${paramType.id} is not allowed under intention $intention") + + if (value == null) { + explicitParams.remove(paramType) + } else { + // check requirements + for (requirement in paramType.requirements) { + if (!requirement.validator(value)) + throw IllegalArgumentException("Context value: $value for parameter type: ${paramType.id} is invalid: ${requirement.errorGenerator(value)}") + } + + explicitParams[paramType] = value + } + + return this + } + + /** + * Builds the context. + */ + fun build(autofill: Boolean = true): Context { + if (autofill) + resolveParams() + + // verify presence of all required params + for (requiredParam in intention.required) { + if (requiredParam !in explicitParams) + throw IllegalStateException("Required context parameter ${requiredParam.id} is not present") + } + + return Context( + intention, + HashMap(explicitParams), + resolvedParams.filterValuesNotNullTo(HashMap()) + ) + } + + private fun resolveParams() { + for (paramType in intention.all) { + resolveParam(paramType) + } + } + + private fun hasParam(paramType: ContextParamType<*>): Boolean = + paramType in explicitParams || paramType in resolvedParams + + private fun getParam(paramType: ContextParamType): V? = + explicitParams[paramType] as V? ?: resolvedParams[paramType] as V? + + private fun resolveParam(paramType: ContextParamType): V? { + if (hasParam(paramType)) + return getParam(paramType) + + // preemptively set this to null to prevent recursive call chains + resolvedParams[paramType] = null + + // try to resolve value through autofillers + val autofillers = paramType.autofillers + var value: V? = null + autofiller@ for ((requiredParamTypes, fillerFunction) in autofillers) { + // load params required by autofiller + val requiredParamValues = arrayOfNulls(requiredParamTypes.size) + for ((i, requiredParamType) in requiredParamTypes.withIndex()) { + val requiredParamValue = resolveParam(requiredParamType) + ?: continue@autofiller // try next autofiller + requiredParamValues[i] = requiredParamValue + } + + // run autofiller function + value = fillerFunction.call(*requiredParamValues) + + if (value != null && paramType.requirements.all { it.validator(value!!) }) + break + } + + // otherwise, use default value if present + if (value == null && paramType is DefaultingContextParamType) + value = paramType.defaultValue + + resolvedParams[paramType] = value + return value + } + + } + +} diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/intention/ContextIntention.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/intention/ContextIntention.kt new file mode 100644 index 00000000000..6ab63edb16e --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/intention/ContextIntention.kt @@ -0,0 +1,24 @@ +package xyz.xenondevs.nova.data.context.intention + +import xyz.xenondevs.nova.data.context.param.ContextParamType + +/** + * Represents an intention for what a context is used for. + * + * @param required The required parameters for this intention. + * @param optional The optional parameters for this intention. + * @param all All parameters for this intention. + */ +abstract class ContextIntention( + val required: Set>, + val optional: Set>, + val all: Set> +) { + + /** + * Creates an intention with the given [required] and [optional] parameters. + */ + constructor(required: Collection>, optional: Collection>) : + this(required.toHashSet(), optional.toHashSet(), (required + optional).toHashSet()) + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/intention/ContextIntentions.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/intention/ContextIntentions.kt new file mode 100644 index 00000000000..19868b8752b --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/intention/ContextIntentions.kt @@ -0,0 +1,66 @@ +package xyz.xenondevs.nova.data.context.intention + +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_BREAK_EFFECTS +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_FACING +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_ITEM_STACK +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_PLACE_EFFECTS +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_POS +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_TYPE +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_TYPE_NOVA +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_TYPE_VANILLA +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BLOCK_WORLD +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.BYPASS_TILE_ENTITY_LIMITS +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.CLICKED_BLOCK_FACE +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.INTERACTION_HAND +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.SOURCE_DIRECTION +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.SOURCE_ENTITY +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.SOURCE_LOCATION +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.SOURCE_TILE_ENTITY +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.SOURCE_UUID +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.SOURCE_WORLD +import xyz.xenondevs.nova.data.context.param.ContextParamTypes.TOOL_ITEM_STACK + +/** + * Contains all built-in [context intentions][ContextIntention]. + */ +object ContextIntentions { + + /** + * The intention to place a block. + */ + data object BlockPlace : ContextIntention( + required = listOf(BLOCK_POS, BLOCK_WORLD, BLOCK_TYPE), + optional = listOf( + BLOCK_TYPE_NOVA, BLOCK_TYPE_VANILLA, + BLOCK_ITEM_STACK, BLOCK_FACING, + SOURCE_ENTITY, SOURCE_TILE_ENTITY, SOURCE_LOCATION, SOURCE_WORLD, SOURCE_DIRECTION, SOURCE_UUID, + BLOCK_PLACE_EFFECTS, BYPASS_TILE_ENTITY_LIMITS + ) + ) + + /** + * The intention to break a block. + */ + data object BlockBreak : ContextIntention( + required = listOf(BLOCK_POS, BLOCK_WORLD), + optional = listOf( + TOOL_ITEM_STACK, + CLICKED_BLOCK_FACE, + SOURCE_ENTITY, SOURCE_TILE_ENTITY, SOURCE_LOCATION, SOURCE_WORLD, SOURCE_DIRECTION, SOURCE_UUID, + BLOCK_BREAK_EFFECTS + ) + ) + + /** + * The intention to interact with a block. + */ + data object BlockInteract : ContextIntention( + required = listOf(BLOCK_POS, BLOCK_WORLD), + optional = listOf( + INTERACTION_HAND, TOOL_ITEM_STACK, + CLICKED_BLOCK_FACE, + SOURCE_ENTITY, SOURCE_TILE_ENTITY, SOURCE_LOCATION, SOURCE_WORLD, SOURCE_DIRECTION, SOURCE_UUID + ) + ) + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/param/ContextParamType.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/param/ContextParamType.kt new file mode 100644 index 00000000000..768783fa610 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/param/ContextParamType.kt @@ -0,0 +1,140 @@ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package xyz.xenondevs.nova.data.context.param + +import net.minecraft.resources.ResourceLocation +import xyz.xenondevs.nova.addon.Addon + +class Requirement( + val validator: (V) -> Boolean, + val errorGenerator: (V) -> String +) + +class Autofiller( + lazyParamTypes: List>>, + val filler: Function +) { + + val params: List> by lazy { lazyParamTypes.map { it.value } } + + operator fun component1(): List> = params + operator fun component2(): Function = filler + +} + +/** + * A context parameter type. + */ +sealed interface ContextParamType { + + /** + * The ID of this parameter type. + */ + val id: ResourceLocation + + /** + * A list of requirements that must be fulfilled for a value of this parameter type to be valid. + */ + val requirements: List> + + val autofillers: List> + + companion object { + + fun builder(addon: Addon, name: String): ContextParamTypeBuilder { + return builder(ResourceLocation(addon.description.id, name)) + } + + internal fun builder(name: String): ContextParamTypeBuilder { + return builder(ResourceLocation("nova", name)) + } + + fun builder(id: ResourceLocation): ContextParamTypeBuilder { + return ContextParamTypeBuilder(id) + } + + } + +} + +/** + * A context parameter type that has a default value instead of null. + */ +sealed interface DefaultingContextParamType : ContextParamType { + + /** + * The default intermediate value of this parameter type. + */ + val defaultValue: V + +} + +internal open class ContextParamTypeImpl( + override val id: ResourceLocation, + override val requirements: List>, + override val autofillers: List>, +) : ContextParamType + +internal class DefaultingContextParamTypeImpl( + id: ResourceLocation, + override val defaultValue: V, + requirements: List>, + autofillers: List>, +) : ContextParamTypeImpl(id, requirements, autofillers), DefaultingContextParamType + +class ContextParamTypeBuilder internal constructor(private val id: ResourceLocation) { + + private val requirements = ArrayList>() + private val autofillers = ArrayList>() + + fun require(validator: (V) -> Boolean, errorGenerator: (V) -> String): ContextParamTypeBuilder { + return require(Requirement(validator, errorGenerator)) + } + + fun require(requirement: Requirement): ContextParamTypeBuilder { + requirements += requirement + return this + } + + fun autofilledBy( + lazyParamType: () -> ContextParamType, + fillValue: (A) -> V? + ) = autofilledBy(fillValue, lazyParamType) + + fun autofilledBy( + lazyParamTypeA: () -> ContextParamType, + lazyParamTypeB: () -> ContextParamType, + fillValue: (A, B) -> V? + ) = autofilledBy(fillValue, lazyParamTypeA, lazyParamTypeB) + + fun autofilledBy( + lazyParamTypeA: () -> ContextParamType, + lazyParamTypeB: () -> ContextParamType, + lazyParamTypeC: () -> ContextParamType, + fillValue: (A, B, C) -> V? + ) = autofilledBy(fillValue, lazyParamTypeA, lazyParamTypeB, lazyParamTypeC) + + fun autofilledBy( + lazyParamTypeA: () -> ContextParamType, + lazyParamTypeB: () -> ContextParamType, + lazyParamTypeC: () -> ContextParamType, + lazyParamTypeD: () -> ContextParamType, + fillValue: (A, B, C, D) -> V? + ) = autofilledBy(fillValue, lazyParamTypeA, lazyParamTypeB, lazyParamTypeC, lazyParamTypeD) + + private fun autofilledBy( + fillValue: Function, + vararg lazyParamTypes: () -> ContextParamType<*> + ): ContextParamTypeBuilder { + val paramTypes = lazyParamTypes.map(::lazy) + autofillers += Autofiller(paramTypes, fillValue) + return this + } + + fun build(): ContextParamType = + ContextParamTypeImpl(id, requirements, autofillers) + + fun build(default: V): DefaultingContextParamType = + DefaultingContextParamTypeImpl(id, default, requirements, autofillers) + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/param/ContextParamTypes.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/param/ContextParamTypes.kt new file mode 100644 index 00000000000..6982928416e --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/context/param/ContextParamTypes.kt @@ -0,0 +1,257 @@ +@file:Suppress("MemberVisibilityCanBePrivate") + +package xyz.xenondevs.nova.data.context.param + +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.resources.ResourceLocation +import org.bukkit.GameMode +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.World +import org.bukkit.block.BlockFace +import org.bukkit.entity.Entity +import org.bukkit.entity.LivingEntity +import org.bukkit.entity.Player +import org.bukkit.inventory.EquipmentSlot +import org.bukkit.inventory.ItemStack +import org.bukkit.util.Vector +import xyz.xenondevs.nova.registry.NovaRegistries +import xyz.xenondevs.nova.tileentity.TileEntity +import xyz.xenondevs.nova.util.BlockFaceUtils +import xyz.xenondevs.nova.util.bukkitMaterial +import xyz.xenondevs.nova.util.item.ItemUtils +import xyz.xenondevs.nova.util.item.ToolUtils +import xyz.xenondevs.nova.util.item.takeUnlessEmpty +import xyz.xenondevs.nova.util.nmsBlock +import xyz.xenondevs.nova.world.BlockPos +import xyz.xenondevs.nova.world.block.NovaBlock +import java.util.* + +object ContextParamTypes { + + /** + * The position of a block. + */ + val BLOCK_POS: ContextParamType = + ContextParamType.builder("block_pos") + .build() + + /** + * The world of a block. + * + * Autofilled by: [BLOCK_POS] + */ + val BLOCK_WORLD: ContextParamType = + ContextParamType.builder("block_world") + .autofilledBy(::BLOCK_POS) { it.world } + .build() + + /** + * The custom block type. + * + * Autofilled by: [BLOCK_TYPE] (if nova block) + */ + val BLOCK_TYPE_NOVA: ContextParamType = + ContextParamType.builder("block_type_nova") + .autofilledBy(::BLOCK_TYPE) { NovaRegistries.BLOCK[it] } + .build() + + /** + * The vanilla block type. + * + * Autofilled by: [BLOCK_TYPE] (if vanilla block) + */ + val BLOCK_TYPE_VANILLA: ContextParamType = + ContextParamType.builder("block_type_vanilla") + .require({ it.isBlock }, { "$it is not a block" }) + .autofilledBy(::BLOCK_TYPE) { BuiltInRegistries.BLOCK[it].bukkitMaterial } + .build() + + /** + * The block type as id. + * + * Autofilled by: [BLOCK_TYPE_NOVA], [BLOCK_TYPE_VANILLA], [BLOCK_ITEM_STACK] + */ + val BLOCK_TYPE: ContextParamType = + ContextParamType.builder("block_type") + .autofilledBy(::BLOCK_TYPE_NOVA) { it.id } + .autofilledBy(::BLOCK_TYPE_VANILLA) { BuiltInRegistries.BLOCK.getKey(it.nmsBlock) } + .autofilledBy(::BLOCK_ITEM_STACK) { ResourceLocation(ItemUtils.getId(it)) } + .build() + + /** + * The direction that a block has or should have. + */ + val BLOCK_FACING: ContextParamType = + ContextParamType.builder("block_facing") + .build() + + /** + * The face of a block that was clicked. + * + * Autofilled by: [SOURCE_PLAYER] + */ + val CLICKED_BLOCK_FACE: ContextParamType = + ContextParamType.builder("clicked_block_face") + .autofilledBy(::SOURCE_PLAYER) { BlockFaceUtils.determineBlockFaceLookingAt(it.eyeLocation) } + .build() + + /** + * The hand that was used to interact. + */ + val INTERACTION_HAND: ContextParamType = + ContextParamType.builder("interaction_hand") + .build() + + /** + * The item stack to be placed as a block. + * + * Autofilled by: [SOURCE_ENTITY] & [INTERACTION_HAND] + */ + val BLOCK_ITEM_STACK: ContextParamType = + ContextParamType.builder("block_item_stack") + // TODO: Validate if item stack represents block. This is currently not supported by CustomItemServices. + .autofilledBy(::SOURCE_ENTITY, ::INTERACTION_HAND) { entity, hand -> + (entity as? LivingEntity)?.equipment?.getItem(hand)?.takeUnless { it.isEmpty || !it.type.isBlock } + } + .build() + + /** + * The item stack used as a tool. + * + * Autofilled by: [SOURCE_ENTITY] & [INTERACTION_HAND] + */ + val TOOL_ITEM_STACK: ContextParamType = + ContextParamType.builder("tool_item_stack") + .autofilledBy(::SOURCE_ENTITY, ::INTERACTION_HAND) { entity, hand -> + (entity as? LivingEntity)?.equipment?.getItem(hand)?.takeUnlessEmpty() + } + .build() + + /** + * The item stack used to interact with a something. + * + * Autofilled by: [SOURCE_ENTITY] & [INTERACTION_HAND] + */ + val INTERACTION_ITEM_STACK: ContextParamType = + ContextParamType.builder("interaction_item_stack") + .autofilledBy(::SOURCE_ENTITY, ::INTERACTION_HAND) { entity, hand -> + (entity as? LivingEntity)?.equipment?.getItem(hand)?.takeUnlessEmpty() + } + .build() + + /** + * The [UUID] of the source of an action. + * + * Autofilled by: [SOURCE_ENTITY], [SOURCE_TILE_ENTITY] + */ + val SOURCE_UUID: ContextParamType = + ContextParamType.builder("source_uuid") + .autofilledBy(::SOURCE_ENTITY) { it.uniqueId } + .autofilledBy(::SOURCE_TILE_ENTITY) { it.uuid } + .build() + + /** + * The location of the source of an action. + * + * Autofilled by: [SOURCE_ENTITY], [SOURCE_TILE_ENTITY] + */ + val SOURCE_LOCATION: ContextParamType = + ContextParamType.builder("source_location") + .autofilledBy(::SOURCE_ENTITY) { it.location } + .autofilledBy(::SOURCE_TILE_ENTITY) { it.location } + .build() + + /** + * The world of the source of an action. + * + * Autofilled by: [SOURCE_LOCATION] + */ + val SOURCE_WORLD: ContextParamType = + ContextParamType.builder("source_world") + .autofilledBy(::SOURCE_LOCATION) { it.world } + .build() + + /** + * The direction that the source of an action is facing. + * + * Autofilled by: [SOURCE_ENTITY], [SOURCE_TILE_ENTITY] + */ + val SOURCE_DIRECTION: ContextParamType = + ContextParamType.builder("source_direction") + .autofilledBy(::SOURCE_ENTITY) { it.location.direction } + .autofilledBy(::SOURCE_TILE_ENTITY) { it.facing.direction } + .build() + + /** + * The player that is the source of an action. + * + * Autofilled by: [SOURCE_ENTITY] (if player) + */ + val SOURCE_PLAYER: ContextParamType = + ContextParamType.builder("source_player") + .autofilledBy(::SOURCE_ENTITY) { it as? Player } + .build() + + /** + * The entity that is the source of an action. + * + * Autofilled by: [SOURCE_PLAYER] + */ + val SOURCE_ENTITY: ContextParamType = + ContextParamType.builder("source_entity") + .autofilledBy(::SOURCE_PLAYER) { it } + .build() + + /** + * The [TileEntity] that is the source of an action. + */ + val SOURCE_TILE_ENTITY: ContextParamType = + ContextParamType.builder("source_tile_entity") + .build() + + /** + * Whether block drops should be dropped. + * + * Autofilled by: [BLOCK_POS] & [TOOL_ITEM_STACK], [BLOCK_POS] + */ + val BLOCK_DROPS: DefaultingContextParamType = + ContextParamType.builder("block_drops") + .autofilledBy(::BLOCK_POS, ::TOOL_ITEM_STACK) { pos, tool -> ToolUtils.isCorrectToolForDrops(pos.block, tool) } + .autofilledBy(::BLOCK_POS) { ToolUtils.isCorrectToolForDrops(it.block, null) } + .build(false) + + /** + * Whether block storage drops should be dropped. + * + * Autofilled by [SOURCE_PLAYER] + */ + val BLOCK_STORAGE_DROPS: DefaultingContextParamType = + ContextParamType.builder("block_storage_drops") + .autofilledBy(::SOURCE_PLAYER) { it.gameMode != GameMode.CREATIVE } + .build(true) + + /** + * Whether block place effects should be played. + */ + val BLOCK_PLACE_EFFECTS: DefaultingContextParamType = + ContextParamType.builder("block_place_effects") + .build(true) + + /** + * Whether block break effects should be played. + */ + val BLOCK_BREAK_EFFECTS: DefaultingContextParamType = + ContextParamType.builder("block_break_effects") + .build(true) + + /** + * Whether tile-entity limits should be bypassed when placing tile-entity blocks. + * + * Placed blocks will still be counted. + */ + val BYPASS_TILE_ENTITY_LIMITS: DefaultingContextParamType = + ContextParamType.builder("bypass_tile_entity_limits") + .build(false) + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/WorldDataManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/WorldDataManager.kt index 4fd01df82d6..b1aa5319c08 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/WorldDataManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/WorldDataManager.kt @@ -4,9 +4,7 @@ package xyz.xenondevs.nova.data.world import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.bukkit.Bukkit -import org.bukkit.Material import org.bukkit.World -import org.bukkit.block.BlockFace import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener @@ -14,12 +12,14 @@ import org.bukkit.event.world.ChunkLoadEvent import org.bukkit.event.world.ChunkUnloadEvent import org.bukkit.event.world.WorldSaveEvent import org.bukkit.event.world.WorldUnloadEvent -import org.bukkit.inventory.ItemStack import xyz.xenondevs.commons.collections.pollFirstWhere import xyz.xenondevs.commons.collections.removeIf import xyz.xenondevs.nova.LOGGER import xyz.xenondevs.nova.NOVA_PLUGIN import xyz.xenondevs.nova.addon.AddonsInitializer +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.block.state.BlockState import xyz.xenondevs.nova.data.world.block.state.NovaBlockState import xyz.xenondevs.nova.data.world.event.NovaChunkLoadedEvent @@ -38,7 +38,6 @@ import xyz.xenondevs.nova.util.toNovaPos import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.ChunkPos import xyz.xenondevs.nova.world.block.NovaBlock -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import xyz.xenondevs.nova.world.pos import java.util.* import java.util.concurrent.ConcurrentLinkedQueue @@ -245,7 +244,11 @@ object WorldDataManager : Listener { } private fun placeOrphanBlock(pos: BlockPos, material: NovaBlock) { - val ctx = BlockPlaceContext(pos, material.item?.createItemStack(0) ?: ItemStack(Material.AIR), null, null, null, pos.below, BlockFace.UP) + val ctx = Context.intention(ContextIntentions.BlockPlace) + .param(ContextParamTypes.BLOCK_POS, pos) + .param(ContextParamTypes.BLOCK_TYPE_NOVA, material) + .build() + val state = material.createNewBlockState(ctx) setBlockState(pos, state) state.handleInitialized(true) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/BlockProperty.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/BlockProperty.kt index 6a4454c5fed..7e71a9403a1 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/BlockProperty.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/BlockProperty.kt @@ -1,14 +1,15 @@ package xyz.xenondevs.nova.data.world.block.property import xyz.xenondevs.cbf.Compound -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace interface BlockProperty { /** * Initializes this [BlockProperty] when the block is being placed */ - fun init(ctx: BlockPlaceContext) + fun init(ctx: Context) /** * Reads the values of this [BlockProperty] from the given [Compound] diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/Directional.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/Directional.kt index f82f7fbc185..38572cc31c0 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/Directional.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/property/Directional.kt @@ -1,17 +1,32 @@ package xyz.xenondevs.nova.data.world.block.property -import org.bukkit.Location import org.bukkit.block.BlockFace import xyz.xenondevs.cbf.Compound +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.util.BlockFaceUtils -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext +import xyz.xenondevs.nova.util.calculateYawPitch class Directional(private val upDown: Boolean = false) : BlockProperty { lateinit var facing: BlockFace - override fun init(ctx: BlockPlaceContext) { - facing = ctx.sourceLocation?.let(::getFacing) ?: BlockFace.NORTH + override fun init(ctx: Context) { + val blockFacing: BlockFace? = ctx[ContextParamTypes.BLOCK_FACING] + if (blockFacing != null) { + this.facing = blockFacing + return + } + + val sourceDirection = ctx[ContextParamTypes.SOURCE_DIRECTION] + if (sourceDirection != null) { + val (yaw, pitch) = sourceDirection.calculateYawPitch() + this.facing = getFacing(yaw, pitch) + return + } + + this.facing = BlockFace.NORTH } override fun read(compound: Compound) { @@ -22,15 +37,15 @@ class Directional(private val upDown: Boolean = false) : BlockProperty { compound["facing"] = facing } - private fun getFacing(loc: Location): BlockFace { + private fun getFacing(yaw: Float, pitch: Float): BlockFace { if (upDown) { - if (loc.pitch < -45) + if (pitch < -45) return BlockFace.DOWN - if (loc.pitch > 45) + if (pitch > 45) return BlockFace.UP } - return BlockFaceUtils.getDirection(loc.yaw + 180) + return BlockFaceUtils.getDirection(yaw + 180) } companion object { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaBlockState.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaBlockState.kt index ed18e144588..835f0ed0a82 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaBlockState.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaBlockState.kt @@ -4,14 +4,16 @@ import org.bukkit.Location import xyz.xenondevs.cbf.CBF import xyz.xenondevs.cbf.Compound import xyz.xenondevs.cbf.io.ByteBuffer +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.WorldDataManager import xyz.xenondevs.nova.data.world.block.property.BlockProperty import xyz.xenondevs.nova.data.world.block.property.BlockPropertyType import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.block.BlockManager import xyz.xenondevs.nova.world.block.NovaBlock -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import kotlin.reflect.KClass import kotlin.reflect.full.superclasses @@ -30,7 +32,7 @@ open class NovaBlockState internal constructor(override val pos: BlockPos, block final override var isLoaded = false private set - internal constructor(material: NovaBlock, ctx: BlockPlaceContext) : this(ctx.pos, material) { + internal constructor(material: NovaBlock, ctx: Context) : this(ctx[ContextParamTypes.BLOCK_POS]!!, material) { properties.values.forEach { it.init(ctx) } } @@ -54,7 +56,13 @@ open class NovaBlockState internal constructor(override val pos: BlockPos, block isLoaded = false if (broken) { - block.multiBlockLoader?.invoke(pos)?.forEach { BlockManager.removeLinkedBlockState(BlockBreakContext(it), breakEffects = true) } + block.multiBlockLoader?.invoke(pos)?.forEach { + val ctx = Context.intention(ContextIntentions.BlockBreak) + .param(ContextParamTypes.BLOCK_POS, it) + .build() + + BlockManager.removeLinkedBlockState(ctx, breakEffects = true) + } } modelProvider.remove(broken) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaTileEntityState.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaTileEntityState.kt index 2b5b9e52f52..f51d644ed18 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaTileEntityState.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/world/block/state/NovaTileEntityState.kt @@ -1,15 +1,18 @@ package xyz.xenondevs.nova.data.world.block.state +import org.bukkit.inventory.ItemStack import xyz.xenondevs.cbf.CBF import xyz.xenondevs.cbf.Compound import xyz.xenondevs.cbf.io.ByteBuffer +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.tileentity.TileEntity import xyz.xenondevs.nova.tileentity.TileEntityManager import xyz.xenondevs.nova.util.UUIDUtils import xyz.xenondevs.nova.util.item.novaCompoundOrNull import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.block.NovaTileEntityBlock -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import java.util.* class NovaTileEntityState : NovaBlockState { @@ -37,14 +40,14 @@ class NovaTileEntityState : NovaBlockState { this.block = material } - internal constructor(material: NovaTileEntityBlock, ctx: BlockPlaceContext) : super(material, ctx) { + internal constructor(material: NovaTileEntityBlock, ctx: Context) : super(material, ctx) { this.block = material this.uuid = UUID.randomUUID() - this.ownerUUID = ctx.ownerUUID + this.ownerUUID = ctx[ContextParamTypes.SOURCE_UUID] this.data = Compound() - val item = ctx.item - val globalData = item.novaCompoundOrNull?.get(TileEntity.TILE_ENTITY_DATA_KEY) + val itemStack: ItemStack? = ctx[ContextParamTypes.BLOCK_ITEM_STACK] + val globalData = itemStack?.novaCompoundOrNull?.get(TileEntity.TILE_ENTITY_DATA_KEY) if (globalData != null) { data["global"] = globalData } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt index a3bb11c0b59..0b1f0a164c4 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt @@ -15,7 +15,7 @@ import org.bukkit.entity.Player import org.bukkit.event.block.Action import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.player.WrappedPlayerInteractEvent -import xyz.xenondevs.nova.util.interactionHand +import xyz.xenondevs.nova.util.nmsInteractionHand import xyz.xenondevs.nova.util.nmsState import xyz.xenondevs.nova.util.runTaskLater import xyz.xenondevs.nova.util.serverLevel @@ -62,7 +62,7 @@ object Stripping : ItemBehavior { val block = event.clickedBlock!! event.isCancelled = stripBlock( player.serverPlayer, - event.hand!!.interactionHand, + event.hand!!.nmsInteractionHand, block.nmsState, block.world.serverLevel, block.pos.nmsPos diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt index c6588771fd6..ea9ead92c48 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt @@ -15,8 +15,8 @@ import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.player.WrappedPlayerInteractEvent import xyz.xenondevs.nova.util.above -import xyz.xenondevs.nova.util.interactionHand import xyz.xenondevs.nova.util.nmsDirection +import xyz.xenondevs.nova.util.nmsInteractionHand import xyz.xenondevs.nova.util.nmsState import xyz.xenondevs.nova.util.runTaskLater import xyz.xenondevs.nova.util.serverLevel @@ -51,7 +51,7 @@ object Tilling : ItemBehavior { val block = event.clickedBlock!! val level = block.world.serverLevel val pos = block.pos.nmsPos - val interactionHand = event.hand!!.interactionHand + val interactionHand = event.hand!!.nmsInteractionHand val (check, newState, drops) = TILLABLES[block.nmsState.block] ?: return if (check.invoke(event)) { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/NetworkedTileEntity.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/NetworkedTileEntity.kt index 5f0cc05e5ff..db1e6570c73 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/NetworkedTileEntity.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/NetworkedTileEntity.kt @@ -4,10 +4,14 @@ import org.bukkit.GameMode import org.bukkit.Material import org.bukkit.Sound import org.bukkit.block.BlockFace +import org.bukkit.entity.Entity import org.bukkit.entity.Player import org.bukkit.inventory.EquipmentSlot import org.bukkit.inventory.ItemStack import xyz.xenondevs.commons.reflection.getRuntimeDelegate +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockInteract +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.block.state.NovaTileEntityState import xyz.xenondevs.nova.tileentity.network.DefaultNetworkTypes import xyz.xenondevs.nova.tileentity.network.EndPointDataHolder @@ -24,7 +28,6 @@ import xyz.xenondevs.nova.tileentity.network.fluid.holder.NovaFluidHolder import xyz.xenondevs.nova.tileentity.network.item.ItemFilter import xyz.xenondevs.nova.tileentity.network.item.holder.ItemHolder import xyz.xenondevs.nova.util.BlockFaceUtils -import xyz.xenondevs.nova.world.block.context.BlockInteractContext import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -61,13 +64,17 @@ abstract class NetworkedTileEntity(blockState: NovaTileEntityState) : TileEntity serializeConnectedNodes() } - final override fun handleRightClick(ctx: BlockInteractContext): Boolean { - val item = ctx.item + final override fun handleRightClick(ctx: Context): Boolean { + val itemStack: ItemStack? = ctx[ContextParamTypes.INTERACTION_ITEM_STACK] + val sourceEntity: Entity? = ctx[ContextParamTypes.SOURCE_ENTITY] + val interactionHand: EquipmentSlot? = ctx[ContextParamTypes.INTERACTION_HAND] + val holder = holders[DefaultNetworkTypes.FLUID] - if (holder is NovaFluidHolder && ctx.source is Player && ctx.hand != null) { - val success = when (item?.type) { - Material.BUCKET -> fillBucket(holder, ctx.source, ctx.hand) - Material.WATER_BUCKET, Material.LAVA_BUCKET -> emptyBucket(holder, ctx.source, ctx.hand) + + if (holder is NovaFluidHolder && sourceEntity is Player && interactionHand != null) { + val success = when (itemStack?.type) { + Material.BUCKET -> fillBucket(holder, sourceEntity, interactionHand) + Material.WATER_BUCKET, Material.LAVA_BUCKET -> emptyBucket(holder, sourceEntity, interactionHand) else -> false } @@ -128,7 +135,7 @@ abstract class NetworkedTileEntity(blockState: NovaTileEntityState) : TileEntity return false } - open fun handleUnknownRightClick(ctx: BlockInteractContext): Boolean { + open fun handleUnknownRightClick(ctx: Context): Boolean { return super.handleRightClick(ctx) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/TileEntity.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/TileEntity.kt index 14816cffbaa..f3c30471539 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/TileEntity.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/tileentity/TileEntity.kt @@ -23,6 +23,9 @@ import xyz.xenondevs.invui.inventory.event.UpdateReason import xyz.xenondevs.invui.window.Window import xyz.xenondevs.invui.window.type.context.setTitle import xyz.xenondevs.nova.data.config.Reloadable +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockInteract +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.serialization.DataHolder import xyz.xenondevs.nova.data.world.block.property.Directional import xyz.xenondevs.nova.data.world.block.state.NovaTileEntityState @@ -49,7 +52,6 @@ import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.ChunkPos import xyz.xenondevs.nova.world.block.NovaTileEntityBlock import xyz.xenondevs.nova.world.block.TileEntityBlockBehavior -import xyz.xenondevs.nova.world.block.context.BlockInteractContext import xyz.xenondevs.nova.world.fakeentity.FakeEntityManager import xyz.xenondevs.nova.world.region.DynamicRegion import xyz.xenondevs.nova.world.region.Region @@ -229,12 +231,15 @@ abstract class TileEntity(val blockState: NovaTileEntityState) : DataHolder(true * * @return If any action was performed. */ - open fun handleRightClick(ctx: BlockInteractContext): Boolean { - val player = ctx.source as? Player ?: return false + open fun handleRightClick(ctx: Context): Boolean { + val player = ctx[ContextParamTypes.SOURCE_ENTITY] as? Player + ?: return false + if (::menuContainer.isInitialized && !player.hasInventoryOpen) { menuContainer.openWindow(player) return true } + return false } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt index 8c305f7603d..5a212fd7726 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/BlockUtils.kt @@ -16,7 +16,6 @@ import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.item.BlockItem import net.minecraft.world.item.context.UseOnContext import net.minecraft.world.item.crafting.AbstractCookingRecipe -import net.minecraft.world.item.crafting.RecipeHolder import net.minecraft.world.level.block.DoorBlock import net.minecraft.world.level.block.TallFlowerBlock import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity @@ -44,21 +43,22 @@ import org.bukkit.event.block.BlockExpEvent import org.bukkit.inventory.ItemStack import xyz.xenondevs.nmsutils.particle.block import xyz.xenondevs.nmsutils.particle.particle +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.block.state.NovaBlockState import xyz.xenondevs.nova.integration.customitems.CustomItemServiceManager import xyz.xenondevs.nova.tileentity.network.fluid.FluidType import xyz.xenondevs.nova.util.data.getOrNull -import xyz.xenondevs.nova.util.item.ToolUtils -import xyz.xenondevs.nova.util.item.novaItem import xyz.xenondevs.nova.util.item.playPlaceSoundEffect import xyz.xenondevs.nova.util.item.soundGroup import xyz.xenondevs.nova.util.item.takeUnlessEmpty import xyz.xenondevs.nova.util.reflection.ReflectionRegistry +import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.block.BlockManager import xyz.xenondevs.nova.world.block.NovaBlock import xyz.xenondevs.nova.world.block.NovaTileEntityBlock -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import xyz.xenondevs.nova.world.block.limits.TileEntityLimits import xyz.xenondevs.nova.world.block.logic.`break`.BlockBreaking import xyz.xenondevs.nova.world.block.sound.SoundGroup @@ -188,23 +188,26 @@ val Block.sourceFluidType: FluidType? * @param playSound If the block placing sound should be played * @return If a block has been placed */ -fun Block.place(ctx: BlockPlaceContext, playSound: Boolean = true): Boolean { - val item = ctx.item - val novaBlock = item.novaItem?.block +fun Block.place(ctx: Context): Boolean { + val novaBlock: NovaBlock? = ctx[ContextParamTypes.BLOCK_TYPE_NOVA] if (novaBlock != null) { if (novaBlock is NovaTileEntityBlock && !TileEntityLimits.canPlace(ctx).allowed) return false - BlockManager.placeBlockState(novaBlock, ctx, playSound) + BlockManager.placeBlockState(novaBlock, ctx) return true } - if (CustomItemServiceManager.placeBlock(item, ctx.pos.location, playSound)) - return true - - if (item.type.isBlock) { - val fakePlayer = EntityUtils.createFakePlayer(ctx.sourceLocation ?: location, UUID.randomUUID(), "") - return placeVanilla(fakePlayer, item) + // TODO: place block by blockstate / id + val itemStack: ItemStack? = ctx[ContextParamTypes.BLOCK_ITEM_STACK] + if (itemStack != null) { + if (CustomItemServiceManager.placeBlock(itemStack, ctx[ContextParamTypes.BLOCK_POS]!!.location, ctx[ContextParamTypes.BLOCK_PLACE_EFFECTS])) + return true + + if (itemStack.type.isBlock) { + val fakePlayer = EntityUtils.createFakePlayer(ctx[ContextParamTypes.SOURCE_LOCATION] ?: location, UUID.randomUUID(), "") + return placeVanilla(fakePlayer, itemStack) + } } return false @@ -218,7 +221,7 @@ fun Block.place(ctx: BlockPlaceContext, playSound: Boolean = true): Boolean { * @param playSound If the block placing sound should be played * @return If the item could be placed */ -fun Block.placeVanilla(player: ServerPlayer, itemStack: ItemStack, playSound: Boolean = true): Boolean { +internal fun Block.placeVanilla(player: ServerPlayer, itemStack: ItemStack, playSound: Boolean = true): Boolean { val location = location val nmsStack = itemStack.nmsCopy val blockItem = nmsStack.item as BlockItem @@ -250,7 +253,7 @@ fun Block.placeVanilla(player: ServerPlayer, itemStack: ItemStack, playSound: Bo * * @param itemStack The [ItemStack] to load the data from */ -fun Block.setBlockEntityDataFromItemStack(itemStack: ItemStack) { +private fun Block.setBlockEntityDataFromItemStack(itemStack: ItemStack) { val itemTag = CompoundTag() ReflectionRegistry.CB_CRAFT_META_APPLY_TO_ITEM_METHOD.invoke(itemStack.itemMeta, itemTag) @@ -264,28 +267,17 @@ fun Block.setBlockEntityDataFromItemStack(itemStack: ItemStack) { /** * Checks if a block is blocked by the hitbox of an entity. */ -fun Block.isUnobstructed(material: Material, player: Player? = null): Boolean { +fun Block.isUnobstructed(player: Entity? = null, blockType: Material = this.type): Boolean { + require(blockType.isBlock) { "Material must be a block" } val level = world.serverLevel val context = player?.let { CollisionContext.of(it.nmsEntity) } ?: CollisionContext.empty() - return level.isUnobstructed(material.nmsBlock.defaultBlockState(), pos.nmsPos, context) + return level.isUnobstructed(blockType.nmsBlock.defaultBlockState(), pos.nmsPos, context) } // endregion // region block breaking -/** - * Removes this block using the given [ctx]. - * - * This method works for vanilla blocks, blocks from Nova and blocks from custom item integrations. - * - * @param ctx The [BlockBreakContext] to be used - * @param playSound If block breaking sounds should be played - * @param showParticles If block break particles should be displayed - */ -@Deprecated("Break sound and particles are not independent from one another", ReplaceWith("remove(ctx, showParticles || playSound)")) -fun Block.remove(ctx: BlockBreakContext, playSound: Boolean, showParticles: Boolean) = remove(ctx, showParticles || playSound) - /** * Breaks this block naturally using the given [ctx]. * @@ -294,19 +286,12 @@ fun Block.remove(ctx: BlockBreakContext, playSound: Boolean, showParticles: Bool * If the source is a player, it will be as if the player broke the block. * The tool item stack will not be damaged. * - * @param ctx The [BlockBreakContext] to be used + * @param ctx The [Context] to be used */ -fun Block.breakNaturally(ctx: BlockBreakContext) { +fun Block.breakNaturally(ctx: Context) { val state = state - - val itemEntities = removeInternal( - ctx, - ToolUtils.isCorrectToolForDrops(this, ctx.item), - breakEffects = true, - sendEffectsToBreaker = true - ) - - val player = ctx.source as? Player ?: return + val itemEntities = removeInternal(ctx, sendEffectsToBreaker = true) + val player = ctx[ContextParamTypes.SOURCE_ENTITY] as? Player ?: return CraftEventFactory.handleBlockDropItemEvent(this, state, player.serverPlayer, itemEntities) } @@ -315,35 +300,29 @@ fun Block.breakNaturally(ctx: BlockBreakContext) { * * This method works for vanilla blocks, blocks from Nova and blocks from custom item services. * - * @param ctx The [BlockBreakContext] to be used - * @param breakEffects If break effects should be displayed (i.e. sounds and particle effects). + * @param ctx The [Context] to be used */ -fun Block.remove(ctx: BlockBreakContext, breakEffects: Boolean = true): List { - return removeInternal( - ctx, - ToolUtils.isCorrectToolForDrops(this, ctx.item), - breakEffects, - true - ).map { it.item.bukkitMirror } +fun Block.remove(ctx: Context): List { + return removeInternal(ctx, true).map { it.item.bukkitMirror } } -internal fun Block.removeInternal(ctx: BlockBreakContext, drops: Boolean, breakEffects: Boolean, sendEffectsToBreaker: Boolean): List { +internal fun Block.removeInternal(ctx: Context, sendEffectsToBreaker: Boolean): List { + val breakEffects = ctx[ContextParamTypes.BLOCK_BREAK_EFFECTS] + val drops = ctx[ContextParamTypes.BLOCK_DROPS] + if (CustomItemServiceManager.getId(this) != null) { - val itemEntities = CustomItemServiceManager.getDrops(this, ctx.item)!!.let(::createDroppedItemEntities) + val itemEntities = CustomItemServiceManager.getDrops(this, ctx[ContextParamTypes.TOOL_ITEM_STACK])!!.let(::createDroppedItemEntities) CustomItemServiceManager.removeBlock(this, breakEffects) return itemEntities } if (BlockManager.getBlockState(pos) != null) { val itemEntities = if (drops) BlockManager.getDrops(ctx)!!.let(::createDroppedItemEntities) else emptyList() - BlockManager.removeBlockStateInternal(ctx, breakEffects, sendEffectsToBreaker) + BlockManager.removeBlockStateInternal(ctx, sendEffectsToBreaker) return itemEntities } - val nmsPlayer = (ctx.source as? Player)?.serverPlayer - ?: ctx.source as? MojangPlayer - ?: EntityUtils.DUMMY_PLAYER - + val nmsPlayer: ServerPlayer = ctx[ContextParamTypes.SOURCE_ENTITY]?.nmsEntity as? ServerPlayer ?: EntityUtils.DUMMY_PLAYER val level = world.serverLevel val pos = pos.nmsPos val state = nmsState @@ -365,8 +344,8 @@ internal fun Block.removeInternal(ctx: BlockBreakContext, drops: Boolean, breakE if (removed) { block.destroy(level, pos, state) - if (!nmsPlayer.isCreative && drops) { - block.playerDestroy(level, nmsPlayer, pos, state, blockEntity, ctx.item.nmsCopy) + if (ctx[ContextParamTypes.BLOCK_DROPS]) { + block.playerDestroy(level, nmsPlayer, pos, state, blockEntity, ctx[ContextParamTypes.TOOL_ITEM_STACK].nmsCopy) } } } @@ -389,8 +368,8 @@ internal fun Block.createDroppedItemEntities(items: Iterable): List { - val tool = ctx.item +fun Block.getAllDrops(ctx: Context): List { + val tool: ItemStack? = ctx[ContextParamTypes.TOOL_ITEM_STACK] CustomItemServiceManager.getDrops(this, tool)?.let { return it } val novaBlockState = BlockManager.getBlockState(pos) @@ -417,10 +396,11 @@ fun Block.getAllDrops(ctx: BlockBreakContext): List { } // don't include the actual block for creative players - if (ctx.source !is Player || ctx.source.gameMode != GameMode.CREATIVE) { + val sourceEntity: Entity? = ctx[ContextParamTypes.SOURCE_ENTITY] + if (sourceEntity !is Player || sourceEntity.gameMode != GameMode.CREATIVE) { val block = getMainHalf() - drops += if (tool != null && ctx.source is Entity) - block.getDrops(tool, ctx.source) + drops += if (tool != null && sourceEntity != null) + block.getDrops(tool, sourceEntity) else block.getDrops(tool) } @@ -449,15 +429,17 @@ private fun Block.getMainHalf(): Block { /** * Gets the experience that would be dropped if the block were to be broken. */ -fun Block.getExp(ctx: BlockBreakContext): Int { - val novaState = BlockManager.getBlockState(ctx.pos) +fun Block.getExp(ctx: Context): Int { + val pos: BlockPos = ctx[ContextParamTypes.BLOCK_POS]!! + val novaState = BlockManager.getBlockState(pos) if (novaState != null) return novaState.block.logic.getExp(novaState, ctx) - val serverLevel = ctx.pos.world.serverLevel - val mojangPos = ctx.pos.nmsPos + val serverLevel = pos.world.serverLevel + val mojangPos = pos.nmsPos - var exp = BlockUtils.getVanillaBlockExp(serverLevel, mojangPos, ctx.item.nmsCopy) + val toolItemStack = ctx[ContextParamTypes.TOOL_ITEM_STACK].nmsCopy + var exp = BlockUtils.getVanillaBlockExp(serverLevel, mojangPos, toolItemStack) // the furnace is the only block entity that can drop exp (I think) val furnace = serverLevel.getBlockEntity(mojangPos) as? AbstractFurnaceBlockEntity @@ -592,7 +574,7 @@ object BlockUtils { } internal fun getVanillaBlockExp(level: ServerLevel, pos: MojangBlockPos, tool: MojangStack): Int { - val blockState = level.getBlockState(pos) ?: return 0 + val blockState = level.getBlockState(pos) val block = blockState.block return block.getExpDrop(blockState, level, pos, tool, true) } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/NMSUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/NMSUtils.kt index 6bdee9fe579..f1f1fd31f1b 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/NMSUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/NMSUtils.kt @@ -80,10 +80,6 @@ val Entity.nmsEntity: MojangEntity val Player.serverPlayer: ServerPlayer get() = (this as CraftPlayer).handle -@Deprecated("Misleading name", replaceWith = ReplaceWith("nmsCopy")) -val ItemStack.nmsStack: MojangStack - get() = CraftItemStack.asNMSCopy(this) - val ItemStack?.nmsCopy: MojangStack get() = CraftItemStack.asNMSCopy(this) @@ -100,10 +96,6 @@ val ItemStack?.nmsVersion: MojangStack return itemStack ?: CraftItemStack.asNMSCopy(this) } -@Deprecated("Misleading name", replaceWith = ReplaceWith("bukkitCopy")) -val MojangStack.bukkitStack: ItemStack - get() = CraftItemStack.asBukkitCopy(this) - val MojangStack.bukkitCopy: ItemStack get() = CraftItemStack.asBukkitCopy(this) @@ -144,23 +136,11 @@ val ResourceLocation.namespacedId: NamespacedId internal val ResourceLocation.name: String get() = path -val InteractionHand.bukkitEquipmentSlot: EquipmentSlot - get() = when (this) { - InteractionHand.MAIN_HAND -> EquipmentSlot.HAND - InteractionHand.OFF_HAND -> EquipmentSlot.OFF_HAND - } - -val EquipmentSlot.interactionHand: InteractionHand +val EquipmentSlot.nmsInteractionHand: InteractionHand get() = when (this) { EquipmentSlot.HAND -> InteractionHand.MAIN_HAND EquipmentSlot.OFF_HAND -> InteractionHand.OFF_HAND - else -> throw UnsupportedOperationException() - } - -val InteractionHand.equipmentSlot: EquipmentSlot - get() = when (this) { - InteractionHand.MAIN_HAND -> EquipmentSlot.HAND - InteractionHand.OFF_HAND -> EquipmentSlot.OFF_HAND + else -> throw UnsupportedOperationException("Not a hand: $this") } val EquipmentSlot.nmsEquipmentSlot: MojangEquipmentSlot @@ -183,6 +163,25 @@ val MojangEquipmentSlot.bukkitEquipmentSlot: EquipmentSlot MojangEquipmentSlot.HEAD -> EquipmentSlot.HEAD } +val MojangEquipmentSlot.nmsInteractionHand: InteractionHand + get() = when(this) { + MojangEquipmentSlot.MAINHAND -> InteractionHand.MAIN_HAND + MojangEquipmentSlot.OFFHAND -> InteractionHand.OFF_HAND + else -> throw UnsupportedOperationException("Not a hand: $this") + } + +val InteractionHand.bukkitEquipmentSlot: EquipmentSlot + get() = when (this) { + InteractionHand.MAIN_HAND -> EquipmentSlot.HAND + InteractionHand.OFF_HAND -> EquipmentSlot.OFF_HAND + } + +val InteractionHand.nmsEquipmentSlot: MojangEquipmentSlot + get() = when (this) { + InteractionHand.MAIN_HAND -> MojangEquipmentSlot.MAINHAND + InteractionHand.OFF_HAND -> MojangEquipmentSlot.OFFHAND + } + val BlockFace.nmsDirection: Direction get() = when (this) { BlockFace.NORTH -> Direction.NORTH diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/PlantUtils.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/PlantUtils.kt index 21048502bba..694d81b5f54 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/PlantUtils.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/item/PlantUtils.kt @@ -14,6 +14,9 @@ import org.bukkit.block.data.Ageable import org.bukkit.block.data.type.CaveVinesPlant import org.bukkit.inventory.ItemStack import xyz.xenondevs.commons.collections.enumMapOf +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.integration.customitems.CustomBlockType import xyz.xenondevs.nova.integration.customitems.CustomItemServiceManager import xyz.xenondevs.nova.integration.customitems.CustomItemType @@ -24,7 +27,6 @@ import xyz.xenondevs.nova.util.getAllDrops import xyz.xenondevs.nova.util.hasSameTypeBelow import xyz.xenondevs.nova.util.remove import xyz.xenondevs.nova.util.serverLevel -import xyz.xenondevs.nova.world.block.context.BlockBreakContext import kotlin.random.Random import net.minecraft.world.item.ItemStack as MojangStack @@ -188,18 +190,18 @@ object PlantUtils { && HARVESTABLE_PLANTS[block.type]?.first?.invoke(block) ?: true } - fun harvest(ctx: BlockBreakContext, playEffects: Boolean) { - val block = ctx.pos.block + fun harvest(ctx: Context) { + val block = ctx[ContextParamTypes.BLOCK_POS]!!.block val type = block.type if (block.type !in HARVESTABLE_PLANTS) return val harvestFunction = HARVESTABLE_PLANTS[type]?.second if (harvestFunction != null) harvestFunction(block, true) - else block.remove(ctx, playEffects) + else block.remove(ctx) } - fun getHarvestDrops(ctx: BlockBreakContext): List? { - val block = ctx.pos.block + fun getHarvestDrops(ctx: Context): List? { + val block = ctx[ContextParamTypes.BLOCK_POS]!!.block val type = block.type if (block.type !in HARVESTABLE_PLANTS) return null diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockBehavior.kt index 331e13fccde..9ea634c95a2 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockBehavior.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockBehavior.kt @@ -3,28 +3,30 @@ package xyz.xenondevs.nova.world.block import org.bukkit.GameMode import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockInteract +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.block.state.NovaBlockState -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockInteractContext -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext interface BlockBehavior { - fun handleInteract(state: T, ctx: BlockInteractContext): Boolean - fun handlePlace(state: T, ctx: BlockPlaceContext) - fun handleBreak(state: T, ctx: BlockBreakContext) - fun getDrops(state: T, ctx: BlockBreakContext): List - fun getExp(state: T, ctx: BlockBreakContext): Int + fun handleInteract(state: T, ctx: Context): Boolean + fun handlePlace(state: T, ctx: Context) + fun handleBreak(state: T, ctx: Context) + fun getDrops(state: T, ctx: Context): List + fun getExp(state: T, ctx: Context): Int open class Default protected constructor() : BlockBehavior { - override fun handleInteract(state: T, ctx: BlockInteractContext) = false - override fun handlePlace(state: T, ctx: BlockPlaceContext) = Unit - override fun handleBreak(state: T, ctx: BlockBreakContext) = Unit - override fun getExp(state: T, ctx: BlockBreakContext) = 0 + override fun handleInteract(state: T, ctx: Context): Boolean = false + override fun handlePlace(state: T, ctx: Context) = Unit + override fun handleBreak(state: T, ctx: Context) = Unit + override fun getExp(state: T, ctx: Context): Int = 0 - override fun getDrops(state: T, ctx: BlockBreakContext): List { - if (ctx.source is Player && ctx.source.gameMode == GameMode.CREATIVE) + override fun getDrops(state: T, ctx: Context): List { + if ((ctx[ContextParamTypes.SOURCE_ENTITY] as? Player)?.gameMode == GameMode.CREATIVE) return emptyList() val item = state.block.item ?: return emptyList() diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockLogic.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockLogic.kt index 83f08bf3d5e..b95817f53ec 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockLogic.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockLogic.kt @@ -1,32 +1,33 @@ package xyz.xenondevs.nova.world.block import org.bukkit.inventory.ItemStack +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockInteract +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace import xyz.xenondevs.nova.data.world.block.state.NovaBlockState -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockInteractContext -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext internal class BlockLogic(private val behaviors: List>) { - fun handleInteract(state: T, ctx: BlockInteractContext): Boolean { + fun handleInteract(state: T, ctx: Context): Boolean { var actionPerformed = false behaviors.forEach { actionPerformed = it.handleInteract(state, ctx) || actionPerformed } return actionPerformed } - fun handlePlace(state: T, ctx: BlockPlaceContext) { + fun handlePlace(state: T, ctx: Context) { behaviors.forEach { it.handlePlace(state, ctx) } } - fun handleBreak(state: T, ctx: BlockBreakContext) { + fun handleBreak(state: T, ctx: Context) { behaviors.forEach { it.handleBreak(state, ctx) } } - fun getDrops(state: T, ctx: BlockBreakContext): List { + fun getDrops(state: T, ctx: Context): List { return behaviors.flatMap { it.getDrops(state, ctx) } } - fun getExp(state: T, ctx: BlockBreakContext): Int { + fun getExp(state: T, ctx: Context): Int { return behaviors.sumOf { it.getExp(state, ctx) } } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockManager.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockManager.kt index 5827a64240c..3be917d87ea 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockManager.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/BlockManager.kt @@ -10,14 +10,18 @@ import net.minecraft.sounds.SoundSource import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.addon.AddonsInitializer +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.resources.model.data.BlockStateBlockModelData import xyz.xenondevs.nova.data.world.WorldDataManager import xyz.xenondevs.nova.data.world.block.state.LinkedBlockState import xyz.xenondevs.nova.data.world.block.state.NovaBlockState import xyz.xenondevs.nova.data.world.block.state.NovaTileEntityState import xyz.xenondevs.nova.initialize.InitFun -import xyz.xenondevs.nova.initialize.InternalInitStage import xyz.xenondevs.nova.initialize.InternalInit +import xyz.xenondevs.nova.initialize.InternalInitStage import xyz.xenondevs.nova.util.MINECRAFT_SERVER import xyz.xenondevs.nova.util.dropItems import xyz.xenondevs.nova.util.getBreakParticlesPacket @@ -27,8 +31,6 @@ import xyz.xenondevs.nova.util.item.soundGroup import xyz.xenondevs.nova.util.serverLevel import xyz.xenondevs.nova.util.serverPlayer import xyz.xenondevs.nova.world.BlockPos -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import xyz.xenondevs.nova.world.block.limits.TileEntityTracker import xyz.xenondevs.nova.world.block.logic.`break`.BlockBreaking import xyz.xenondevs.nova.world.block.logic.interact.BlockInteracting @@ -67,31 +69,31 @@ object BlockManager { return getBlockState(pos, useLinkedStates) != null } - fun placeBlockState(material: NovaBlock, ctx: BlockPlaceContext, playSound: Boolean = true) { + fun placeBlockState(material: NovaBlock, ctx: Context) { val state = material.createNewBlockState(ctx) - WorldDataManager.setBlockState(ctx.pos, state) + WorldDataManager.setBlockState(ctx[ContextParamTypes.BLOCK_POS]!!, state) state.handleInitialized(true) material.logic.handlePlace(state, ctx) - if (playSound) + if (ctx[ContextParamTypes.BLOCK_PLACE_EFFECTS]) playPlaceSound(state, ctx) if (state is NovaTileEntityState) TileEntityTracker.handleBlockPlace(state.block, ctx) } - fun removeBlockState(ctx: BlockBreakContext, breakEffects: Boolean = true): Boolean = - removeBlockStateInternal(ctx, breakEffects, true) + fun removeBlockState(ctx: Context): Boolean = + removeBlockStateInternal(ctx, true) - internal fun removeBlockStateInternal(ctx: BlockBreakContext, breakEffects: Boolean, sendEffectsToBreaker: Boolean): Boolean { - val pos = ctx.pos + internal fun removeBlockStateInternal(ctx: Context, sendEffectsToBreaker: Boolean): Boolean { + val pos: BlockPos = ctx[ContextParamTypes.BLOCK_POS]!! val state = getBlockState(pos) ?: return false if (state is NovaTileEntityState) TileEntityTracker.handleBlockBreak(state.tileEntity, ctx) - if (breakEffects) { + if (ctx[ContextParamTypes.BLOCK_BREAK_EFFECTS]) { playBreakEffects(state, ctx, pos, sendEffectsToBreaker) } @@ -104,8 +106,8 @@ object BlockManager { return true } - internal fun removeLinkedBlockState(ctx: BlockBreakContext, breakEffects: Boolean): Boolean { - val pos = ctx.pos + internal fun removeLinkedBlockState(ctx: Context, breakEffects: Boolean): Boolean { + val pos: BlockPos = ctx[ContextParamTypes.BLOCK_POS]!! val state = WorldDataManager.getBlockState(pos, takeUnloaded = true) as? LinkedBlockState ?: return false @@ -119,20 +121,21 @@ object BlockManager { return true } - fun getDrops(ctx: BlockBreakContext): List? { - val state = getBlockState(ctx.pos) ?: return null + fun getDrops(ctx: Context): List? { + val state = getBlockState(ctx[ContextParamTypes.BLOCK_POS]!!) ?: return null return state.block.logic.getDrops(state, ctx) } - fun breakBlockState(ctx: BlockBreakContext, breakEffects: Boolean = true): Boolean { - if (!removeBlockState(ctx, breakEffects)) return false - getDrops(ctx)?.let { ctx.pos.location.add(0.5, 0.5, 0.5).dropItems(it) } + fun breakBlockState(ctx: Context): Boolean { + if (!removeBlockState(ctx)) return false + val pos: BlockPos = ctx[ContextParamTypes.BLOCK_POS]!! + getDrops(ctx)?.let { pos.location.add(0.5, 0.5, 0.5).dropItems(it) } return true } - private fun playBreakEffects(state: NovaBlockState, ctx: BlockBreakContext, pos: BlockPos, sendEffectsToBreaker: Boolean) { - val player = ctx.source as? Player + private fun playBreakEffects(state: NovaBlockState, ctx: Context, pos: BlockPos, sendEffectsToBreaker: Boolean) { + val player = ctx[ContextParamTypes.SOURCE_ENTITY] as? Player val material = state.block val level = pos.world.serverLevel val dimension = level.dimension() @@ -180,21 +183,11 @@ object BlockManager { } } - private fun playPlaceSound(state: NovaBlockState, ctx: BlockPlaceContext) { + private fun playPlaceSound(state: NovaBlockState, ctx: Context) { val soundGroup = state.block.options.soundGroup if (soundGroup != null) { - ctx.pos.playSound(soundGroup.placeSound, soundGroup.placeVolume, soundGroup.placePitch) + ctx[ContextParamTypes.BLOCK_POS]!!.playSound(soundGroup.placeSound, soundGroup.placeVolume, soundGroup.placePitch) } } - // - @Deprecated("Break sound and particles are not independent from one another", ReplaceWith("removeBlock(ctx, playSound || showParticles)")) - fun removeBlockState(ctx: BlockBreakContext, playSound: Boolean = true, showParticles: Boolean = true): Boolean = - removeBlockState(ctx, playSound || showParticles) - - @Deprecated("Break sound and particles are not independent from one another", ReplaceWith("breakBlock(ctx, playSound || showParticles)")) - fun breakBlockState(ctx: BlockBreakContext, playSound: Boolean = true, showParticles: Boolean = true): Boolean = - breakBlockState(ctx, playSound || showParticles) - // - } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaBlock.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaBlock.kt index baf42de12df..20a45d639d8 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaBlock.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaBlock.kt @@ -10,6 +10,8 @@ import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.data.config.ConfigProvider import xyz.xenondevs.nova.data.config.Configs +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace import xyz.xenondevs.nova.data.resources.lookup.ResourceLookups import xyz.xenondevs.nova.data.resources.model.data.BlockModelData import xyz.xenondevs.nova.data.resources.model.data.BlockStateBlockModelData @@ -20,7 +22,6 @@ import xyz.xenondevs.nova.item.NovaItem import xyz.xenondevs.nova.item.options.BlockOptions import xyz.xenondevs.nova.registry.NovaRegistries import xyz.xenondevs.nova.world.BlockPos -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import java.util.concurrent.CompletableFuture /** @@ -79,7 +80,7 @@ open class NovaBlock internal constructor( internal open fun createBlockState(pos: BlockPos): NovaBlockState = NovaBlockState(pos, this) - internal open fun createNewBlockState(ctx: BlockPlaceContext): NovaBlockState = + internal open fun createNewBlockState(ctx: Context): NovaBlockState = NovaBlockState(this, ctx) companion object { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaTileEntityBlock.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaTileEntityBlock.kt index b655d996703..e05b5b496e8 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaTileEntityBlock.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/NovaTileEntityBlock.kt @@ -3,13 +3,14 @@ package xyz.xenondevs.nova.world.block import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.Style import net.minecraft.resources.ResourceLocation +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions import xyz.xenondevs.nova.data.world.block.property.BlockPropertyType import xyz.xenondevs.nova.data.world.block.state.NovaBlockState import xyz.xenondevs.nova.data.world.block.state.NovaTileEntityState import xyz.xenondevs.nova.item.options.BlockOptions import xyz.xenondevs.nova.tileentity.TileEntity import xyz.xenondevs.nova.world.BlockPos -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext typealias TileEntityConstructor = ((NovaTileEntityState) -> TileEntity) @@ -40,7 +41,7 @@ class NovaTileEntityBlock internal constructor( override fun createBlockState(pos: BlockPos): NovaTileEntityState = NovaTileEntityState(pos, this) - override fun createNewBlockState(ctx: BlockPlaceContext): NovaTileEntityState = + override fun createNewBlockState(ctx: Context): NovaTileEntityState = NovaTileEntityState(this, ctx) } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/TileEntityBlockBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/TileEntityBlockBehavior.kt index 84348f582b0..6dab4b1100d 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/TileEntityBlockBehavior.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/TileEntityBlockBehavior.kt @@ -1,19 +1,23 @@ package xyz.xenondevs.nova.world.block import org.bukkit.GameMode +import org.bukkit.entity.Entity import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockInteract +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.block.state.NovaTileEntityState import xyz.xenondevs.nova.util.runTask -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockInteractContext open class TileEntityBlockBehavior protected constructor(private val interactive: Boolean) : BlockBehavior.Default() { - override fun handleInteract(state: NovaTileEntityState, ctx: BlockInteractContext): Boolean { + override fun handleInteract(state: NovaTileEntityState, ctx: Context): Boolean { if (interactive) { - if (ctx.source is Player) - runTask { ctx.source.swingMainHand() } + val sourcePlayer = ctx[ContextParamTypes.SOURCE_ENTITY] as? Player + if (sourcePlayer != null) + runTask { sourcePlayer.swingMainHand() } return state.tileEntity.handleRightClick(ctx) } @@ -21,11 +25,12 @@ open class TileEntityBlockBehavior protected constructor(private val interactive return false } - override fun getDrops(state: NovaTileEntityState, ctx: BlockBreakContext): List { - return state.tileEntity.getDrops(ctx.source !is Player || ctx.source.gameMode != GameMode.CREATIVE) + override fun getDrops(state: NovaTileEntityState, ctx: Context): List { + val sourceEntity: Entity? = ctx[ContextParamTypes.SOURCE_ENTITY] + return state.tileEntity.getDrops(sourceEntity !is Player || sourceEntity.gameMode != GameMode.CREATIVE) } - override fun getExp(state: NovaTileEntityState, ctx: BlockBreakContext): Int { + override fun getExp(state: NovaTileEntityState, ctx: Context): Int { return state.tileEntity.getExp() } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/context/BlockContexts.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/context/BlockContexts.kt deleted file mode 100644 index dec1726d5f6..00000000000 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/context/BlockContexts.kt +++ /dev/null @@ -1,110 +0,0 @@ -package xyz.xenondevs.nova.world.block.context - -import org.bukkit.Location -import org.bukkit.Material -import org.bukkit.block.BlockFace -import org.bukkit.entity.Entity -import org.bukkit.inventory.EquipmentSlot -import org.bukkit.inventory.ItemStack -import xyz.xenondevs.nova.data.world.block.state.BlockState -import xyz.xenondevs.nova.tileentity.TileEntity -import xyz.xenondevs.nova.util.UUIDUtils -import xyz.xenondevs.nova.world.BlockPos -import xyz.xenondevs.nova.world.pos -import java.util.* -import xyz.xenondevs.nova.api.block.NovaBlock as INovaBlock - -private fun getSourceLocation(source: Any?): Location? = - when (source) { - is Entity -> source.location - is BlockState -> source.pos.location - is TileEntity -> source.location - is Location -> source - else -> null - } - -private fun getOwnerUUID(source: Any?): UUID? = - when (source) { - is Entity -> source.uniqueId - is TileEntity -> source.ownerUUID - is UUID -> source - else -> null - } - -data class BlockPlaceContext( - val pos: BlockPos, - val item: ItemStack, - val source: Any?, - val sourceLocation: Location?, - val ownerUUID: UUID?, - val placedOn: BlockPos, - val placedOnFace: BlockFace -) { - - init { - require(ownerUUID != UUIDUtils.ZERO) { "Owner UUID must not be 0-0. Use null instead." } - require(!item.type.isAir) { "empty item stacks are not allowed" } - } - - internal companion object { - - fun forAPI(location: Location, material: INovaBlock, source: Any?): BlockPlaceContext { - val pos = location.pos - return BlockPlaceContext( - pos, - material.item?.createItemStack(1) ?: ItemStack(Material.AIR), - source, - getSourceLocation(source) ?: location, - getOwnerUUID(source) ?: UUID(0L, 0L), - pos.add(0, -1, 0), - BlockFace.UP - ) - } - - } - -} - -data class BlockBreakContext( - val pos: BlockPos, - val source: Any? = null, - val sourceLocation: Location? = null, - val clickedFace: BlockFace? = null, - val item: ItemStack? = null -) { - - init { - require(item?.type?.isAir != true) { "empty item stacks are not allowed" } - } - - internal companion object { - - fun forAPI(location: Location, source: Any?, tool: ItemStack?): BlockBreakContext { - return BlockBreakContext( - location.pos, - source, - getSourceLocation(source) ?: location, - null, - tool - ) - } - - } - -} - -data class BlockInteractContext( - val pos: BlockPos, - val source: Any? = null, - val sourceLocation: Location? = null, - val clickedFace: BlockFace? = null, - val item: ItemStack? = null, - val hand: EquipmentSlot? = null -) { - - init { - require(item?.type?.isAir != true) { "empty item stacks are not allowed" } - require(hand == null || hand == EquipmentSlot.HAND || hand == EquipmentSlot.OFF_HAND) { "equipment slot is not a hand" } - } - -} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/BlockLimiter.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/BlockLimiter.kt index 402124eda58..dae54d1a3fa 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/BlockLimiter.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/BlockLimiter.kt @@ -1,14 +1,19 @@ package xyz.xenondevs.nova.world.block.limits import net.minecraft.resources.ResourceLocation +import org.bukkit.World +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes +import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.block.NovaBlock -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import xyz.xenondevs.nova.world.block.limits.BlockLimiter.Companion.ALLOWED import xyz.xenondevs.nova.world.block.limits.TileEntityLimits.PlaceResult +import java.util.* internal interface BlockLimiter { - fun canPlace(material: NovaBlock, ctx: BlockPlaceContext): PlaceResult + fun canPlace(material: NovaBlock, ctx: Context): PlaceResult companion object { @@ -22,17 +27,17 @@ internal abstract class SimpleBlockLimiter(denyMessage: String) : BlockLimiter { private val denied = PlaceResult(false, denyMessage) - final override fun canPlace(material: NovaBlock, ctx: BlockPlaceContext): PlaceResult { + final override fun canPlace(material: NovaBlock, ctx: Context): PlaceResult { return if (testPlace(material, ctx)) ALLOWED else denied } - abstract fun testPlace(material: NovaBlock, ctx: BlockPlaceContext): Boolean + abstract fun testPlace(material: NovaBlock, ctx: Context): Boolean } internal class TypeBlacklist(private val blacklist: Set) : SimpleBlockLimiter("nova.tile_entity_limits.type_blacklist.deny") { - override fun testPlace(material: NovaBlock, ctx: BlockPlaceContext): Boolean { + override fun testPlace(material: NovaBlock, ctx: Context): Boolean { return material.id !in blacklist } @@ -40,17 +45,18 @@ internal class TypeBlacklist(private val blacklist: Set) : Sim internal class WorldBlacklist(private val blacklist: Set) : SimpleBlockLimiter("nova.tile_entity_limits.world_blacklist.deny") { - override fun testPlace(material: NovaBlock, ctx: BlockPlaceContext): Boolean { - return !blacklist.contains("*") && ctx.pos.world.name !in blacklist + override fun testPlace(material: NovaBlock, ctx: Context): Boolean { + return !blacklist.contains("*") && ctx.get(ContextParamTypes.BLOCK_WORLD)!!.name !in blacklist } } internal class TypeWorldBlacklist(private val blacklist: Map>) : SimpleBlockLimiter("nova.tile_entity_limits.type_world_blacklist.deny") { - override fun testPlace(material: NovaBlock, ctx: BlockPlaceContext): Boolean { + override fun testPlace(material: NovaBlock, ctx: Context): Boolean { val id = material.id - return blacklist["*"]?.contains(id) != true && blacklist[ctx.pos.world.name]?.contains(id) != true + val world: World = ctx[ContextParamTypes.BLOCK_WORLD]!! + return blacklist["*"]?.contains(id) != true && blacklist[world.name]?.contains(id) != true } } @@ -60,9 +66,10 @@ internal class AmountLimiter(private val type: Type, private val limits: Map): PlaceResult { + val id: ResourceLocation = material.id + val owner: UUID = ctx[ContextParamTypes.SOURCE_UUID] ?: return ALLOWED + val pos: BlockPos = ctx[ContextParamTypes.BLOCK_POS]!! val specificLimit = limits[id] val totalLimit = limits[null] @@ -70,8 +77,8 @@ internal class AmountLimiter(private val type: Type, private val limits: Map TileEntityTracker.getBlocksPlacedAmount(owner, id) - Type.PER_WORLD -> TileEntityTracker.getBlocksPlacedAmount(owner, ctx.pos.world.uid, id) - Type.PER_CHUNK -> TileEntityTracker.getBlocksPlacedAmount(owner, ctx.pos.chunkPos, id) + Type.PER_WORLD -> TileEntityTracker.getBlocksPlacedAmount(owner, pos.world.uid, id) + Type.PER_CHUNK -> TileEntityTracker.getBlocksPlacedAmount(owner, pos.chunkPos, id) } if (amount >= specificLimit) @@ -81,8 +88,8 @@ internal class AmountLimiter(private val type: Type, private val limits: Map TileEntityTracker.getBlocksPlacedAmount(owner) - Type.PER_WORLD -> TileEntityTracker.getBlocksPlacedAmount(owner, ctx.pos.world.uid) - Type.PER_CHUNK -> TileEntityTracker.getBlocksPlacedAmount(owner, ctx.pos.chunkPos) + Type.PER_WORLD -> TileEntityTracker.getBlocksPlacedAmount(owner, pos.world.uid) + Type.PER_CHUNK -> TileEntityTracker.getBlocksPlacedAmount(owner, pos.chunkPos) } if (amount >= totalLimit) diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityLimits.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityLimits.kt index 70012d84a41..9f75bb29047 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityLimits.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityLimits.kt @@ -2,17 +2,18 @@ package xyz.xenondevs.nova.world.block.limits import xyz.xenondevs.nova.data.config.MAIN_CONFIG import xyz.xenondevs.nova.data.config.optionalEntry -import xyz.xenondevs.nova.util.item.novaItem +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.world.block.NovaTileEntityBlock -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import xyz.xenondevs.nova.world.block.limits.BlockLimiter.Companion.ALLOWED internal object TileEntityLimits { private val limiters: List? by MAIN_CONFIG.optionalEntry>("performance", "tile_entity_limits") - fun canPlace(ctx: BlockPlaceContext): PlaceResult { - val block = ctx.item.novaItem?.block as? NovaTileEntityBlock + fun canPlace(ctx: Context): PlaceResult { + val block: NovaTileEntityBlock = ctx[ContextParamTypes.BLOCK_TYPE_NOVA] as? NovaTileEntityBlock ?: return ALLOWED limiters?.forEach { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityTracker.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityTracker.kt index a3701a41a97..1320fc6aa5c 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityTracker.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/limits/TileEntityTracker.kt @@ -2,6 +2,10 @@ package xyz.xenondevs.nova.world.block.limits import net.minecraft.resources.ResourceLocation import xyz.xenondevs.nova.data.config.PermanentStorage +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockPlace +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.initialize.DisableFun import xyz.xenondevs.nova.initialize.InitFun import xyz.xenondevs.nova.initialize.InternalInit @@ -11,8 +15,6 @@ import xyz.xenondevs.nova.util.runTaskTimer import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.ChunkPos import xyz.xenondevs.nova.world.block.NovaTileEntityBlock -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import java.lang.Integer.max import java.util.* @@ -38,14 +40,14 @@ internal object TileEntityTracker { PermanentStorage.store("block_chunk_counter", BLOCK_CHUNK_COUNTER) } - internal fun handleBlockPlace(material: NovaTileEntityBlock, ctx: BlockPlaceContext) { - if (ctx.ownerUUID != null) - modifyCounters(ctx.ownerUUID, ctx.pos, material.id, 1) + internal fun handleBlockPlace(block: NovaTileEntityBlock, ctx: Context) { + val sourceUuid = ctx[ContextParamTypes.SOURCE_UUID] ?: return + modifyCounters(sourceUuid, ctx[ContextParamTypes.BLOCK_POS]!!, block.id, 1) } - internal fun handleBlockBreak(tileEntity: TileEntity, ctx: BlockBreakContext) { + internal fun handleBlockBreak(tileEntity: TileEntity, ctx: Context) { if (tileEntity.ownerUUID != null) - modifyCounters(tileEntity.ownerUUID, ctx.pos, tileEntity.block.id, -1) + modifyCounters(tileEntity.ownerUUID, ctx[ContextParamTypes.BLOCK_POS]!!, tileEntity.block.id, -1) } private fun modifyCounters(player: UUID, pos: BlockPos, id: ResourceLocation, add: Int) { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt index 0ce84100fdd..5a583b47f39 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/break/BlockBreaker.kt @@ -26,6 +26,9 @@ import xyz.xenondevs.nmsutils.particle.item import xyz.xenondevs.nmsutils.particle.particle import xyz.xenondevs.nova.data.config.MAIN_CONFIG import xyz.xenondevs.nova.data.config.entry +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.block.state.NovaBlockState import xyz.xenondevs.nova.integration.protection.ProtectionManager import xyz.xenondevs.nova.item.tool.ToolCategory @@ -47,7 +50,6 @@ import xyz.xenondevs.nova.util.send import xyz.xenondevs.nova.util.serverLevel import xyz.xenondevs.nova.util.serverPlayer import xyz.xenondevs.nova.util.serverTick -import xyz.xenondevs.nova.world.block.context.BlockBreakContext import xyz.xenondevs.nova.world.block.event.BlockBreakActionEvent import xyz.xenondevs.nova.world.block.sound.SoundGroup import xyz.xenondevs.nova.world.pos @@ -219,12 +221,11 @@ internal sealed class BlockBreaker(val player: Player, val block: Block, val sta fun breakBlock(brokenClientside: Boolean, sequence: Int) { // create a block breaking context - val ctx = BlockBreakContext( - block.pos, - player, player.location, - BlockFaceUtils.determineBlockFaceLookingAt(player.eyeLocation), - tool - ) + val ctx = Context.intention(ContextIntentions.BlockBreak) + .param(ContextParamTypes.BLOCK_POS, block.pos) + .param(ContextParamTypes.SOURCE_ENTITY, player) + .param(ContextParamTypes.TOOL_ITEM_STACK, tool) + .param(ContextParamTypes.BLOCK_DROPS, drops) val level = block.world.serverLevel val blockPos = block.pos.nmsPos @@ -233,11 +234,12 @@ internal sealed class BlockBreaker(val player: Player, val block: Block, val sta val event = BlockBreakEvent(block, player) if (drops) { event.expToDrop = when (this) { - is NovaBlockBreaker -> material.logic.getExp(blockState, ctx) + is NovaBlockBreaker -> material.logic.getExp(blockState, ctx.build()) is VanillaBlockBreaker -> BlockUtils.getVanillaBlockExp(level, blockPos, tool.nmsCopy) } } callEvent(event) + ctx.param(ContextParamTypes.BLOCK_DROPS, drops && event.isDropItems) // if (!event.isCancelled && !ProtectionManager.isVanillaProtected(player, block.location)) { @@ -272,7 +274,7 @@ internal sealed class BlockBreaker(val player: Player, val block: Block, val sta val state = block.state // remove block - val itemEntities = block.removeInternal(ctx, event.isDropItems && drops, true, !brokenClientside) + val itemEntities = block.removeInternal(ctx.build(), !brokenClientside) // drop items if (event.isDropItems) { diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt index 312e3b81cef..a93ecdcfed8 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt @@ -15,12 +15,14 @@ import org.bukkit.event.block.BlockPistonRetractEvent import org.bukkit.event.entity.EntityChangeBlockEvent import org.bukkit.event.entity.EntityExplodeEvent import org.bukkit.event.inventory.InventoryCreativeEvent +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions +import xyz.xenondevs.nova.data.context.intention.ContextIntentions.BlockBreak +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.integration.protection.ProtectionManager import xyz.xenondevs.nova.player.WrappedPlayerInteractEvent import xyz.xenondevs.nova.util.registerEvents import xyz.xenondevs.nova.world.block.BlockManager -import xyz.xenondevs.nova.world.block.context.BlockBreakContext -import xyz.xenondevs.nova.world.block.context.BlockInteractContext import xyz.xenondevs.nova.world.pos internal object BlockInteracting : Listener { @@ -42,7 +44,15 @@ internal object BlockInteracting : Listener { val blockState = BlockManager.getBlockState(pos) if (blockState != null && ProtectionManager.canUseBlock(player, event.item, pos.location).get()) { val material = blockState.block - val ctx = BlockInteractContext(pos, player, player.location, event.blockFace, event.item, event.hand) + + val ctx = Context.intention(ContextIntentions.BlockInteract) + .param(ContextParamTypes.BLOCK_POS, pos) + .param(ContextParamTypes.BLOCK_TYPE_NOVA, material) + .param(ContextParamTypes.SOURCE_ENTITY, player) + .param(ContextParamTypes.CLICKED_BLOCK_FACE, event.blockFace) + .param(ContextParamTypes.INTERACTION_HAND, event.hand) + .param(ContextParamTypes.INTERACTION_ITEM_STACK, event.item) + .build() val actionPerformed = material.logic.handleInteract(blockState, ctx) event.isCancelled = actionPerformed @@ -76,7 +86,11 @@ internal object BlockInteracting : Listener { val pos = event.block.pos val state = BlockManager.getBlockState(pos) if (state != null && Material.AIR == event.block.type) { - BlockManager.breakBlockState(BlockBreakContext(pos, null, null, null, null), false) + val ctx = Context.intention(BlockBreak) + .param(ContextParamTypes.BLOCK_POS, pos) + .param(ContextParamTypes.BLOCK_BREAK_EFFECTS, false) + .build() + BlockManager.breakBlockState(ctx) } } @@ -96,7 +110,13 @@ internal object BlockInteracting : Listener { private fun handleExplosion(blockList: MutableList) { val novaBlocks = blockList.filter { BlockManager.getBlockState(it.pos) != null } blockList.removeAll(novaBlocks) - novaBlocks.forEach { BlockManager.breakBlockState(BlockBreakContext(it.pos, null, null, null, null), false) } + novaBlocks.forEach { + val ctx = Context.intention(BlockBreak) + .param(ContextParamTypes.BLOCK_POS, it.pos) + .param(ContextParamTypes.BLOCK_BREAK_EFFECTS, false) + .build() + BlockManager.breakBlockState(ctx) + } } } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/place/BlockPlacing.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/place/BlockPlacing.kt index 0a2d77d88d1..1bfa8c33b5f 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/place/BlockPlacing.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/place/BlockPlacing.kt @@ -12,6 +12,9 @@ import org.bukkit.event.block.Action import org.bukkit.event.block.BlockMultiPlaceEvent import org.bukkit.event.block.BlockPlaceEvent import org.bukkit.inventory.ItemStack +import xyz.xenondevs.nova.data.context.Context +import xyz.xenondevs.nova.data.context.intention.ContextIntentions +import xyz.xenondevs.nova.data.context.param.ContextParamTypes import xyz.xenondevs.nova.data.world.WorldDataManager import xyz.xenondevs.nova.data.world.block.state.NovaBlockState import xyz.xenondevs.nova.integration.protection.ProtectionManager @@ -33,7 +36,6 @@ import xyz.xenondevs.nova.util.yaw import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.block.BlockManager import xyz.xenondevs.nova.world.block.NovaBlock -import xyz.xenondevs.nova.world.block.context.BlockPlaceContext import xyz.xenondevs.nova.world.block.limits.TileEntityLimits import xyz.xenondevs.nova.world.pos import java.util.concurrent.CompletableFuture @@ -100,7 +102,7 @@ internal object BlockPlacing : Listener { clicked.location else clicked.location.advance(event.blockFace) - if (!placeLoc.isInsideWorldRestrictions() || !placeLoc.block.isUnobstructed(material.vanillaBlockMaterial, player)) + if (!placeLoc.isInsideWorldRestrictions() || !placeLoc.block.isUnobstructed(player, material.vanillaBlockMaterial)) return val futures = ArrayList>() @@ -121,11 +123,12 @@ internal object BlockPlacing : Listener { if (!canPlace(player, handItem, placeLoc.pos, placeLoc.clone().advance(event.blockFace.oppositeFace).pos)) return@runIfTrueOnSimilarThread - val ctx = BlockPlaceContext( - placeLoc.pos, handItem, - player, player.location, player.uniqueId, - event.clickedBlock!!.pos, event.blockFace - ) + val ctx = Context.intention(ContextIntentions.BlockPlace) + .param(ContextParamTypes.BLOCK_POS, placeLoc.pos) + .param(ContextParamTypes.BLOCK_ITEM_STACK, handItem) + .param(ContextParamTypes.SOURCE_ENTITY, player) + .param(ContextParamTypes.CLICKED_BLOCK_FACE, event.blockFace) + .build() val result = TileEntityLimits.canPlace(ctx) if (result.allowed) {