- NeoForge 1.21.1 mod using Java 21.
- Core loop: run research at Research Station, then unlock gated
drive_craftingrecipes. - Research is datapack-driven from
data/*/research/*.jsonand loaded on server reload. - Server-authoritative flow: UI click -> packet -> server validation -> timed completion -> recipe ID written to drive NBT.
- Implementation status: complete and playable (build, UI, networking, fluids, compat integrations, guidebook, advancements).
- Key blocks: Research Station (research + unlock recipes), Drive Crafting Table (research-gated crafting), Processing Station (fluid + item processing).
- NeoForge
21.1.219 - GeckoLib
4.7.1 - Java
21, Gradle8.10.2 - Parchment mappings
2024.11.17 - Optional compat (
compileOnly): JEI, EMI, Jade, Patchouli - Item data:
DataComponents.CUSTOM_DATAviaNbtUtil(never raw itemCompoundTag) - Recipe codecs:
MapCodec+StreamCodec - Datapack loading:
SimpleJsonResourceReloadListener - Research persistence:
ResearchSavedDatakeyed byteam:<teamname>with UUID fallback - Menu creation:
IMenuTypeExtension.create(...)withFriendlyByteBuf - Menu sync storage:
SimpleContainerDatamust backContainerData - Research Station tank:
FluidTankcapacity8000mB
- ResearchTableScreen: Main research station GUI with slot display, fluid tank, progress bar, and "View Research Tree" button.
- View modes:
NORMAL(see slots + controls) andTREE(research tree overlay). - Control buttons: Start/Cancel research, View Tree, Wipe Tank (when not researching).
- View modes:
- ResearchTreeScreen: Displays available research nodes in a tree structure.
- Shows prerequisites, tier, costs, completion status.
- Click to select research; "Start Research" button navigates back with selection.
- Control buttons: Back to table, Start Research (when valid research selected).
- GUI packet flow: Client screen -> packet -> server handler -> block entity logic -> menu sync -> client screen update.
Three distinct recipe systems:
researchcube:drive_crafting— Drive Crafting Table recipes (research-locked)- Shaped:
pattern(3-row string array) +key(char → item map) +result - Shapeless:
ingredients(list) +result - Always needs
recipe_idfield matching"researchcube:<filename>" - Requires drive with matching
recipe_idin NBT from research unlock
- Shaped:
researchcube:processing— Processing Station recipes (freely available unless in research pool)- Fields:
inputs,fluid_inputs,outputs,fluid_output(optional),duration - NO
recipe_idfield — ID comes from filename
- Fields:
minecraft:crafting_shaped/minecraft:crafting_shapeless— vanilla crafting (always available)
- Keep all registrations namespaced with
ResearchCubeMod.rl(...)and mod idresearchcube. - Use DeferredRegister in
registry/Mod*classes and wire inResearchCubeMod. - Never perform research start/complete/cancel logic on the client.
- Never write raw
CompoundTagdirectly onItemStack; useNbtUtil. - Drives are never consumed by drive crafting;
recipe_idremains on drive. - Always log silent research validation failures in
tryStartResearch()with[ResearchCube] WARNand reason. - For
ResearchTableMenusync, use writableSimpleContainerDatastorage; no-opset()breaks client state. textures/block/baum.txtis an inside joke. Never delete or modify it.
- Research Station slots:
SLOT_DRIVE=0SLOT_CUBE=1COST_SLOT_START=2SLOT_BUCKET_IN=8SLOT_BUCKET_OUT=9SLOT_IDEA_CHIP=10TOTAL_SLOTS=11
- Item costs are only slots
2..7(iterate fromCOST_SLOT_STARTtoSLOT_BUCKET_INexclusive). - Tank capacity is
TANK_CAPACITY=8000mB. ResearchTableMenudata indexes:DATA_PROGRESS=0(0..1000)DATA_IS_RESEARCHING=1(0/1)DATA_FLUID_AMOUNT=2(0..8000)DATA_FLUID_TYPE=3(0 empty, 1 thinking, 2 pondering, 3 reasoning, 4 imagination)
- Menu buffer pattern: write extra fields in
openMenu(..., bufWriter)and read in same order in menu buffer constructor.
- Completed research is team-shared:
- key format
team:<teamname>when player has scoreboard team - UUID-string fallback when no team
- key format
- Use
ResearchSavedData.getResearchKey(ServerPlayer)consistently.
ResearchTableBlock.useWithoutItemopens menu and writesBlockPosplus completed set for current research key.ResearchTableScreensendsStartResearchPacket(client -> server).StartResearchPacket.handlecallsResearchTableBlockEntity.tryStartResearch(tier/prereq/drive/cost/fluid checks).- On success, costs are consumed and snapshot for refund.
serverTicktracks progress; completion writes weighted recipe ID into drive and records completed research.CancelResearchPackettriggerscancelResearchWithRefund().
- Enforce with
TierUtil.canResearch(cubeTier, driveTier, researchTier):- cube tier must be >= research tier
- drive tier must be == research tier
Order: IRRECOVERABLE, UNSTABLE, BASIC, ADVANCED, PRECISE, FLAWLESS, SELF_AWARE
Capacity by tier:
- IRRECOVERABLE
0 - UNSTABLE
2 - BASIC
4 - ADVANCED
8 - PRECISE
12 - FLAWLESS
16 - SELF_AWARE
-1(unlimited)
Tier colors (getColor()):
- IRRECOVERABLE
0x888888 - UNSTABLE
0xFFFFFF - BASIC
0x55FF55 - ADVANCED
0x5555FF - PRECISE
0xAA00AA - FLAWLESS
0xFFAA00 - SELF_AWARE
0xFF5555
{
"name": "Basic Circuit",
"description": "Shown in tooltips/UI.",
"category": "circuits",
"tier": "BASIC",
"duration": 1200,
"prerequisites": "other_research_id",
"item_costs": [{ "item": "minecraft:iron_ingot", "count": 4 }],
"fluid_cost": { "fluid": "researchcube:thinking_fluid", "amount": 1000 },
"recipe_pool": [
"researchcube:basic_circuit_recipe_1",
{ "id": "researchcube:basic_circuit_recipe_2", "weight": 3 }
]
}- Optional fields:
name,description,category,item_costs,fluid_cost,recipe_pool. prerequisitessupports string, recursiveAND, recursiveOR.recipe_poolsupports string (weight 1) or object withweight.
{
"type": "researchcube:drive_crafting",
"recipe_id": "researchcube:basic_circuit_recipe_1",
"ingredients": [{ "item": "minecraft:iron_ingot" }],
"result": { "id": "minecraft:iron_block", "count": 1 }
}- CRITICAL: Drive crafting recipes MUST have
recipe_idfield matching"researchcube:<filename>" - Drive with matching
recipe_idin NBT is required and returned unchanged - Supports shapeless (ingredient list) and shaped mode (
pattern+key) - Player must have completed research that includes this
recipe_idin itsrecipe_pool
{
"type": "researchcube:processing",
"inputs": [{ "item": "minecraft:iron_ingot", "count": 2 }],
"fluid_inputs": [{ "fluid": "researchcube:thinking_fluid", "amount": 100 }],
"outputs": [{ "item": "researchcube:research_chip", "count": 1 }],
"duration": 200
}- NO
recipe_idfield — recipe ID comes from filename only - Most processing recipes are freely available (no research lock)
- Some may be gated via research
recipe_poolif needed
Registered fluids (source + flowing + bucket item, no placeable liquid block):
researchcube:thinking_fluid(cyan) — for UNSTABLE/BASIC tier researchresearchcube:pondering_fluid(purple) — for ADVANCED tier researchresearchcube:reasoning_fluid(gold) — for PRECISE/FLAWLESS tier researchresearchcube:imagination_fluid(pink) — for SELF_AWARE tier research
Fluid behavior:
- Bucket class:
ResearchFluidBucketItem(stacksTo(1), remainderItems.BUCKET). - Station fills from slot 8, outputs empty bucket to slot 9.
WipeTankPacketdrains the tank.- Both Research Station and Processing Station expose
IFluidHandlercapability.
- Drives:
metadata_unstable,metadata_reclaimed,metadata_enhanced,metadata_elaborate,metadata_cybernetic,metadata_self_aware - Cubes:
cube_unstable,cube_basic,cube_advanced,cube_precise,cube_flawless,cube_self_aware - Blocks:
research_station_item,drive_crafting_table,processing_station - Other:
research_chip,research_book,*_fluid_bucket(thinking/pondering/reasoning/imagination)
src/main/java/com/researchcube/ResearchCubeMod.javasrc/main/java/com/researchcube/block/ResearchTableBlockEntity.javasrc/main/java/com/researchcube/block/ResearchTableBlock.javasrc/main/java/com/researchcube/menu/ResearchTableMenu.javasrc/main/java/com/researchcube/research/ResearchManager.javasrc/main/java/com/researchcube/research/ResearchSavedData.java
src/main/java/com/researchcube/client/screen/ResearchTableScreen.javasrc/main/java/com/researchcube/client/screen/ResearchTreeScreen.java
src/main/java/com/researchcube/recipe/DriveCraftingRecipe.javasrc/main/java/com/researchcube/recipe/ProcessingRecipe.java
src/main/java/com/researchcube/network/StartResearchPacket.javasrc/main/java/com/researchcube/network/CancelResearchPacket.javasrc/main/java/com/researchcube/network/WipeTankPacket.java
src/main/java/com/researchcube/registry/ModFluids.javasrc/main/java/com/researchcube/registry/ModBlocks.javasrc/main/java/com/researchcube/registry/ModItems.java
- Research definitions:
src/main/resources/data/researchcube/research/ - Recipe files:
src/main/resources/data/researchcube/recipe/
- Build:
./gradlew.bat build - Client dev run:
./gradlew.bat runClient - Server dev run:
./gradlew.bat runServer - Data generation:
./gradlew.bat runData - No automated test suite: validate with build + in-game behavior.
Use Conventional Commits:
- format:
type(scope): short imperative summary - preferred types:
feat,fix,refactor,docs,chore,test,build,ci,style,perf - examples:
fix(research): preserve selected research id during screen refreshfeat(processing): add fluid tank sync to station menudocs(readme): clarify datapack research format
- Read
[AI INSTRUCTIONS]at the top before starting work. - Do not touch tasks marked
[DONE]. [MAJOR]and[DANGER]items require explicit user approval.[LOW PRIO]items can be deferred.- If adding AI-created tasks, prefix with
[AI].
- Use
NbtUtil.getRecipeId(ItemStack)to read drive recipe ID - Use
NbtUtil.setRecipeId(ItemStack, ResourceLocation)to write drive recipe ID - Use
TierUtil.getCubeTier(ItemStack)/getDriveTier(ItemStack)for tier checks
- Check drive tier matches research tier exactly
- Check cube tier >= research tier
- Verify prerequisites are completed (check saved data)
- Verify sufficient item costs in slots 2-7
- Verify sufficient fluid in tank (matches research fluid type)
- If any check fails, log warning and return false
- Always use
SimpleContainerDatawith writable backing storage - Update
containerData.set(index, value)on server side - Client reads via
containerData.get(index)in screen - Never use no-op storage that ignores
set()calls
- Packets arrive on network thread; always use
source.enqueueWork(() -> {...}) - Validate player permissions and world state before proceeding
- Return
InteractionResult.SUCCESSonly after successful operation - Log failures for debugging
- Use
ResearchCubeMod.rl("path")for mod namespace - Recipe IDs must match research
recipe_poolentries exactly - Research IDs come from filename (e.g.,
basic_circuit.json→researchcube:basic_circuit)
- Research validation failures are logged as
[ResearchCube] WARNwith reason - Check server logs for packet handling issues
- Use
/researchcube debugcommands to inspect saved data (if implemented) - Tank sync issues: verify
DATA_FLUID_AMOUNTandDATA_FLUID_TYPEare updated - Drive not working: verify
recipe_idexists in drive NBT and matches recipe file