Imported from wdlpiaoyi/justenoughhiding (
AGENTS.md). Install upstream withnpx skills add wdlpiaoyi/justenoughhiding. Copyright stays with the author.
AGENTS.md
Minecraft Forge 1.20.1 client-side mod "Just Enough Hiding" (JEH): records what hides JEI content, reveals it, and applies the user's own hide list through JEI's visibility API.
Commands (Windows / PowerShell)
- Build:
.\gradlew.bat build→build\libs\justenoughhiding-<version>.jar - Smoke test (always run after a build):
.\gradlew.bat runData - Skip optional deps for local runs:
-PnoJei,-PnoKubeJS,-PnoEmi - No unit tests and no
runClientflow; testing is manual in the user's pack (see Testing). - Needs JDK 17. This machine points Gradle at it through the global
~/.gradle/gradle.properties(org.gradle.java.home).
Versioning & commits
- Bump
mod_versioningradle.propertieson every change; keep the-devsuffix. Bugfix → patch, feature → minor (e.g.0.14.2-dev). - Only commit when the user explicitly asks. Write the Chinese commit message to a UTF-8 file and
git commit -F <file>(avoids mojibake); stage withgit add -Afirst. - Reply to the user in Chinese.
Architecture (not obvious from filenames)
- The GUI must stay free of JEI/
ItemStack. Viewer-specific code lives behindclient/viewer/ViewerAdapter+Adapters(JEI impl:client/viewer/jei/JeiAdapter);intent/andlistehiding/are viewer-agnostic. Do not import JEI intoclient/gui. IntentTargetis a sealed interface:Ingredient/Recipe/RecipeCategory/Tag/Pattern/Unset.client/viewer/TargetKeys(id, kind key, friendly label) andTargetMatcher(glob/regex) are the shared helpers — reuse them instead of re-parsing.- Intent recording is done by mixins (
mixin/jei/intent/*) callingJeiIntentRecorder. Any JEI mutation JEH makes itself must run insideJeiIntentRecorder.runSuppressed(...), otherwise it is recorded withsource=unknown. - Reveal:
mixin/jei/RevealMixinforcesIngredientVisibility.isIngredientVisibletotrue.client/jehide/JeHideoverrides that for its own targets viaJeHide.isHidden(...); JEHide is driven by bothListEHidingand recorded hide-intents. - Config files live in
config/jeh/:client.toml(JehConfig),listehiding.json(ListEHidingStore),intentoverrides.json(IntentOverrides). On first run the mod writes a defaultlistehiding.json(armor-trim recipe pattern enabled; AE2 facade and Create/Refined Storage disguise plates listed but disabled; allnote="default") viaListEHidingStore.ensureDefaults, which also serves as the format example;client.tomlis generated by Forge fromJehConfig. The repodefaultconfigs/jeh/client.tomlis a template for pack authors to copy into an instance. - New mixins must be registered in
src/main/resources/justenoughhiding.mixins.json(client).mixin/JehMixinPluginonly applies*.mixin.jei.*when JEI is loaded, and*.mixin.emi.*when EMI is loaded. - No JEI/EMI class may be referenced from code that runs unconditionally (
client/gui,client/jehide/JeHide,JehClientEvents, KubeJS binding) — otherwise the mod hard-depends on that viewer.client/jehide/JeHideis a viewer-agnostic facade that only callsAdapters.active().reapplyHides()/tickHides(); the JEI engine lives inclient/jehide/JeiHide.
Optional integrations
- JEI:
compileOnlyAPI +runtimeOnlyforge jar;mods.tomlrequires[15.55.0,). The forge jar is runtime-only, so JEI internal classes (e.g.IClientToggleState,IngredientFilter) are not on the compile classpath — reach them with a mixin or reflection. - KubeJS:
compileOnly; discovered viasrc/main/resources/kubejs.plugins.txt(<class> client). Exposes the globalJEHbinding tokubejs/client_scripts(integration/kubejs/). Script changes are in-memory only untilJEH.save(). - EMI:
compileOnly; discovered via@dev.emi.emi.api.EmiEntrypoint(integration/emi/JehEmiPlugin).Adapters.active()picks the highestpriority()adapter, and EMI (100) outranks JEI (10) because EMI overrides the overlay. EMI has no runtime hide API: JEH expresses hiding as a predicate inEmiStackList/EmiRecipes.invalidators, re-added atbake()HEAD bymixin/emi/EmiStackListBakeRevealMixin/EmiRecipesBakeRevealMixinafter clear.client/jehide/EmiHidecomputes the hidden id sets (list + hide-intents) and is EMI-type-free;client/viewer/emi/EmiRevealSupportbridges EMIEmiStack/EmiRecipeto it. Reveal lives inmixin/emi/EmiStackListRevealMixinandmixin/emi/EmiHiddenRevealMixin(force-hidden checks to false unless JEH hides it). EMI-native intents (pluginremoveEmiStacks/removeRecipespredicates, edit-modeEmiHidden.setVisibility, andassets/emi/index/stacks+recipe/filtersdata-pack hides attributed to the providing pack) are recorded bymixin/emi/EmiRegistryIntentMixin/EmiHiddenIntentMixinthroughintegration/emi/EmiIntentRecorder. Its data-pack/registry scan is cached per(ResourceManager, Level)and its recipe-predicate matches per(recipe list, captured version).client/JehClientReloadEventsclears these on every client resource reload andclient/JehServerReloadEventsclears them on every server data reload (world join,/reload), both viaclient/jehide/JehReloadHooks(the manager instance isfinal, so identity alone is not enough). Removed packs' intents are dropped at reload time (IntentRegistry.retainSources), and content changes are pruned per scan byIntentRegistry.retainResourcePackTargets(per kind, so stack/recipe scans don't clobber each other).HidingListScreenonly re-applies on close when the list actually changed. Because JEH ships an EMI plugin, EMI'sjei.PluginCallerMixintreats it as an EMI-handled mod and skips its JEI plugin callbacks;mixin/jei/intent/PluginCallerContextMixinre-invokes JEH's own plugin for the runtime phases so the JEI-side reveal/scanner still run under EMI.
JEI gotchas
- JEI caches its ingredient list;
hideIngredients/unhideIngredientsalone do not refresh it. JEHide asks the internal (mezz.jei.gui.ingredients.IngredientFilter)updateHidden()/invalidateCache()to recompute — the instance is captured bymixin/jei/IngredientFilterMixinbecause deep reflection on the privateIngredientFilterApi.ingredientFilterfield is blocked by the module system. Only called when something actually changed; never force a resource reload. - Only
VanillaTypes.ITEM_STACKand (via Forge)ForgeTypes.FLUID_STACKexist; there is no energy ingredient type. Enumerate dynamically viagetRegisteredIngredientTypes().
Environment gotchas
- Edit source files with the dedicated file tools. PowerShell
Set-Contentwrites ANSI by default and corrupts non-ASCII (e.g. Chinese inmods.toml). mods.tomllogoFilemust be a square PNG: HMCL rejects non-square mod icons (|width-height| < 1). Keepsrc/main/resources/justenoughhiding.pngsquare.reference/is gitignored third-party mod source for reading only; never build or commit it.- The mixin config
package(...justenhoughiding.mixin) is owned by Mixin: classes under it may only be mixins listed injustenoughhiding.mixins.json(plus the configplugin). Put helper classes elsewhere (e.g.client/viewer/...), or the game crashes withIllegalClassLoadError: ... is in a defined mixin package ... cannot be referenced directly.
Testing
- Build, then
.\gradlew.bat runDataas a smoke test. - Deliver temporary test files (test packs,
listehiding.json, etc.) inbuild/libs/— the same folder as the built mod jar. - EMI data (
emi:index_stacks,recipe/filters,aliases, ...) is loaded as client resources fromassets/emi/...(see EMI's ownassets/emi/aliases/emi.json), so test files go in a resource pack (<instance>/resourcepacks/), not adata/datapack. - Hand-write test data to
build/libs/listehiding.json, then copy it toconfig/jeh/listehiding.jsonin the user's test instance (a client profile with JEI installed). - In-game entrypoints:
/jeh intents,/jeh list; keybinds default unbound.