Contexts
A Context<I : ContextIntention>, at its core, is a simple key-value storage for (ContextParamType<T, I>, T) pairs and
a ContextIntention, which defines their allowed parameter types.
The context system can also infer parameters from other parameters, for example, you don't need to provide a
BLOCK_WORLD parameter if you provide a BLOCK_POS parameter, and you can still read BLOCK_WORLD from the context.
You can find a list of default context intentions here. Each context intention object contains their allowed parameter types as properties and documents which autofillers (which infer context parameter values) are available.
Creating and using a Context
And this is how you create and use a Context:
// create context
val ctx: Context<BlockPlace> = Context.intention(BlockPlace /* (1)! */)
.param(BlockPlace.BLOCK_POS, pos)
.param(BlockPlace.BLOCK_TYPE_NOVA, Blocks.EXAMPLE_BLOCK)
.build()
// read from context
val world: World = ctx[BlockPlace.BLOCK_WORLD] // (2)!
val whoPlaced: Player? = ctx[BlockPlace.SOURCE_PLAYER] // (3)!
// context usage: place block
BlockUtils.placeBlock(context)
-
The
ContextIntentionBlockPlacespecifies the set of allowed context param types that are available, for exampleBlockPlace.BLOCK_POSorBlockPlace.BLOCK_TYPE_NOVA. It also specifies how context parameter values can be inferred from each other, for example,BLOCK_WORLDcan be inferred fromBLOCK_POS. -
This reads the
BLOCK_WORLDparameter from the context, which is inferred from the providedBLOCK_POSparameter. SinceBLOCK_WORLDis a required parameter for theBlockPlaceintention, we can be sure that it is present in the context and thus non-null. -
This reads the
SOURCE_PLAYERparameter from the context, which is optional for theBlockPlaceintention. The returned value is nullable as it is not a required parameter type in this intention. In this specific case, the player will be null because no suitable parameter that could be used to infer the player (likeSOURCE_UUID) was defined.
Generic ContextIntention parameters
The built-in context intentions split their parameter types into reusable interfaces. For example, BlockPlace implements HasRequiredBlock, which is responsible for parameter types like BLOCK_POS and BLOCK_WORLD and their autofillers. In some cases, you may want to write a function that generically accepts any intention that implements HasRequiredBlock. This can be done like so:
fun <I : HasRequiredBlock<I>> doSomethingWithBlock(ctx: Context<I>) {
val world: World = ctx[HasRequiredBlock.blockWorld()]
// ...
}
Custom ContextParameterType
You can create a custom parameter type tied to a specific intention like this:
For required parameters, create a RequiredParamType instead:
val BLOCK_NAME = RequiredContextParamType<Component, BlockPlace>(
Key.key(ExampleAddon, "block_name")
)
In this case, you will also need to register it as a required parameter type for the BlockPlace intention.
Otherwise, context builders will not validate whether your required parameter type is present on build.
Custom Autofiller
Once you have created a custom ContextParamType, you may also want to register an autofiller for it. This can be done via ContextIntention.addAutofiller:
BlockPlace.addAutofiller(
BLOCK_NAME,
Autofiller.from(
BlockPlace.BLOCK_TYPE_NOVA
) { type: NovaBlock -> type.name }
)
BlockPlace.addAutofiller(
BLOCK_NAME,
Autofiller.from(
BlockPlace.BLOCK_TYPE_VANILLA
) { type: BlockType -> Component.translatable(type.translationKey()) }
)
Custom ContextIntention
Of course, you can also create a completely custom ContextIntention. For that, create a singleton object that inherits from AbstractContextIntention:
object MyIntention : AbstractContextIntention<MyIntention>() {
val BLOCK_NAME = RequiredContextParamType<Component, MyIntention>(
Key.key(MyAddon, "block_name")
)
init {
require(BLOCK_NAME)
}
}
You can also use Nova's built-in intention interfaces to reuse their parameters and autofillers:
object MyIntention : AbstractContextIntention<MyIntention>(), HasRequiredBlock<MyIntention> {
val BLOCK_NAME = RequiredContextParamType<Component, MyIntention>(
Key.key(MyAddon, "block_name")
)
init {
HasRequiredBlock.applyDefaults(this) // (1)!
require(BLOCK_NAME)
addAutofiller(
BLOCK_NAME,
Autofiller.from(
BLOCK_TYPE_NOVA // (2)!
) { type: NovaBlock -> type.name }
)
addAutofiller(
BLOCK_NAME,
Autofiller.from(
BLOCK_TYPE_VANILLA
) { type: BlockType -> Component.translatable(type.translationKey()) }
)
}
}
- This applies the default required parameters and autofillers that come from the
HasRequiredBlockinterface. - The property
BLOCK_TYPE_NOVAis inherited from theHasRequiredBlockinterface.