English
Public API
Compile-time dependency
xml
<repositories><repository><id>jiuwu-releases</id><url>https://repo.crypticlib.com/repository/maven-public/</url></repository></repositories>
<dependency><groupId>emaki.jiuwu.craft</groupId><artifactId>emaki-forge-api</artifactId><version>4.8.6</version><scope>provided</scope></dependency>kotlin
repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-forge-api:4.8.6") }Compile only — never install, shade, or relocate this jar
Do not drop the API jar into plugins/, and do not bundle, shade, or relocate it. EmakiForge-4.8.8.jar already embeds these un-relocated API/event classes; a duplicate ClassLoader copy breaks Bukkit events and the static bridge
Use EmakiForgeApi.status().usable(). catalog(), operations(), and the currently empty experimental extensions() accessor are non-null; when EmakiForge is absent they return empty queries, unavailable results, or an empty extension layer. install, uninstall, and Bridge belong to the runtime lifecycle and must not be called, implemented, or proxied by third parties
Catalog
recipes() returns loaded recipes in recipe-book order (empty when unavailable) and recipe(String) looks up an id normalised with Locale.ROOT. recipes(), recipe(String), materialById(String), and materialByItem(ItemStack) are any-thread reads of the immutable loaded definitions; live item stacks must respect their holder's owner thread. Player-scoped matchRecipe, validate, experimental previewResult, and experimental mastery require the player's owner thread. validate represents a rule refusal as Success<ForgeValidation> with allowed()==false and carries reasonKey() plus placeholders() for rendering the rejection. accepting() reports whether new attempts are accepted
Experimental mastery(Player, String) reads the player's persisted successful craft count for that recipe; when the current session data is not loaded yet the result is UNAVAILABLE, never a fabricated zero
Programmatic forging
operations().forgeAsync(player, recipeId, inputs) is experimental and accepts calls from any thread. It uses the real preparation, validation, actions, quality, delivery, history, and event path, dispatching Bukkit work to the player's owner thread. Its future carries EmakiResult<ForgeOutcome>; cancelling the future cannot undo an attempt that crossed the delivery commit boundary
ForgeInputs is detached escrow with the fields targetItem, blueprints, requiredMaterials, and optionalMaterials; every stack is cloned on construction and on access, and ForgeInputs.empty() / ForgeInputs.ofRequired(map) are convenience constructors. EmakiForge never scans or removes stacks from arbitrary inventory slots, so the caller must reserve/commit/release the physical inventory items itself. ForgeOutcome exists only after a successful committed delivery and contains the canonical recipe id, a cloned result item, the quality id, and the multiplier. Chance failure, validation rejection, event cancellation, runtime unavailability, and execution errors are outer EmakiResult failures, not outcomes
Other operations are owner-thread GUI/book access, viewingRecipeBook, refreshItem, and refreshPlayer; extensions() invents no extra pipeline
openForgeGui(Player[, recipeId]),openRecipeBook(Player, page),viewingRecipeBook(Player): player owner thread.pageis zero-based and a negative value is treated as0;viewingRecipeBookusesEmakiResult<Boolean>so "not open" is not collapsed into "unavailable"refreshItem(ItemStack): holder owner thread; returnsPartialfor items that carry no EmakiForge datarefreshPlayer(Player): refreshes inventory, armour, off hand, and cursor; player owner thread
Results
Use Success, Partial, Failure, and optionalValue(). Failure kinds: UNAVAILABLE, NOT_FOUND, INVALID_INPUT, REJECTED, CANCELLED, TARGET_OFFLINE, WRONG_THREAD, INTERNAL_ERROR