Skip to content

Search API

Search is the entry-point object for performing recipe searches in CustomCrafterAPI. It matches the input item arrangement against registered recipes and returns the matching recipes along with their coordinate-mapping information.


CraftView is the class that serves as the argument for recipe searches, representing the item arrangement in the crafting UI.

data class CraftView(
val materials: Map<CoordinateComponent, ItemStack>, // arrangement of items placed by the player
val result: ItemStack // item in the result slot
)

The size of materials must be between 1 and 36 inclusive. Otherwise, Search.search / Search.asyncSearch will throw an IllegalArgumentException.

// Example: create a CraftView with stone placed at (0,0)
val view = CraftView(
materials = mapOf(CoordinateComponent(0, 0) to ItemStack.of(Material.STONE)),
result = ItemStack.empty()
)

fun search(
crafterID: UUID,
view: CraftView,
forceSearchVanillaRecipe: Boolean = true,
onlyFirst: Boolean = false,
sourceRecipes: List<CRecipe> = CustomCrafterAPI.getRecipes()
): SearchResult

A synchronous search method that runs on the main thread. If there are many registered recipes or if predicates contain heavy processing, this may affect server TPS. In such cases, using asyncSearch (described below) is recommended.

ArgumentDescription
crafterIDThe UUID of the player performing the craft
viewThe arrangement of input items
forceSearchVanillaRecipeWhen true, always searches vanilla recipes. When false and a custom recipe is found, vanilla is not searched
onlyFirstWhen true, returns only the first matching custom recipe
sourceRecipesThe list of recipes to search (defaults to all registered recipes)
val player: Player = /* ... */
val view = CraftView(
materials = mapOf(CoordinateComponent(0, 0) to ItemStack.of(Material.STONE)),
result = ItemStack.empty()
)
val result: Search.SearchResult = Search.search(player.uniqueId, view)

fun asyncSearch(
crafterID: UUID,
view: CraftView,
query: SearchQuery = SearchQuery.ASYNC_DEFAULT,
sourceRecipes: List<CRecipe> = CustomCrafterAPI.getRecipes()
): CompletableFuture<SearchResult>

An asynchronous search method using virtual threads (available since 5.0.17). Each recipe search is executed in parallel on individual threads, making it especially effective when there are many recipes with heavy predicates that call databases or external APIs. This method is used internally by CustomCrafterAPI’s standard crafting screen and the all-candidates display feature.

val player: Player = /* ... */
val view = CraftView(
materials = mapOf(CoordinateComponent(0, 0) to ItemStack.of(Material.STONE)),
result = ItemStack.empty()
)
val future: CompletableFuture<Search.SearchResult> = Search.asyncSearch(player.uniqueId, view)
future.thenAccept { result ->
// Process results asynchronously
val customs = result.customs()
println("Matching custom recipe count: ${customs.size}")
}

SearchQuery is the class that controls the search behavior of asyncSearch.

class SearchQuery(
val searchMode: SearchMode,
val vanillaSearchMode: VanillaSearchMode,
val asyncContext: AsyncContext? = null
)
ValueDescription
ALL (default)Returns all matching custom recipes
ONLY_FIRSTReturns only the first matching custom recipe. Cancels other search tasks as soon as one is found
ValueDescription
IF_CUSTOMS_NOT_FOUND (default)Searches vanilla only if no custom recipe is found
FORCEAlways searches vanilla regardless of whether custom recipes were found
// Search in ONLY_FIRST mode
val query = Search.SearchQuery(
searchMode = Search.SearchQuery.SearchMode.ONLY_FIRST,
vanillaSearchMode = Search.SearchQuery.VanillaSearchMode.IF_CUSTOMS_NOT_FOUND,
asyncContext = AsyncContext.ofTurnOff()
)
val future = Search.asyncSearch(player.uniqueId, view, query)

SearchResult is the class that holds the search results.

MethodReturn typeDescription
vanilla()Recipe?The vanilla recipe. null if not found or not searched
customs()List<Pair<CRecipe, MappedRelation>>The list of matching custom recipes and their coordinate mappings
size()IntThe total number of matches across vanilla and custom recipes
getMergedResults()List<Pair<CRecipe, MappedRelation?>>A combined list of vanilla and custom results. The vanilla entry has null for MappedRelation
val result: Search.SearchResult = Search.search(player.uniqueId, view)
// Get vanilla recipe
val vanilla: Recipe? = result.vanilla()
vanilla?.let { println("Vanilla recipe: ${it.result.type}") }
// Get custom recipes
val customs: List<Pair<CRecipe, MappedRelation>> = result.customs()
customs.forEach { (recipe, relation) ->
println("Custom recipe: ${recipe.name}")
}
// Get all results combined
result.getMergedResults().forEach { (recipe, relation) ->
println("Recipe: ${recipe.name}, has coordinate mapping: ${relation != null}")
}

VanillaSearch is an object for searching only vanilla recipes without going through the CustomCrafterAPI search flow.

// Example: search for a vanilla recipe to craft a furnace from cobblestone
val view = CraftView(
materials = CoordinateComponent.squareFill(3)
.filter { it.x < 3 && it.y < 3 }
.associate { it to ItemStack.of(Material.COBBLESTONE) },
result = ItemStack.empty()
)
val vanillaRecipe: Recipe? = VanillaSearch.search(Bukkit.getWorlds().first(), view)
vanillaRecipe?.let { println("Result item: ${it.result.type}") }

MappedRelation and MappedRelationComponent

Section titled “MappedRelation and MappedRelationComponent”

MappedRelation is a class that holds the correspondence between coordinates in the recipe and the actual input slot coordinates. MappedRelationComponent represents a single correspondence pair (recipe coordinate → input coordinate).

data class MappedRelation(
val components: Set<MappedRelationComponent>
)
data class MappedRelationComponent(
val recipe: CoordinateComponent, // coordinate in the recipe
val input: CoordinateComponent // coordinate of the actual input slot
)

For example, if a shaped recipe requires stone at (0,0), there are cases where the recipe still matches even if the player places the item at (2,2). In that case the MappedRelationComponent would be recipe = (0,0), input = (2,2). This information is passed as ResultSupplier.Context.relation and CRecipePredicate.Context.relation, and is used to track which item was placed in which slot.