Skip to content

Events

CustomCrafterAPI fires several custom events that implement Bukkit’s Event. These can be listened to and handled in the same way as ordinary Bukkit events.


Fired when a recipe is about to be registered via CustomCrafterAPI.registerRecipe().

PropertyTypeDescription
recipesList<CRecipe>The list of recipes that are about to be registered
isAsyncBooleanWhether the call originated from an asynchronous thread (since 5.2.0)

Calling registererMeta() returns the PluginMeta of the plugin that registered the recipes. Returns null if the registration was performed by CustomCrafterAPI itself (since 5.2.0).

class RecipeRegisterListener : Listener {
@EventHandler
fun onRegister(event: RegisterCustomRecipeEvent) {
event.recipes.forEach { recipe ->
println("Recipe registered: ${recipe.name}")
}
// Example: check which plugin registered the recipes
val registerer = event.registererMeta()
if (registerer != null) {
println("Registered by: ${registerer.name}")
}
}
}

Fired when a recipe is about to be unregistered via CustomCrafterAPI.unregisterRecipe() or CustomCrafterAPI.unregisterAllRecipes().

PropertyTypeDescription
recipesList<CRecipe>The list of recipes that are about to be unregistered
isAsyncBooleanWhether the call originated from an asynchronous thread (since 5.2.0)
class RecipeUnregisterListener : Listener {
@EventHandler
fun onUnregister(event: UnregisterCustomRecipeEvent) {
event.recipes.forEach { recipe ->
println("Recipe unregistered: ${recipe.name}")
}
}
}

Fired when an item is crafted (i.e., a craft operation is executed). This event is not cancellable.

PropertyTypeDescription
playerPlayerThe player who performed the craft
viewCraftViewThe UI input state at the time of crafting
resultSearch.SearchResult?The search result. null indicates the craft did not succeed
shiftUsedBooleanWhether the player bulk-crafted by holding Shift (since 5.0.17)
isAsyncBooleanWhether the call originated from an asynchronous thread (since 5.0.17)
class CreateItemListener : Listener {
@EventHandler
fun onCreateItem(event: CreateCustomItemEvent) {
val result = event.result ?: return
// Log the name of the custom recipe that was crafted
result.customs().forEach { (recipe, _) ->
println("${event.player.name} crafted ${recipe.name}.")
}
// Example: perform additional processing when bulk-crafting
if (event.shiftUsed) {
println("Bulk crafting was used.")
}
}
}

Fired when a mutable property (configuration value) of CustomCrafterAPI is changed. Both the old and new values are included.

This event is generic and carries the type T of the changed property.

PropertyTypeDescription
propertyNameStringThe name of the changed property
oldValueProperty<T>The value before the change
newValueProperty<T>The value after the change
isAsyncBooleanWhether the change originated from an asynchronous thread

To extract a value from Property<T> in a type-safe manner, use PropertyKey<T>.

val key = CustomCrafterAPIPropertiesChangeEvent.PropertyKey.BASE_BLOCK
val value: Material? = event.newValue.getOrNull(key)

The predefined PropertyKey values are as follows:

KeyCorresponding TypeCorresponding Property
RESULT_GIVE_CANCELBooleanResultGiveCancel
BASE_BLOCKMaterialBaseBlock
USE_MULTIPLE_RESULT_CANDIDATE_FEATUREBooleanUseMultipleResultCandidateFeature
USE_CUSTOM_CRAFT_UIBooleanUseCustomCraftUI
BASE_BLOCK_SIDEIntBaseBlockSide
CRAFT_UI_DESIGNERCraftUIDesignerCraftUIDesigner
ALL_CANDIDATE_UI_DESIGNERAllCandidateUIDesignerAllCandidateUIDesigner
RECIPE_NAME_STRICT_LEVELCustomCrafterAPI.NameStrictLevelRecipeNameStrictLevel
class PropertiesChangeListener : Listener {
@EventHandler
fun <T> onPropertiesChange(event: CustomCrafterAPIPropertiesChangeEvent<T>) {
val key = CustomCrafterAPIPropertiesChangeEvent.PropertyKey.BASE_BLOCK
// Filter to only the relevant event by property name
if (event.propertyName != key.name) return
val oldBlock: Material = event.oldValue.getOrNull(key) ?: return
val newBlock: Material = event.newValue.getOrNull(key) ?: return
println("Base block changed from ${oldBlock.name} to ${newBlock.name}.")
}
}

Example: Monitoring Multiple Properties Together

Section titled “Example: Monitoring Multiple Properties Together”
class AllPropertiesChangeListener : Listener {
@EventHandler
fun <T> onPropertiesChange(event: CustomCrafterAPIPropertiesChangeEvent<T>) {
when (event.propertyName) {
CustomCrafterAPIPropertiesChangeEvent.PropertyKey.BASE_BLOCK.name -> {
val newValue = event.newValue.getOrNull(
CustomCrafterAPIPropertiesChangeEvent.PropertyKey.BASE_BLOCK
)
println("Base block changed: $newValue")
}
CustomCrafterAPIPropertiesChangeEvent.PropertyKey.USE_CUSTOM_CRAFT_UI.name -> {
val newValue = event.newValue.getOrNull(
CustomCrafterAPIPropertiesChangeEvent.PropertyKey.USE_CUSTOM_CRAFT_UI
)
println("Custom UI enabled: $newValue")
}
}
}
}

Fired when a player interacts with the crafting input slots or closes the CraftUI while a craft process (recipe search or result generation) is already in progress, causing the ongoing process to be interrupted. This event is not cancellable.

PropertyTypeDescription
interrupterPlayerThe player who caused the interruption
isAsyncBooleanWhether the event was fired from an asynchronous thread
class CraftInputInterruptListener : Listener {
@EventHandler
fun onInterrupt(event: CraftInputInterruptEvent) {
println("${event.interrupter.name} interrupted an ongoing craft.")
}
}

Fired when a player attempts to start a new craft while a craft process (recipe search or result generation) is already running, causing the new attempt to be blocked. This event is not cancellable.

PropertyTypeDescription
playerPlayerThe player who attempted the double craft
isAsyncBooleanWhether the event was fired from an asynchronous thread
class PreventDoubleCraftListener : Listener {
@EventHandler
fun onPrevent(event: PreventDoubleCraftEvent) {
event.player.sendMessage("Please wait for the current craft to finish.")
}
}

Fired when CustomCrafterAPI fails to deliver one or more result items to the player (for example, when the player’s inventory is full). This event is not cancellable.

Use getResultsIfNotObtained() to claim the remaining items and handle delivery yourself. Once claimed, subsequent calls to getResultsIfNotObtained() return null.

Property / MethodTypeDescription
usedSupplierContextResultSupplier.Context?The context used by the ResultSupplier that produced the items; null if unavailable
getResultsIfNotObtained()List<ItemStack>?Returns the undistributed items and marks them as obtained. Returns null if already claimed
isResultObtained()BooleanReturns whether the items have already been claimed
class ResultGiveFailListener : Listener {
@EventHandler
fun onResultGiveFail(event: ResultItemGiveFailEvent) {
val items: List<ItemStack> = event.getResultsIfNotObtained() ?: return
// Fall back: drop items at the player's location
val player = event.usedSupplierContext?.crafterId
?.let { Bukkit.getPlayer(it) } ?: return
items.forEach { player.world.dropItemNaturally(player.location, it) }
}
}