Add a list-based crafting interface - #227
Open
rubensworks wants to merge 4 commits into
Open
Conversation
A new crafting interface variant with a single variable slot that accepts a list of recipes, so that a whole recipe set can be derived with logic instead of placed card by card. Closes #10. The variable machinery of the existing crafting interface is extracted into PartTypeInterfaceCraftingVariableBase, with a per-slot recipe list instead of a single recipe, so both variants share the evaluator, invalidation, validation and slot messages. The container and screen are reused for both, since the inventory size and the accepted value type are derived from the part type. List reads are guarded: infinite lists are rejected, the number of recipes read from a list is capped, duplicates are removed, and the network index is patched with only what changed. Because reader-backed list variables are invalidated on every reader tick, reloads of a list slot are throttled to a configurable minimum interval. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bn8brBLvFiJsfnBHpwf8ZN
The single slot of the list interface sat in the top-left corner. The first slot's x position is now derived from the slot count, which centers one slot and leaves the nine-slot row exactly where it was. "List-Based Crafting Interface" ran into the settings button in the gui title, so it becomes "List Crafting Interface". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bn8brBLvFiJsfnBHpwf8ZN
…load cost The list interface had its own config to throttle how often invalidations were acted on. The part's update interval already governs this, so that config is gone. The list interface now has its own minimum update interval config instead, defaulting to 20 ticks, which is what the settings gui exposes. Reload cost is cut so the interval is a preference rather than a necessity, measured against a machine reader on a crafting table (987 recipes): * Validation results are cached per part until it fully reloads, when the target may have changed. The cache is keyed by identity, as recipe handlers hand out the same instances on every read while hashing a recipe by value turned out to be expensive. * A slot whose recipes did not change no longer syncs to the client, posts a contents-updated event, or touches the network recipe index. * The network index diff uses sets rather than repeated list scans. At the default cap of 256 recipes, a repeated reload goes from ~1.4 ms plus network work to ~0.3 ms with none. Uncapped at 987 recipes, from ~9 ms to ~2.7 ms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bn8brBLvFiJsfnBHpwf8ZN
The attuned crafting interface already exposes every recipe of its target with no limit at all, and indexing those 987 recipes of a crafting table costs it the same as it costs a list interface (~95-100 ms for a network remove+add in both cases). Limiting lists to 256 was therefore inconsistent: it withheld something the mod already allows elsewhere. The limit now defaults to 4096, which no regular machine reaches. It stays as a guard against a computed list that runs away, since a list is not bounded by a real machine the way an attuned interface is. Set it to 0 for no limit. The machine reader game test asserted against the limit, which made it pass for the wrong reason once the limit exceeded the machine's recipe count. It now asserts that the interface exposes exactly the recipes its target holds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bn8brBLvFiJsfnBHpwf8ZN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #10.
Adds a List-Based Crafting Interface part: a single variable slot that accepts a
ValueTypeListofValueRecipe, and exposes every recipe in that list to the crafting network. This lets a whole recipe set be derived with logic (Machine Reader,recipesbyinput/recipesbyoutput, filtered lists) instead of being placed card by card, which is the limitation raised in the issue: nine slots per interface is not enough in large packs, and the attuned interface offers no control over which of the target's recipes are used.Approach
The variable machinery that lived in
PartTypeInterfaceCraftingmoves into a newcore/part/PartTypeInterfaceCraftingVariableBase, shared by both variants:currentRecipesbecomes one list of recipes per slot instead of a single recipe, with a cached flattened view forgetRecipes()(it is called on everyaddCraftingInterface).extractRecipes(int slot, IValue value). The base keeps the type check, theEvaluationExceptionto slot-message path, and theisValidloop honouringvalidateRecipesCraftingInterface/disableCraftingCheck.variables,recipeSlotMessages,recipeSlotValidated,disableCraftingCheck) are unchanged, so existing worlds load as before.PartVariableDrivenVariableContentsUpdatedEventno longer hardcodesPartTypes.INTERFACE_CRAFTINGas its part type.PartTypeInterfaceCraftingreduces to a 9-slot part whoseextractRecipesreturns zero or one recipe.PartTypeInterfaceCraftingListis a 1-slot part overValueTypes.LIST.The container and screen are reused for both: the inventory size already travels in the GUI packet, the slot filter now comes from
getPartType().getSlotValueType(), and the background texture is derived from the part name. No new menu type,GuiConfigorRegistryEntriesholder. The settings GUI's twoinstanceof PartTypeInterfaceCrafting.Stateguards are widened to the shared base, so the list variant also gets the crafting-check checkbox.Guards on list reading
Recipe lists are not necessarily cheap or finite, so
extractRecipesfollows the guard order used upstream inOperatorsandTunnelAspectWriteBuilders:isInfinite()) are rejected with a slot message;OBJECT_RECIPE, with a per-element check as well, sinceCATEGORY_ANYpasses the list-level check;ListFactoryIteratorswallows per-elementEvaluationExceptionand substitutes defaults, which would hide errors instead of surfacing them in the slot;maxCraftingInterfaceListRecipes(default 256). A Machine Reader'sValueTypeListProxyPositionedRecipesis an uncached positioned view, where eachget(i)is a capability lookup plus anIterables.get;CraftingNetworkdrops a recipe from its index as soon as one removal is requested for it, so the same recipe must never be registered twice from one interface. Not reachable with one recipe per slot; very reachable with a list.Two related changes to how reloads reach the network:
updatenow patches only what actually changed between the old and new recipes of a slot, instead of removing all and re-adding all.AspectReadBase.update→LazyAspectVariable.invalidate), and each invalidation triggers a reload. With a list that means re-materializing up to 256 recipes several times a second. Reloads of a list slot are therefore throttled tocraftingInterfaceListMinReloadInterval(default 20 ticks). This only bounds how often invalidations are acted on; it adds no polling, and the regular interface is unaffected.Config
interfaceCraftingListBaseConsumptionmaxCraftingInterfaceListRecipescraftingInterfaceListMinReloadIntervalRecipe
Mirrors the attuned interface, with
c:gems/diamondin place of the emeralds: two crafting interfaces and a crystalized chorus block in the middle row, diamonds above and below.Tests
GameTestsItemsCraftListcovers crafting the first and the last recipe of a list, re-indexing after the variable card is replaced, duplicate removal, rejection of a non-recipe list, and a Machine Reader-backed lazy list. The last one also covers the invalidation path specifically: it removes the reader's target block and asserts the interface drops its recipes, which can only happen through the variable's invalidation listener.GameTestsAdvancementsgets positive and negative tests for the new advancement../gradlew buildand./gradlew runGameTestServerboth pass (62 game tests). Also verified in a dev client with clientdevbridge-cli: the part renders on a cable, the new one-slot GUI lines up, the existing nine-slot GUI is unchanged, the settings GUI opens with the crafting-check checkbox, and the recipe and advancement resolve in game.Notes for review
part/interface_crafting_list.pngis the base interface texture hue-rotated to cyan, andgui/part_interface_crafting_list.pngis the existing background with the eight extra slot frames painted out. They are correctly sized and render fine, but they deserve real art before release.CHANGELOGentry: those blocks are generated per release by the ReleaseHelpers scripts, so writing one by hand would need an invented version and timestamp.root.json'srewards.recipesfollows the existing sibling entries, which useintegratedcrafting:part_interface_crafting…rather than the recipe ids (integratedcrafting:crafting/…). That looks like a pre-existing mismatch for all three interfaces; I kept the file uniform rather than fixing only the new line.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bn8brBLvFiJsfnBHpwf8ZN
Generated by Claude Code