### Build Project (Bash) Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Clone the AnvilLib repository and build the project using Gradle wrapper scripts. Ensure you have Java 21+ and NeoForge 21.1.x installed. ```bash # Clone repository git clone https://github.com/Anvil-Dev/AnvilLib.git cd AnvilLib # Build on macOS / Linux ./gradlew build # Build on Windows (PowerShell / CMD) gradlew.bat build ``` -------------------------------- ### Create Example Payload with StreamCodec Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Define a record with an Item and an integer count, and create a StreamCodec for it using composite and utility codecs. ```java import dev.architectury.networking.streamcodec.StreamCodec; import dev.architectury.networking.streamcodec.StreamCodecUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.world.item.Item; public record ExamplePayload(Item item, int count) { public static final StreamCodec STREAM_CODEC = StreamCodec.composite( StreamCodecUtil.ITEM, ExamplePayload::item, ByteBufCodecs.VAR_INT, ExamplePayload::count, ExamplePayload::new ); } ``` -------------------------------- ### Register a SimpleController for Dynamic Multiblocks Source: https://context7.com/anvil-dev/anvillib/llms.txt Register a SimpleController to handle formation and unformation events for a dynamic multiblock. This example shows how to trigger a block state change upon formation. ```java // 2. Register the controller in your mod initialiser public static void init() { ControllerRecord.register(new SimpleController( Blocks.DISPENSER, FUSION_REACTOR.location() ) { @Override public void onFormed(Level level, MultiblockState state) { // Fires on the server when all blocks match BlockPos center = state.getControllerPos(); level.setBlock(center, Blocks.DISPENSER.defaultBlockState() .setValue(DispenserBlock.TRIGGERED, true), 3); } @Override public void onUnformed(Level level, MultiblockState state) { // Fires when any member block is broken or changed } }); } ``` -------------------------------- ### Pre-built Stream Codecs and Composite Overloads Source: https://context7.com/anvil-dev/anvillib/llms.txt Utilize pre-built StreamCodec constants for common game types like Item, Block, and EntityType. The composite factory supports up to 16 fields, exceeding vanilla's limit of 6. Examples also show wrapping Codecs via NBT, creating enum codecs, and bridging Codec to StreamCodec. ```java import dev.anvilcraft.lib.v2.codec.StreamCodecUtil; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.ByteBufCodecs; // Pre-built stream codecs StreamCodec ITEM = StreamCodecUtil.ITEM; StreamCodec BLOCK = StreamCodecUtil.BLOCK; StreamCodec> ENT = StreamCodecUtil.ENTITY; StreamCodec BS = StreamCodecUtil.BLOCK_STATE; // 7-field composite (vanilla StreamCodec.composite only goes to 6) record BigPayload(Item item, Block block, int x, int y, int z, boolean flag, String name) { static final StreamCodec STREAM_CODEC = StreamCodecUtil.composite( StreamCodecUtil.ITEM, BigPayload::item, StreamCodecUtil.BLOCK, BigPayload::block, ByteBufCodecs.VAR_INT, BigPayload::x, ByteBufCodecs.VAR_INT, BigPayload::y, ByteBufCodecs.VAR_INT, BigPayload::z, ByteBufCodecs.BOOL, BigPayload::flag, ByteBufCodecs.STRING_UTF8, BigPayload::name, BigPayload::new ); } // Wrap any Codec as StreamCodec via NBT StreamCodec viaCodec = StreamCodecUtil.nbtWrapped(MyData.CODEC); // Enum by ordinal (compact varint on the wire) StreamCodec enumCodec = StreamCodecUtil.enumStreamCodec(OperationMode.class); // Registry-aware Codec → StreamCodec bridge (uses RegistryOps + NBT) StreamCodec bridged = StreamCodecUtil.codec2Stream(MyData.CODEC); ``` -------------------------------- ### Register Network Packets Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Register network packet handlers using the NetworkRegistrar. This example shows registration for a specific mod ID and protocol version. ```java @SubscribeEvent public static void onRegisterPayload(RegisterPayloadHandlersEvent event) { PayloadRegistrar registrar = event.registrar("1"); NetworkRegistrar.register(registrar, "my_mod"); } ``` -------------------------------- ### Build Project with Gradle Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.md Commands to clone the AnvilLib repository and build the project using Gradle wrapper scripts. Use the appropriate script for your operating system (macOS/Linux or Windows). ```bash # 克隆仓库 git clone https://github.com/Anvil-Dev/AnvilLib.git cd AnvilLib # macOS / Linux 构建 ./gradlew build # Windows PowerShell / CMD 构建 gradlew.bat build ``` -------------------------------- ### Create and Open Wheel Menu Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.md Builds a wheel menu with custom actions and opens it using the TAP interaction mode. The menu can be configured with a specific number of slots per page and custom renderers for actions. ```java WheelMenuModel model = WheelMenuBuilder.create() .slotsPerPage(8) .action( "heal", Component.literal("Heal"), iconRenderer, ctx -> { } ) .build(); WheelScreenController controller = new WheelScreenController(); controller.openTap(model); // HOLD 模式:按键按下/松开边沿分别调用 controller.onHoldKeyPressed(model); controller.onHoldKeyReleased(); ``` -------------------------------- ### Create and Open a Radial Menu Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Builds a radial menu model with specified slots and actions, then opens it using the WheelScreenController in TAP mode. Supports custom icon renderers and callbacks. ```java WheelMenuModel model = WheelMenuBuilder.create() .slotsPerPage(8) .action("heal", Component.literal("Heal"), iconRenderer, ctx -> {}) .build(); WheelScreenController controller = new WheelScreenController(); controller.openTap(model); // HOLD mode: call on key press/release edges controller.onHoldKeyPressed(model); controller.onHoldKeyReleased(); ``` -------------------------------- ### Build and Open Radial Wheel Menu with WheelMenuBuilder and WheelScreenController Source: https://context7.com/anvil-dev/anvillib/llms.txt Build a radial menu model using WheelMenuBuilder and open it with WheelScreenController. Supports TAP mode for normal screens and HOLD mode for key-press activation. ```java import dev.anvilcraft.lib.v2.wheel.api.*; import dev.anvilcraft.lib.v2.wheel.client.input.WheelScreenController; // 1. Build the menu model (typically stored as a static field) WheelMenuModel MY_WHEEL = WheelMenuBuilder.create() .slotsPerPage(8) // entries per ring page, default 8 .deadZone(20) // pixel radius that counts as "centre / no selection" .action( "heal", Component.translatable("my_mod.wheel.heal"), ctx -> PacketDistributor.sendToServer(new HealRequestPacket()) ) .action( "home", Component.translatable("my_mod.wheel.home"), iconRenderer, // optional custom WheelEntryRenderer ctx -> PacketDistributor.sendToServer(new TeleportHomePacket()) ) .submenu( "tools", Component.translatable("my_mod.wheel.tools"), sub -> sub .action("pick", Component.literal("Pickaxe"), ctx -> selectTool("pick")) .action("shovel", Component.literal("Shovel"), ctx -> selectTool("shovel")) ) .build(); // 2. Create a controller per key binding (usually a static field on client init) WheelScreenController WHEEL_CTRL = new WheelScreenController(); // 3. Open as a tap screen (normal open/close) WHEEL_CTRL.openTap(MY_WHEEL); // 4. Or bind to a hold key (open on press, fire selected action on release) // In your key input handler: if (WHEEL_KEY.consumeClick()) { WHEEL_CTRL.onHoldKeyPressed(MY_WHEEL); // call on key-down edge } if (!WHEEL_KEY.isDown()) { WHEEL_CTRL.onHoldKeyReleased(); // call on key-up edge } ``` -------------------------------- ### Define and Register Common Configuration Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Use annotations to define configuration fields and their properties. Register the configuration class using ConfigManager. ```java import dev.architectury.config.ConfigManager; import dev.architectury.config.ModConfig; import dev.architectury.config.annotations.Comment; import dev.architectury.config.annotations.Config; import dev.architectury.config.annotations.BoundedDiscrete; import dev.architectury.config.annotations.CollapsibleObject; @Config(name = "my_mod", type = ModConfig.Type.COMMON) public class MyModConfig { @Comment("Enable debug mode") public boolean debugMode = false; @Comment("Maximum count") @BoundedDiscrete(min = 1, max = 100) public int maxCount = 10; } // Register configuration MyModConfig config = ConfigManager.register("my_mod", MyModConfig::new); ``` -------------------------------- ### Declare and Register Mod Configuration with @Config Source: https://context7.com/anvil-dev/anvillib/llms.txt Annotate a POJO with `@Config` to register it as a mod configuration. `ConfigManager.register` handles wiring into the NeoForge config system and client-side screen registration. Values are live-reloading. ```java import dev.anvilcraft.lib.v2.config.*; import net.neoforged.fml.config.ModConfig; // 1. Define your config class @Config(name = "my_mod", type = ModConfig.Type.COMMON) public class MyModConfig { @Comment("Enable verbose debug output") public boolean debugMode = false; @Comment("Maximum number of active machines") @BoundedDiscrete(min = 1, max = 256) public int maxMachines = 16; @Comment("Machine operation mode") public OperationMode mode = OperationMode.NORMAL; @CollapsibleObject public NetworkSettings network = new NetworkSettings(); public static class NetworkSettings { @Comment("Sync interval in ticks") @BoundedDiscrete(min = 1, max = 200) public int syncInterval = 20; } public enum OperationMode { NORMAL, FAST, ECO } } // 2. Register during mod construction (e.g. in your @Mod class constructor) public class MyMod { public static MyModConfig CONFIG; public MyMod(IEventBus bus) { CONFIG = ConfigManager.register("my_mod", MyModConfig::new); // CONFIG is now live-reloading: values update when the config file changes. } } // 3. Read values anywhere if (MyMod.CONFIG.debugMode) { LOGGER.info("Machines allowed: {}", MyMod.CONFIG.maxMachines); } ``` -------------------------------- ### Utilize Pre-built Mojang Codec Instances with CodecUtil Source: https://context7.com/anvil-dev/anvillib/llms.txt CodecUtil provides ready-made `Codec` constants for common game types and factory helpers for enums, optionals, ingredient lists, and block-state properties. Use these for serialization and deserialization tasks. ```java import dev.anvilcraft.lib.v2.codec.CodecUtil; import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.Codec; // Built-in codecs Codec ITEM = CodecUtil.ITEM; // serialized as "minecraft:diamond" Codec BLOCK = CodecUtil.BLOCK; // serialized as "minecraft:stone" Codec> ENTITY = CodecUtil.ENTITY; // serialized as ResourceLocation Codec NUMBER = CodecUtil.NUMBER_PROVIDER; // accepts int shorthand or full provider JSON // Enum by ordinal index (compact) Codec byOrdinal = CodecUtil.enumCodecInInt(MyEnum.class); // Enum by lowercase name Codec byName = CodecUtil.enumCodecInLowerName(MyEnum.class); // Optional with explicit isPresent flag (useful for network codecs) Codec> optStack = CodecUtil.createOptionalCodec(ItemStack.CODEC); // Ingredient list with size cap (for recipe serializers) MapCodec> ingredients = CodecUtil.createIngredientListCodec("ingredients", 9, "my_recipe"); // BlockState with full property encoding MapCodec bsCodec = CodecUtil.BLOCK_STATE_MAP_CODEC; // Composite record using CodecUtil types record MachineData(Item input, Block outputBlock, int count) { static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( CodecUtil.ITEM.fieldOf("input").forGetter(MachineData::input), CodecUtil.BLOCK.fieldOf("output_block").forGetter(MachineData::outputBlock), Codec.INT.fieldOf("count").forGetter(MachineData::count) ).apply(i, MachineData::new)); } ``` -------------------------------- ### AnvilLib Gradle Dependencies (Kotlin) Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.md Specifies how to include AnvilLib in a Gradle project using the Kotlin DSL. It shows how to add the central repository and declare dependencies for the full library or individual modules. ```kotlin repositories { mavenCentral() // 本项目已经上传至 Maven Central } dependencies { // 完整库 implementation("dev.anvilcraft.lib:anvillib-neoforge-1.21.1:2.0.0") // 或按需引入单独模块 implementation("dev.anvilcraft.lib:anvillib-config-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-codec-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-integration-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-network-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-recipe-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-moveable-entity-block-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-multiblock-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-registrum-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-util-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-wheel-neoforge-1.21.1:2.0.0") } ``` -------------------------------- ### General-purpose utility functions Source: https://context7.com/anvil-dev/anvillib/llms.txt Util provides null-safe casting, environment checks (client/server), and a tap-style 'run' helper for chaining operations. ```java import dev.anvilcraft.lib.v2.util.Util; // Safe cast returning Optional Optional mbe = Util.castSafely(level.getBlockEntity(pos), MyBlockEntity.class); mbe.ifPresent(be -> be.doSomething()); // Unchecked cast (throws ClassCastException on mismatch) MyBlockEntity be = Util.cast(level.getBlockEntity(pos)); // Run a consumer and return the original value (tap pattern) ItemStack configured = Util.run(new ItemStack(Items.DIAMOND), stack -> { stack.set(DataComponents.CUSTOM_NAME, Component.literal("Star Diamond")); }); // Environment checks if (Util.isClient()) { /* client-only logic */ } if (Util.isServer()) { /* server thread logic */ } // Check if another mod is loaded if (Util.isLoaded("jei")) { /* JEI-specific code */ } ``` -------------------------------- ### AnvilLib Gradle Dependencies (Groovy) Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.md Specifies how to include AnvilLib in a Gradle project using the Groovy DSL. It shows how to add the central repository and declare dependencies for the full library or individual modules. ```groovy repositories { mavenCentral() // 本项目已经上传至 Maven Central } dependencies { // 完整库 implementation "dev.anvilcraft.lib:anvillib-neoforge-1.21.1:2.0.0" // 或按需引入单独模块 implementation "dev.anvilcraft.lib:anvillib-config-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-codec-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-integration-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-network-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-recipe-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-moveable-entity-block-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-multiblock-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-registrum-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-util-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-wheel-neoforge-1.21.1:2.0.0" } ``` -------------------------------- ### Register Item with Registrum Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.md Demonstrates registering a new item with custom properties using the Registrum module. Ensure Registrum is initialized with your mod ID. ```java public static final Registrum REGISTRUM = Registrum.create("my_mod"); public static final RegistryEntry MY_ITEM = REGISTRUM .item("my_item", Item::new) .properties(p -> p.stacksTo(16)) .register(); ``` -------------------------------- ### Declare Mod Integration with Version Matching Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Annotate a class with @Integration to specify the mod ID and version range for integration. The init method contains integration logic. ```java import dev.architectury.integration.Integration; @Integration(value = "jei", version = "[19.0,)") public class JEIIntegration { public void init() { // JEI integration logic } } ``` -------------------------------- ### VoxelShape composition with ShapeUtil Source: https://context7.com/anvil-dev/anvillib/llms.txt ShapeUtil simplifies building complex block collision and outline shapes by merging, cutting, rotating, and mirroring VoxelShapes and AABBs. ```java import dev.anvilcraft.lib.v2.util.ShapeUtil; import net.minecraft.core.Direction; // Merge multiple AABB boxes into one VoxelShape VoxelShape MY_SHAPE = ShapeUtil.merge( new AABB(0, 0, 0, 16, 4, 16), // base slab (16x coords) new AABB(4, 4, 4, 12, 16, 12) // central pillar ); // Cut a hole out of a shape VoxelShape HOLLOW_BOX = ShapeUtil.cut( new AABB(0, 0, 0, 16, 16, 16), // full cube new AABB(2, 2, 2, 14, 14, 14) // cavity ); // Rotate a shape 90° counter-clockwise around Y VoxelShape ROTATED_NORTH = ShapeUtil.rotate( Direction.Axis.Y, 90f, MY_SHAPE ); // Mirror a shape along the X axis VoxelShape MIRRORED = ShapeUtil.mirror(Direction.Axis.X, MY_SHAPE); // Convert AABB to VoxelShape (equivalent to Block.box but accepts AABB) VoxelShape box = ShapeUtil.box(new AABB(1, 0, 1, 15, 12, 15)); ``` -------------------------------- ### Multiblock Module Source: https://context7.com/anvil-dev/anvillib/llms.txt Define dynamic multiblock structures using MultiblockDefinition and ControllerRecord. Register definitions as datapack objects and bind controllers to handle formation and unformation events. ```APIDOC ## Multiblock Module ### `MultiblockDefinition` + `ControllerRecord` — Dynamic multiblock structures Define a multiblock structure via `MultiblockDefinition.seriaBuilder()` (layer-string notation) or the programmatic `builder()` API, register it as a datapack object, then bind a `SimpleController` (or custom `IController`) to it. The `DynamicMultiblockManager` handles formation/unformation events server-side. #### Registering a Multiblock Definition ##### Using `seriaBuilder()` (Layer-string notation) ```java import dev.anvilcraft.lib.v2.multiblock.dynamic.definition.MultiblockDefinition; import dev.anvilcraft.lib.v2.multiblock.dynamic.controller.ControllerRecord; import dev.anvilcraft.lib.v2.multiblock.dynamic.controller.SimpleController; import dev.anvilcraft.lib.v2.multiblock.dynamic.MultiblockState; import net.minecraft.core.registries.BootstrapContext; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.core.Registry; import net.minecraft.world.level.block.Blocks; import net.minecraft.core.Vec3i; // 1. Register the definition in your datapack bootstrap (RegistryBootstrap method) public static final ResourceKey FUSION_REACTOR = ResourceKey.create(Registry.MULTIBLOCK_DEFINITION_REGISTRY, // Assuming LibRegistries.MULTIBLOCK_DEFINITION_REGISTRY_KEY is Registry.MULTIBLOCK_DEFINITION_REGISTRY ResourceLocation.fromNamespaceAndPath("my_mod", "fusion_reactor")); public static void bootstrap(BootstrapContext ctx) { MultiblockDefinition definition = MultiblockDefinition.seriaBuilder() .layer( // top layer (y=1) " # ", "#0#", " # " ) .layer( // bottom layer (y=0) "###", "###", "###" ) .mapController(Blocks.DISPENSER) // '0' char = controller at Vec3i.ZERO .map('#', Blocks.IRON_BLOCK) .build(); ctx.register(FUSION_REACTOR, definition); } ``` ##### Using `builder()` (Programmatic API) ```java // Programmatic builder alternative MultiblockDefinition altDef = MultiblockDefinition.builder() .addController(Blocks.DISPENSER) // Vec3i.ZERO .add(new Vec3i(1, 0, 0), Blocks.IRON_BLOCK) .add(new Vec3i(-1, 0, 0), Blocks.IRON_BLOCK) .build(); ``` #### Registering a Controller ```java import dev.anvilcraft.lib.v2.multiblock.dynamic.definition.MultiblockDefinition; import dev.anvilcraft.lib.v2.multiblock.dynamic.controller.ControllerRecord; import dev.anvilcraft.lib.v2.multiblock.dynamic.controller.SimpleController; import dev.anvilcraft.lib.v2.multiblock.dynamic.MultiblockState; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.DispenserBlock; // 2. Register the controller in your mod initialiser public static void init() { ControllerRecord.register(new SimpleController( Blocks.DISPENSER, FUSION_REACTOR.location() // Assuming FUSION_REACTOR is accessible here ) { @Override public void onFormed(Level level, MultiblockState state) { // Fires on the server when all blocks match BlockPos center = state.getControllerPos(); level.setBlock(center, Blocks.DISPENSER.defaultBlockState() .setValue(DispenserBlock.TRIGGERED, true), 3); } @Override public void onUnformed(Level level, MultiblockState state) { // Fires when any member block is broken or changed } }); } ``` ``` -------------------------------- ### Define Dynamic Multiblock Structures with Layer Strings Source: https://context7.com/anvil-dev/anvillib/llms.txt Define a multiblock structure using layer-string notation. Register it as a datapack object and map controllers and blocks. This method is suitable for complex or predefined structures. ```java import dev.anvilcraft.lib.v2.multiblock.dynamic.definition.MultiblockDefinition; import dev.anvilcraft.lib.v2.multiblock.dynamic.controller.ControllerRecord; import dev.anvilcraft.lib.v2.multiblock.dynamic.controller.SimpleController; import dev.anvilcraft.lib.v2.multiblock.dynamic.MultiblockState; // 1. Register the definition in your datapack bootstrap (RegistryBootstrap method) public static final ResourceKey FUSION_REACTOR = ResourceKey.create(LibRegistries.MULTIBLOCK_DEFINITION_REGISTRY_KEY, ResourceLocation.fromNamespaceAndPath("my_mod", "fusion_reactor")); public static void bootstrap(BootstrapContext ctx) { MultiblockDefinition definition = MultiblockDefinition.seriaBuilder() .layer( // top layer (y=1) " # ", "#0#", " # " ) .layer( // bottom layer (y=0) "###", "###", "###" ) .mapController(Blocks.DISPENSER) // '0' char = controller at Vec3i.ZERO .map('#', Blocks.IRON_BLOCK) .build(); ctx.register(FUSION_REACTOR, definition); } ``` -------------------------------- ### Define Dynamic Multiblock Structures Programmatically Source: https://context7.com/anvil-dev/anvillib/llms.txt Define a multiblock structure using a programmatic builder API. This offers more flexibility for dynamic generation or complex relationships between blocks. ```java MultiblockDefinition altDef = MultiblockDefinition.builder() .addController(Blocks.DISPENSER) // Vec3i.ZERO .add(new Vec3i(1, 0, 0), Blocks.IRON_BLOCK) .add(new Vec3i(-1, 0, 0), Blocks.IRON_BLOCK) .build(); ``` -------------------------------- ### Gradle Dependencies (Kotlin DSL) Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Add AnvilLib as a dependency in your Gradle project using Kotlin DSL. You can include the full library or individual modules. ```kotlin repositories { mavenCentral() // This project is already uploaded to Maven Central } dependencies { implementation("dev.anvilcraft.lib:anvillib-neoforge-1.21.1:2.0.0") // Optional single-module example implementation("dev.anvilcraft.lib:anvillib-config-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-codec-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-integration-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-moveable-entity-block-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-multiblock-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-network-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-recipe-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-registrum-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-util-neoforge-1.21.1:2.0.0") implementation("dev.anvilcraft.lib:anvillib-wheel-neoforge-1.21.1:2.0.0") } ``` -------------------------------- ### Register Multiblock Controller Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Register a multiblock controller when initializing the mod. Includes callbacks for when the multiblock is formed or unformed. ```java public static void init() { ControllerRecord.register(new SimpleController( Blocks.DISPENSER, RESOURCE_KEY // The resource key of this multiblock ) { @Override public void onFormed(Level level, MultiblockState state) { // when formed... } @Override public void onUnformed(Level level, MultiblockState state) { // when unformed... } }); } ``` -------------------------------- ### Gradle Dependencies (Groovy DSL) Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Add AnvilLib as a dependency in your Gradle project using Groovy DSL. You can include the full library or individual modules. ```groovy repositories { mavenCentral() // This project is already uploaded to Maven Central } dependencies { // Full library implementation "dev.anvilcraft.lib:anvillib-neoforge-1.21.1:2.0.0" // Or import individual modules as needed implementation "dev.anvilcraft.lib:anvillib-config-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-codec-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-integration-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-moveable-entity-block-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-multiblock-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-network-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-recipe-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-registrum-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-util-neoforge-1.21.1:2.0.0" implementation "dev.anvilcraft.lib:anvillib-wheel-neoforge-1.21.1:2.0.0" } ``` -------------------------------- ### Register Multiblock Definition with Builder Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Register a multiblock definition during datapack bootstrapping using the builder API. Maps controller blocks and structural components. ```java public static void bootstrap(BootstrapContext ctx) { // Define a simple multiblock from a builder MultiblockDefinition furnaceArray = MultiblockDefinition.seriaBuilder() .layer( // bottom layer "###", "#0#", "###" ) .mapController(Blocks.DISPENSER) .map('#', Blocks.STONE) .build(); ctx.register( RESOURCE_KEY, // The resource key of this multiblock furnaceArray ); } ``` -------------------------------- ### Registering game objects with Registrum Source: https://context7.com/anvil-dev/anvillib/llms.txt Use Registrum for chain-style registration of items, blocks, block entities, fluids, and menus. It integrates with NeoForge data-gen for automatic language entries, loot tables, and blockstate models. ```java import dev.anvilcraft.lib.v2.registrum.Registrum; import dev.anvilcraft.lib.v2.registrum.util.entry.*; public class MyModRegistration { public static final Registrum REG = Registrum.create("my_mod"); // Register a plain item public static final ItemEntry WIDGET = REG.item("widget", Item::new) .properties(p -> p.stacksTo(16)) .lang("Fancy Widget") // auto-adds en_us lang entry .register(); // Register a block + its block item public static final BlockEntry MACHINE_BLOCK = REG.block("machine", MyMachineBlock::new) .properties(p -> p.strength(3.5f).requiresCorrectToolForDrops()) .item() // creates a BlockItem automatically .lang("My Machine") .build() .register(); // Register a block entity tied to a block public static final BlockEntityEntry MACHINE_BE = REG.blockEntity("machine", MyMachineBlockEntity::new) .validBlock(() -> MACHINE_BLOCK) .register(); // Creative tab population static { REG.addToTab(CreativeModeTabs.FUNCTIONAL_BLOCKS, () -> WIDGET.get().getDefaultInstance()); REG.addToTab(CreativeModeTabs.FUNCTIONAL_BLOCKS, () -> MACHINE_BLOCK.get().asItem().getDefaultInstance()); } } ``` -------------------------------- ### Implement Moveable Entity Block Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Implement the IMoveableEntityBlock interface to allow blocks with block entities to be pushed by pistons while preserving their data. ```java import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import dev.architectury.block.IMoveableEntityBlock; public class MyBlock extends Block implements IMoveableEntityBlock { @Override public CompoundTag clearData(Level level, BlockPos pos) { // Return block entity data to preserve BlockEntity be = level.getBlockEntity(pos); return be != null ? be.saveWithoutMetadata(level.registryAccess()) : new CompoundTag(); } @Override public void setData(Level level, BlockPos pos, CompoundTag nbt) { // Restore block entity data at new position BlockEntity be = level.getBlockEntity(pos); if (be != null) { be.loadAdditional(nbt, level.registryAccess()); } } } ``` -------------------------------- ### Build In-World Recipes with InWorldRecipeBuilder Source: https://context7.com/anvil-dev/anvillib/llms.txt Use InWorldRecipeBuilder for a fluent API to compose InWorldRecipes from triggers, item/block predicates, and outcomes. Recipes are data-driven and fire when the trigger occurs and predicates match. ```java import dev.anvilcraft.lib.v2.recipe.builder.InWorldRecipeBuilder; // In a data generator's RecipeProvider: InWorldRecipeBuilder.compatible(MyTriggers.EXPLOSION_TRIGGER.get()) // Icon shown in JEI / recipe viewers .icon(new ItemStack(Items.DIAMOND)) // Require a specific item entity at the trigger location .hasItem(Items.NETHERITE_INGOT) // Require a specific block one unit below .hasBlock(0, -1, 0, Blocks.CRYING_OBSIDIAN) // Consume the netherite ingot and spawn a diamond .spawnItem(new ItemStack(Items.DIAMOND, 4)) // Replace the block below with obsidian .setBlock(0, -1, 0, Blocks.OBSIDIAN.defaultBlockState()) // Optional: set explicit priority (higher = checked first) .priority(100) .unlockedBy("has_netherite", has(Items.NETHERITE_INGOT)) .group("transmutation") .save(recipeOutput, ResourceLocation.fromNamespaceAndPath("my_mod", "netherite_to_diamond")); // Incompatible builder: conflicting predicates must ALL fail (none match) for the recipe to trigger InWorldRecipeBuilder.incompatible(MyTriggers.ITEM_DROP_TRIGGER.get()) .hasItem(Items.DIRT) .hasBlock(Blocks.GRASS_BLOCK) .spawnItem(new ItemStack(Items.GRASS)) .save(recipeOutput, ResourceLocation.fromNamespaceAndPath("my_mod", "dirt_on_grass")); ``` -------------------------------- ### Main Thread - Initiating Asynchronous Checks Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/module.multiblock/ASYNC_MULTIBLOCK_CHECK_PLAN.md This snippet shows the main thread's role in collecting candidates and submitting snapshots for asynchronous checking. It limits the number of checks performed per tick. ```java if (!shouldCheck) return; List candidates = collectCandidates(maxChecksPerTick); for (state : candidates) { Snapshot snap = buildSnapshot(level, state); // 只读 BlockState 列表 submitSnapshotForCheck(level, snap); } ``` -------------------------------- ### Implement IMoveableEntityBlock for Piston-Pushable Blocks Source: https://context7.com/anvil-dev/anvillib/llms.txt Implement this interface on EntityBlocks to allow pistons to push them while preserving BlockEntity data. Override clearData to extract NBT before the push and setData to restore it at the destination. ```java import dev.anvilcraft.lib.v2.piston.IMoveableEntityBlock; public class MyMachineBlock extends Block implements IMoveableEntityBlock { public MyMachineBlock(Properties props) { super(props); } @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new MyMachineBlockEntity(pos, state); } /** * Called just before the block is moved: extract and return the data to preserve. * The block entity will be removed from its current position after this call. */ @Override public CompoundTag clearData(Level level, BlockPos pos) { BlockEntity be = level.getBlockEntity(pos); if (be instanceof MyMachineBlockEntity machine) { CompoundTag tag = machine.saveWithoutMetadata(level.registryAccess()); machine.clearInventory(); // prevent item duplication return tag; } return new CompoundTag(); } /** * Called after the block has arrived at its new position: restore the saved data. */ @Override public void setData(Level level, BlockPos pos, CompoundTag nbt) { if (level.getBlockEntity(pos) instanceof MyMachineBlockEntity machine) { machine.loadAdditional(nbt, level.registryAccess()); machine.setChanged(); } } } ``` -------------------------------- ### Auto-Register Network Packets with @Network Source: https://context7.com/anvil-dev/anvillib/llms.txt Annotate package-info.java with @Network to auto-register IPacket implementations. Call NetworkRegistrar.register in your event handler to scan and register packets. ```java // --- package-info.java in your packet package --- @Network(protocol = PacketProtocol.PLAY) package com.example.mymod.network.packets; import dev.anvilcraft.lib.v2.network.register.Network; import dev.anvilcraft.lib.v2.network.register.PacketProtocol; // --- A server→client packet --- // CustomPayloadType requires a static TYPE and STREAM_CODEC field by NeoForge convention public record SyncDataPacket(int value, String message) implements IClientboundPacket { public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath("my_mod", "sync_data")); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( ByteBufCodecs.VAR_INT, SyncDataPacket::value, ByteBufCodecs.STRING_UTF8, SyncDataPacket::message, SyncDataPacket::new ); @Override public CustomPacketPayload.Type type() { return TYPE; } @Override public void handleOnClient(Player player) { // runs on the client game thread ClientStorage.update(this.value, this.message); } } // --- A client→server packet --- public record RequestActionPacket(ResourceLocation actionId) implements IServerboundPacket { public static final CustomPacketPayload.Type TYPE = ...; public static final StreamCodec STREAM_CODEC = ...; @Override public CustomPacketPayload.Type type() { return TYPE; } @Override public void handleOnServer(Player player) { // player is a ServerPlayer here ServerLogic.perform((ServerPlayer) player, actionId); } } // --- A bidirectional packet (same logic on both sides) --- public record PingPacket(long timestamp) implements IInsensitiveBiPacket { public static final CustomPacketPayload.Type TYPE = ...; public static final StreamCodec STREAM_CODEC = ...; @Override public CustomPacketPayload.Type type() { return TYPE; } @Override public void handleOnBothSide(Player player) { LOGGER.info("Ping from {} at {}", player.getName().getString(), timestamp); } } // --- Register everything from your mod event bus --- @SubscribeEvent public static void onRegisterPayloads(RegisterPayloadHandlersEvent event) { PayloadRegistrar registrar = event.registrar("1"); NetworkRegistrar.register(registrar, "my_mod"); // SyncDataPacket → playToClient, RequestActionPacket → playToServer, // PingPacket → playBidirectional — all resolved automatically. } ``` -------------------------------- ### Worker Thread - Performing Off-Thread Predicate Tests Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/module.multiblock/ASYNC_MULTIBLOCK_CHECK_PLAN.md This snippet illustrates the work done by a worker thread, testing block states against predicates using only the provided snapshot data. It returns false immediately if any test fails. ```java boolean testSnapshot(Snapshot snap) { for (pos, blockState) in snap.blocks { if (!predicate.testWithBlockState(blockState)) return false; } return true; } ``` -------------------------------- ### Construct VoxelShape using ShapeUtil Source: https://github.com/anvil-dev/anvillib/blob/dev/1.21.1/README.en.md Merges two Axis-Aligned Bounding Boxes (AABBs) to create a complex VoxelShape. Useful for defining custom block hitboxes. ```java VoxelShape shape = ShapeUtil.merge( new AABB(0, 0, 0, 10, 10, 10), new AABB(1, 10, 1, 9, 16, 9) ); ``` -------------------------------- ### Conditional Mod Compatibility Loading with @Integration Source: https://context7.com/anvil-dev/anvillib/llms.txt Annotate classes with @Integration to declare compatibility bridges for other mods. The IntegrationManager automatically handles checking for mod presence and version ranges at startup, simplifying conditional loading logic. ```java import dev.anvilcraft.lib.v2.integration.Integration; import dev.anvilcraft.lib.v2.integration.IntegrationManager; import dev.anvilcraft.lib.v2.integration.IntegrationType; // 1. Define an integration class — annotated, no special supertype required. // The class must have a no-arg constructor. @Integration( value = "jei", version = "[19.0,)", // Maven version range; "*" = any version type = {IntegrationType.CLIENT} // Only load on CLIENT dist ) public class JEIIntegration { // Called during common mod setup when JEI >= 19.0 is present on client public void init() { // register JEI categories, recipe handlers, etc. } // Optional client-only initialisation hook public void initClient() { } } // 2. Create and drive the manager from your mod initialiser public class MyMod { private final IntegrationManager integrationManager; public MyMod(IEventBus bus) { this.integrationManager = new IntegrationManager("my_mod"); this.integrationManager.compileContent(); // scans annotations bus.addListener(this::onCommonSetup); bus.addListener(this::onClientSetup); } private void onCommonSetup(FMLCommonSetupEvent event) { integrationManager.loadAllIntegrations(); // loads SERVER + CLIENT integrations } private void onClientSetup(FMLClientSetupEvent event) { integrationManager.loadAllClientIntegrations(); // loads CLIENT-only integrations } } ``` -------------------------------- ### IMoveableEntityBlock Source: https://context7.com/anvil-dev/anvillib/llms.txt Implement this interface on an EntityBlock to allow pistons to push it while preserving its BlockEntity data. Override clearData to extract NBT before the push and setData to restore it at the destination. ```APIDOC ## IMoveableEntityBlock ### Description Implement `IMoveableEntityBlock` on any `EntityBlock` to allow pistons to push it while preserving its `BlockEntity` data. Override `clearData` to extract NBT before the push and `setData` to restore it at the destination. ### Methods #### `clearData(Level level, BlockPos pos)` - **Description**: Called just before the block is moved: extract and return the data to preserve. The block entity will be removed from its current position after this call. - **Returns**: `CompoundTag` containing the block entity data. #### `setData(Level level, BlockPos pos, CompoundTag nbt)` - **Description**: Called after the block has arrived at its new position: restore the saved data. - **Parameters**: - `nbt` (CompoundTag) - The data to restore. ### Example Usage ```java import dev.anvilcraft.lib.v2.piston.IMoveableEntityBlock; public class MyMachineBlock extends Block implements IMoveableEntityBlock { public MyMachineBlock(Properties props) { super(props); } @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new MyMachineBlockEntity(pos, state); } /** * Called just before the block is moved: extract and return the data to preserve. * The block entity will be removed from its current position after this call. */ @Override public CompoundTag clearData(Level level, BlockPos pos) { BlockEntity be = level.getBlockEntity(pos); if (be instanceof MyMachineBlockEntity machine) { CompoundTag tag = machine.saveWithoutMetadata(level.registryAccess()); machine.clearInventory(); // prevent item duplication return tag; } return new CompoundTag(); } /** * Called after the block has arrived at its new position: restore the saved data. */ @Override public void setData(Level level, BlockPos pos, CompoundTag nbt) { if (level.getBlockEntity(pos) instanceof MyMachineBlockEntity machine) { machine.loadAdditional(nbt, level.registryAccess()); machine.setChanged(); } } } ``` ```