From 8a1e1cefce3d90dd5efaf423ec0ad562d19f4b49 Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:14:01 +0800 Subject: [PATCH 01/13] =?UTF-8?q?feat(dynamic-multiblock):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=A4=9A=E6=96=B9=E5=9D=97=E5=BA=93=EF=BC=88=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了多方块库(未完成) --- module.dynamic-multiblock/build.gradle | 275 +++++++++++++++ module.dynamic-multiblock/gradle.properties | 31 ++ .../multiblock/AnvilLibDynamicMultiblock.java | 19 ++ .../multiblock/DynamicMultiblockManager.java | 25 ++ .../lib/v2/multiblock/Multiblock.java | 25 ++ .../v2/multiblock/MultiblockDefinition.java | 321 ++++++++++++++++++ .../v2/multiblock/MultiblockValidator.java | 25 ++ .../DynamicMultiblockCapability.java | 4 + .../controller/IMultiblockController.java | 19 ++ .../MultiblockControllerInstance.java | 6 + .../SimpleMultiblockController.java | 6 + .../multiblock/event/BlockEventListener.java | 14 + .../v2/multiblock/init/LibCapabilities.java | 12 + .../lib/v2/multiblock/init/LibRegistries.java | 23 ++ .../lib/v2/multiblock/part/IMultiPart.java | 22 ++ .../resources/META-INF/neoforge.mods.toml | 86 +++++ .../anvillib_dynamic_multiblock.mixins.json | 14 + settings.gradle | 2 + 18 files changed, 929 insertions(+) create mode 100644 module.dynamic-multiblock/build.gradle create mode 100644 module.dynamic-multiblock/gradle.properties create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java create mode 100644 module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json diff --git a/module.dynamic-multiblock/build.gradle b/module.dynamic-multiblock/build.gradle new file mode 100644 index 0000000..c2baa3a --- /dev/null +++ b/module.dynamic-multiblock/build.gradle @@ -0,0 +1,275 @@ +plugins { + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + alias libs.plugins.modDevGradle + alias libs.plugins.lombok + alias libs.plugins.machete + id 'org.jreleaser' version '1.23.0' + id "me.modmuss50.mod-publish-plugin" version "1.1.0" +} + +String buildType = 'snapshot' +String buildNumber +if (System.getenv("CI_BUILD") == 'false') buildNumber = null +else { + if (System.getenv("PR_BUILD") != 'false') buildType = 'pr' + buildNumber = System.getenv("GITHUB_RUN_NUMBER") +} +version = "${mod_version}" + (buildNumber != null ? "+${buildType}.${buildNumber}" : "") +group = mod_group_id + +base { + archivesName = "${project.mod_id}-neoforge-${libs.versions.minecraft.get()}" +} + +repositories { + maven { url = "https://server.cjsah.net:1002/maven/" } + mavenLocal() + mavenCentral() +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +neoForge { + // Specify the version of NeoForge to use. + version = libs.versions.neoForge.get() + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version + } + + // This line is optional. Access Transformers are automatically detected + // accessTransformers.add('src/main/resources/META-INF/accesstransformer.cfg') + + interfaceInjectionData { + // from 'src/main/resources/interface_injections.json' + // publish file('src/main/resources/interface_injections.json') + } + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + client() + + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + type = "gameTestServer" + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + data() + + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // gameDirectory = project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + + // applies to all the run configs above + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + logLevel = org.slf4j.event.Level.DEBUG + } + } + + mods { + // define mod <-> source bindings + // these are used to tell the game which sources are for which mod + // mostly optional in a single mod project + // but multi mod projects should define one per mod + "${mod_id}" { + sourceSet(sourceSets.main) + } + } +} + +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + + +dependencies { + if (System.getenv("NOT_DEV") == 'true') { + jarJar(implementation("dev.anvilcraft.lib:anvillib-recipe-neoforge-1.21.1:latest.release")) + } else { + jarJar(implementation project(":anvillib-recipe-neoforge-1.21.1")) + } +} + +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. +// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : version, + mod_authors : mod_authors, + mod_description : mod_description] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} + +tasks.register('sourcesJar', Jar) { + from delombok.outputs.files + dependsOn delombok + archiveClassifier = "sources" +} + +tasks.register('javadocJar', Jar) { + from javadoc.destinationDir + dependsOn javadoc + archiveClassifier = "javadoc" +} + +javadoc { + options.encoding = 'UTF-8' + options.addStringOption('Xdoclint:none', '-quiet') +} + +jar { + from(rootProject.file("LICENSE")) + from(rootProject.file("ASSETS_LICENSE")) + from(rootProject.file("README.md")) + from(rootProject.file("README_en.md")) +} + +artifacts { + archives tasks.jar + archives tasks.sourcesJar + archives tasks.javadocJar +} + +def this_name = base.archivesName.get() +def this_description = mod_description + +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + + artifact sourcesJar + artifact javadocJar + + versionMapping { + allVariants { + fromResolutionResult() + } + } + + pom { + name = this_name + description = this_description + url = 'https://github.com/Anvil-Dev/AnvilLib' + + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/license/mit' + } + } + developers { + developer { + id = 'Anvil-Dev' + name = 'AnvilCraft Dev' + email = 'admin@anvilcraft.dev' + } + } + scm { + url = 'https://github.com/Anvil-Dev/AnvilLib' + connection = 'https://github.com/Anvil-Dev/AnvilLib.git' + developerConnection = 'https://github.com/Anvil-Dev/AnvilLib.git' + } + } + } + } + repositories { + // Add repositories to publish to here. + def MAVEN_URL = System.getenv("MAVEN_URL") + if (MAVEN_URL != null) { + maven { + url MAVEN_URL + credentials { + username System.getenv("MAVEN_USERNAME") + password System.getenv("MAVEN_PASSWORD") + } + } + } + mavenLocal() + maven { + name = "staging" + url = layout.buildDirectory.dir("staging-deploy") + } + } +} + +jreleaser { + signing { + active = 'ALWAYS' + armored = true + } + + deploy { + maven { + mavenCentral { + sonatype { + active = 'ALWAYS' + url = 'https://central.sonatype.com/api/v1/publisher' + stagingRepository('build/staging-deploy') + } + } + } + } +} + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} + +machete { + // disable machete locally for faster builds + enabled = false +} + +lombok { + version = "1.18.34" +} diff --git a/module.dynamic-multiblock/gradle.properties b/module.dynamic-multiblock/gradle.properties new file mode 100644 index 0000000..2cced56 --- /dev/null +++ b/module.dynamic-multiblock/gradle.properties @@ -0,0 +1,31 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +java_version=21 +org.gradle.jvmargs=-Xmx6G +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +## Environment Properties +# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge +# The Minecraft version must agree with the Neo version to get a valid artifact +minecraft_version=1.21.1 +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +minecraft_version_range=[1.21.1,1.22) +# The Neo version must agree with the Minecraft version to get a valid artifact +neo_version=21.1.79 +# The Neo version range can use any version of Neo as bounds +neo_version_range=[21,) +# The loader version range can only use the major version of FML as bounds +loader_version_range=[4,) +parchment_minecraft_version=1.21.1 +parchment_mappings_version=2024.11.17 +## Mod Properties +# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} +# Must match the String constant located in the main mod class annotated with @Mod. +mod_id=anvillib_dynamic_multiblock +# The human-readable display name for the mod. +mod_name=AnvilLib-Dynamic-Multiblock +# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. +mod_description=In-world dynamic multiblock system diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java new file mode 100644 index 0000000..892b788 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java @@ -0,0 +1,19 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; + +@Mod(AnvilLibDynamicMultiblock.MOD_ID) +public class AnvilLibDynamicMultiblock { + public static final String MAIN_ID = "anvillib"; + public static final String MOD_ID = "anvillib_dynamic_multiblock"; + + public AnvilLibDynamicMultiblock(IEventBus modEventBus, ModContainer modContainer) { + } + + public static ResourceLocation of(String path) { + return ResourceLocation.fromNamespaceAndPath(MAIN_ID, path); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java new file mode 100644 index 0000000..61a81c1 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import net.minecraft.world.level.Level; + +import java.util.HashMap; +import java.util.Map; + +public class DynamicMultiblockManager { + + // ========================= ↓ 静态方法 ↓ ========================= // + + private static final Map MANAGERS = new HashMap<>(); + + public static DynamicMultiblockManager get(Level level) { + return DynamicMultiblockManager.MANAGERS.computeIfAbsent(level, DynamicMultiblockManager::new); + } + + // ========================= ↓ 成员方法 ↓ ========================= // + + private transient final Level level; + + public DynamicMultiblockManager(Level level) { + this.level = level; + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java new file mode 100644 index 0000000..5b76f23 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import dev.anvilcraft.lib.v2.multiblock.controller.IMultiblockController; +import lombok.Getter; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; + +@Getter +public class Multiblock { + private final IMultiblockController controller; + private final BlockPos controllerPos; + private final MultiblockDefinition definition; + + public Multiblock(IMultiblockController controller, BlockPos controllerPos, MultiblockDefinition definition) { + this.controller = controller; + this.controllerPos = controllerPos; + this.definition = definition; + } + + public Multiblock(Block controller, BlockPos controllerPos, MultiblockDefinition definition) { + this.controller = IMultiblockController.of(controller); + this.controllerPos = controllerPos; + this.definition = definition; + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java new file mode 100644 index 0000000..f1c0b03 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java @@ -0,0 +1,321 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableList; +import com.mojang.serialization.Codec; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.anvilcraft.lib.v2.recipe.component.BlockStatePredicate; +import dev.anvilcraft.lib.v2.recipe.util.CodecUtil; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.chars.Char2ObjectMaps; +import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Vec3i; +import net.minecraft.data.worldgen.BootstrapContext; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * 多方块定义类 + */ +@Getter +@Accessors(fluent = true, chain = false) +public class MultiblockDefinition { + public static final char CONTROLLER = '0'; + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(ins -> ins.group( + Codec.STRING + .listOf() + .listOf() + .xmap(MultiblockDefinition::toArrays, MultiblockDefinition::toLists) + .fieldOf("structure") + .forGetter(MultiblockDefinition::structure), + Codec.unboundedMap(CodecUtil.CHAR_CODEC, BlockStatePredicate.CODEC) + .xmap(MultiblockDefinition::toC2OMap, Function.identity()) + .fieldOf("definitions") + .forGetter(MultiblockDefinition::definitions) + ).apply(ins, MultiblockDefinition::new)); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8 + .apply(ByteBufCodecs.list()) + .apply(ByteBufCodecs.list()) + .map(MultiblockDefinition::toArrays, MultiblockDefinition::toLists), + MultiblockDefinition::structure, + ByteBufCodecs.>map( + HashMap::new, + CodecUtil.CHAR_STREAM_CODEC, + BlockStatePredicate.STREAM_CODEC + ).map(MultiblockDefinition::toC2OMap, Function.identity()), + MultiblockDefinition::definitions, + MultiblockDefinition::new + ); + private final @Unmodifiable String[][] structure; + private final @Unmodifiable Char2ObjectMap definitions; + @Getter(AccessLevel.NONE) + private Vec3i controllerOffset; + private Iterable localPoses; + + /** + * 构建多方块定义类 + * + * @param structure 结构,按 {@code String(x)[reversed y][z]} 的顺序定义 + * @param definitions 定义,记录了字符与方块状态的对应情况。 + */ + private MultiblockDefinition( + @Unmodifiable String[][] structure, + @Unmodifiable Char2ObjectMap definitions + ) { + this.structure = structure; + this.definitions = definitions; + } + + public static Builder builder() { + return new Builder(); + } + + public boolean test(LevelAccessor level, Vec3i localPos, BlockState state, @Nullable BlockEntity entity) { + return this.getState(localPos).test(level, state, entity); + } + + public BlockStatePredicate getState(Vec3i localPos) { + return this.definitions.get(this.getChar(localPos)); + } + + public char getChar(Vec3i localPos) { + return this.getChar(localPos.getX(), localPos.getY(), localPos.getZ()); + } + + public char getChar(int x, int y, int z) { + return this.structure[y][z].charAt(x); + } + + public boolean isEmpty(char key) { + return key == Character.MIN_VALUE; + } + + public boolean isEmpty(Vec3i localPos) { + return this.isEmpty(this.getChar(localPos)); + } + + public boolean isEmpty(int x, int y, int z) { + return this.isEmpty(this.getChar(x, y, z)); + } + + public Vec3i getControllerOffset() { + if (this.controllerOffset != null) return this.controllerOffset; + for (int y = 0; y < this.structure.length; y++) { + String[] xz = this.structure[y]; + for (int z = 0; z < xz.length; z++) { + String xs = xz[z]; + for (int x = 0; x < xs.length(); x++) { + if (xs.charAt(x) != MultiblockDefinition.CONTROLLER) continue; + return this.controllerOffset = new Vec3i(x, y, z); + } + } + } + throw new IllegalStateException("Unexpected no controller in structure"); + } + + public Iterable getGlobalPoses(BlockPos controllerPos) { + return () -> new AbstractIterator<>() { + private final Iterator localPoses = MultiblockDefinition.this.getLocalPoses().iterator(); + + @Override + protected @Nullable BlockPos computeNext() { + BlockPos pos = this.localPoses.next(); + if (pos == null) return this.endOfData(); + return pos.offset(controllerPos); + } + }; + } + + public Iterable getLocalPoses() { + if (this.localPoses != null) return this.localPoses; + Vec3i controllerOffset = this.getControllerOffset(); + int sizeX = this.structure[0][0].length(); + int sizeZ = this.structure[0].length; + int sizeXZ = sizeX * sizeZ; + int sizeY = this.structure.length; + int sizeXYZ = sizeXZ * sizeY; + return this.localPoses = () -> new AbstractIterator<>() { + private final BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(); + private int cursor = 0; + + @Override + protected @Nullable BlockPos computeNext() { + while (this.cursor < sizeXYZ) { + int indexX = this.cursor % sizeX; + int indexY = this.cursor / sizeXZ; + int indexZ = (this.cursor % sizeXZ) / sizeX; + + if (MultiblockDefinition.this.isEmpty(indexX, indexY, indexZ)) { + this.cursor++; + continue; + } + + // 找到非空位置,计算世界坐标 + int worldX = indexX - controllerOffset.getX(); + int worldY = sizeY - 1 - indexY - controllerOffset.getY(); + int worldZ = indexZ - controllerOffset.getZ(); + this.cursor++; + return this.mut.set(worldX, worldY, worldZ); + } + return this.endOfData(); + } + }; + } + + public static class Builder { + private final List> structure = new ArrayList<>(); + private final Char2ObjectMap definitions = new Char2ObjectOpenHashMap<>(); + + public Builder() { + } + + public Builder layer(String... layer) { + this.structure.add(List.of(layer)); + return this; + } + + public Builder defineController(BlockStatePredicate.Builder state) { + this.definitions.put(MultiblockDefinition.CONTROLLER, state.build()); + return this; + } + + public Builder define(char key, BlockStatePredicate.Builder state) { + this.definitions.put(key, state.build()); + return this; + } + + public void validate(String dmbId) { + if (!this.definitions.containsKey(MultiblockDefinition.CONTROLLER)) { + throw new IllegalArgumentException( + "Unlinked controller key %s in DMB id %s" + .formatted(MultiblockDefinition.CONTROLLER, dmbId) + ); + } + + int sizeX = -1; + int sizeZ = -1; + boolean checkedController = false; + for (int indexY = 0; indexY < this.structure.size(); indexY++) { + List layer = this.structure.get(indexY); + if (sizeZ == -1) sizeZ = layer.size(); + if (sizeZ != layer.size()) { + throw new IllegalArgumentException( + "Inconsistent z-width was found in layer %d, DMB id %s. Need %d, found %d" + .formatted(indexY, dmbId, sizeZ, layer.size()) + ); + } + + for (String xs : layer) { + for (int indexX = 0; indexX < xs.length(); indexX++) { + char key = xs.charAt(indexX); + if (sizeX == -1) sizeX = xs.length(); + if (sizeX != xs.length()) { + throw new IllegalArgumentException( + "Inconsistent x-width was found in layer %d, DMB id %s. Need %d, found %d" + .formatted(indexY, dmbId, sizeX, xs.length()) + ); + } + if (key == MultiblockDefinition.CONTROLLER) { + if (!checkedController) { + checkedController = true; + continue; + } else { + throw new IllegalArgumentException( + "Multiple center key (%s) found in DMB id %s" + .formatted(MultiblockDefinition.CONTROLLER, dmbId) + ); + } + } + if (!this.definitions.containsKey(key)) { + throw new IllegalArgumentException( + "Unlinked key %s in DMB id %s" + .formatted(key, dmbId) + ); + } + } + } + } + + if (!checkedController) { + throw new IllegalArgumentException( + "Undefined controller key (%s) in DMB id %s" + .formatted(MultiblockDefinition.CONTROLLER, dmbId) + ); + } + } + + public Holder.Reference register( + BootstrapContext context, + ResourceKey key + ) { + this.validate(key.location().toString()); + return context.register(key, this.build()); + } + + public Holder.Reference register( + BootstrapContext context, + ResourceKey key, + Lifecycle registryLifecycle + ) { + this.validate(key.location().toString()); + return context.register(key, this.build(), registryLifecycle); + } + + public Holder create() { + this.validate(""); + return Holder.direct(this.build()); + } + + public MultiblockDefinition build() { + return new MultiblockDefinition( + MultiblockDefinition.toArrays(this.structure), + Char2ObjectMaps.unmodifiable(this.definitions) + ); + } + } + + // 工具方法 + + private static @Unmodifiable String[][] toArrays(List> lists) { + int size = lists.size(); + String[][] result = new String[size][]; + for (int i = 0; i < size; i++) { + result[i] = lists.get(i).toArray(String[]::new); + } + return result; + } + + private static @Unmodifiable List> toLists(String[][] arrays) { + List> result = new ArrayList<>(); + for (String[] array : arrays) { + result.add(List.of(array)); + } + return ImmutableList.copyOf(result); + } + + private static @Unmodifiable Char2ObjectMap toC2OMap(Map map) { + return Char2ObjectMaps.unmodifiable(new Char2ObjectOpenHashMap<>(map)); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java new file mode 100644 index 0000000..5ef2dbf --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class MultiblockValidator { + private static final Direction[] DIRECTIONS = new Direction[] { + Direction.NORTH, + Direction.SOUTH, + Direction.EAST, + Direction.WEST, + }; + + public enum State { + /** + * (非控制器)已与控制器连接 + */ + BOUND + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java new file mode 100644 index 0000000..551d482 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java @@ -0,0 +1,4 @@ +package dev.anvilcraft.lib.v2.multiblock.capability; + +public class DynamicMultiblockCapability { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java new file mode 100644 index 0000000..83e2292 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java @@ -0,0 +1,19 @@ +package dev.anvilcraft.lib.v2.multiblock.controller; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public interface IMultiblockController { + Block block(); + + static IMultiblockController of(Block block) { + if (block instanceof IMultiblockController controller) return controller; + return new SimpleMultiblockController(block); + } + + void onStructureValid(Level level, BlockPos pos, BlockState state); + + void onStructureInvalid(Level level, BlockPos pos, BlockState state); +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java new file mode 100644 index 0000000..7276c09 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java @@ -0,0 +1,6 @@ +package dev.anvilcraft.lib.v2.multiblock.controller; + +import net.minecraft.core.BlockPos; + +public record MultiblockControllerInstance(IMultiblockController controller, BlockPos pos) { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java new file mode 100644 index 0000000..972bce4 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java @@ -0,0 +1,6 @@ +package dev.anvilcraft.lib.v2.multiblock.controller; + +import net.minecraft.world.level.block.Block; + +public record SimpleMultiblockController(Block block) implements IMultiblockController { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java new file mode 100644 index 0000000..5c799c0 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java @@ -0,0 +1,14 @@ +package dev.anvilcraft.lib.v2.multiblock.event; + +import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.level.BlockEvent; + +@EventBusSubscriber(modid = AnvilLibDynamicMultiblock.MOD_ID) +public class BlockEventListener { + @SubscribeEvent + public static void onBlockBreak(BlockEvent.BreakEvent event) { + event.getState().anvillib$unbind(event.getLevel()); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java new file mode 100644 index 0000000..94eb0fd --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java @@ -0,0 +1,12 @@ +package dev.anvilcraft.lib.v2.multiblock.init; + +import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; +import dev.anvilcraft.lib.v2.multiblock.part.IMultiPart; +import net.neoforged.neoforge.capabilities.BlockCapability; + +public class LibCapabilities { + public static final BlockCapability MULTI_PART_CAPABILITY = BlockCapability.createVoid( + AnvilLibDynamicMultiblock.of("multipart"), + IMultiPart.class + ); +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java new file mode 100644 index 0000000..3669b10 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java @@ -0,0 +1,23 @@ +package dev.anvilcraft.lib.v2.multiblock.init; + +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; +import dev.anvilcraft.lib.v2.multiblock.MultiblockDefinition; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.registries.DataPackRegistryEvent; + +@EventBusSubscriber(modid = AnvilLibDynamicMultiblock.MOD_ID, bus = EventBusSubscriber.Bus.MOD) +public class LibRegistries { + public static final ResourceKey> DEFINITION_KEY = ResourceKey.createRegistryKey( + AnvilLibDynamicMultiblock.of("trigger") + ); + + @SubscribeEvent + public static void registerDataRegistries(DataPackRegistryEvent.NewRegistry event) { + MapCodec codec = MultiblockDefinition.CODEC; + event.dataPackRegistry(DEFINITION_KEY, codec.codec(), codec.codec()); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java new file mode 100644 index 0000000..5cd5868 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java @@ -0,0 +1,22 @@ +package dev.anvilcraft.lib.v2.multiblock.part; + +import dev.anvilcraft.lib.v2.multiblock.Multiblock; +import net.minecraft.world.level.LevelAccessor; + +public interface IMultiPart { + default boolean anvillib$isController() { + throw new AssertionError(); + } + + default void anvillib$bind(Multiblock multiblock) { + throw new AssertionError(); + } + + default boolean anvillib$isBound() { + throw new AssertionError(); + } + + default void anvillib$unbind(LevelAccessor level) { + throw new AssertionError(); + } +} diff --git a/module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml b/module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..27f7ba7 --- /dev/null +++ b/module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,86 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader = "javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. +loaderVersion = "${loader_version_range}" #mandatory +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license = "${mod_license}" +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId = "${mod_id}" #mandatory +# The version number of the mod +version = "${mod_version}" #mandatory +# A display name for the mod +displayName = "${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforge.net/docs/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +displayURL="https://github.com/Anvil-Dev/AnvilLib" +# A file name (in the root of the mod JAR) containing a logo for display +logoFile="icon.png" #optional +# A text field displayed in the mod UI +#credits="" #optional +# A text field displayed in the mod UI +authors = "${mod_authors}" #optional +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) + +# The description text for the mod (multi line!) (#mandatory) +description = '''${mod_description}''' + +# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. +[[mixins]] +config = "${mod_id}.mixins.json" + +# The [[accessTransformers]] block allows you to declare where your AT file is. +# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg +#[[accessTransformers]] +#file="META-INF/accesstransformer.cfg" + +# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json + +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies."${mod_id}"]] #optional +# the modid of the dependency +modId = "neoforge" #mandatory +# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). +# 'required' requires the mod to exist, 'optional' does not +# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning +type = "required" #mandatory +# Optional field describing why the dependency is required or why it is incompatible +# reason="..." +# The version range of the dependency +versionRange = "${neo_version_range}" #mandatory +# An ordering relationship for the dependency. +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency +ordering = "NONE" +# Side this dependency is applied on - BOTH, CLIENT, or SERVER +side = "BOTH" +# Here's another dependency +[[dependencies."${mod_id}"]] +modId = "minecraft" +type = "required" +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features."${mod_id}"] +#openGLVersion="[3.2,)" diff --git a/module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json b/module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json new file mode 100644 index 0000000..867e285 --- /dev/null +++ b/module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.anvilcraft.lib.v2.multiblock.mixin", + "compatibilityLevel": "JAVA_8", + "refmap": "anvillib.refmap.json", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/settings.gradle b/settings.gradle index 68be2d5..1307d50 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ plugins { } include 'module.config' +include 'module.dynamic-multiblock' include 'module.integration' include 'module.moveable-entity-block' include 'module.recipe' @@ -18,6 +19,7 @@ include 'module.registrum' include 'module.main' project(':module.config').name = 'anvillib-config-neoforge-1.21.1' +project(':module.dynamic-multiblock').name = 'anvillib-dynamic-multiblock-neoforge-1.21.1' project(':module.integration').name = 'anvillib-integration-neoforge-1.21.1' project(':module.moveable-entity-block').name = 'anvillib-moveable-entity-block-neoforge-1.21.1' project(':module.recipe').name = 'anvillib-recipe-neoforge-1.21.1' From a222300d0889f0d60d055f06e2465c75d9fae7a4 Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:39:16 +0800 Subject: [PATCH 02/13] =?UTF-8?q?feat(network):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了网络库 功能: - 更方便的网络包API - 网络包自动注册系统 --- module.network/build.gradle | 270 ++++++++++++++++++ module.network/gradle.properties | 31 ++ .../v2/network/packet/IClientboundPacket.java | 15 + .../network/packet/IInsensitiveBiPacket.java | 32 +++ .../lib/v2/network/packet/IPacket.java | 11 + .../v2/network/packet/ISensitiveBiPacket.java | 13 + .../v2/network/packet/IServerboundPacket.java | 15 + .../lib/v2/network/packet/package-info.java | 7 + .../lib/v2/network/register/Network.java | 15 + .../v2/network/register/NetworkRegistrar.java | 97 +++++++ .../lib/v2/network/register/PacketData.java | 81 ++++++ .../v2/network/register/PacketDirection.java | 5 + .../v2/network/register/PacketProtocol.java | 5 + .../lib/v2/network/register/package-info.java | 7 + .../resources/META-INF/neoforge.mods.toml | 86 ++++++ settings.gradle | 2 + 16 files changed, 692 insertions(+) create mode 100644 module.network/build.gradle create mode 100644 module.network/gradle.properties create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/package-info.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java create mode 100644 module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/package-info.java create mode 100644 module.network/src/main/resources/META-INF/neoforge.mods.toml diff --git a/module.network/build.gradle b/module.network/build.gradle new file mode 100644 index 0000000..eeb756d --- /dev/null +++ b/module.network/build.gradle @@ -0,0 +1,270 @@ +plugins { + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + alias libs.plugins.modDevGradle + alias libs.plugins.lombok + alias libs.plugins.machete + id 'org.jreleaser' version '1.23.0' + id "me.modmuss50.mod-publish-plugin" version "1.1.0" +} + +String buildType = 'snapshot' +String buildNumber +if (System.getenv("CI_BUILD") == 'false') buildNumber = null +else { + if (System.getenv("PR_BUILD") != 'false') buildType = 'pr' + buildNumber = System.getenv("GITHUB_RUN_NUMBER") +} +version = "${mod_version}" + (buildNumber != null ? "+${buildType}.${buildNumber}" : "") +group = mod_group_id + +base { + archivesName = "${project.mod_id}-neoforge-${libs.versions.minecraft.get()}" +} + +repositories { + maven { url = "https://server.cjsah.net:1002/maven/" } + mavenLocal() + mavenCentral() +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +neoForge { + // Specify the version of NeoForge to use. + version = libs.versions.neoForge.get() + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version + } + + // This line is optional. Access Transformers are automatically detected + // accessTransformers.add('src/main/resources/META-INF/accesstransformer.cfg') + + interfaceInjectionData { + // from 'src/main/resources/interface_injections.json' + // publish file('src/main/resources/interface_injections.json') + } + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + client() + + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + type = "gameTestServer" + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + data() + + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // gameDirectory = project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + + // applies to all the run configs above + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + logLevel = org.slf4j.event.Level.DEBUG + } + } + + mods { + // define mod <-> source bindings + // these are used to tell the game which sources are for which mod + // mostly optional in a single mod project + // but multi mod projects should define one per mod + "${mod_id}" { + sourceSet(sourceSets.main) + } + } +} + +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + + +dependencies { +} + +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. +// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : version, + mod_authors : mod_authors, + mod_description : mod_description] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} + +tasks.register('sourcesJar', Jar) { + from delombok.outputs.files + dependsOn delombok + archiveClassifier = "sources" +} + +tasks.register('javadocJar', Jar) { + from javadoc.destinationDir + dependsOn javadoc + archiveClassifier = "javadoc" +} + +javadoc { + options.encoding = 'UTF-8' + options.addStringOption('Xdoclint:none', '-quiet') +} + +jar { + from(rootProject.file("LICENSE")) + from(rootProject.file("ASSETS_LICENSE")) + from(rootProject.file("README.md")) + from(rootProject.file("README_en.md")) +} + +artifacts { + archives tasks.jar + archives tasks.sourcesJar + archives tasks.javadocJar +} + +def this_name = base.archivesName.get() +def this_description = mod_description + +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + + artifact sourcesJar + artifact javadocJar + + versionMapping { + allVariants { + fromResolutionResult() + } + } + + pom { + name = this_name + description = this_description + url = 'https://github.com/Anvil-Dev/AnvilLib' + + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/license/mit' + } + } + developers { + developer { + id = 'Anvil-Dev' + name = 'AnvilCraft Dev' + email = 'admin@anvilcraft.dev' + } + } + scm { + url = 'https://github.com/Anvil-Dev/AnvilLib' + connection = 'https://github.com/Anvil-Dev/AnvilLib.git' + developerConnection = 'https://github.com/Anvil-Dev/AnvilLib.git' + } + } + } + } + repositories { + // Add repositories to publish to here. + def MAVEN_URL = System.getenv("MAVEN_URL") + if (MAVEN_URL != null) { + maven { + url MAVEN_URL + credentials { + username System.getenv("MAVEN_USERNAME") + password System.getenv("MAVEN_PASSWORD") + } + } + } + mavenLocal() + maven { + name = "staging" + url = layout.buildDirectory.dir("staging-deploy") + } + } +} + +jreleaser { + signing { + active = 'ALWAYS' + armored = true + } + + deploy { + maven { + mavenCentral { + sonatype { + active = 'ALWAYS' + url = 'https://central.sonatype.com/api/v1/publisher' + stagingRepository('build/staging-deploy') + } + } + } + } +} + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} + +machete { + // disable machete locally for faster builds + enabled = false +} + +lombok { + version = "1.18.34" +} diff --git a/module.network/gradle.properties b/module.network/gradle.properties new file mode 100644 index 0000000..36e1950 --- /dev/null +++ b/module.network/gradle.properties @@ -0,0 +1,31 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +java_version=21 +org.gradle.jvmargs=-Xmx6G +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +## Environment Properties +# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge +# The Minecraft version must agree with the Neo version to get a valid artifact +minecraft_version=1.21.1 +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +minecraft_version_range=[1.21.1,1.22) +# The Neo version must agree with the Minecraft version to get a valid artifact +neo_version=21.1.79 +# The Neo version range can use any version of Neo as bounds +neo_version_range=[21,) +# The loader version range can only use the major version of FML as bounds +loader_version_range=[4,) +parchment_minecraft_version=1.21.1 +parchment_mappings_version=2024.11.17 +## Mod Properties +# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} +# Must match the String constant located in the main mod class annotated with @Mod. +mod_id=anvillib_network +# The human-readable display name for the mod. +mod_name=AnvilLib-Network +# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. +mod_description=Provide a more convenience API to use Network Syncing diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java new file mode 100644 index 0000000..f41edf8 --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java @@ -0,0 +1,15 @@ +package dev.anvilcraft.lib.v2.network.packet; + +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +/** + * Server -> Client + */ +public non-sealed interface IClientboundPacket extends IPacket { + default void clientHandler(IPayloadContext ctx) { + ctx.enqueueWork(() -> this.clientHandler(ctx.player())); + } + + void clientHandler(Player player); +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java new file mode 100644 index 0000000..c3ccfe2 --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java @@ -0,0 +1,32 @@ +package dev.anvilcraft.lib.v2.network.packet; + +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public interface IInsensitiveBiPacket extends IClientboundPacket, IServerboundPacket { + default void bidirectionalHandler(IPayloadContext ctx) { + ctx.enqueueWork(() -> this.bidirectionalHandler(ctx.player())); + } + + void bidirectionalHandler(Player player); + + @Override + default void clientHandler(IPayloadContext ctx) { + this.bidirectionalHandler(ctx); + } + + @Override + default void clientHandler(Player player) { + this.bidirectionalHandler(player); + } + + @Override + default void serverHandler(IPayloadContext ctx) { + this.bidirectionalHandler(ctx); + } + + @Override + default void serverHandler(Player player) { + this.bidirectionalHandler(player); + } +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java new file mode 100644 index 0000000..cf6ccab --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java @@ -0,0 +1,11 @@ +package dev.anvilcraft.lib.v2.network.packet; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +public sealed interface IPacket extends CustomPacketPayload permits IClientboundPacket, IServerboundPacket { + static Type type(ResourceLocation id) { + return new Type<>(id); + } +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java new file mode 100644 index 0000000..15e9e15 --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java @@ -0,0 +1,13 @@ +package dev.anvilcraft.lib.v2.network.packet; + +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public interface ISensitiveBiPacket extends IClientboundPacket, IServerboundPacket { + default void bidirectionalHandler(IPayloadContext ctx) { + if (ctx.flow().isClientbound()) { + this.clientHandler(ctx); + } else if (ctx.flow().isServerbound()) { + this.serverHandler(ctx); + } + } +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java new file mode 100644 index 0000000..de709c8 --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java @@ -0,0 +1,15 @@ +package dev.anvilcraft.lib.v2.network.packet; + +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +/** + * Client -> Server + */ +public non-sealed interface IServerboundPacket extends IPacket { + default void serverHandler(IPayloadContext ctx) { + ctx.enqueueWork(() -> this.serverHandler(ctx.player())); + } + + void serverHandler(Player player); +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/package-info.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/package-info.java new file mode 100644 index 0000000..15ec89c --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package dev.anvilcraft.lib.v2.network.packet; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java new file mode 100644 index 0000000..6f12f41 --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java @@ -0,0 +1,15 @@ +package dev.anvilcraft.lib.v2.network.register; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * 将该软件包标识为网络包软件包 + */ +@Target(ElementType.PACKAGE) +public @interface Network { + /** + * 决定了该软件包下所有网络包将要注册的连接协议 + */ + PacketProtocol protocol() default PacketProtocol.PLAY; +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java new file mode 100644 index 0000000..758607f --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java @@ -0,0 +1,97 @@ +package dev.anvilcraft.lib.v2.network.register; + +import dev.anvilcraft.lib.v2.network.packet.IPacket; +import lombok.extern.slf4j.Slf4j; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.modscan.ModAnnotation; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import net.neoforged.neoforgespi.language.IModFileInfo; +import net.neoforged.neoforgespi.language.ModFileScanData; +import org.objectweb.asm.Type; + +import java.lang.annotation.ElementType; + +@Slf4j +public class NetworkRegistrar { + public static final String ANNOTATION_NAME = "L" + Network.class.getName().replace(".", "/") + ";"; + public static final String PACKET_PACKAGE_PREFIX = "L" + IPacket.class.getPackageName().replace(".", "/"); + + @SuppressWarnings("unchecked") + public static void register(PayloadRegistrar registrar, String modId) { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + IModFileInfo fileInfo = ModList.get().getModFileById(modId); + ModFileScanData scanData = fileInfo.getFile().getScanResult(); + for (ModFileScanData.AnnotationData annotation : scanData.getAnnotations()) { + if ( + !annotation.annotationType().getDescriptor().equals(ANNOTATION_NAME) + || annotation.targetType() != ElementType.PACKAGE + ) { + continue; + } + + ModAnnotation.EnumHolder protocolHolder = (ModAnnotation.EnumHolder) annotation.annotationData().get("protocol"); + PacketProtocol protocol = PacketProtocol.PLAY; + if (protocolHolder != null) { + protocol = switch (protocolHolder.value()) { + case "CONFIGURATION" -> PacketProtocol.CONFIGURATION; + case "PLAY" -> PacketProtocol.PLAY; + case "COMMON" -> PacketProtocol.COMMON; + default -> throw new IllegalArgumentException("Unknown packet protocol: " + protocolHolder.value()); + }; + } + String packageName = annotation.memberName().substring(0, annotation.memberName().lastIndexOf('.') - 1); + log.info("Considering network package {}", packageName); + + for (ModFileScanData.ClassData classData : scanData.getClasses()) { + if (!classData.clazz().getClassName().startsWith(packageName)) continue; + + String interfaceName = null; + for (Type anInterface : classData.interfaces()) { + if (!anInterface.getDescriptor().startsWith(NetworkRegistrar.PACKET_PACKAGE_PREFIX)) continue; + interfaceName = anInterface.getClassName(); + break; + } + if (interfaceName == null) continue; + + try { + Class packetClass = (Class) loader.loadClass(classData.clazz().getClassName()); + NetworkRegistrar.register(registrar, protocol, packetClass); + } catch (ClassNotFoundException e) { + log.error("Cannot register packet {}", classData.clazz().getClassName(), e); + throw new IllegalStateException(); + } + } + } + } + + private static void register(PayloadRegistrar registrar, PacketProtocol protocol, Class packetClass) { + switch (protocol) { + case CONFIGURATION -> { + PacketData data = PacketData.find(packetClass); + switch (data.direction()) { + case CLIENTBOUND -> registrar.configurationToClient(data.type(), data.streamCodec(), data.handler()); + case SERVERBOUND -> registrar.configurationToServer(data.type(), data.streamCodec(), data.handler()); + case BIDIRECTIONAL -> registrar.configurationBidirectional(data.type(), data.streamCodec(), data.handler()); + } + } + case PLAY -> { + PacketData data = PacketData.find(packetClass); + switch (data.direction()) { + case CLIENTBOUND -> registrar.playToClient(data.type(), data.streamCodec(), data.handler()); + case SERVERBOUND -> registrar.playToServer(data.type(), data.streamCodec(), data.handler()); + case BIDIRECTIONAL -> registrar.playBidirectional(data.type(), data.streamCodec(), data.handler()); + } + } + case COMMON -> { + PacketData data = PacketData.find(packetClass); + switch (data.direction()) { + case CLIENTBOUND -> registrar.commonToClient(data.type(), data.streamCodec(), data.handler()); + case SERVERBOUND -> registrar.commonToServer(data.type(), data.streamCodec(), data.handler()); + case BIDIRECTIONAL -> registrar.commonBidirectional(data.type(), data.streamCodec(), data.handler()); + } + } + } + } +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java new file mode 100644 index 0000000..3ea4967 --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java @@ -0,0 +1,81 @@ +package dev.anvilcraft.lib.v2.network.register; + +import dev.anvilcraft.lib.v2.network.packet.IClientboundPacket; +import dev.anvilcraft.lib.v2.network.packet.IInsensitiveBiPacket; +import dev.anvilcraft.lib.v2.network.packet.IPacket; +import dev.anvilcraft.lib.v2.network.packet.ISensitiveBiPacket; +import dev.anvilcraft.lib.v2.network.packet.IServerboundPacket; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.IPayloadHandler; + +import java.lang.reflect.AccessFlag; +import java.lang.reflect.Field; +import java.util.Set; + +@Slf4j +record PacketData( + CustomPacketPayload.Type type, + StreamCodec streamCodec, + PacketDirection direction, + IPayloadHandler handler +) { + @SuppressWarnings("unchecked") + static PacketData find(Class packetClass) { + CustomPacketPayload.Type type = null; + StreamCodec codec = null; + try { + Field[] fields = packetClass.getDeclaredFields(); + for (Field field : fields) { + Set accessFlags = field.accessFlags(); + if ( + !accessFlags.contains(AccessFlag.STATIC) + || !accessFlags.contains(AccessFlag.FINAL) + ) { + continue; + } + Class declaringClass = field.getDeclaringClass(); + if (declaringClass.isAssignableFrom(CustomPacketPayload.Type.class)) { + field.setAccessible(true); + type = (CustomPacketPayload.Type) field.get(null); + } else if (declaringClass.isAssignableFrom(StreamCodec.class)) { + field.setAccessible(true); + codec = (StreamCodec) field.get(null); + } + } + } catch (IllegalAccessException e) { + log.error("Cannot access the type/codec of packet {}", packetClass.getName(), e); + throw new IllegalStateException(); + } + if (type == null) { + log.error("Cannot find static final type of packet {}", packetClass.getName()); + throw new IllegalArgumentException(); + } + if (codec == null) { + log.error("Cannot find static final codec of packet {}", packetClass.getName()); + throw new IllegalArgumentException(); + } + + PacketDirection direction; + IPayloadHandler handler; + if (packetClass.isAssignableFrom(IInsensitiveBiPacket.class)) { + direction = PacketDirection.BIDIRECTIONAL; + handler = (packet, ctx) -> ((IInsensitiveBiPacket) packet).bidirectionalHandler(ctx); + } else if (packetClass.isAssignableFrom(ISensitiveBiPacket.class)) { + direction = PacketDirection.BIDIRECTIONAL; + handler = (packet, ctx) -> ((ISensitiveBiPacket) packet).bidirectionalHandler(ctx); + } else if (packetClass.isAssignableFrom(IClientboundPacket.class)) { + direction = PacketDirection.CLIENTBOUND; + handler = (packet, ctx) -> ((IClientboundPacket) packet).clientHandler(ctx); + } else if (packetClass.isAssignableFrom(IServerboundPacket.class)) { + direction = PacketDirection.SERVERBOUND; + handler = (packet, ctx) -> ((IServerboundPacket) packet).serverHandler(ctx); + } else { + log.error("Class {} extends IPacket, but not extends IClientboundPacket or IServerboundPacket", packetClass.getName()); + throw new IllegalStateException(); + } + return new PacketData<>(type, codec, direction, handler); + } +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java new file mode 100644 index 0000000..dc5a3f7 --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java @@ -0,0 +1,5 @@ +package dev.anvilcraft.lib.v2.network.register; + +public enum PacketDirection { + CLIENTBOUND, SERVERBOUND, BIDIRECTIONAL +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java new file mode 100644 index 0000000..158887c --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java @@ -0,0 +1,5 @@ +package dev.anvilcraft.lib.v2.network.register; + +public enum PacketProtocol { + CONFIGURATION, PLAY, COMMON +} diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/package-info.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/package-info.java new file mode 100644 index 0000000..692698a --- /dev/null +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package dev.anvilcraft.lib.v2.network.register; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/module.network/src/main/resources/META-INF/neoforge.mods.toml b/module.network/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..27f7ba7 --- /dev/null +++ b/module.network/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,86 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader = "javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. +loaderVersion = "${loader_version_range}" #mandatory +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license = "${mod_license}" +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId = "${mod_id}" #mandatory +# The version number of the mod +version = "${mod_version}" #mandatory +# A display name for the mod +displayName = "${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforge.net/docs/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +displayURL="https://github.com/Anvil-Dev/AnvilLib" +# A file name (in the root of the mod JAR) containing a logo for display +logoFile="icon.png" #optional +# A text field displayed in the mod UI +#credits="" #optional +# A text field displayed in the mod UI +authors = "${mod_authors}" #optional +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) + +# The description text for the mod (multi line!) (#mandatory) +description = '''${mod_description}''' + +# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. +[[mixins]] +config = "${mod_id}.mixins.json" + +# The [[accessTransformers]] block allows you to declare where your AT file is. +# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg +#[[accessTransformers]] +#file="META-INF/accesstransformer.cfg" + +# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json + +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies."${mod_id}"]] #optional +# the modid of the dependency +modId = "neoforge" #mandatory +# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). +# 'required' requires the mod to exist, 'optional' does not +# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning +type = "required" #mandatory +# Optional field describing why the dependency is required or why it is incompatible +# reason="..." +# The version range of the dependency +versionRange = "${neo_version_range}" #mandatory +# An ordering relationship for the dependency. +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency +ordering = "NONE" +# Side this dependency is applied on - BOTH, CLIENT, or SERVER +side = "BOTH" +# Here's another dependency +[[dependencies."${mod_id}"]] +modId = "minecraft" +type = "required" +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features."${mod_id}"] +#openGLVersion="[3.2,)" diff --git a/settings.gradle b/settings.gradle index 68be2d5..7c2f767 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ plugins { include 'module.config' include 'module.integration' include 'module.moveable-entity-block' +include 'module.network' include 'module.recipe' include 'module.registrum' include 'module.main' @@ -20,6 +21,7 @@ include 'module.main' project(':module.config').name = 'anvillib-config-neoforge-1.21.1' project(':module.integration').name = 'anvillib-integration-neoforge-1.21.1' project(':module.moveable-entity-block').name = 'anvillib-moveable-entity-block-neoforge-1.21.1' +project(':module.network').name = 'anvillib-network-neoforge-1.21.1' project(':module.recipe').name = 'anvillib-recipe-neoforge-1.21.1' project(':module.registrum').name = 'anvillib-registrum-neoforge-1.21.1' project(':module.main').name = 'anvillib-neoforge-1.21.1' From 19d98e3b7e0f18874bf8179d97027885aa14a787 Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Sun, 15 Mar 2026 08:01:23 +0800 Subject: [PATCH 03/13] =?UTF-8?q?docs(network):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E5=BA=93=E7=9A=84=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化了网络库的描述 --- module.network/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.network/gradle.properties b/module.network/gradle.properties index 36e1950..710c5cc 100644 --- a/module.network/gradle.properties +++ b/module.network/gradle.properties @@ -28,4 +28,4 @@ mod_id=anvillib_network # The human-readable display name for the mod. mod_name=AnvilLib-Network # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. -mod_description=Provide a more convenience API to use Network Syncing +mod_description=Network API with direction-specific packets and automatic registry From 10069a03e9f7f3d056c15740c14c9992888ffc76 Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Sun, 15 Mar 2026 08:33:30 +0800 Subject: [PATCH 04/13] =?UTF-8?q?perf(network):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=A1=A5=E5=85=85javadoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化了部分网络库的方法名称 - 为部分网络库的方法添加了合适的注解 - 优化了部分代码 - 修改并补充了网络库的javadoc --- .../v2/network/packet/IClientboundPacket.java | 18 ++++++++++-- .../network/packet/IInsensitiveBiPacket.java | 29 +++++++++++++++---- .../lib/v2/network/packet/IPacket.java | 10 +++++++ .../v2/network/packet/ISensitiveBiPacket.java | 10 +++++++ .../v2/network/packet/IServerboundPacket.java | 18 ++++++++++-- .../lib/v2/network/register/Network.java | 6 ++-- .../v2/network/register/NetworkRegistrar.java | 25 +++++++++++++--- .../v2/network/register/PacketDirection.java | 2 +- .../v2/network/register/PacketProtocol.java | 2 +- 9 files changed, 100 insertions(+), 20 deletions(-) diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java index f41edf8..b4d1c59 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IClientboundPacket.java @@ -2,14 +2,26 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.jetbrains.annotations.ApiStatus; /** - * Server -> Client + * 客户端侧网络包,允许服务端向客户端发送 */ public non-sealed interface IClientboundPacket extends IPacket { + /** + * 客户端处理器 + * + * @param ctx 网络包上下文 + */ default void clientHandler(IPayloadContext ctx) { - ctx.enqueueWork(() -> this.clientHandler(ctx.player())); + ctx.enqueueWork(() -> this.handleOnClient(ctx.player())); } - void clientHandler(Player player); + /** + * 客户端侧处理逻辑 + * + * @param player 客户端玩家。其类型恒为 {@link net.minecraft.client.player.LocalPlayer LocalPlayer} + */ + @ApiStatus.OverrideOnly + void handleOnClient(Player player); } diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java index c3ccfe2..b03d5eb 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IInsensitiveBiPacket.java @@ -3,12 +3,29 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.handling.IPayloadContext; +/** + * 双端网络包,允许两端各自向对方发送 + * + *

处理逻辑时方向不敏感,两端共用一套逻辑

+ */ public interface IInsensitiveBiPacket extends IClientboundPacket, IServerboundPacket { + /** + * 双端处理器 + * + * @param ctx 网络包上下文 + */ default void bidirectionalHandler(IPayloadContext ctx) { - ctx.enqueueWork(() -> this.bidirectionalHandler(ctx.player())); + ctx.enqueueWork(() -> this.handleOnBothSide(ctx.player())); } - void bidirectionalHandler(Player player); + /** + * 两端共用的处理逻辑 + * + * @param player 玩家。客户端为 + * {@link net.minecraft.client.player.LocalPlayer LocalPlayer},服务端为 + * {@link net.minecraft.server.level.ServerPlayer ServerPlayer} + */ + void handleOnBothSide(Player player); @Override default void clientHandler(IPayloadContext ctx) { @@ -16,8 +33,8 @@ default void clientHandler(IPayloadContext ctx) { } @Override - default void clientHandler(Player player) { - this.bidirectionalHandler(player); + default void handleOnClient(Player player) { + this.handleOnBothSide(player); } @Override @@ -26,7 +43,7 @@ default void serverHandler(IPayloadContext ctx) { } @Override - default void serverHandler(Player player) { - this.bidirectionalHandler(player); + default void handleOnServer(Player player) { + this.handleOnBothSide(player); } } diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java index cf6ccab..39b8da3 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IPacket.java @@ -4,7 +4,17 @@ import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.ApiStatus; +/** + * 网络包根接口 + */ public sealed interface IPacket extends CustomPacketPayload permits IClientboundPacket, IServerboundPacket { + /** + * 构建网络包类型 + * + * @param id 网络包 ID + * @return 网络包类型 + * @param 网络包 Java 类型 + */ static Type type(ResourceLocation id) { return new Type<>(id); } diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java index 15e9e15..fa3c994 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/ISensitiveBiPacket.java @@ -2,7 +2,17 @@ import net.neoforged.neoforge.network.handling.IPayloadContext; +/** + * 双端网络包,允许两端各自向对方发送 + * + *

处理逻辑时方向敏感,两端各自用一套逻辑

+ */ public interface ISensitiveBiPacket extends IClientboundPacket, IServerboundPacket { + /** + * 双端处理器 + * + * @param ctx 网络包上下文 + */ default void bidirectionalHandler(IPayloadContext ctx) { if (ctx.flow().isClientbound()) { this.clientHandler(ctx); diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java index de709c8..07f7aab 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/packet/IServerboundPacket.java @@ -2,14 +2,26 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.jetbrains.annotations.ApiStatus; /** - * Client -> Server + * 服务端侧网络包,允许客户端向服务端发送 */ public non-sealed interface IServerboundPacket extends IPacket { + /** + * 服务端处理器 + * + * @param ctx 网络包上下文 + */ default void serverHandler(IPayloadContext ctx) { - ctx.enqueueWork(() -> this.serverHandler(ctx.player())); + ctx.enqueueWork(() -> this.handleOnServer(ctx.player())); } - void serverHandler(Player player); + /** + * 服务端侧处理逻辑 + * + * @param player 服务端玩家。其类型恒为 {@link net.minecraft.server.level.ServerPlayer ServerPlayer} + */ + @ApiStatus.OverrideOnly + void handleOnServer(Player player); } diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java index 6f12f41..09b91c6 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/Network.java @@ -4,12 +4,14 @@ import java.lang.annotation.Target; /** - * 将该软件包标识为网络包软件包 + * 表示该软件包为网络包软件包 */ @Target(ElementType.PACKAGE) public @interface Network { /** - * 决定了该软件包下所有网络包将要注册的连接协议 + * 返回该软件包下所有网络包的连接协议 + * + * @return 该软件包下所有网络包的连接协议 */ PacketProtocol protocol() default PacketProtocol.PLAY; } diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java index 758607f..fc5b709 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java @@ -4,7 +4,7 @@ import lombok.extern.slf4j.Slf4j; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf; -import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.LoadingModList; import net.neoforged.fml.loading.modscan.ModAnnotation; import net.neoforged.neoforge.network.registration.PayloadRegistrar; import net.neoforged.neoforgespi.language.IModFileInfo; @@ -13,15 +13,32 @@ import java.lang.annotation.ElementType; +/** + * 网络包注册器 + * + *

应在 {@link net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent RegisterPayloadHandlersEvent} 的侦听器中使用

+ * + * @see NetworkRegistrar#register(PayloadRegistrar, String) + */ @Slf4j public class NetworkRegistrar { - public static final String ANNOTATION_NAME = "L" + Network.class.getName().replace(".", "/") + ";"; - public static final String PACKET_PACKAGE_PREFIX = "L" + IPacket.class.getPackageName().replace(".", "/"); + private static final String ANNOTATION_NAME = "L" + Network.class.getName().replace(".", "/") + ";"; + private static final String PACKET_PACKAGE_PREFIX = "L" + IPacket.class.getPackageName().replace(".", "/"); + /** + * 注册对应 {@code modId} 的模组中所有使用 {@link Network} 注解的软件包下的网络包 + * + * @param registrar 网络包注册器。应通过 + * {@link + * net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent#registrar(String) + * RegisterPayloadHandlersEvent.registrar() + * } 获取 + * @param modId 模组 ID + */ @SuppressWarnings("unchecked") public static void register(PayloadRegistrar registrar, String modId) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); - IModFileInfo fileInfo = ModList.get().getModFileById(modId); + IModFileInfo fileInfo = LoadingModList.get().getModFileById(modId); ModFileScanData scanData = fileInfo.getFile().getScanResult(); for (ModFileScanData.AnnotationData annotation : scanData.getAnnotations()) { if ( diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java index dc5a3f7..f171164 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketDirection.java @@ -1,5 +1,5 @@ package dev.anvilcraft.lib.v2.network.register; -public enum PacketDirection { +enum PacketDirection { CLIENTBOUND, SERVERBOUND, BIDIRECTIONAL } diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java index 158887c..930d756 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketProtocol.java @@ -1,5 +1,5 @@ package dev.anvilcraft.lib.v2.network.register; -public enum PacketProtocol { +enum PacketProtocol { CONFIGURATION, PLAY, COMMON } From c88618c2c3dfa399bdeb85784e73b46eb28db7ff Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:31:36 +0800 Subject: [PATCH 05/13] =?UTF-8?q?change(multiblock):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=B1=BB=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将定义类拆分为Java部分和序列化部分 --- .../lib/v2/multiblock/Multiblock.java | 1 + .../definition/DefinitionSerializer.java | 146 ++++++++++++++++ .../MultiblockDefinition.java | 164 +++--------------- .../definition/MultiblockPosInfo.java | 6 + .../multiblock/definition/package-info.java | 7 + .../lib/v2/multiblock/init/LibRegistries.java | 2 +- .../lib/v2/multiblock/package-info.java | 7 + 7 files changed, 195 insertions(+), 138 deletions(-) create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java rename module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/{ => definition}/MultiblockDefinition.java (54%) create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java index 5b76f23..6a4142e 100644 --- a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java @@ -1,6 +1,7 @@ package dev.anvilcraft.lib.v2.multiblock; import dev.anvilcraft.lib.v2.multiblock.controller.IMultiblockController; +import dev.anvilcraft.lib.v2.multiblock.definition.MultiblockDefinition; import lombok.Getter; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.Block; diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java new file mode 100644 index 0000000..87165a5 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java @@ -0,0 +1,146 @@ +package dev.anvilcraft.lib.v2.multiblock.definition; + +import com.google.common.collect.ImmutableList; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.anvilcraft.lib.v2.recipe.component.BlockStatePredicate; +import dev.anvilcraft.lib.v2.recipe.util.CodecUtil; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.chars.Char2ObjectMaps; +import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public record DefinitionSerializer(@Unmodifiable String[][] structure, @Unmodifiable Char2ObjectMap definitions) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(ins -> ins.group( + Codec.STRING + .listOf() + .listOf() + .xmap(DefinitionSerializer::toArrays, DefinitionSerializer::toLists) + .fieldOf("structure") + .forGetter(DefinitionSerializer::structure), + Codec.unboundedMap(CodecUtil.CHAR_CODEC, BlockStatePredicate.CODEC) + .xmap(DefinitionSerializer::toC2OMap, Function.identity()) + .fieldOf("definitions") + .forGetter(DefinitionSerializer::definitions) + ).apply(ins, DefinitionSerializer::new)); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8 + .apply(ByteBufCodecs.list()) + .apply(ByteBufCodecs.list()) + .map(DefinitionSerializer::toArrays, DefinitionSerializer::toLists), + DefinitionSerializer::structure, + ByteBufCodecs.>map( + HashMap::new, + CodecUtil.CHAR_STREAM_CODEC, + BlockStatePredicate.STREAM_CODEC + ).map(DefinitionSerializer::toC2OMap, Function.identity()), + DefinitionSerializer::definitions, + DefinitionSerializer::new + ); + + MultiblockDefinition toDefinition() { + Vec3i controllerOffset = this.getControllerOffset(); + int sizeX = this.structure[0][0].length(); + int sizeZ = this.structure[0].length; + int sizeXZ = sizeX * sizeZ; + int sizeY = this.structure.length; + int sizeXYZ = sizeXZ * sizeY; + ImmutableList.Builder structure = ImmutableList.builder(); + int cursor = 0; + while (cursor < sizeXYZ) { + int indexX = cursor % sizeX; + int indexY = cursor / sizeXZ; + int indexZ = (cursor % sizeXZ) / sizeX; + + char key = this.getChar(indexX, indexY, indexZ); + if (this.isEmpty(key)) { + cursor++; + continue; + } + + // 找到非空位置,计算世界坐标 + int worldX = indexX - controllerOffset.getX(); + int worldY = sizeY - 1 - indexY - controllerOffset.getY(); + int worldZ = indexZ - controllerOffset.getZ(); + cursor++; + structure.add(new MultiblockPosInfo( + key, + new BlockPos(worldX, worldY, worldZ), + new BlockPos(indexX, indexY, indexZ) + )); + } + return new MultiblockDefinition(structure.build(), this.definitions, sizeX, sizeY, sizeZ); + } + + static DefinitionSerializer fromDefinition(MultiblockDefinition definition) { + String[][] structure = new String[definition.sizeY()][definition.sizeZ()]; + for (int y = 0; y < definition.sizeY(); y++) { + for (int z = 0; z < definition.sizeZ(); z++) { + char[] xs = new char[definition.sizeX()]; + Arrays.fill(xs, ' '); + for (MultiblockPosInfo pos : definition.structure()) { + if (pos.pos().getY() != y || pos.pos().getZ() != z) continue; + xs[pos.pos().getX()] = pos.key(); + } + structure[y][z] = new String(xs); + } + } + return new DefinitionSerializer(structure, definition.definitions()); + } + + public char getChar(int x, int y, int z) { + return this.structure[y][z].charAt(x); + } + + public boolean isEmpty(char key) { + return key == Character.MIN_VALUE; + } + + private Vec3i getControllerOffset() { + for (int y = 0; y < this.structure.length; y++) { + String[] xz = this.structure[y]; + for (int z = 0; z < xz.length; z++) { + String xs = xz[z]; + for (int x = 0; x < xs.length(); x++) { + if (xs.charAt(x) != MultiblockDefinition.CONTROLLER) continue; + return new Vec3i(x, y, z); + } + } + } + throw new IllegalStateException("Unexpected no controller in structure"); + } + + static @Unmodifiable String[][] toArrays(List> lists) { + int size = lists.size(); + String[][] result = new String[size][]; + for (int i = 0; i < size; i++) { + result[i] = lists.get(i).toArray(String[]::new); + } + return result; + } + + private static @Unmodifiable List> toLists(String[][] arrays) { + List> result = new ArrayList<>(); + for (String[] array : arrays) { + result.add(List.of(array)); + } + return ImmutableList.copyOf(result); + } + + private static @Unmodifiable Char2ObjectMap toC2OMap(Map map) { + return Char2ObjectMaps.unmodifiable(new Char2ObjectOpenHashMap<>(map)); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockDefinition.java similarity index 54% rename from module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java rename to module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockDefinition.java index f1c0b03..418d102 100644 --- a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockDefinition.java @@ -1,25 +1,17 @@ -package dev.anvilcraft.lib.v2.multiblock; +package dev.anvilcraft.lib.v2.multiblock.definition; -import com.google.common.collect.AbstractIterator; -import com.google.common.collect.ImmutableList; -import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; import com.mojang.serialization.MapCodec; -import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.anvilcraft.lib.v2.recipe.component.BlockStatePredicate; -import dev.anvilcraft.lib.v2.recipe.util.CodecUtil; import it.unimi.dsi.fastutil.chars.Char2ObjectMap; import it.unimi.dsi.fastutil.chars.Char2ObjectMaps; import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; -import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; -import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Vec3i; import net.minecraft.data.worldgen.BootstrapContext; import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.LevelAccessor; @@ -29,11 +21,7 @@ import org.jetbrains.annotations.Unmodifiable; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.function.Function; /** * 多方块定义类 @@ -42,50 +30,36 @@ @Accessors(fluent = true, chain = false) public class MultiblockDefinition { public static final char CONTROLLER = '0'; - public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(ins -> ins.group( - Codec.STRING - .listOf() - .listOf() - .xmap(MultiblockDefinition::toArrays, MultiblockDefinition::toLists) - .fieldOf("structure") - .forGetter(MultiblockDefinition::structure), - Codec.unboundedMap(CodecUtil.CHAR_CODEC, BlockStatePredicate.CODEC) - .xmap(MultiblockDefinition::toC2OMap, Function.identity()) - .fieldOf("definitions") - .forGetter(MultiblockDefinition::definitions) - ).apply(ins, MultiblockDefinition::new)); - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.STRING_UTF8 - .apply(ByteBufCodecs.list()) - .apply(ByteBufCodecs.list()) - .map(MultiblockDefinition::toArrays, MultiblockDefinition::toLists), - MultiblockDefinition::structure, - ByteBufCodecs.>map( - HashMap::new, - CodecUtil.CHAR_STREAM_CODEC, - BlockStatePredicate.STREAM_CODEC - ).map(MultiblockDefinition::toC2OMap, Function.identity()), - MultiblockDefinition::definitions, - MultiblockDefinition::new + public static final MapCodec CODEC = DefinitionSerializer.CODEC.xmap( + DefinitionSerializer::toDefinition, + DefinitionSerializer::fromDefinition ); - private final @Unmodifiable String[][] structure; + public static final StreamCodec STREAM_CODEC = DefinitionSerializer.STREAM_CODEC.map( + DefinitionSerializer::toDefinition, + DefinitionSerializer::fromDefinition + ); + private final @Unmodifiable List structure; private final @Unmodifiable Char2ObjectMap definitions; - @Getter(AccessLevel.NONE) - private Vec3i controllerOffset; - private Iterable localPoses; + private final int sizeX, sizeY, sizeZ; /** * 构建多方块定义类 * - * @param structure 结构,按 {@code String(x)[reversed y][z]} 的顺序定义 + * @param structure 结构 * @param definitions 定义,记录了字符与方块状态的对应情况。 */ - private MultiblockDefinition( - @Unmodifiable String[][] structure, - @Unmodifiable Char2ObjectMap definitions + MultiblockDefinition( + @Unmodifiable List structure, + @Unmodifiable Char2ObjectMap definitions, + int sizeX, + int sizeY, + int sizeZ ) { this.structure = structure; this.definitions = definitions; + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; } public static Builder builder() { @@ -105,7 +79,10 @@ public char getChar(Vec3i localPos) { } public char getChar(int x, int y, int z) { - return this.structure[y][z].charAt(x); + for (MultiblockPosInfo pos : this.structure) { + if (pos.pos().getX() == x && pos.pos().getY() == y && pos.pos().getZ() == z) return pos.key(); + } + return Character.MIN_VALUE; } public boolean isEmpty(char key) { @@ -120,70 +97,6 @@ public boolean isEmpty(int x, int y, int z) { return this.isEmpty(this.getChar(x, y, z)); } - public Vec3i getControllerOffset() { - if (this.controllerOffset != null) return this.controllerOffset; - for (int y = 0; y < this.structure.length; y++) { - String[] xz = this.structure[y]; - for (int z = 0; z < xz.length; z++) { - String xs = xz[z]; - for (int x = 0; x < xs.length(); x++) { - if (xs.charAt(x) != MultiblockDefinition.CONTROLLER) continue; - return this.controllerOffset = new Vec3i(x, y, z); - } - } - } - throw new IllegalStateException("Unexpected no controller in structure"); - } - - public Iterable getGlobalPoses(BlockPos controllerPos) { - return () -> new AbstractIterator<>() { - private final Iterator localPoses = MultiblockDefinition.this.getLocalPoses().iterator(); - - @Override - protected @Nullable BlockPos computeNext() { - BlockPos pos = this.localPoses.next(); - if (pos == null) return this.endOfData(); - return pos.offset(controllerPos); - } - }; - } - - public Iterable getLocalPoses() { - if (this.localPoses != null) return this.localPoses; - Vec3i controllerOffset = this.getControllerOffset(); - int sizeX = this.structure[0][0].length(); - int sizeZ = this.structure[0].length; - int sizeXZ = sizeX * sizeZ; - int sizeY = this.structure.length; - int sizeXYZ = sizeXZ * sizeY; - return this.localPoses = () -> new AbstractIterator<>() { - private final BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(); - private int cursor = 0; - - @Override - protected @Nullable BlockPos computeNext() { - while (this.cursor < sizeXYZ) { - int indexX = this.cursor % sizeX; - int indexY = this.cursor / sizeXZ; - int indexZ = (this.cursor % sizeXZ) / sizeX; - - if (MultiblockDefinition.this.isEmpty(indexX, indexY, indexZ)) { - this.cursor++; - continue; - } - - // 找到非空位置,计算世界坐标 - int worldX = indexX - controllerOffset.getX(); - int worldY = sizeY - 1 - indexY - controllerOffset.getY(); - int worldZ = indexZ - controllerOffset.getZ(); - this.cursor++; - return this.mut.set(worldX, worldY, worldZ); - } - return this.endOfData(); - } - }; - } - public static class Builder { private final List> structure = new ArrayList<>(); private final Char2ObjectMap definitions = new Char2ObjectOpenHashMap<>(); @@ -289,33 +202,10 @@ public Holder create() { } public MultiblockDefinition build() { - return new MultiblockDefinition( - MultiblockDefinition.toArrays(this.structure), + return new DefinitionSerializer( + DefinitionSerializer.toArrays(this.structure), Char2ObjectMaps.unmodifiable(this.definitions) - ); - } - } - - // 工具方法 - - private static @Unmodifiable String[][] toArrays(List> lists) { - int size = lists.size(); - String[][] result = new String[size][]; - for (int i = 0; i < size; i++) { - result[i] = lists.get(i).toArray(String[]::new); + ).toDefinition(); } - return result; - } - - private static @Unmodifiable List> toLists(String[][] arrays) { - List> result = new ArrayList<>(); - for (String[] array : arrays) { - result.add(List.of(array)); - } - return ImmutableList.copyOf(result); - } - - private static @Unmodifiable Char2ObjectMap toC2OMap(Map map) { - return Char2ObjectMaps.unmodifiable(new Char2ObjectOpenHashMap<>(map)); } } diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java new file mode 100644 index 0000000..d7670f7 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java @@ -0,0 +1,6 @@ +package dev.anvilcraft.lib.v2.multiblock.definition; + +import net.minecraft.core.BlockPos; + +public record MultiblockPosInfo(char key, BlockPos offset, BlockPos pos) { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java new file mode 100644 index 0000000..02fdb14 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package dev.anvilcraft.lib.v2.multiblock.definition; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java index 3669b10..5cfc5e1 100644 --- a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java @@ -2,7 +2,7 @@ import com.mojang.serialization.MapCodec; import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; -import dev.anvilcraft.lib.v2.multiblock.MultiblockDefinition; +import dev.anvilcraft.lib.v2.multiblock.definition.MultiblockDefinition; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; import net.neoforged.bus.api.SubscribeEvent; diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java new file mode 100644 index 0000000..172b5c2 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package dev.anvilcraft.lib.v2.multiblock; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; From 0d6c1b446bf87aa6ed38edd7205d77936bffa727 Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Wed, 18 Mar 2026 00:22:02 +0800 Subject: [PATCH 06/13] =?UTF-8?q?feat(ci):=20=E8=A1=A5=E5=85=85=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E5=BA=93=E7=BC=BA=E5=A4=B1=E7=9A=84ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ .github/workflows/pull_request.yml | 19 +++++++++++++++++++ .github/workflows/release.yml | 26 ++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f7fedb..3cb888e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,6 +89,31 @@ jobs: signing_key: ${{ secrets.SIGNING_KEY }} signing_public_key: ${{ secrets.SIGNING_PUBLIC_KEY }} signing_password: ${{ secrets.SIGNING_PASSWORD }} + network: + uses: ./.github/workflows/build_and_test.yml + with: + module: network + module_id: anvillib-network + mod_id: anvillib_network + ci_build: true + pr_build: false + secrets: + maven_url: ${{ secrets.MAVEN_URL }} + maven_user: ${{ secrets.MAVEN_USER }} + maven_pass: ${{ secrets.MAVEN_PASS }} + network-maven-central-deploy: + uses: ./.github/workflows/publish_maven_central.yml + needs: + - network + with: + module: network + module_id: anvillib-network + secrets: + maven_central_username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + maven_central_password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + signing_key: ${{ secrets.SIGNING_KEY }} + signing_public_key: ${{ secrets.SIGNING_PUBLIC_KEY }} + signing_password: ${{ secrets.SIGNING_PASSWORD }} recipe: needs: - config @@ -146,6 +171,7 @@ jobs: - config - integration - moveable-entity-block + - network - recipe - registrum uses: ./.github/workflows/build_and_test.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b4c0791..4aef354 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -67,6 +67,24 @@ jobs: signing_public_key: ${{ secrets.SIGNING_PUBLIC_KEY }} signing_password: ${{ secrets.SIGNING_PASSWORD }} + network: + uses: ./.github/workflows/build_and_test.yml + with: + module: network + module_id: anvillib-network + mod_id: anvillib_network + ci_build: true + pr_build: true + secrets: + maven_url: ${{ secrets.MAVEN_URL }} + maven_user: ${{ secrets.MAVEN_USER }} + maven_pass: ${{ secrets.MAVEN_PASS }} + maven_central_username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + maven_central_password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + signing_key: ${{ secrets.SIGNING_KEY }} + signing_public_key: ${{ secrets.SIGNING_PUBLIC_KEY }} + signing_password: ${{ secrets.SIGNING_PASSWORD }} + recipe: needs: - config @@ -110,6 +128,7 @@ jobs: - config - integration - moveable-entity-block + - network - recipe - registrum uses: ./.github/workflows/build_and_test.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3ce959..c4e3495 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -89,6 +89,31 @@ jobs: signing_key: ${{ secrets.SIGNING_KEY }} signing_public_key: ${{ secrets.SIGNING_PUBLIC_KEY }} signing_password: ${{ secrets.SIGNING_PASSWORD }} + network: + uses: ./.github/workflows/build_and_test.yml + with: + module: network + module_id: anvillib-network + mod_id: anvillib_network + ci_build: false + pr_build: false + secrets: + maven_url: ${{ secrets.MAVEN_URL }} + maven_user: ${{ secrets.MAVEN_USER }} + maven_pass: ${{ secrets.MAVEN_PASS }} + network-maven-central-deploy: + uses: ./.github/workflows/publish_maven_central.yml + needs: + - network + with: + module: network + module_id: anvillib-network + secrets: + maven_central_username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + maven_central_password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + signing_key: ${{ secrets.SIGNING_KEY }} + signing_public_key: ${{ secrets.SIGNING_PUBLIC_KEY }} + signing_password: ${{ secrets.SIGNING_PASSWORD }} recipe: needs: - config @@ -146,6 +171,7 @@ jobs: - config - integration - moveable-entity-block + - network - recipe - registrum uses: ./.github/workflows/build_and_test.yml From c7a790b8ff6ccea0bd32fa0f5f8309054ba741ec Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Wed, 18 Mar 2026 04:41:42 +0800 Subject: [PATCH 07/13] =?UTF-8?q?fix(packaging):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E5=BA=93=E4=B8=8D=E4=BC=9A=E6=89=93=E5=8C=85?= =?UTF-8?q?=E8=BF=9B=E4=B8=BB=E5=BA=93=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module.main/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module.main/build.gradle b/module.main/build.gradle index 463e7bb..3d683df 100644 --- a/module.main/build.gradle +++ b/module.main/build.gradle @@ -119,12 +119,14 @@ dependencies { jarJar(implementation("dev.anvilcraft.lib:anvillib-config-neoforge-1.21.1:latest.release")) jarJar(implementation("dev.anvilcraft.lib:anvillib-integration-neoforge-1.21.1:latest.release")) jarJar(implementation("dev.anvilcraft.lib:anvillib-moveable-entity-block-neoforge-1.21.1:latest.release")) + jarJar(implementation("dev.anvilcraft.lib:anvillib-network-neoforge-1.21.1:latest.release")) jarJar(implementation("dev.anvilcraft.lib:anvillib-recipe-neoforge-1.21.1:latest.release")) jarJar(implementation("dev.anvilcraft.lib:anvillib-registrum-neoforge-1.21.1:latest.release")) } else { jarJar(implementation project(":anvillib-config-neoforge-1.21.1")) jarJar(implementation project(":anvillib-integration-neoforge-1.21.1")) jarJar(implementation project(":anvillib-moveable-entity-block-neoforge-1.21.1")) + jarJar(implementation project(":anvillib-network-neoforge-1.21.1")) jarJar(implementation project(":anvillib-recipe-neoforge-1.21.1")) jarJar(implementation project(":anvillib-registrum-neoforge-1.21.1")) } From 00d65d007ca2d1eff45a53e59ca81390b0a66d3e Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:08:01 +0800 Subject: [PATCH 08/13] =?UTF-8?q?fix(mixin):=20=E4=BF=AE=E5=A4=8D=E5=9B=A0?= =?UTF-8?q?=E7=BC=BA=E5=A4=B1mixin=E6=96=87=E4=BB=B6=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E5=B4=A9=E6=BA=83=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/anvillib_network.mixins.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 module.network/src/main/resources/anvillib_network.mixins.json diff --git a/module.network/src/main/resources/anvillib_network.mixins.json b/module.network/src/main/resources/anvillib_network.mixins.json new file mode 100644 index 0000000..5135eb6 --- /dev/null +++ b/module.network/src/main/resources/anvillib_network.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.anvilcraft.lib.v2.network.mixin", + "compatibilityLevel": "JAVA_8", + "refmap": "anvillib.refmap.json", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} From 6d2741f4cfd08e45118ca86612c670b5695c602a Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Wed, 18 Mar 2026 06:35:17 +0800 Subject: [PATCH 09/13] =?UTF-8?q?fix(network):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E5=8C=85=E8=87=AA=E5=8A=A8=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复了网络包无法自动注册的问题 --- .../v2/network/register/NetworkRegistrar.java | 28 +++++++++++-------- .../lib/v2/network/register/PacketData.java | 13 ++++----- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java index fc5b709..4a2b3bf 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/NetworkRegistrar.java @@ -15,10 +15,10 @@ /** * 网络包注册器 - * + * *

应在 {@link net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent RegisterPayloadHandlersEvent} 的侦听器中使用

- * - * @see NetworkRegistrar#register(PayloadRegistrar, String) + * + * @see NetworkRegistrar#register(PayloadRegistrar, String) */ @Slf4j public class NetworkRegistrar { @@ -27,7 +27,7 @@ public class NetworkRegistrar { /** * 注册对应 {@code modId} 的模组中所有使用 {@link Network} 注解的软件包下的网络包 - * + * * @param registrar 网络包注册器。应通过 * {@link * net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent#registrar(String) @@ -43,7 +43,7 @@ public static void register(PayloadRegistrar registrar, String modId) { for (ModFileScanData.AnnotationData annotation : scanData.getAnnotations()) { if ( !annotation.annotationType().getDescriptor().equals(ANNOTATION_NAME) - || annotation.targetType() != ElementType.PACKAGE + || annotation.targetType() != ElementType.TYPE ) { continue; } @@ -58,25 +58,26 @@ public static void register(PayloadRegistrar registrar, String modId) { default -> throw new IllegalArgumentException("Unknown packet protocol: " + protocolHolder.value()); }; } - String packageName = annotation.memberName().substring(0, annotation.memberName().lastIndexOf('.') - 1); + String packageName = annotation.memberName().substring(0, annotation.memberName().lastIndexOf('.')); log.info("Considering network package {}", packageName); for (ModFileScanData.ClassData classData : scanData.getClasses()) { - if (!classData.clazz().getClassName().startsWith(packageName)) continue; + String className = classData.clazz().getClassName(); + if (!className.substring(0, className.lastIndexOf('.')).equals(packageName)) continue; - String interfaceName = null; + boolean isPacket = false; for (Type anInterface : classData.interfaces()) { if (!anInterface.getDescriptor().startsWith(NetworkRegistrar.PACKET_PACKAGE_PREFIX)) continue; - interfaceName = anInterface.getClassName(); + isPacket = true; break; } - if (interfaceName == null) continue; + if (!isPacket) continue; try { - Class packetClass = (Class) loader.loadClass(classData.clazz().getClassName()); + Class packetClass = (Class) loader.loadClass(className); NetworkRegistrar.register(registrar, protocol, packetClass); } catch (ClassNotFoundException e) { - log.error("Cannot register packet {}", classData.clazz().getClassName(), e); + log.error("Cannot find packet class {}", className, e); throw new IllegalStateException(); } } @@ -87,6 +88,7 @@ private static void register(PayloadRegistrar registrar, Pac switch (protocol) { case CONFIGURATION -> { PacketData data = PacketData.find(packetClass); + log.debug("Registered packet {} for 'CONFIGURATION', '{}'", data.type().id(), data.direction()); switch (data.direction()) { case CLIENTBOUND -> registrar.configurationToClient(data.type(), data.streamCodec(), data.handler()); case SERVERBOUND -> registrar.configurationToServer(data.type(), data.streamCodec(), data.handler()); @@ -95,6 +97,7 @@ private static void register(PayloadRegistrar registrar, Pac } case PLAY -> { PacketData data = PacketData.find(packetClass); + log.debug("Registered packet {} for 'PLAY', '{}'", data.type().id(), data.direction()); switch (data.direction()) { case CLIENTBOUND -> registrar.playToClient(data.type(), data.streamCodec(), data.handler()); case SERVERBOUND -> registrar.playToServer(data.type(), data.streamCodec(), data.handler()); @@ -103,6 +106,7 @@ private static void register(PayloadRegistrar registrar, Pac } case COMMON -> { PacketData data = PacketData.find(packetClass); + log.debug("Registered packet {} for 'COMMON', '{}'", data.type().id(), data.direction()); switch (data.direction()) { case CLIENTBOUND -> registrar.commonToClient(data.type(), data.streamCodec(), data.handler()); case SERVERBOUND -> registrar.commonToServer(data.type(), data.streamCodec(), data.handler()); diff --git a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java index 3ea4967..440dd80 100644 --- a/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java +++ b/module.network/src/main/java/dev/anvilcraft/lib/v2/network/register/PacketData.java @@ -27,8 +27,7 @@ static PacketData find(Class pac CustomPacketPayload.Type type = null; StreamCodec codec = null; try { - Field[] fields = packetClass.getDeclaredFields(); - for (Field field : fields) { + for (Field field : packetClass.getDeclaredFields()) { Set accessFlags = field.accessFlags(); if ( !accessFlags.contains(AccessFlag.STATIC) @@ -36,7 +35,7 @@ static PacketData find(Class pac ) { continue; } - Class declaringClass = field.getDeclaringClass(); + Class declaringClass = field.getType(); if (declaringClass.isAssignableFrom(CustomPacketPayload.Type.class)) { field.setAccessible(true); type = (CustomPacketPayload.Type) field.get(null); @@ -60,16 +59,16 @@ static PacketData find(Class pac PacketDirection direction; IPayloadHandler handler; - if (packetClass.isAssignableFrom(IInsensitiveBiPacket.class)) { + if (IInsensitiveBiPacket.class.isAssignableFrom(packetClass)) { direction = PacketDirection.BIDIRECTIONAL; handler = (packet, ctx) -> ((IInsensitiveBiPacket) packet).bidirectionalHandler(ctx); - } else if (packetClass.isAssignableFrom(ISensitiveBiPacket.class)) { + } else if (ISensitiveBiPacket.class.isAssignableFrom(packetClass)) { direction = PacketDirection.BIDIRECTIONAL; handler = (packet, ctx) -> ((ISensitiveBiPacket) packet).bidirectionalHandler(ctx); - } else if (packetClass.isAssignableFrom(IClientboundPacket.class)) { + } else if (IClientboundPacket.class.isAssignableFrom(packetClass)) { direction = PacketDirection.CLIENTBOUND; handler = (packet, ctx) -> ((IClientboundPacket) packet).clientHandler(ctx); - } else if (packetClass.isAssignableFrom(IServerboundPacket.class)) { + } else if (IServerboundPacket.class.isAssignableFrom(packetClass)) { direction = PacketDirection.SERVERBOUND; handler = (packet, ctx) -> ((IServerboundPacket) packet).serverHandler(ctx); } else { From 1dca02f728afe18be75affa9a6de04a492b51513 Mon Sep 17 00:00:00 2001 From: Gugle Date: Wed, 18 Mar 2026 20:13:00 +0800 Subject: [PATCH 10/13] feat(release): bump version to 2.0.0 and update README with new features --- README.en.md | 48 +++++++++++++++++++++++++++++++++++++++++++++--- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- build.gradle | 2 +- 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/README.en.md b/README.en.md index e04bd54..9cb0f09 100644 --- a/README.en.md +++ b/README.en.md @@ -3,7 +3,7 @@ [![Minecraft](https://img.shields.io/badge/Minecraft-1.21.1-green.svg)](https://minecraft.net/) [![Maven Central](https://img.shields.io/maven-central/v/dev.anvilcraft.lib/anvillib-neoforge-1.21.1)](https://central.sonatype.com/search?q=anvillib) [![NeoForge](https://img.shields.io/badge/NeoForge-21.1.x-orange.svg)](https://neoforged.net/) -[![License](https://img.shields.io/badge/License-MIT%20License-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) +[![License](https://img.shields.io/badge/License-MIT%20License-blue.svg)](https://opensource.org/licenses/MIT) **AnvilLib** is a NeoForge mod library developed by [Anvil Dev](https://github.com/Anvil-Dev), providing Minecraft mod developers with a series of practical tools and frameworks. @@ -15,9 +15,11 @@ AnvilLib adopts a modular design and includes the following functional modules: |---------------------------|---------------| | **Config** | Annotation-based configuration system | | **Integration** | Mod compatibility integration framework | +| **Network** | Networking API with automatic packet registration | | **Recipe** | In-world recipe system | | **Moveable Entity Block** | Support for block entities movable by pistons | | **Registrum** | Simplified registration system | +| **Main** | Aggregated module that bundles all submodules | ## Module Introduction @@ -36,7 +38,7 @@ Provides an annotation-based configuration management system to simplify the def **Usage Example:** ```java -@Config(name = "my_mod", typebirdsprite_ModConfig.Type.COMMON) +@Config(name = "my_mod", type = ModConfig.Type.COMMON) public class MyModConfig { @Comment("Enable debug mode") public boolean debugMode = false; @@ -71,6 +73,26 @@ public class JEIIntegration { } ``` +### Network Module + +Provides a NeoForge networking abstraction with package-based packet auto-registration. + +**Key Features:** + +- Define packet direction using `IClientboundPacket` / `IServerboundPacket` / `IInsensitiveBiPacket` +- Automatically register packet classes in a package via `NetworkRegistrar.register(...)` +- Supports `PLAY`, `CONFIGURATION`, and `COMMON` protocols + +**Usage Example:** + +```java +@SubscribeEvent +public static void onRegisterPayload(RegisterPayloadHandlersEvent event) { + PayloadRegistrar registrar = event.registrar("1"); + NetworkRegistrar.register(registrar, "my_mod"); +} +``` + ### Recipe Module Provides an in-world recipe system, allowing recipes to be executed in the world (rather than in crafting tables). @@ -137,6 +159,17 @@ public static final RegistryEntry MY_ITEM = REGISTRUM .register(); ``` +### Main Module + +`anvillib-neoforge-1.21.1` is the aggregate artifact. It bundles and re-exports: + +- `config` +- `integration` +- `network` +- `recipe` +- `moveable-entity-block` +- `registrum` + ## Dependency Integration ### Gradle (Groovy DSL) @@ -153,6 +186,7 @@ dependencies { // Or import individual modules as needed implementation "dev.anvilcraft.lib:anvillib-config-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-registrum-neoforge-1.21.1:2.0.0" @@ -168,9 +202,14 @@ repositories { dependencies { implementation("dev.anvilcraft.lib:anvillib-neoforge-1.21.1:2.0.0") + + // Optional single-module example + implementation("dev.anvilcraft.lib:anvillib-network-neoforge-1.21.1:2.0.0") } ``` +> Keep the dependency version aligned with release tags (current project property is `mod_version=2.0.0`). + ## Building the Project ```bash @@ -178,8 +217,11 @@ dependencies { git clone https://github.com/Anvil-Dev/AnvilLib.git cd AnvilLib -# Build +# Build on macOS / Linux ./gradlew build + +# Build on Windows (PowerShell / CMD) +gradlew.bat build ``` ## Requirements diff --git a/README.md b/README.md index 0f5ad09..17d7775 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Minecraft](https://img.shields.io/badge/Minecraft-1.21.1-green.svg)](https://minecraft.net/) [![Maven Central](https://img.shields.io/maven-central/v/dev.anvilcraft.lib/anvillib-neoforge-1.21.1)](https://central.sonatype.com/search?q=anvillib) [![NeoForge](https://img.shields.io/badge/NeoForge-21.1.x-orange.svg)](https://neoforged.net/) -[![License](https://img.shields.io/badge/License-MIT%20License-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) +[![License](https://img.shields.io/badge/License-MIT%20License-blue.svg)](https://opensource.org/licenses/MIT) **AnvilLib** 是一个由 [Anvil Dev](https://github.com/Anvil-Dev) 开发的 NeoForge 模组库,为 Minecraft 模组开发者提供一系列实用的工具和框架。 @@ -15,9 +15,11 @@ AnvilLib 采用模块化设计,包含以下功能模块: |---------------------------|---------------| | **Config** | 基于注解的配置系统 | | **Integration** | 模组兼容性集成框架 | +| **Network** | 网络通信与数据包自动注册框架 | | **Recipe** | 世界内配方系统 | | **Moveable Entity Block** | 可被活塞推动的方块实体支持 | | **Registrum** | 简化的注册系统 | +| **Main** | 聚合模块(包含全部子模块) | ## 模块介绍 @@ -71,6 +73,26 @@ public class JEIIntegration { } ``` +### Network 模块 + +提供面向 NeoForge 的网络通信抽象,支持按包扫描并自动注册数据包。 + +**主要特性:** + +- 使用 `IClientboundPacket` / `IServerboundPacket` / `IInsensitiveBiPacket` 定义通信方向 +- 通过 `NetworkRegistrar.register(...)` 自动注册同一包下的数据包 +- 支持 `PLAY` / `CONFIGURATION` / `COMMON` 三种协议通道 + +**使用示例:** + +```java +@SubscribeEvent +public static void onRegisterPayload(RegisterPayloadHandlersEvent event) { + PayloadRegistrar registrar = event.registrar("1"); + NetworkRegistrar.register(registrar, "my_mod"); +} +``` + ### Recipe 模块 提供世界内配方系统,允许定义在世界中(而非工作台)执行的配方。 @@ -137,6 +159,17 @@ public static final RegistryEntry MY_ITEM = REGISTRUM .register(); ``` +### Main 模块 + +`anvillib-neoforge-1.21.1` 为聚合发行模块,默认打包并重导出以下子模块: + +- `config` +- `integration` +- `network` +- `recipe` +- `moveable-entity-block` +- `registrum` + ## 依赖引入 ### Gradle (Groovy DSL) @@ -153,6 +186,7 @@ dependencies { // 或按需引入单独模块 implementation "dev.anvilcraft.lib:anvillib-config-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-registrum-neoforge-1.21.1:2.0.0" @@ -168,9 +202,14 @@ repositories { dependencies { implementation("dev.anvilcraft.lib:anvillib-neoforge-1.21.1:2.0.0") + + // 按需引入示例 + implementation("dev.anvilcraft.lib:anvillib-network-neoforge-1.21.1:2.0.0") } ``` +> 版本号建议与项目发布版本保持一致(当前工程配置为 `mod_version=2.0.0`)。 + ## 构建项目 ```bash @@ -178,8 +217,11 @@ dependencies { git clone https://github.com/Anvil-Dev/AnvilLib.git cd AnvilLib -# 构建 +# macOS / Linux 构建 ./gradlew build + +# Windows PowerShell / CMD 构建 +gradlew.bat build ``` ## 环境要求 diff --git a/build.gradle b/build.gradle index 9995314..9b94d9b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { } group = 'dev.anvilcraft.lib' -version = '1.4.0' +version = '2.0.0' repositories { mavenCentral() From a74671a3977ab93a56ca8d75b24198eaf82605da Mon Sep 17 00:00:00 2001 From: Gugle Date: Wed, 18 Mar 2026 20:24:27 +0800 Subject: [PATCH 11/13] docs(readme): format improvements and consistency updates in English and Chinese README files --- README.en.md | 28 +++++++++++++++++----------- README.md | 19 +++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/README.en.md b/README.en.md index 9cb0f09..a727bd5 100644 --- a/README.en.md +++ b/README.en.md @@ -5,21 +5,22 @@ [![NeoForge](https://img.shields.io/badge/NeoForge-21.1.x-orange.svg)](https://neoforged.net/) [![License](https://img.shields.io/badge/License-MIT%20License-blue.svg)](https://opensource.org/licenses/MIT) -**AnvilLib** is a NeoForge mod library developed by [Anvil Dev](https://github.com/Anvil-Dev), providing Minecraft mod developers with a series of practical tools and frameworks. +**AnvilLib** is a NeoForge mod library developed by [Anvil Dev](https://github.com/Anvil-Dev), providing Minecraft mod developers with a +series of practical tools and frameworks. ## Features AnvilLib adopts a modular design and includes the following functional modules: -| Module | Description | -|---------------------------|---------------| -| **Config** | Annotation-based configuration system | -| **Integration** | Mod compatibility integration framework | +| Module | Description | +|---------------------------|---------------------------------------------------| +| **Config** | Annotation-based configuration system | +| **Integration** | Mod compatibility integration framework | | **Network** | Networking API with automatic packet registration | -| **Recipe** | In-world recipe system | -| **Moveable Entity Block** | Support for block entities movable by pistons | -| **Registrum** | Simplified registration system | -| **Main** | Aggregated module that bundles all submodules | +| **Recipe** | In-world recipe system | +| **Moveable Entity Block** | Support for block entities movable by pistons | +| **Registrum** | Simplified registration system | +| **Main** | Aggregated module that bundles all submodules | ## Module Introduction @@ -38,6 +39,7 @@ Provides an annotation-based configuration management system to simplify the def **Usage Example:** ```java + @Config(name = "my_mod", type = ModConfig.Type.COMMON) public class MyModConfig { @Comment("Enable debug mode") @@ -65,6 +67,7 @@ Provides a framework for mod integrations, supporting automatic loading of integ **Usage Example:** ```java + @Integration(value = "jei", version = "[19.0,)") public class JEIIntegration { public void init() { @@ -86,6 +89,7 @@ Provides a NeoForge networking abstraction with package-based packet auto-regist **Usage Example:** ```java + @SubscribeEvent public static void onRegisterPayload(RegisterPayloadHandlersEvent event) { PayloadRegistrar registrar = event.registrar("1"); @@ -139,7 +143,8 @@ public class MyBlock extends Block implements IMoveableEntityBlock { ### Registrum Module -A registration system based on [Registrate](https://github.com/IThundxr/Registrate), simplifying the registration process for items, blocks, entities, etc. +A registration system based on [Registrate](https://github.com/IThundxr/Registrate), simplifying the registration process for items, blocks, +entities, etc. **Key Features:** @@ -234,7 +239,8 @@ gradlew.bat build This project is licensed under the [MIT License](https://www.opensource.org/licenses/MIT). -Part of the Registrum module code is based on [Registrate](https://github.com/IThundxr/Registrate) and follows the Mozilla Public License 2.0. +Part of the Registrum module code is based on [Registrate](https://github.com/IThundxr/Registrate) and follows the Mozilla Public License +2.0. ## Author diff --git a/README.md b/README.md index 17d7775..72f992f 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,15 @@ AnvilLib 采用模块化设计,包含以下功能模块: -| 模块 | 说明 | -|---------------------------|---------------| -| **Config** | 基于注解的配置系统 | -| **Integration** | 模组兼容性集成框架 | +| 模块 | 说明 | +|---------------------------|----------------| +| **Config** | 基于注解的配置系统 | +| **Integration** | 模组兼容性集成框架 | | **Network** | 网络通信与数据包自动注册框架 | -| **Recipe** | 世界内配方系统 | -| **Moveable Entity Block** | 可被活塞推动的方块实体支持 | -| **Registrum** | 简化的注册系统 | -| **Main** | 聚合模块(包含全部子模块) | +| **Recipe** | 世界内配方系统 | +| **Moveable Entity Block** | 可被活塞推动的方块实体支持 | +| **Registrum** | 简化的注册系统 | +| **Main** | 聚合模块(包含全部子模块) | ## 模块介绍 @@ -38,6 +38,7 @@ AnvilLib 采用模块化设计,包含以下功能模块: **使用示例:** ```java + @Config(name = "my_mod", type = ModConfig.Type.COMMON) public class MyModConfig { @Comment("启用调试模式") @@ -65,6 +66,7 @@ MyModConfig config = ConfigManager.register("my_mod", MyModConfig::new); **使用示例:** ```java + @Integration(value = "jei", version = "[19.0,)") public class JEIIntegration { public void init() { @@ -86,6 +88,7 @@ public class JEIIntegration { **使用示例:** ```java + @SubscribeEvent public static void onRegisterPayload(RegisterPayloadHandlersEvent event) { PayloadRegistrar registrar = event.registrar("1"); From 4fc52e2f861fc4fa65a93a5212cd4177bc238ff5 Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:14:01 +0800 Subject: [PATCH 12/13] =?UTF-8?q?feat(dynamic-multiblock):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=A4=9A=E6=96=B9=E5=9D=97=E5=BA=93=EF=BC=88=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了多方块库(未完成) --- module.dynamic-multiblock/build.gradle | 275 +++++++++++++++ module.dynamic-multiblock/gradle.properties | 31 ++ .../multiblock/AnvilLibDynamicMultiblock.java | 19 ++ .../multiblock/DynamicMultiblockManager.java | 25 ++ .../lib/v2/multiblock/Multiblock.java | 25 ++ .../v2/multiblock/MultiblockDefinition.java | 321 ++++++++++++++++++ .../v2/multiblock/MultiblockValidator.java | 25 ++ .../DynamicMultiblockCapability.java | 4 + .../controller/IMultiblockController.java | 19 ++ .../MultiblockControllerInstance.java | 6 + .../SimpleMultiblockController.java | 6 + .../multiblock/event/BlockEventListener.java | 14 + .../v2/multiblock/init/LibCapabilities.java | 12 + .../lib/v2/multiblock/init/LibRegistries.java | 23 ++ .../lib/v2/multiblock/part/IMultiPart.java | 22 ++ .../resources/META-INF/neoforge.mods.toml | 86 +++++ .../anvillib_dynamic_multiblock.mixins.json | 14 + settings.gradle | 2 + 18 files changed, 929 insertions(+) create mode 100644 module.dynamic-multiblock/build.gradle create mode 100644 module.dynamic-multiblock/gradle.properties create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java create mode 100644 module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json diff --git a/module.dynamic-multiblock/build.gradle b/module.dynamic-multiblock/build.gradle new file mode 100644 index 0000000..c2baa3a --- /dev/null +++ b/module.dynamic-multiblock/build.gradle @@ -0,0 +1,275 @@ +plugins { + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + alias libs.plugins.modDevGradle + alias libs.plugins.lombok + alias libs.plugins.machete + id 'org.jreleaser' version '1.23.0' + id "me.modmuss50.mod-publish-plugin" version "1.1.0" +} + +String buildType = 'snapshot' +String buildNumber +if (System.getenv("CI_BUILD") == 'false') buildNumber = null +else { + if (System.getenv("PR_BUILD") != 'false') buildType = 'pr' + buildNumber = System.getenv("GITHUB_RUN_NUMBER") +} +version = "${mod_version}" + (buildNumber != null ? "+${buildType}.${buildNumber}" : "") +group = mod_group_id + +base { + archivesName = "${project.mod_id}-neoforge-${libs.versions.minecraft.get()}" +} + +repositories { + maven { url = "https://server.cjsah.net:1002/maven/" } + mavenLocal() + mavenCentral() +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +neoForge { + // Specify the version of NeoForge to use. + version = libs.versions.neoForge.get() + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version + } + + // This line is optional. Access Transformers are automatically detected + // accessTransformers.add('src/main/resources/META-INF/accesstransformer.cfg') + + interfaceInjectionData { + // from 'src/main/resources/interface_injections.json' + // publish file('src/main/resources/interface_injections.json') + } + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + client() + + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + type = "gameTestServer" + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + data() + + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // gameDirectory = project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + + // applies to all the run configs above + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + logLevel = org.slf4j.event.Level.DEBUG + } + } + + mods { + // define mod <-> source bindings + // these are used to tell the game which sources are for which mod + // mostly optional in a single mod project + // but multi mod projects should define one per mod + "${mod_id}" { + sourceSet(sourceSets.main) + } + } +} + +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + + +dependencies { + if (System.getenv("NOT_DEV") == 'true') { + jarJar(implementation("dev.anvilcraft.lib:anvillib-recipe-neoforge-1.21.1:latest.release")) + } else { + jarJar(implementation project(":anvillib-recipe-neoforge-1.21.1")) + } +} + +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. +// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : version, + mod_authors : mod_authors, + mod_description : mod_description] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} + +tasks.register('sourcesJar', Jar) { + from delombok.outputs.files + dependsOn delombok + archiveClassifier = "sources" +} + +tasks.register('javadocJar', Jar) { + from javadoc.destinationDir + dependsOn javadoc + archiveClassifier = "javadoc" +} + +javadoc { + options.encoding = 'UTF-8' + options.addStringOption('Xdoclint:none', '-quiet') +} + +jar { + from(rootProject.file("LICENSE")) + from(rootProject.file("ASSETS_LICENSE")) + from(rootProject.file("README.md")) + from(rootProject.file("README_en.md")) +} + +artifacts { + archives tasks.jar + archives tasks.sourcesJar + archives tasks.javadocJar +} + +def this_name = base.archivesName.get() +def this_description = mod_description + +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + + artifact sourcesJar + artifact javadocJar + + versionMapping { + allVariants { + fromResolutionResult() + } + } + + pom { + name = this_name + description = this_description + url = 'https://github.com/Anvil-Dev/AnvilLib' + + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/license/mit' + } + } + developers { + developer { + id = 'Anvil-Dev' + name = 'AnvilCraft Dev' + email = 'admin@anvilcraft.dev' + } + } + scm { + url = 'https://github.com/Anvil-Dev/AnvilLib' + connection = 'https://github.com/Anvil-Dev/AnvilLib.git' + developerConnection = 'https://github.com/Anvil-Dev/AnvilLib.git' + } + } + } + } + repositories { + // Add repositories to publish to here. + def MAVEN_URL = System.getenv("MAVEN_URL") + if (MAVEN_URL != null) { + maven { + url MAVEN_URL + credentials { + username System.getenv("MAVEN_USERNAME") + password System.getenv("MAVEN_PASSWORD") + } + } + } + mavenLocal() + maven { + name = "staging" + url = layout.buildDirectory.dir("staging-deploy") + } + } +} + +jreleaser { + signing { + active = 'ALWAYS' + armored = true + } + + deploy { + maven { + mavenCentral { + sonatype { + active = 'ALWAYS' + url = 'https://central.sonatype.com/api/v1/publisher' + stagingRepository('build/staging-deploy') + } + } + } + } +} + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} + +machete { + // disable machete locally for faster builds + enabled = false +} + +lombok { + version = "1.18.34" +} diff --git a/module.dynamic-multiblock/gradle.properties b/module.dynamic-multiblock/gradle.properties new file mode 100644 index 0000000..2cced56 --- /dev/null +++ b/module.dynamic-multiblock/gradle.properties @@ -0,0 +1,31 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +java_version=21 +org.gradle.jvmargs=-Xmx6G +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +## Environment Properties +# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge +# The Minecraft version must agree with the Neo version to get a valid artifact +minecraft_version=1.21.1 +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +minecraft_version_range=[1.21.1,1.22) +# The Neo version must agree with the Minecraft version to get a valid artifact +neo_version=21.1.79 +# The Neo version range can use any version of Neo as bounds +neo_version_range=[21,) +# The loader version range can only use the major version of FML as bounds +loader_version_range=[4,) +parchment_minecraft_version=1.21.1 +parchment_mappings_version=2024.11.17 +## Mod Properties +# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} +# Must match the String constant located in the main mod class annotated with @Mod. +mod_id=anvillib_dynamic_multiblock +# The human-readable display name for the mod. +mod_name=AnvilLib-Dynamic-Multiblock +# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. +mod_description=In-world dynamic multiblock system diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java new file mode 100644 index 0000000..892b788 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/AnvilLibDynamicMultiblock.java @@ -0,0 +1,19 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; + +@Mod(AnvilLibDynamicMultiblock.MOD_ID) +public class AnvilLibDynamicMultiblock { + public static final String MAIN_ID = "anvillib"; + public static final String MOD_ID = "anvillib_dynamic_multiblock"; + + public AnvilLibDynamicMultiblock(IEventBus modEventBus, ModContainer modContainer) { + } + + public static ResourceLocation of(String path) { + return ResourceLocation.fromNamespaceAndPath(MAIN_ID, path); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java new file mode 100644 index 0000000..61a81c1 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/DynamicMultiblockManager.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import net.minecraft.world.level.Level; + +import java.util.HashMap; +import java.util.Map; + +public class DynamicMultiblockManager { + + // ========================= ↓ 静态方法 ↓ ========================= // + + private static final Map MANAGERS = new HashMap<>(); + + public static DynamicMultiblockManager get(Level level) { + return DynamicMultiblockManager.MANAGERS.computeIfAbsent(level, DynamicMultiblockManager::new); + } + + // ========================= ↓ 成员方法 ↓ ========================= // + + private transient final Level level; + + public DynamicMultiblockManager(Level level) { + this.level = level; + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java new file mode 100644 index 0000000..5b76f23 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import dev.anvilcraft.lib.v2.multiblock.controller.IMultiblockController; +import lombok.Getter; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; + +@Getter +public class Multiblock { + private final IMultiblockController controller; + private final BlockPos controllerPos; + private final MultiblockDefinition definition; + + public Multiblock(IMultiblockController controller, BlockPos controllerPos, MultiblockDefinition definition) { + this.controller = controller; + this.controllerPos = controllerPos; + this.definition = definition; + } + + public Multiblock(Block controller, BlockPos controllerPos, MultiblockDefinition definition) { + this.controller = IMultiblockController.of(controller); + this.controllerPos = controllerPos; + this.definition = definition; + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java new file mode 100644 index 0000000..f1c0b03 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java @@ -0,0 +1,321 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableList; +import com.mojang.serialization.Codec; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.anvilcraft.lib.v2.recipe.component.BlockStatePredicate; +import dev.anvilcraft.lib.v2.recipe.util.CodecUtil; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.chars.Char2ObjectMaps; +import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Vec3i; +import net.minecraft.data.worldgen.BootstrapContext; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * 多方块定义类 + */ +@Getter +@Accessors(fluent = true, chain = false) +public class MultiblockDefinition { + public static final char CONTROLLER = '0'; + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(ins -> ins.group( + Codec.STRING + .listOf() + .listOf() + .xmap(MultiblockDefinition::toArrays, MultiblockDefinition::toLists) + .fieldOf("structure") + .forGetter(MultiblockDefinition::structure), + Codec.unboundedMap(CodecUtil.CHAR_CODEC, BlockStatePredicate.CODEC) + .xmap(MultiblockDefinition::toC2OMap, Function.identity()) + .fieldOf("definitions") + .forGetter(MultiblockDefinition::definitions) + ).apply(ins, MultiblockDefinition::new)); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8 + .apply(ByteBufCodecs.list()) + .apply(ByteBufCodecs.list()) + .map(MultiblockDefinition::toArrays, MultiblockDefinition::toLists), + MultiblockDefinition::structure, + ByteBufCodecs.>map( + HashMap::new, + CodecUtil.CHAR_STREAM_CODEC, + BlockStatePredicate.STREAM_CODEC + ).map(MultiblockDefinition::toC2OMap, Function.identity()), + MultiblockDefinition::definitions, + MultiblockDefinition::new + ); + private final @Unmodifiable String[][] structure; + private final @Unmodifiable Char2ObjectMap definitions; + @Getter(AccessLevel.NONE) + private Vec3i controllerOffset; + private Iterable localPoses; + + /** + * 构建多方块定义类 + * + * @param structure 结构,按 {@code String(x)[reversed y][z]} 的顺序定义 + * @param definitions 定义,记录了字符与方块状态的对应情况。 + */ + private MultiblockDefinition( + @Unmodifiable String[][] structure, + @Unmodifiable Char2ObjectMap definitions + ) { + this.structure = structure; + this.definitions = definitions; + } + + public static Builder builder() { + return new Builder(); + } + + public boolean test(LevelAccessor level, Vec3i localPos, BlockState state, @Nullable BlockEntity entity) { + return this.getState(localPos).test(level, state, entity); + } + + public BlockStatePredicate getState(Vec3i localPos) { + return this.definitions.get(this.getChar(localPos)); + } + + public char getChar(Vec3i localPos) { + return this.getChar(localPos.getX(), localPos.getY(), localPos.getZ()); + } + + public char getChar(int x, int y, int z) { + return this.structure[y][z].charAt(x); + } + + public boolean isEmpty(char key) { + return key == Character.MIN_VALUE; + } + + public boolean isEmpty(Vec3i localPos) { + return this.isEmpty(this.getChar(localPos)); + } + + public boolean isEmpty(int x, int y, int z) { + return this.isEmpty(this.getChar(x, y, z)); + } + + public Vec3i getControllerOffset() { + if (this.controllerOffset != null) return this.controllerOffset; + for (int y = 0; y < this.structure.length; y++) { + String[] xz = this.structure[y]; + for (int z = 0; z < xz.length; z++) { + String xs = xz[z]; + for (int x = 0; x < xs.length(); x++) { + if (xs.charAt(x) != MultiblockDefinition.CONTROLLER) continue; + return this.controllerOffset = new Vec3i(x, y, z); + } + } + } + throw new IllegalStateException("Unexpected no controller in structure"); + } + + public Iterable getGlobalPoses(BlockPos controllerPos) { + return () -> new AbstractIterator<>() { + private final Iterator localPoses = MultiblockDefinition.this.getLocalPoses().iterator(); + + @Override + protected @Nullable BlockPos computeNext() { + BlockPos pos = this.localPoses.next(); + if (pos == null) return this.endOfData(); + return pos.offset(controllerPos); + } + }; + } + + public Iterable getLocalPoses() { + if (this.localPoses != null) return this.localPoses; + Vec3i controllerOffset = this.getControllerOffset(); + int sizeX = this.structure[0][0].length(); + int sizeZ = this.structure[0].length; + int sizeXZ = sizeX * sizeZ; + int sizeY = this.structure.length; + int sizeXYZ = sizeXZ * sizeY; + return this.localPoses = () -> new AbstractIterator<>() { + private final BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(); + private int cursor = 0; + + @Override + protected @Nullable BlockPos computeNext() { + while (this.cursor < sizeXYZ) { + int indexX = this.cursor % sizeX; + int indexY = this.cursor / sizeXZ; + int indexZ = (this.cursor % sizeXZ) / sizeX; + + if (MultiblockDefinition.this.isEmpty(indexX, indexY, indexZ)) { + this.cursor++; + continue; + } + + // 找到非空位置,计算世界坐标 + int worldX = indexX - controllerOffset.getX(); + int worldY = sizeY - 1 - indexY - controllerOffset.getY(); + int worldZ = indexZ - controllerOffset.getZ(); + this.cursor++; + return this.mut.set(worldX, worldY, worldZ); + } + return this.endOfData(); + } + }; + } + + public static class Builder { + private final List> structure = new ArrayList<>(); + private final Char2ObjectMap definitions = new Char2ObjectOpenHashMap<>(); + + public Builder() { + } + + public Builder layer(String... layer) { + this.structure.add(List.of(layer)); + return this; + } + + public Builder defineController(BlockStatePredicate.Builder state) { + this.definitions.put(MultiblockDefinition.CONTROLLER, state.build()); + return this; + } + + public Builder define(char key, BlockStatePredicate.Builder state) { + this.definitions.put(key, state.build()); + return this; + } + + public void validate(String dmbId) { + if (!this.definitions.containsKey(MultiblockDefinition.CONTROLLER)) { + throw new IllegalArgumentException( + "Unlinked controller key %s in DMB id %s" + .formatted(MultiblockDefinition.CONTROLLER, dmbId) + ); + } + + int sizeX = -1; + int sizeZ = -1; + boolean checkedController = false; + for (int indexY = 0; indexY < this.structure.size(); indexY++) { + List layer = this.structure.get(indexY); + if (sizeZ == -1) sizeZ = layer.size(); + if (sizeZ != layer.size()) { + throw new IllegalArgumentException( + "Inconsistent z-width was found in layer %d, DMB id %s. Need %d, found %d" + .formatted(indexY, dmbId, sizeZ, layer.size()) + ); + } + + for (String xs : layer) { + for (int indexX = 0; indexX < xs.length(); indexX++) { + char key = xs.charAt(indexX); + if (sizeX == -1) sizeX = xs.length(); + if (sizeX != xs.length()) { + throw new IllegalArgumentException( + "Inconsistent x-width was found in layer %d, DMB id %s. Need %d, found %d" + .formatted(indexY, dmbId, sizeX, xs.length()) + ); + } + if (key == MultiblockDefinition.CONTROLLER) { + if (!checkedController) { + checkedController = true; + continue; + } else { + throw new IllegalArgumentException( + "Multiple center key (%s) found in DMB id %s" + .formatted(MultiblockDefinition.CONTROLLER, dmbId) + ); + } + } + if (!this.definitions.containsKey(key)) { + throw new IllegalArgumentException( + "Unlinked key %s in DMB id %s" + .formatted(key, dmbId) + ); + } + } + } + } + + if (!checkedController) { + throw new IllegalArgumentException( + "Undefined controller key (%s) in DMB id %s" + .formatted(MultiblockDefinition.CONTROLLER, dmbId) + ); + } + } + + public Holder.Reference register( + BootstrapContext context, + ResourceKey key + ) { + this.validate(key.location().toString()); + return context.register(key, this.build()); + } + + public Holder.Reference register( + BootstrapContext context, + ResourceKey key, + Lifecycle registryLifecycle + ) { + this.validate(key.location().toString()); + return context.register(key, this.build(), registryLifecycle); + } + + public Holder create() { + this.validate(""); + return Holder.direct(this.build()); + } + + public MultiblockDefinition build() { + return new MultiblockDefinition( + MultiblockDefinition.toArrays(this.structure), + Char2ObjectMaps.unmodifiable(this.definitions) + ); + } + } + + // 工具方法 + + private static @Unmodifiable String[][] toArrays(List> lists) { + int size = lists.size(); + String[][] result = new String[size][]; + for (int i = 0; i < size; i++) { + result[i] = lists.get(i).toArray(String[]::new); + } + return result; + } + + private static @Unmodifiable List> toLists(String[][] arrays) { + List> result = new ArrayList<>(); + for (String[] array : arrays) { + result.add(List.of(array)); + } + return ImmutableList.copyOf(result); + } + + private static @Unmodifiable Char2ObjectMap toC2OMap(Map map) { + return Char2ObjectMaps.unmodifiable(new Char2ObjectOpenHashMap<>(map)); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java new file mode 100644 index 0000000..5ef2dbf --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockValidator.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.multiblock; + +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class MultiblockValidator { + private static final Direction[] DIRECTIONS = new Direction[] { + Direction.NORTH, + Direction.SOUTH, + Direction.EAST, + Direction.WEST, + }; + + public enum State { + /** + * (非控制器)已与控制器连接 + */ + BOUND + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java new file mode 100644 index 0000000..551d482 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/capability/DynamicMultiblockCapability.java @@ -0,0 +1,4 @@ +package dev.anvilcraft.lib.v2.multiblock.capability; + +public class DynamicMultiblockCapability { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java new file mode 100644 index 0000000..83e2292 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/IMultiblockController.java @@ -0,0 +1,19 @@ +package dev.anvilcraft.lib.v2.multiblock.controller; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public interface IMultiblockController { + Block block(); + + static IMultiblockController of(Block block) { + if (block instanceof IMultiblockController controller) return controller; + return new SimpleMultiblockController(block); + } + + void onStructureValid(Level level, BlockPos pos, BlockState state); + + void onStructureInvalid(Level level, BlockPos pos, BlockState state); +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java new file mode 100644 index 0000000..7276c09 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/MultiblockControllerInstance.java @@ -0,0 +1,6 @@ +package dev.anvilcraft.lib.v2.multiblock.controller; + +import net.minecraft.core.BlockPos; + +public record MultiblockControllerInstance(IMultiblockController controller, BlockPos pos) { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java new file mode 100644 index 0000000..972bce4 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/controller/SimpleMultiblockController.java @@ -0,0 +1,6 @@ +package dev.anvilcraft.lib.v2.multiblock.controller; + +import net.minecraft.world.level.block.Block; + +public record SimpleMultiblockController(Block block) implements IMultiblockController { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java new file mode 100644 index 0000000..5c799c0 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/event/BlockEventListener.java @@ -0,0 +1,14 @@ +package dev.anvilcraft.lib.v2.multiblock.event; + +import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.level.BlockEvent; + +@EventBusSubscriber(modid = AnvilLibDynamicMultiblock.MOD_ID) +public class BlockEventListener { + @SubscribeEvent + public static void onBlockBreak(BlockEvent.BreakEvent event) { + event.getState().anvillib$unbind(event.getLevel()); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java new file mode 100644 index 0000000..94eb0fd --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibCapabilities.java @@ -0,0 +1,12 @@ +package dev.anvilcraft.lib.v2.multiblock.init; + +import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; +import dev.anvilcraft.lib.v2.multiblock.part.IMultiPart; +import net.neoforged.neoforge.capabilities.BlockCapability; + +public class LibCapabilities { + public static final BlockCapability MULTI_PART_CAPABILITY = BlockCapability.createVoid( + AnvilLibDynamicMultiblock.of("multipart"), + IMultiPart.class + ); +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java new file mode 100644 index 0000000..3669b10 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java @@ -0,0 +1,23 @@ +package dev.anvilcraft.lib.v2.multiblock.init; + +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; +import dev.anvilcraft.lib.v2.multiblock.MultiblockDefinition; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.registries.DataPackRegistryEvent; + +@EventBusSubscriber(modid = AnvilLibDynamicMultiblock.MOD_ID, bus = EventBusSubscriber.Bus.MOD) +public class LibRegistries { + public static final ResourceKey> DEFINITION_KEY = ResourceKey.createRegistryKey( + AnvilLibDynamicMultiblock.of("trigger") + ); + + @SubscribeEvent + public static void registerDataRegistries(DataPackRegistryEvent.NewRegistry event) { + MapCodec codec = MultiblockDefinition.CODEC; + event.dataPackRegistry(DEFINITION_KEY, codec.codec(), codec.codec()); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java new file mode 100644 index 0000000..5cd5868 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/part/IMultiPart.java @@ -0,0 +1,22 @@ +package dev.anvilcraft.lib.v2.multiblock.part; + +import dev.anvilcraft.lib.v2.multiblock.Multiblock; +import net.minecraft.world.level.LevelAccessor; + +public interface IMultiPart { + default boolean anvillib$isController() { + throw new AssertionError(); + } + + default void anvillib$bind(Multiblock multiblock) { + throw new AssertionError(); + } + + default boolean anvillib$isBound() { + throw new AssertionError(); + } + + default void anvillib$unbind(LevelAccessor level) { + throw new AssertionError(); + } +} diff --git a/module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml b/module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..27f7ba7 --- /dev/null +++ b/module.dynamic-multiblock/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,86 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader = "javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. +loaderVersion = "${loader_version_range}" #mandatory +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license = "${mod_license}" +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId = "${mod_id}" #mandatory +# The version number of the mod +version = "${mod_version}" #mandatory +# A display name for the mod +displayName = "${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforge.net/docs/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +displayURL="https://github.com/Anvil-Dev/AnvilLib" +# A file name (in the root of the mod JAR) containing a logo for display +logoFile="icon.png" #optional +# A text field displayed in the mod UI +#credits="" #optional +# A text field displayed in the mod UI +authors = "${mod_authors}" #optional +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) + +# The description text for the mod (multi line!) (#mandatory) +description = '''${mod_description}''' + +# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. +[[mixins]] +config = "${mod_id}.mixins.json" + +# The [[accessTransformers]] block allows you to declare where your AT file is. +# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg +#[[accessTransformers]] +#file="META-INF/accesstransformer.cfg" + +# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json + +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies."${mod_id}"]] #optional +# the modid of the dependency +modId = "neoforge" #mandatory +# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). +# 'required' requires the mod to exist, 'optional' does not +# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning +type = "required" #mandatory +# Optional field describing why the dependency is required or why it is incompatible +# reason="..." +# The version range of the dependency +versionRange = "${neo_version_range}" #mandatory +# An ordering relationship for the dependency. +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency +ordering = "NONE" +# Side this dependency is applied on - BOTH, CLIENT, or SERVER +side = "BOTH" +# Here's another dependency +[[dependencies."${mod_id}"]] +modId = "minecraft" +type = "required" +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features."${mod_id}"] +#openGLVersion="[3.2,)" diff --git a/module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json b/module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json new file mode 100644 index 0000000..867e285 --- /dev/null +++ b/module.dynamic-multiblock/src/main/resources/anvillib_dynamic_multiblock.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.anvilcraft.lib.v2.multiblock.mixin", + "compatibilityLevel": "JAVA_8", + "refmap": "anvillib.refmap.json", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/settings.gradle b/settings.gradle index 7c2f767..76b04e2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ plugins { } include 'module.config' +include 'module.dynamic-multiblock' include 'module.integration' include 'module.moveable-entity-block' include 'module.network' @@ -19,6 +20,7 @@ include 'module.registrum' include 'module.main' project(':module.config').name = 'anvillib-config-neoforge-1.21.1' +project(':module.dynamic-multiblock').name = 'anvillib-dynamic-multiblock-neoforge-1.21.1' project(':module.integration').name = 'anvillib-integration-neoforge-1.21.1' project(':module.moveable-entity-block').name = 'anvillib-moveable-entity-block-neoforge-1.21.1' project(':module.network').name = 'anvillib-network-neoforge-1.21.1' From 6f7d74a57fb2fde5e3e8ad36a7f7c1efb5480b8a Mon Sep 17 00:00:00 2001 From: QiuShui1012 <150409561+QiuShui1012@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:31:36 +0800 Subject: [PATCH 13/13] =?UTF-8?q?change(multiblock):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=B1=BB=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将定义类拆分为Java部分和序列化部分 --- .../lib/v2/multiblock/Multiblock.java | 1 + .../definition/DefinitionSerializer.java | 146 ++++++++++++++++ .../MultiblockDefinition.java | 164 +++--------------- .../definition/MultiblockPosInfo.java | 6 + .../multiblock/definition/package-info.java | 7 + .../lib/v2/multiblock/init/LibRegistries.java | 2 +- .../lib/v2/multiblock/package-info.java | 7 + 7 files changed, 195 insertions(+), 138 deletions(-) create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java rename module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/{ => definition}/MultiblockDefinition.java (54%) create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java create mode 100644 module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java index 5b76f23..6a4142e 100644 --- a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/Multiblock.java @@ -1,6 +1,7 @@ package dev.anvilcraft.lib.v2.multiblock; import dev.anvilcraft.lib.v2.multiblock.controller.IMultiblockController; +import dev.anvilcraft.lib.v2.multiblock.definition.MultiblockDefinition; import lombok.Getter; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.Block; diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java new file mode 100644 index 0000000..87165a5 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/DefinitionSerializer.java @@ -0,0 +1,146 @@ +package dev.anvilcraft.lib.v2.multiblock.definition; + +import com.google.common.collect.ImmutableList; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.anvilcraft.lib.v2.recipe.component.BlockStatePredicate; +import dev.anvilcraft.lib.v2.recipe.util.CodecUtil; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.chars.Char2ObjectMaps; +import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public record DefinitionSerializer(@Unmodifiable String[][] structure, @Unmodifiable Char2ObjectMap definitions) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(ins -> ins.group( + Codec.STRING + .listOf() + .listOf() + .xmap(DefinitionSerializer::toArrays, DefinitionSerializer::toLists) + .fieldOf("structure") + .forGetter(DefinitionSerializer::structure), + Codec.unboundedMap(CodecUtil.CHAR_CODEC, BlockStatePredicate.CODEC) + .xmap(DefinitionSerializer::toC2OMap, Function.identity()) + .fieldOf("definitions") + .forGetter(DefinitionSerializer::definitions) + ).apply(ins, DefinitionSerializer::new)); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8 + .apply(ByteBufCodecs.list()) + .apply(ByteBufCodecs.list()) + .map(DefinitionSerializer::toArrays, DefinitionSerializer::toLists), + DefinitionSerializer::structure, + ByteBufCodecs.>map( + HashMap::new, + CodecUtil.CHAR_STREAM_CODEC, + BlockStatePredicate.STREAM_CODEC + ).map(DefinitionSerializer::toC2OMap, Function.identity()), + DefinitionSerializer::definitions, + DefinitionSerializer::new + ); + + MultiblockDefinition toDefinition() { + Vec3i controllerOffset = this.getControllerOffset(); + int sizeX = this.structure[0][0].length(); + int sizeZ = this.structure[0].length; + int sizeXZ = sizeX * sizeZ; + int sizeY = this.structure.length; + int sizeXYZ = sizeXZ * sizeY; + ImmutableList.Builder structure = ImmutableList.builder(); + int cursor = 0; + while (cursor < sizeXYZ) { + int indexX = cursor % sizeX; + int indexY = cursor / sizeXZ; + int indexZ = (cursor % sizeXZ) / sizeX; + + char key = this.getChar(indexX, indexY, indexZ); + if (this.isEmpty(key)) { + cursor++; + continue; + } + + // 找到非空位置,计算世界坐标 + int worldX = indexX - controllerOffset.getX(); + int worldY = sizeY - 1 - indexY - controllerOffset.getY(); + int worldZ = indexZ - controllerOffset.getZ(); + cursor++; + structure.add(new MultiblockPosInfo( + key, + new BlockPos(worldX, worldY, worldZ), + new BlockPos(indexX, indexY, indexZ) + )); + } + return new MultiblockDefinition(structure.build(), this.definitions, sizeX, sizeY, sizeZ); + } + + static DefinitionSerializer fromDefinition(MultiblockDefinition definition) { + String[][] structure = new String[definition.sizeY()][definition.sizeZ()]; + for (int y = 0; y < definition.sizeY(); y++) { + for (int z = 0; z < definition.sizeZ(); z++) { + char[] xs = new char[definition.sizeX()]; + Arrays.fill(xs, ' '); + for (MultiblockPosInfo pos : definition.structure()) { + if (pos.pos().getY() != y || pos.pos().getZ() != z) continue; + xs[pos.pos().getX()] = pos.key(); + } + structure[y][z] = new String(xs); + } + } + return new DefinitionSerializer(structure, definition.definitions()); + } + + public char getChar(int x, int y, int z) { + return this.structure[y][z].charAt(x); + } + + public boolean isEmpty(char key) { + return key == Character.MIN_VALUE; + } + + private Vec3i getControllerOffset() { + for (int y = 0; y < this.structure.length; y++) { + String[] xz = this.structure[y]; + for (int z = 0; z < xz.length; z++) { + String xs = xz[z]; + for (int x = 0; x < xs.length(); x++) { + if (xs.charAt(x) != MultiblockDefinition.CONTROLLER) continue; + return new Vec3i(x, y, z); + } + } + } + throw new IllegalStateException("Unexpected no controller in structure"); + } + + static @Unmodifiable String[][] toArrays(List> lists) { + int size = lists.size(); + String[][] result = new String[size][]; + for (int i = 0; i < size; i++) { + result[i] = lists.get(i).toArray(String[]::new); + } + return result; + } + + private static @Unmodifiable List> toLists(String[][] arrays) { + List> result = new ArrayList<>(); + for (String[] array : arrays) { + result.add(List.of(array)); + } + return ImmutableList.copyOf(result); + } + + private static @Unmodifiable Char2ObjectMap toC2OMap(Map map) { + return Char2ObjectMaps.unmodifiable(new Char2ObjectOpenHashMap<>(map)); + } +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockDefinition.java similarity index 54% rename from module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java rename to module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockDefinition.java index f1c0b03..418d102 100644 --- a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/MultiblockDefinition.java +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockDefinition.java @@ -1,25 +1,17 @@ -package dev.anvilcraft.lib.v2.multiblock; +package dev.anvilcraft.lib.v2.multiblock.definition; -import com.google.common.collect.AbstractIterator; -import com.google.common.collect.ImmutableList; -import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; import com.mojang.serialization.MapCodec; -import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.anvilcraft.lib.v2.recipe.component.BlockStatePredicate; -import dev.anvilcraft.lib.v2.recipe.util.CodecUtil; import it.unimi.dsi.fastutil.chars.Char2ObjectMap; import it.unimi.dsi.fastutil.chars.Char2ObjectMaps; import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; -import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; -import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Vec3i; import net.minecraft.data.worldgen.BootstrapContext; import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.LevelAccessor; @@ -29,11 +21,7 @@ import org.jetbrains.annotations.Unmodifiable; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.function.Function; /** * 多方块定义类 @@ -42,50 +30,36 @@ @Accessors(fluent = true, chain = false) public class MultiblockDefinition { public static final char CONTROLLER = '0'; - public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(ins -> ins.group( - Codec.STRING - .listOf() - .listOf() - .xmap(MultiblockDefinition::toArrays, MultiblockDefinition::toLists) - .fieldOf("structure") - .forGetter(MultiblockDefinition::structure), - Codec.unboundedMap(CodecUtil.CHAR_CODEC, BlockStatePredicate.CODEC) - .xmap(MultiblockDefinition::toC2OMap, Function.identity()) - .fieldOf("definitions") - .forGetter(MultiblockDefinition::definitions) - ).apply(ins, MultiblockDefinition::new)); - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.STRING_UTF8 - .apply(ByteBufCodecs.list()) - .apply(ByteBufCodecs.list()) - .map(MultiblockDefinition::toArrays, MultiblockDefinition::toLists), - MultiblockDefinition::structure, - ByteBufCodecs.>map( - HashMap::new, - CodecUtil.CHAR_STREAM_CODEC, - BlockStatePredicate.STREAM_CODEC - ).map(MultiblockDefinition::toC2OMap, Function.identity()), - MultiblockDefinition::definitions, - MultiblockDefinition::new + public static final MapCodec CODEC = DefinitionSerializer.CODEC.xmap( + DefinitionSerializer::toDefinition, + DefinitionSerializer::fromDefinition ); - private final @Unmodifiable String[][] structure; + public static final StreamCodec STREAM_CODEC = DefinitionSerializer.STREAM_CODEC.map( + DefinitionSerializer::toDefinition, + DefinitionSerializer::fromDefinition + ); + private final @Unmodifiable List structure; private final @Unmodifiable Char2ObjectMap definitions; - @Getter(AccessLevel.NONE) - private Vec3i controllerOffset; - private Iterable localPoses; + private final int sizeX, sizeY, sizeZ; /** * 构建多方块定义类 * - * @param structure 结构,按 {@code String(x)[reversed y][z]} 的顺序定义 + * @param structure 结构 * @param definitions 定义,记录了字符与方块状态的对应情况。 */ - private MultiblockDefinition( - @Unmodifiable String[][] structure, - @Unmodifiable Char2ObjectMap definitions + MultiblockDefinition( + @Unmodifiable List structure, + @Unmodifiable Char2ObjectMap definitions, + int sizeX, + int sizeY, + int sizeZ ) { this.structure = structure; this.definitions = definitions; + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; } public static Builder builder() { @@ -105,7 +79,10 @@ public char getChar(Vec3i localPos) { } public char getChar(int x, int y, int z) { - return this.structure[y][z].charAt(x); + for (MultiblockPosInfo pos : this.structure) { + if (pos.pos().getX() == x && pos.pos().getY() == y && pos.pos().getZ() == z) return pos.key(); + } + return Character.MIN_VALUE; } public boolean isEmpty(char key) { @@ -120,70 +97,6 @@ public boolean isEmpty(int x, int y, int z) { return this.isEmpty(this.getChar(x, y, z)); } - public Vec3i getControllerOffset() { - if (this.controllerOffset != null) return this.controllerOffset; - for (int y = 0; y < this.structure.length; y++) { - String[] xz = this.structure[y]; - for (int z = 0; z < xz.length; z++) { - String xs = xz[z]; - for (int x = 0; x < xs.length(); x++) { - if (xs.charAt(x) != MultiblockDefinition.CONTROLLER) continue; - return this.controllerOffset = new Vec3i(x, y, z); - } - } - } - throw new IllegalStateException("Unexpected no controller in structure"); - } - - public Iterable getGlobalPoses(BlockPos controllerPos) { - return () -> new AbstractIterator<>() { - private final Iterator localPoses = MultiblockDefinition.this.getLocalPoses().iterator(); - - @Override - protected @Nullable BlockPos computeNext() { - BlockPos pos = this.localPoses.next(); - if (pos == null) return this.endOfData(); - return pos.offset(controllerPos); - } - }; - } - - public Iterable getLocalPoses() { - if (this.localPoses != null) return this.localPoses; - Vec3i controllerOffset = this.getControllerOffset(); - int sizeX = this.structure[0][0].length(); - int sizeZ = this.structure[0].length; - int sizeXZ = sizeX * sizeZ; - int sizeY = this.structure.length; - int sizeXYZ = sizeXZ * sizeY; - return this.localPoses = () -> new AbstractIterator<>() { - private final BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(); - private int cursor = 0; - - @Override - protected @Nullable BlockPos computeNext() { - while (this.cursor < sizeXYZ) { - int indexX = this.cursor % sizeX; - int indexY = this.cursor / sizeXZ; - int indexZ = (this.cursor % sizeXZ) / sizeX; - - if (MultiblockDefinition.this.isEmpty(indexX, indexY, indexZ)) { - this.cursor++; - continue; - } - - // 找到非空位置,计算世界坐标 - int worldX = indexX - controllerOffset.getX(); - int worldY = sizeY - 1 - indexY - controllerOffset.getY(); - int worldZ = indexZ - controllerOffset.getZ(); - this.cursor++; - return this.mut.set(worldX, worldY, worldZ); - } - return this.endOfData(); - } - }; - } - public static class Builder { private final List> structure = new ArrayList<>(); private final Char2ObjectMap definitions = new Char2ObjectOpenHashMap<>(); @@ -289,33 +202,10 @@ public Holder create() { } public MultiblockDefinition build() { - return new MultiblockDefinition( - MultiblockDefinition.toArrays(this.structure), + return new DefinitionSerializer( + DefinitionSerializer.toArrays(this.structure), Char2ObjectMaps.unmodifiable(this.definitions) - ); - } - } - - // 工具方法 - - private static @Unmodifiable String[][] toArrays(List> lists) { - int size = lists.size(); - String[][] result = new String[size][]; - for (int i = 0; i < size; i++) { - result[i] = lists.get(i).toArray(String[]::new); + ).toDefinition(); } - return result; - } - - private static @Unmodifiable List> toLists(String[][] arrays) { - List> result = new ArrayList<>(); - for (String[] array : arrays) { - result.add(List.of(array)); - } - return ImmutableList.copyOf(result); - } - - private static @Unmodifiable Char2ObjectMap toC2OMap(Map map) { - return Char2ObjectMaps.unmodifiable(new Char2ObjectOpenHashMap<>(map)); } } diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java new file mode 100644 index 0000000..d7670f7 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/MultiblockPosInfo.java @@ -0,0 +1,6 @@ +package dev.anvilcraft.lib.v2.multiblock.definition; + +import net.minecraft.core.BlockPos; + +public record MultiblockPosInfo(char key, BlockPos offset, BlockPos pos) { +} diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java new file mode 100644 index 0000000..02fdb14 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/definition/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package dev.anvilcraft.lib.v2.multiblock.definition; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java index 3669b10..5cfc5e1 100644 --- a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/init/LibRegistries.java @@ -2,7 +2,7 @@ import com.mojang.serialization.MapCodec; import dev.anvilcraft.lib.v2.multiblock.AnvilLibDynamicMultiblock; -import dev.anvilcraft.lib.v2.multiblock.MultiblockDefinition; +import dev.anvilcraft.lib.v2.multiblock.definition.MultiblockDefinition; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; import net.neoforged.bus.api.SubscribeEvent; diff --git a/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java new file mode 100644 index 0000000..172b5c2 --- /dev/null +++ b/module.dynamic-multiblock/src/main/java/dev/anvilcraft/lib/v2/multiblock/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package dev.anvilcraft.lib.v2.multiblock; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault;