├── forge ├── gradle.properties ├── src │ └── main │ │ ├── resources │ │ ├── pack.mcmeta │ │ ├── reasonable-sorting-forge.mixins.json │ │ └── META-INF │ │ │ └── mods.toml │ │ └── java │ │ └── pers │ │ └── solid │ │ └── mod │ │ └── forge │ │ ├── BridgeImpl.java │ │ ├── mixin │ │ ├── CreativeInventoryScreenForgeMixin.java │ │ └── ForgeRegistryMixin.java │ │ ├── ExtShapeBridgeImpl.java │ │ └── ReasonableSortingForge.java └── build.gradle ├── quilt ├── gradle.properties ├── src │ └── main │ │ ├── resources │ │ ├── assets │ │ │ └── reasonable-sorting │ │ │ │ └── icon.png │ │ └── quilt.mod.json │ │ └── java │ │ └── pers │ │ └── solid │ │ └── mod │ │ ├── fabric │ │ ├── BridgeImpl.java │ │ └── ExtShapeBridgeImpl.java │ │ └── quilt │ │ ├── ConfigScreenQuilt.java │ │ └── ReasonableSortingQuilt.java └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── fabric ├── src │ └── main │ │ ├── resources │ │ ├── assets │ │ │ └── reasonable-sorting │ │ │ │ └── icon.png │ │ └── fabric.mod.json │ │ └── java │ │ └── pers │ │ └── solid │ │ └── mod │ │ └── fabric │ │ ├── ConfigScreenFabric.java │ │ ├── ExtShapeBridgeImpl.java │ │ ├── BridgeImpl.java │ │ └── ReasonableSortingFabric.java └── build.gradle ├── .gitignore ├── common ├── src │ └── main │ │ ├── resources │ │ ├── reasonable-sorting.mixins.json │ │ └── assets │ │ │ └── reasonable-sorting │ │ │ └── lang │ │ │ ├── zh_hk.json │ │ │ ├── zh_tw.json │ │ │ ├── zh_cn.json │ │ │ ├── ru_ru.json │ │ │ └── en_us.json │ │ └── java │ │ └── pers │ │ └── solid │ │ └── mod │ │ ├── ConditionalSortingRule.java │ │ ├── MultimapSortingRule.java │ │ ├── mixin │ │ ├── BlockFamiliesAccessor.java │ │ ├── ItemGroupMixin.java │ │ ├── ItemMixin.java │ │ ├── CreativeInventoryScreenMixin.java │ │ └── SimpleRegistryMixin.java │ │ ├── ConditionalTransferRule.java │ │ ├── SortingInfluenceRange.java │ │ ├── SimpleRegistryExtension.java │ │ ├── SortingCalculationType.java │ │ ├── BlockItemRule.java │ │ ├── BaseToVariantRule.java │ │ ├── ExtShapeBridge.java │ │ ├── VariantToVariantRule.java │ │ ├── ColorSortingRule.java │ │ ├── Bridge.java │ │ ├── TransferRule.java │ │ ├── TransferRules.java │ │ ├── SortingRules.java │ │ ├── Configs.java │ │ ├── ConfigsHelper.java │ │ ├── SortingRule.java │ │ └── ConfigScreen.java └── build.gradle ├── settings.gradle ├── .github └── workflows │ └── build.yml ├── gradle.properties ├── gradlew.bat ├── README.md ├── gradlew ├── LICENSE └── README-en.md /forge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=forge -------------------------------------------------------------------------------- /quilt/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=quilt -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolidBlock-cn/reasonable-sorting/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /forge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Reasonable Sorting", 4 | "pack_format": 9 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /fabric/src/main/resources/assets/reasonable-sorting/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolidBlock-cn/reasonable-sorting/HEAD/fabric/src/main/resources/assets/reasonable-sorting/icon.png -------------------------------------------------------------------------------- /quilt/src/main/resources/assets/reasonable-sorting/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolidBlock-cn/reasonable-sorting/HEAD/quilt/src/main/resources/assets/reasonable-sorting/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/ 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # macos 28 | 29 | *.DS_Store 30 | 31 | # fabric 32 | 33 | run/ 34 | -------------------------------------------------------------------------------- /forge/src/main/resources/reasonable-sorting-forge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "pers.solid.mod.forge.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": ["ForgeRegistryMixin"], 7 | "client": ["CreativeInventoryScreenForgeMixin"], 8 | "injectors": { 9 | "defaultRequire": 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/src/main/resources/reasonable-sorting.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "pers.solid.mod.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "BlockFamiliesAccessor", "ItemGroupMixin", "ItemMixin", 8 | "SimpleRegistryMixin" 9 | ], 10 | "client": [ 11 | "CreativeInventoryScreenMixin" 12 | ], 13 | "injectors": { 14 | "defaultRequire": 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url "https://bmclapi2.bangbang93.com/maven/" } 4 | maven { url "https://maven.fabricmc.net/" } 5 | maven { url "https://maven.architectury.dev/" } 6 | maven { url "https://maven.minecraftforge.net/" } 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | include "common", "fabric", "forge", "quilt" 12 | rootProject.name = "reasonable-sorting" -------------------------------------------------------------------------------- /quilt/src/main/java/pers/solid/mod/fabric/BridgeImpl.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.fabric; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.util.Identifier; 5 | import net.minecraft.util.registry.Registry; 6 | 7 | public class BridgeImpl { 8 | public static Identifier getItemId(Item item) { 9 | return Registry.ITEM.getId(item); 10 | } 11 | 12 | public static Item getItemById(Identifier identifier) { 13 | return Registry.ITEM.get(identifier); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/ConditionalSortingRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.util.function.BooleanSupplier; 6 | 7 | public record ConditionalSortingRule(BooleanSupplier condition, SortingRule sortingRule) implements SortingRule { 8 | @Override 9 | public @Nullable Iterable getFollowers(T leadingObj) { 10 | return condition.getAsBoolean() ? sortingRule.getFollowers(leadingObj) : null; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /quilt/src/main/java/pers/solid/mod/quilt/ConfigScreenQuilt.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.quilt; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | import pers.solid.mod.ConfigScreen; 6 | 7 | public class ConfigScreenQuilt implements ModMenuApi { 8 | public static final ConfigScreen INSTANCE = new ConfigScreen(); 9 | 10 | @Override 11 | public ConfigScreenFactory getModConfigScreenFactory() { 12 | return INSTANCE::createScreen; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /fabric/src/main/java/pers/solid/mod/fabric/ConfigScreenFabric.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.fabric; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | import pers.solid.mod.ConfigScreen; 6 | 7 | public class ConfigScreenFabric implements ModMenuApi { 8 | public static final ConfigScreen INSTANCE = new ConfigScreen(); 9 | 10 | @Override 11 | public ConfigScreenFactory getModConfigScreenFactory() { 12 | return INSTANCE::createScreen; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/MultimapSortingRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.collect.Multimap; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public final class MultimapSortingRule implements SortingRule { 7 | private final Multimap multimap; 8 | 9 | public MultimapSortingRule(Multimap multimap) { 10 | this.multimap = multimap; 11 | } 12 | 13 | @Override 14 | public @NotNull Iterable getFollowers(T leadingObj) { 15 | return multimap.get(leadingObj); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /quilt/src/main/java/pers/solid/mod/fabric/ExtShapeBridgeImpl.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.fabric; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.quiltmc.loader.api.QuiltLoader; 5 | import pers.solid.mod.ExtShapeBridge; 6 | 7 | public class ExtShapeBridgeImpl { 8 | public static @NotNull ExtShapeBridge getInstance() { 9 | for (ExtShapeBridge entrypoint : QuiltLoader.getEntrypoints("reasonable-sorting:extshape-bridge", ExtShapeBridge.class)) { 10 | return entrypoint; 11 | } 12 | return new ExtShapeBridge(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /fabric/src/main/java/pers/solid/mod/fabric/ExtShapeBridgeImpl.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.fabric; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import org.jetbrains.annotations.NotNull; 5 | import pers.solid.mod.ExtShapeBridge; 6 | 7 | public class ExtShapeBridgeImpl { 8 | public static @NotNull ExtShapeBridge getInstance() { 9 | for (ExtShapeBridge entrypoint : FabricLoader.getInstance().getEntrypoints("reasonable-sorting:extshape-bridge", ExtShapeBridge.class)) { 10 | return entrypoint; 11 | } 12 | return new ExtShapeBridge(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/mixin/BlockFamiliesAccessor.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.mixin; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.data.family.BlockFamilies; 5 | import net.minecraft.data.family.BlockFamily; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | import java.util.Map; 10 | 11 | @Mixin(BlockFamilies.class) 12 | public interface BlockFamiliesAccessor { 13 | @Accessor("BASE_BLOCKS_TO_FAMILIES") 14 | static Map getBaseBlocksToFamilies() { 15 | throw new AssertionError(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/ConditionalTransferRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.item.ItemGroup; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.function.BooleanSupplier; 9 | 10 | public record ConditionalTransferRule(@NotNull BooleanSupplier condition, @NotNull TransferRule transferRule) implements TransferRule { 11 | 12 | @Override 13 | public @Nullable Iterable getTransferredGroups(Item item) { 14 | return condition.getAsBoolean() ? transferRule.getTransferredGroups(item) : null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/mixin/ItemGroupMixin.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.mixin; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.item.ItemGroup; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 9 | import pers.solid.mod.SortingRule; 10 | 11 | import java.util.Iterator; 12 | 13 | @Mixin(ItemGroup.class) 14 | public abstract class ItemGroupMixin { 15 | @Shadow 16 | public abstract String getName(); 17 | 18 | @ModifyVariable(method = "appendStacks", at = @At("STORE")) 19 | public Iterator modifiedAppendStacks(Iterator value) { 20 | return SortingRule.modifyIteratorInInventory(value, getName()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/SortingInfluenceRange.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import net.minecraft.text.MutableText; 4 | import net.minecraft.text.Text; 5 | import net.minecraft.util.StringIdentifiable; 6 | 7 | /** 8 | * 指定本模组修改的排序可以影响哪些范围 9 | */ 10 | public enum SortingInfluenceRange implements StringIdentifiable { 11 | REGISTRY, 12 | INVENTORY_ONLY; 13 | private final String name; 14 | 15 | SortingInfluenceRange() { 16 | this.name = name().toLowerCase(); 17 | } 18 | 19 | @Override 20 | public String asString() { 21 | return name; 22 | } 23 | 24 | public MutableText getName() { 25 | return Text.translatable("sortingInfluenceRange." + asString()); 26 | } 27 | 28 | public MutableText getDescription() { 29 | return Text.translatable("sortingInfluenceRange." + asString() + ".description"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/SimpleRegistryExtension.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.base.Predicates; 4 | import net.minecraft.util.registry.Registry; 5 | import org.jetbrains.annotations.Contract; 6 | 7 | /** 8 | * @see pers.solid.mod.mixin.SimpleRegistryMixin 9 | */ 10 | public interface SimpleRegistryExtension { 11 | static void removeAllCachedEntries() { 12 | if (Configs.instance.sortingCalculationType == SortingCalculationType.STANDARD) { 13 | Registry.REGISTRIES.stream().filter(Predicates.instanceOf(SimpleRegistryExtension.class)).forEach(r -> ((SimpleRegistryExtension) r).removeCachedEntries()); 14 | SortingRule.Internal.cachedInventoryItems = null; 15 | } 16 | } 17 | 18 | /** 19 | * 移除注册表的 {@code cachedEntries} 字段,使得在下次运行 {@code getEntries} 时重新迭代其内容。 20 | */ 21 | @Contract(mutates = "this") 22 | void removeCachedEntries(); 23 | } 24 | -------------------------------------------------------------------------------- /forge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[41,)" 3 | issueTrackerURL = "https://github.com/SolidBlock-cn/reasonable-sorting/issues" 4 | license = "LGPL-3.0" 5 | 6 | [[mods]] 7 | modId = "reasonable_sorting" 8 | version = "${version}" 9 | displayName = "Reasonable Sorting" 10 | authors = "SolidBlock" 11 | description = ''' 12 | Sorts blocks and items in the creative inventory reasonably. 13 | ''' 14 | #logoFile = "" 15 | 16 | [[dependencies.reasonable_sorting]] 17 | modId = "forge" 18 | mandatory = true 19 | versionRange = "[41,)" 20 | ordering = "NONE" 21 | side = "BOTH" 22 | 23 | [[dependencies.reasonable_sorting]] 24 | modId = "minecraft" 25 | mandatory = true 26 | versionRange = "[1.19,1.20)" 27 | ordering = "NONE" 28 | side = "BOTH" 29 | 30 | [[dependencies.reasonable_sorting]] 31 | modId = "cloth_config" 32 | mandatory = true 33 | versionRange = "[5.0.0,)" 34 | ordering = "NONE" 35 | side = "BOTH" -------------------------------------------------------------------------------- /fabric/src/main/java/pers/solid/mod/fabric/BridgeImpl.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.fabric; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.item.Item; 5 | import net.minecraft.util.Identifier; 6 | import net.minecraft.util.registry.Registry; 7 | 8 | import java.util.Optional; 9 | 10 | public class BridgeImpl { 11 | public static Identifier getItemId(Item item) { 12 | return Registry.ITEM.getId(item); 13 | } 14 | 15 | public static Item getItemById(Identifier identifier) { 16 | return Registry.ITEM.get(identifier); 17 | } 18 | 19 | public static Block getBlockById(Identifier identifier) { 20 | return Registry.BLOCK.get(identifier); 21 | } 22 | 23 | public static Identifier getBlockId(Block block) { 24 | return Registry.BLOCK.getId(block); 25 | } 26 | 27 | public static Optional getBlockByIdOrEmpty(Identifier identifier) { 28 | return Registry.BLOCK.getOrEmpty(identifier); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/SortingCalculationType.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import net.minecraft.text.MutableText; 4 | import net.minecraft.text.Text; 5 | import net.minecraft.util.StringIdentifiable; 6 | 7 | public enum SortingCalculationType implements StringIdentifiable { 8 | /** 9 | * 当游戏初始化、加载数据、排序规则改变时,计算一次排序后的迭代结果,此后就直接使用缓存好的迭代结果。 10 | */ 11 | STANDARD, 12 | /** 13 | * 当游戏初始化、加载数据或者排序规则改变时,计算一次计算后的排序跟随映射,每次迭代时根据这个映射实时进行排序。 14 | */ 15 | SEMI_REAL_TIME, 16 | /** 17 | * 每一次迭代时,实时计算排序结果。这会导致每次迭代注册表时(例如游戏初始化时、翻页时)出现卡顿。 18 | */ 19 | REAL_TIME; 20 | 21 | private final String name; 22 | 23 | SortingCalculationType() { 24 | this.name = name().toLowerCase(); 25 | } 26 | 27 | @Override 28 | public String asString() { 29 | return name; 30 | } 31 | 32 | public MutableText getName() { 33 | return Text.translatable("sortingCalculationType." + asString()); 34 | } 35 | 36 | public MutableText getDescription() { 37 | return Text.translatable("sortingCalculationType." + asString() + ".description"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /forge/src/main/java/pers/solid/mod/forge/BridgeImpl.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.forge; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.item.Item; 5 | import net.minecraft.util.Identifier; 6 | import net.minecraft.util.registry.RegistryEntry; 7 | import net.minecraftforge.registries.ForgeRegistries; 8 | 9 | import java.util.Optional; 10 | 11 | public class BridgeImpl { 12 | 13 | public static Identifier getItemId(Item item) { 14 | return ForgeRegistries.ITEMS.getKey(item); 15 | } 16 | 17 | public static Item getItemById(Identifier identifier) { 18 | return ForgeRegistries.ITEMS.getValue(identifier); 19 | } 20 | 21 | public static Block getBlockById(Identifier identifier) { 22 | return ForgeRegistries.BLOCKS.getValue(identifier); 23 | } 24 | 25 | public static Identifier getBlockId(Block block) { 26 | return ForgeRegistries.BLOCKS.getKey(block); 27 | } 28 | 29 | public static Optional getBlockByIdOrEmpty(Identifier identifier) { 30 | return ForgeRegistries.BLOCKS.getDelegate(identifier).map(RegistryEntry.Reference::value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/BlockItemRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.collect.Iterables; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.item.BlockItem; 6 | import net.minecraft.item.Item; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.Objects; 10 | 11 | /** 12 | * 方块物品规则用于将方块排序规则应用到对应的物品上。当物品为方块物品上,就会应用对应的方块排序规则并转化为相应的物品。对非方块物品不起作用。 13 | * 14 | * @param rule 对应的方块规则。 15 | */ 16 | public record BlockItemRule(SortingRule rule) implements SortingRule { 17 | /** 18 | * 若物品为方块物品,则返回对应方块排序规则返回的方块对应物品的可迭代对象(集合)。若方块没有对应物品,则会略过。对于非方块物品,不产生作用。 19 | * 20 | * @param leadingObj 可能是方块物品的物品。 21 | * @return 对应的方块排序规则返回的结果(方块集合)对应的物品集合。 22 | */ 23 | @Override 24 | public @Nullable Iterable getFollowers(Item leadingObj) { 25 | if (!(leadingObj instanceof BlockItem blockItem)) return null; 26 | final Iterable followers = rule.getFollowers(blockItem.getBlock()); 27 | return followers == null ? null : Iterables.filter(Iterables.transform(followers, Block::asItem), Objects::nonNull); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /fabric/src/main/java/pers/solid/mod/fabric/ReasonableSortingFabric.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.fabric; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; 5 | import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import pers.solid.mod.Configs; 10 | import pers.solid.mod.SortingRules; 11 | import pers.solid.mod.TransferRules; 12 | 13 | public class ReasonableSortingFabric implements ModInitializer { 14 | public static final Logger LOGGER = LoggerFactory.getLogger(ReasonableSortingFabric.class); 15 | 16 | @Override 17 | public void onInitialize() { 18 | SortingRules.initialize(); 19 | TransferRules.initialize(); 20 | switch (FabricLoader.getInstance().getEnvironmentType()) { 21 | case CLIENT -> ClientLifecycleEvents.CLIENT_STARTED.register(client -> Configs.loadAndUpdate()); 22 | case SERVER -> ServerLifecycleEvents.SERVER_STARTED.register(server -> Configs.loadAndUpdate()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /forge/src/main/java/pers/solid/mod/forge/mixin/CreativeInventoryScreenForgeMixin.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.forge.mixin; 2 | 3 | import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; 4 | import net.minecraft.item.Item; 5 | import net.minecraft.item.ItemGroup; 6 | import net.minecraft.util.registry.DefaultedRegistry; 7 | import net.minecraftforge.api.distmarker.Dist; 8 | import net.minecraftforge.api.distmarker.OnlyIn; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | import pers.solid.mod.SortingRule; 13 | 14 | import java.util.Iterator; 15 | 16 | @OnlyIn(Dist.CLIENT) 17 | @Mixin(CreativeInventoryScreen.class) 18 | public abstract class CreativeInventoryScreenForgeMixin { 19 | 20 | @Redirect(method = "search", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/registry/DefaultedRegistry;iterator()Ljava/util/Iterator;")) 21 | public Iterator modifiedIteratorInSearch(DefaultedRegistry instance) { 22 | return SortingRule.modifyIteratorInInventory(instance.iterator(), ItemGroup.SEARCH.getName()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | architectury { 2 | common(rootProject.enabled_platforms.split(",")) 3 | } 4 | 5 | archivesBaseName += "-common" 6 | 7 | loom { 8 | // accessWidenerPath = file("src/main/resources/examplemod.accesswidener") 9 | } 10 | 11 | dependencies { 12 | // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies 13 | // Do NOT use other classes from fabric loader 14 | modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" 15 | modCompileOnlyApi("me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_config_version}") { 16 | exclude(group: "net.fabricmc.fabric-api") 17 | exclude(module: "fabric-loadeer") 18 | } 19 | } 20 | 21 | publishing { 22 | publications { 23 | mavenCommon(MavenPublication) { 24 | artifactId = rootProject.archives_base_name 25 | from components.java 26 | } 27 | } 28 | 29 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 30 | repositories { 31 | // Add repositories to publish to here. 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quilt/src/main/java/pers/solid/mod/quilt/ReasonableSortingQuilt.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.quilt; 2 | 3 | import org.quiltmc.loader.api.ModContainer; 4 | import org.quiltmc.loader.api.minecraft.MinecraftQuiltLoader; 5 | import org.quiltmc.qsl.base.api.entrypoint.ModInitializer; 6 | import org.quiltmc.qsl.lifecycle.api.client.event.ClientLifecycleEvents; 7 | import org.quiltmc.qsl.lifecycle.api.event.ServerLifecycleEvents; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import pers.solid.mod.Configs; 11 | import pers.solid.mod.SortingRules; 12 | import pers.solid.mod.TransferRules; 13 | 14 | public class ReasonableSortingQuilt implements ModInitializer { 15 | public static final Logger LOGGER = LoggerFactory.getLogger(ReasonableSortingQuilt.class); 16 | 17 | @Override 18 | public void onInitialize(ModContainer mod) { 19 | SortingRules.initialize(); 20 | TransferRules.initialize(); 21 | switch (MinecraftQuiltLoader.getEnvironmentType()) { 22 | case CLIENT -> ClientLifecycleEvents.READY.register(client -> Configs.loadAndUpdate()); 23 | case SERVER -> ServerLifecycleEvents.READY.register(server -> Configs.loadAndUpdate()); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /forge/src/main/java/pers/solid/mod/forge/ExtShapeBridgeImpl.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.forge; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import pers.solid.mod.ExtShapeBridge; 5 | 6 | import java.util.function.Supplier; 7 | 8 | public class ExtShapeBridgeImpl { 9 | private static Supplier valueSupplier = null; 10 | 11 | /** 12 | * 这是 {@link ExtShapeBridge#INSTANCE} 的值。当 {@link ExtShapeBridge} 初始化时,就会立即调用此值,并赋值给 final 变量。这个方法不应该由外部调用。 13 | */ 14 | @ApiStatus.Internal 15 | public static ExtShapeBridge getInstance() { 16 | return valueSupplier == null ? new ExtShapeBridge() : valueSupplier.get(); 17 | } 18 | 19 | /** 20 | * 在装有 Reasonable Sorting 的情况下,调用此函数,以提供 ExtShapeBridge。这个方法可以在较早的时候调用。 21 | */ 22 | public static void setValue(Supplier bridge) { 23 | if (valueSupplier != null) { 24 | ReasonableSortingForge.LOGGER.warn("The ExtShapeBridgeEvent seems to have posted multiple times! The value {} will override the existing value {}.", valueSupplier, bridge); 25 | } else { 26 | ReasonableSortingForge.LOGGER.info("Receiving ExtShapeBridge object."); 27 | } 28 | valueSupplier = bridge; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "reasonable-sorting", 4 | "version": "${version}", 5 | "name": "Reasonable Sorting", 6 | "description": "Sorts blocks and items in the creative inventory reasonably.", 7 | "authors": [ 8 | "SolidBlock" 9 | ], 10 | "contact": { 11 | "homepage": "https://space.bilibili.com/87328531", 12 | "sources": "https://github.com/SolidBlock-cn/reasonable-sorting", 13 | "issues": "https://github.com/SolidBlock-cn/reasonable-sorting/issues" 14 | }, 15 | 16 | "license": "LGPL-3.0", 17 | "icon": "assets/reasonable-sorting/icon.png", 18 | 19 | "environment": "*", 20 | "entrypoints": { 21 | "main": [ 22 | "pers.solid.mod.fabric.ReasonableSortingFabric" 23 | ], 24 | "modmenu": [ 25 | "pers.solid.mod.fabric.ConfigScreenFabric" 26 | ] 27 | }, 28 | "mixins": [ 29 | "reasonable-sorting.mixins.json" 30 | ], 31 | "depends": { 32 | "fabricloader": ">=0.11.3", 33 | "fabric": "*", 34 | "minecraft": [ 35 | ">=1.19-beta.1", 36 | ">=1.19" 37 | ], 38 | "java": ">=17", 39 | "cloth-config2": ">=5.0.0" 40 | }, 41 | "provides": [ 42 | "reasonable_sorting" 43 | ], 44 | "recommends": { 45 | "modmenu": "*" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/BaseToVariantRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.collect.Streams; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.data.family.BlockFamily; 6 | import org.jetbrains.annotations.Nullable; 7 | import pers.solid.mod.mixin.BlockFamiliesAccessor; 8 | 9 | import java.util.Objects; 10 | import java.util.function.Predicate; 11 | 12 | /** 13 | * BaseToVariantRule 是让特定的方块变种的方块紧随其基础方块的规则。例如,让所有的楼梯和台阶紧随其基础方块。 14 | * 15 | * @param blockPredicate 方块应用此规则所需要的条件。 16 | * @param variants 需要跟随在其后的方块变种。 17 | */ 18 | public record BaseToVariantRule(Predicate blockPredicate, Iterable variants) implements SortingRule { 19 | /** 20 | * 每个基础方块的跟随者是其对应变种的方块。非基础方块会被略过。 21 | * 22 | * @param block 可能是基础方块的方块。 23 | * @return 由方块对应的变种组成的流。若方块不是基础方块,则返回 null。 24 | * @see BlockFamily 25 | */ 26 | @Override 27 | public @Nullable Iterable getFollowers(Block block) { 28 | if (!blockPredicate.test(block)) return null; 29 | final @Nullable BlockFamily blockFamily = BlockFamiliesAccessor.getBaseBlocksToFamilies().get(block); 30 | if (blockFamily == null) return null; 31 | return Streams.stream(variants).map(blockFamily::getVariant).filter(Objects::nonNull)::iterator; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/mixin/ItemMixin.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.mixin; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.item.ItemGroup; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 10 | import pers.solid.mod.Configs; 11 | import pers.solid.mod.TransferRule; 12 | 13 | import java.util.Set; 14 | import java.util.stream.Collectors; 15 | 16 | @Mixin(Item.class) 17 | public abstract class ItemMixin { 18 | @Shadow 19 | public abstract Item asItem(); 20 | 21 | /** 22 | * 判断物品是否在转移规则中指定的组中的任意一个。如果转移规则没有此物品,则按照原版进行。 23 | */ 24 | @Inject(method = "isIn", at = @At("HEAD"), cancellable = true) 25 | public void isInMixin(ItemGroup group, CallbackInfoReturnable cir) { 26 | if (group == ItemGroup.INVENTORY || group == ItemGroup.SEARCH || group == ItemGroup.HOTBAR || !Configs.instance.enableGroupTransfer) return; 27 | final Item item = this.asItem(); 28 | final Set groups = TransferRule.streamTransferredGroupOf(item).collect(Collectors.toSet()); 29 | if (!groups.isEmpty()) { 30 | cir.setReturnValue(groups.contains(group)); 31 | cir.cancel(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /forge/src/main/java/pers/solid/mod/forge/ReasonableSortingForge.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.forge; 2 | 3 | import net.minecraftforge.api.distmarker.Dist; 4 | import net.minecraftforge.client.ConfigScreenHandler; 5 | import net.minecraftforge.fml.DistExecutor; 6 | import net.minecraftforge.fml.ModLoadingContext; 7 | import net.minecraftforge.fml.common.Mod; 8 | import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; 9 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import pers.solid.mod.ConfigScreen; 13 | import pers.solid.mod.Configs; 14 | import pers.solid.mod.SortingRules; 15 | import pers.solid.mod.TransferRules; 16 | 17 | @Mod("reasonable_sorting") 18 | public class ReasonableSortingForge { 19 | public static final Logger LOGGER = LoggerFactory.getLogger(ReasonableSortingForge.class); 20 | 21 | public ReasonableSortingForge() { 22 | SortingRules.initialize(); 23 | TransferRules.initialize(); 24 | FMLJavaModLoadingContext.get().getModEventBus().addListener((FMLLoadCompleteEvent event) -> Configs.loadAndUpdate() 25 | ); 26 | 27 | DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ModLoadingContext.get().registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class, () -> new ConfigScreenHandler.ConfigScreenFactory((client, screen) -> new ConfigScreen().createScreen(screen)))); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain platforms or Java versions, and provides a first line of defence 4 | # against bad commits. 5 | 6 | name: build 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | # Use these Java versions 14 | java: [ 15 | 17 # Minimum supported by Minecraft 16 | ] 17 | # and run on both Linux and Windows 18 | os: [ubuntu-20.04, windows-latest] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: checkout repository 22 | uses: actions/checkout@v2 23 | - name: validate gradle wrapper 24 | uses: gradle/wrapper-validation-action@v1 25 | - name: setup jdk ${{ matrix.java }} 26 | uses: actions/setup-java@v1 27 | with: 28 | java-version: ${{ matrix.java }} 29 | - name: make gradle wrapper executable 30 | if: ${{ runner.os != 'Windows' }} 31 | run: chmod +x ./gradlew 32 | - name: build 33 | run: ./gradlew build 34 | - name: capture build artifacts 35 | if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: Artifacts 39 | path: build/libs/ 40 | -------------------------------------------------------------------------------- /quilt/src/main/resources/quilt.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": 1, 3 | "mixin": [ 4 | "reasonable-sorting.mixins.json" 5 | ], 6 | "quilt_loader": { 7 | "group": "${group}", 8 | "id": "reasonable-sorting", 9 | "version": "${version}", 10 | "metadata": { 11 | "name": "Reasonable Sorting", 12 | "description": "Sorts blocks and items in the creative inventory reasonably.", 13 | "authors": ["SolidBlock"], 14 | "contributors": { 15 | "SolidBlock": "Mod author" 16 | }, 17 | "contact": { 18 | "homepage": "https://space.bilibili.com/87328531", 19 | "sources": "https://github.com/SolidBlock-cn/reasonable-sorting", 20 | "issues": "https://github.com/SolidBlock-cn/reasonable-sorting/issues" 21 | }, 22 | "license": "LGPL-3.0", 23 | "icon": "assets/reasonable-sorting/icon.png" 24 | }, 25 | "intermediate_mappings": "net.fabricmc:intermediary", 26 | "environment": "*", 27 | "entrypoints": { 28 | "init": [ 29 | "pers.solid.mod.quilt.ReasonableSortingQuilt" 30 | ], 31 | "modmenu": [ 32 | "pers.solid.mod.quilt.ConfigScreenQuilt" 33 | ] 34 | }, 35 | "depends": [ 36 | "quilt_loader", 37 | "quilt_base", 38 | { 39 | "id": "minecraft", 40 | "version": ">=1.19" 41 | }, 42 | { 43 | "id": "cloth-config2", 44 | "version": ">=5.0.0" 45 | } 46 | ], 47 | "provides": [ 48 | "reasonable_sorting" 49 | ], 50 | "recommends": [ 51 | "modmenu" 52 | ] 53 | } 54 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx2048M 3 | 4 | minecraft_version=1.19.2 5 | supported_minecraft_versions=1.19,1.19.1,1.19.2 6 | enabled_platforms=quilt,fabric,forge 7 | 8 | ##### Fabric \u90E8\u5206 9 | # \u66F4\u65B0\u7F51\u5740\uFF1Ahttps://fabricmc.net/develop/ 10 | yarn_mappings=1.19.2+build.28 11 | fabric_loader_version=0.14.11 12 | fabric_api_version=0.69.0+1.19.2 13 | 14 | # \u66F4\u65B0\u7F51\u5740\uFF1Ahttps://www.curseforge.com/minecraft/mc-mods/modmenu/ 15 | modmenu_version=4.+ 16 | # \u66F4\u65B0\u7F51\u5740\uFF1Ahttps://www.curseforge.com/minecraft/mc-mods/cloth-config 17 | cloth_config_version=8.2.+ 18 | 19 | ##### Forge \u90E8\u5206 20 | # \u66F4\u65B0\u7F51\u5740\uFF1Ahttps://files.minecraftforge.net/net/minecraftforge/forge/ 21 | forge_version=1.19.2-43.2.0 22 | 23 | ##### Quilt \u90E8\u5206 24 | # \u66F4\u65B0\u7F51\u5740\uFF1Ahttps://lambdaurora.dev/tools/import_quilt.html 25 | quilt_loader_version=0.18.1-beta.23 26 | quilted_fabric_api_version=4.0.0-beta.24+0.68.0 27 | qsl_version=3.0.0-beta.24 28 | 29 | ##### Mod Info 30 | mod_version=2.1.0 31 | maven_group=pers.solid.mod 32 | archives_base_name=reasonable-sorting 33 | 34 | ##### Loom properties 35 | loom_libraries_base=https://bmclapi2.bangbang93.com/maven/ 36 | loom_resources_base=https://bmclapi2.bangbang93.com/assets/ 37 | loom_version_manifests=https://bmclapi2.bangbang93.com/mc/game/version_manifest.json 38 | loom_experimental_versions=https://maven.fabricmc.net/net/minecraft/experimental_versions.json 39 | #loom_fabric_repository=https://repository.hanbings.io/proxy/ -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/ExtShapeBridge.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import dev.architectury.injectables.annotations.ExpectPlatform; 4 | import org.jetbrains.annotations.ApiStatus; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.List; 8 | import java.util.stream.Stream; 9 | 10 | /** 11 | * 本类用于兼容不同平台,同时也在 Reasonable Sorting 与 Extended Block Shapes 模组之间搭建桥梁。 12 | */ 13 | @SuppressWarnings({"EmptyMethod", "unused"}) 14 | public class ExtShapeBridge { 15 | /** 16 | * 当没有安装 Reasonable Sorting 的时候,这个字段是一个空的实例。装了 Reasonable Sorting 后,这个字段就会是由 Reasonable Sorting 提供的对象,其类型为 ExtShapeBridge 的子类型。注意:避免此类在过早初始化,否则可能导致 {@link #getInstance()} 无法获取到由 Reasonable Sorting 提供的对象(尤其是在 Forge 模组中)。 17 | */ 18 | public static final ExtShapeBridge INSTANCE = getInstance(); 19 | 20 | /** 21 | * 返回一个 ExtShapeBridge 的实例。当装有 Reasonable Sorting 时,返回的值是由 Reasonable Sorting 模组提供的一个 ExtShapeBridge 的子类型对象。 22 | *

23 | * 这个方法只会在初始化类时给 {@link #INSTANCE} 复制时调用一次。 24 | *

25 | * 注意,根据 Architectury Plugin,这个方法的实际方法体取决于具体平台。请参见 {@code ExtShapeBridgeImpl}。 26 | */ 27 | @ExpectPlatform 28 | @ApiStatus.Internal 29 | private static @NotNull ExtShapeBridge getInstance() { 30 | return new ExtShapeBridge(); 31 | } 32 | 33 | public boolean modHasLoaded() { 34 | return false; 35 | } 36 | 37 | public boolean isValidShapeName(String s) { 38 | return false; 39 | } 40 | 41 | public Stream getValidShapeNames() { 42 | return Stream.empty(); 43 | } 44 | 45 | public void updateShapeList(String s) { 46 | } 47 | 48 | public void updateShapeTransferRules(List list) { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/VariantToVariantRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.collect.Streams; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.data.family.BlockFamily; 6 | import org.jetbrains.annotations.Nullable; 7 | import pers.solid.mod.mixin.BlockFamiliesAccessor; 8 | 9 | import java.util.Objects; 10 | import java.util.function.Predicate; 11 | 12 | /** 13 | * 让一个方块变种被特定相应方块变种的方块跟随的排序规则。例如,让所有的栅栏都被对应的栅栏门跟随: 14 | *

15 |  *   {@code new VariantToVariantRule(
16 |  *       predicate = block -> block instanceof FenceBlock,
17 |  *     variantFrom = BlockFamily.Variant.FENCE,
18 |  *       variantTo = Collections.singleton(BlockFamily.Variant.FENCE_GATE))}
19 |  * 
20 | * 21 | * @param blockPredicate 应用此规则的方块需要满足的条件。 22 | * @param variantFrom 被跟随的方块变种。 23 | * @param variantTo 跟随的方块变种,这些变种的对应方块会跟随在后面。可以是多个值。 24 | */ 25 | public record VariantToVariantRule(Predicate blockPredicate, BlockFamily.Variant variantFrom, Iterable variantTo) implements SortingRule { 26 | /** 27 | * 若方块本身是 {@link #variantFrom} 中的方块变种,则会被对应的 {@code variantTo} 中的方块变种跟随。 28 | * 29 | * @param block 可能属于 {@link #variantFrom} 中的变种的方块。 30 | * @return 对应的 {@code variantTo} 中的方块变种的方块。若方块不属于该变种,或不符合 {@link #blockPredicate},则返回 {@code null}。 31 | */ 32 | @Override 33 | public @Nullable Iterable getFollowers(Block block) { 34 | for (BlockFamily blockFamily : BlockFamiliesAccessor.getBaseBlocksToFamilies().values()) { 35 | if (blockPredicate.test(block) && blockFamily.getVariant(variantFrom) == block) { 36 | return Streams.stream(variantTo).map(blockFamily::getVariant).filter(Objects::nonNull)::iterator; 37 | } 38 | } 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /forge/src/main/java/pers/solid/mod/forge/mixin/ForgeRegistryMixin.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.forge.mixin; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.minecraft.util.registry.Registry; 5 | import net.minecraft.util.registry.RegistryKey; 6 | import net.minecraftforge.registries.ForgeRegistry; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | import pers.solid.mod.Configs; 14 | import pers.solid.mod.SortingInfluenceRange; 15 | import pers.solid.mod.SortingRule; 16 | 17 | import java.util.Collection; 18 | import java.util.Iterator; 19 | import java.util.stream.Stream; 20 | 21 | /** 22 | * 这个是专用于 {@link ForgeRegistry} 的 mixin。由于 Forge 的注册表替代了原版的注册表,故需要为 Forge 单独写一个 mixin。 23 | */ 24 | @Mixin(ForgeRegistry.class) 25 | public abstract class ForgeRegistryMixin { 26 | @Shadow 27 | public abstract RegistryKey> getRegistryKey(); 28 | 29 | @Shadow 30 | public abstract @Nullable RegistryKey getKey(int id); 31 | 32 | @Inject(method = "iterator", at = @At("RETURN"), remap = false, cancellable = true) 33 | private void reasonableSortedIterator(CallbackInfoReturnable> cir) { 34 | if (!Configs.instance.enableSorting || Configs.instance.sortingInfluenceRange != SortingInfluenceRange.REGISTRY) { 35 | return; 36 | } 37 | final Collection> sortingRules = SortingRule.getSortingRules(getRegistryKey()); 38 | if (!sortingRules.isEmpty()) { 39 | final Stream stream = SortingRule.streamOfRegistry(getRegistryKey(), ImmutableList.copyOf(cir.getReturnValue()), sortingRules); 40 | cir.setReturnValue(stream.iterator()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/ColorSortingRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.collect.Streams; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.item.Item; 6 | import net.minecraft.util.DyeColor; 7 | import net.minecraft.util.Identifier; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | 13 | /** 14 | * 找到指定颜色({@link #baseColor})的方块和物品,其后面紧随 {@link #followingColors} 中的方块和物品。 15 | * 16 | * @param baseColor 基础颜色,如白色。 17 | * @param followingColors 方块或物品其后续跟随的颜色。 18 | * @param 这个参数一般是方块或者物品。 19 | */ 20 | public record ColorSortingRule(DyeColor baseColor, Iterable followingColors) implements SortingRule { 21 | @SuppressWarnings({"unchecked", "rawtypes"}) 22 | @Override 23 | public @Nullable Iterable getFollowers(T leadingObj) { 24 | final Identifier identifier; 25 | if (leadingObj instanceof Block block) { 26 | identifier = Bridge.getBlockId(block); 27 | } else if (leadingObj instanceof Item item) { 28 | identifier = Bridge.getItemId(item); 29 | } else { 30 | return null; 31 | } 32 | if (identifier.getPath().contains(baseColor.asString())) { 33 | if (baseColor == DyeColor.BLUE || baseColor == DyeColor.GRAY) { 34 | if (identifier.getPath().contains("light_" + baseColor.asString())) return null; 35 | } 36 | return (Iterable) (Iterable) Streams.stream(followingColors) 37 | .map(DyeColor::asString) 38 | .map(name -> Identifier.of(identifier.getNamespace(), identifier.getPath().replace(baseColor.asString(), name))) 39 | .filter(Objects::nonNull) 40 | .map(leadingObj instanceof Block ? Bridge::getBlockByIdOrEmpty : Bridge::getItemByIdOrEmpty) 41 | .filter(Optional::isPresent) 42 | .map(Optional::get) 43 | ::iterator; 44 | } 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/Bridge.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import dev.architectury.injectables.annotations.ExpectPlatform; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.item.Item; 6 | import net.minecraft.util.Identifier; 7 | import net.minecraft.util.registry.Registry; 8 | import org.slf4j.Logger; 9 | 10 | import java.util.Optional; 11 | 12 | /** 13 | * 本类用于兼容不同平台。 14 | */ 15 | public class Bridge { 16 | @ExpectPlatform 17 | public static Identifier getBlockId(Block block) { 18 | return Registry.BLOCK.getId(block); 19 | } 20 | 21 | @ExpectPlatform 22 | public static Block getBlockById(Identifier identifier) { 23 | return Registry.BLOCK.get(identifier); 24 | } 25 | 26 | @ExpectPlatform 27 | public static Optional getBlockByIdOrEmpty(Identifier identifier) { 28 | return Registry.BLOCK.getOrEmpty(identifier); 29 | } 30 | 31 | public static Optional getBlockByIdOrWarn(Identifier identifier, Logger logger) { 32 | final Optional value = getBlockByIdOrEmpty(identifier); 33 | if (value.isEmpty()) { 34 | logger.warn("Unidentified block id: {}. This may be because the configuration is loaded before block is registered.", identifier); 35 | } 36 | return value; 37 | } 38 | 39 | @ExpectPlatform 40 | public static Identifier getItemId(Item item) { 41 | return Registry.ITEM.getId(item); 42 | } 43 | 44 | @ExpectPlatform 45 | public static Item getItemById(Identifier identifier) { 46 | return Registry.ITEM.get(identifier); 47 | } 48 | 49 | public static Optional getItemByIdOrEmpty(Identifier identifier) { 50 | return Registry.ITEM.getOrEmpty(identifier); 51 | } 52 | 53 | public static Optional getItemByIdOrWarn(Identifier identifier, Logger logger) { 54 | final Optional value = getItemByIdOrEmpty(identifier); 55 | if (value.isEmpty()) { 56 | logger.warn("Unidentified item id: {}. This may be because the configuration is loaded before item is registered.", identifier); 57 | } 58 | return value; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /forge/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.github.johnrengelman.shadow" version "7.1.2" 3 | } 4 | 5 | archivesBaseName += "-forge" 6 | 7 | architectury { 8 | platformSetupLoomIde() 9 | forge() 10 | } 11 | 12 | loom { 13 | forge { 14 | mixinConfig "reasonable-sorting.mixins.json", "reasonable-sorting-forge.mixins.json" 15 | } 16 | } 17 | 18 | 19 | configurations { 20 | common 21 | shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this. 22 | compileClasspath.extendsFrom common 23 | runtimeClasspath.extendsFrom common 24 | developmentForge.extendsFrom common 25 | } 26 | 27 | repositories { 28 | maven { url "https://maven.shedaniel.me/" } 29 | } 30 | 31 | dependencies { 32 | forge "net.minecraftforge:forge:${rootProject.forge_version}" 33 | 34 | common(project(path: ":common", configuration: "namedElements")) { transitive false } 35 | shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { transitive = false } 36 | 37 | modApi("me.shedaniel.cloth:cloth-config-forge:${rootProject.cloth_config_version}") 38 | } 39 | 40 | processResources { 41 | inputs.property "version", rootProject.mod_version 42 | 43 | filesMatching("META-INF/mods.toml") { 44 | expand "version": rootProject.mod_version 45 | } 46 | } 47 | 48 | shadowJar { 49 | exclude "fabric.mod.json" 50 | exclude "architectury.common.json" 51 | 52 | configurations = [project.configurations.shadowCommon] 53 | classifier "dev-shadow" 54 | } 55 | 56 | remapJar { 57 | inputFile.set shadowJar.archiveFile 58 | dependsOn shadowJar 59 | classifier null 60 | } 61 | 62 | jar { 63 | classifier "dev" 64 | } 65 | 66 | sourcesJar { 67 | def commonSources = project(":common").sourcesJar 68 | dependsOn commonSources 69 | from commonSources.archiveFile.map { zipTree(it) } 70 | } 71 | 72 | components.java { 73 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 74 | skip() 75 | } 76 | } 77 | 78 | publishing { 79 | publications { 80 | mavenForge(MavenPublication) { 81 | artifactId = rootProject.archives_base_name + "-" + project.name 82 | from components.java 83 | } 84 | } 85 | 86 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 87 | repositories { 88 | // Add repositories to publish to here. 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /fabric/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.github.johnrengelman.shadow" version "7.1.2" 3 | } 4 | 5 | archivesBaseName += "-fabric" 6 | 7 | architectury { 8 | platformSetupLoomIde() 9 | fabric() 10 | } 11 | 12 | loom { 13 | // accessWidenerPath = project(":common").loom.accessWidenerPath 14 | } 15 | 16 | configurations { 17 | common 18 | shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this. 19 | compileClasspath.extendsFrom common 20 | runtimeClasspath.extendsFrom common 21 | developmentFabric.extendsFrom common 22 | } 23 | 24 | repositories { 25 | maven { url "https://maven.shedaniel.me/" } 26 | 27 | maven { 28 | name = 'TerraformersMC' 29 | url = 'https://maven.terraformersmc.com/releases' 30 | } 31 | } 32 | 33 | dependencies { 34 | modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" 35 | modImplementation "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" 36 | 37 | common(project(path: ":common", configuration: "namedElements")) { transitive false } 38 | shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } 39 | 40 | modApi("me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_config_version}") { 41 | exclude(group: "net.fabricmc.fabric-api") 42 | } 43 | modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}") 44 | } 45 | 46 | processResources { 47 | inputs.property "version", rootProject.mod_version 48 | 49 | filesMatching("fabric.mod.json") { 50 | expand "version": rootProject.mod_version 51 | } 52 | } 53 | 54 | shadowJar { 55 | exclude "architectury.common.json" 56 | 57 | configurations = [project.configurations.shadowCommon] 58 | classifier "dev-shadow" 59 | } 60 | 61 | remapJar { 62 | injectAccessWidener = true 63 | inputFile.set shadowJar.archiveFile 64 | dependsOn shadowJar 65 | classifier null 66 | } 67 | 68 | jar { 69 | classifier "dev" 70 | } 71 | 72 | sourcesJar { 73 | def commonSources = project(":common").sourcesJar 74 | dependsOn commonSources 75 | from commonSources.archiveFile.map { zipTree(it) } 76 | } 77 | 78 | components.java { 79 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 80 | skip() 81 | } 82 | } 83 | 84 | publishing { 85 | publications { 86 | mavenFabric(MavenPublication) { 87 | artifactId = rootProject.archives_base_name + "-" + project.name 88 | from components.java 89 | } 90 | } 91 | 92 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 93 | repositories { 94 | // Add repositories to publish to here. 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/mixin/CreativeInventoryScreenMixin.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.mixin; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; 6 | import net.minecraft.client.util.math.MatrixStack; 7 | import net.minecraft.item.Item; 8 | import net.minecraft.item.ItemGroup; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.text.MutableText; 11 | import net.minecraft.text.Text; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 16 | import org.spongepowered.asm.mixin.injection.Slice; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 19 | import pers.solid.mod.Configs; 20 | import pers.solid.mod.SortingRule; 21 | import pers.solid.mod.TransferRule; 22 | 23 | import java.util.Collection; 24 | import java.util.Iterator; 25 | import java.util.List; 26 | 27 | @Environment(EnvType.CLIENT) 28 | @Mixin(CreativeInventoryScreen.class) 29 | public abstract class CreativeInventoryScreenMixin { 30 | /** 31 | * 在创造模式物品栏的搜索部分,选中物品会显示其物品组。该 mixin 则会使其显示修改后的物品组。修改后的物品组可能为多个,都会显示。 32 | */ 33 | @Inject( 34 | method = "renderTooltip", 35 | at = @At(value = "INVOKE", target = "Ljava/util/List;add(ILjava/lang/Object;)V", shift = At.Shift.AFTER), 36 | slice = @Slice( 37 | from = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemGroup;getDisplayName()Lnet/minecraft/text/Text;"), 38 | to = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getTooltipData()Ljava/util/Optional;")), 39 | locals = LocalCapture.CAPTURE_FAILSOFT) 40 | protected void renderTooltipMixin(MatrixStack matrices, ItemStack stack, int x, int y, CallbackInfo ci, List list, List list2) { 41 | final Collection itemGroups = TransferRule.streamTransferredGroupOf(stack.getItem()).toList(); 42 | if (Configs.instance.enableGroupTransfer && !itemGroups.isEmpty()) { 43 | MutableText text = Text.empty().styled(style -> style.withColor(0x88ccff)); 44 | for (Iterator iterator = itemGroups.iterator(); iterator.hasNext(); ) { 45 | ItemGroup group = iterator.next(); 46 | text.append(group.getDisplayName()); 47 | if (iterator.hasNext()) { 48 | text.append(" / "); 49 | } 50 | } 51 | list2.set(1, text); 52 | } 53 | } 54 | 55 | @ModifyVariable(method = "search", at = @At(value = "STORE")) 56 | public Iterator modifiedIteratorInSearch(Iterator value) { 57 | return SortingRule.modifyIteratorInInventory(value, ItemGroup.SEARCH.getName()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /quilt/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.github.johnrengelman.shadow" version "7.1.2" 3 | } 4 | 5 | archivesBaseName += "-quilt" 6 | 7 | repositories { 8 | maven { url "https://maven.quiltmc.org/repository/release/" } \ 9 | 10 | maven { url "https://maven.shedaniel.me/" } 11 | 12 | maven { 13 | name = 'TerraformersMC' 14 | url = 'https://maven.terraformersmc.com/releases' 15 | } 16 | } 17 | 18 | architectury { 19 | platformSetupLoomIde() 20 | loader("quilt") 21 | } 22 | 23 | loom { 24 | // accessWidenerPath = project(":common").loom.accessWidenerPath 25 | } 26 | 27 | configurations { 28 | common 29 | shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this. 30 | compileClasspath.extendsFrom common 31 | runtimeClasspath.extendsFrom common 32 | developmentQuilt.extendsFrom common 33 | } 34 | 35 | dependencies { 36 | modImplementation "org.quiltmc:quilt-loader:${rootProject.quilt_loader_version}" 37 | 38 | modImplementation("me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_config_version}") { 39 | exclude(group: "net.fabricmc.fabric-api") 40 | exclude(module: "fabric-loader") 41 | } 42 | modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { 43 | exclude(group: "net.fabricmc.fabric-api") 44 | exclude(module: "fabric-loader") 45 | } 46 | 47 | modImplementation "org.quiltmc.quilted-fabric-api:quilted-fabric-api:${rootProject.quilted_fabric_api_version}-${minecraft_version}" 48 | modImplementation "org.quiltmc:qsl:${rootProject.qsl_version}+${rootProject.minecraft_version}" 49 | 50 | common(project(path: ":common", configuration: "namedElements")) { transitive false } 51 | shadowCommon(project(path: ":common", configuration: "transformProductionQuilt")) { transitive false } 52 | } 53 | 54 | processResources { 55 | inputs.property "group", rootProject.maven_group 56 | inputs.property "version", rootProject.mod_version 57 | 58 | filesMatching("quilt.mod.json") { 59 | expand "group": rootProject.maven_group, 60 | "version": rootProject.mod_version 61 | } 62 | } 63 | 64 | shadowJar { 65 | exclude "architectury.common.json" 66 | 67 | configurations = [project.configurations.shadowCommon] 68 | classifier "dev-shadow" 69 | } 70 | 71 | remapJar { 72 | injectAccessWidener = true 73 | inputFile.set shadowJar.archiveFile 74 | dependsOn shadowJar 75 | classifier null 76 | } 77 | 78 | jar { 79 | classifier "dev" 80 | } 81 | 82 | sourcesJar { 83 | def commonSources = project(":common").sourcesJar 84 | dependsOn commonSources 85 | from commonSources.archiveFile.map { zipTree(it) } 86 | } 87 | 88 | components.java { 89 | withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 90 | skip() 91 | } 92 | } 93 | 94 | publishing { 95 | publications { 96 | mavenQuilt(MavenPublication) { 97 | artifactId = rootProject.archives_base_name + "-" + project.name 98 | from components.java 99 | } 100 | } 101 | 102 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 103 | repositories { 104 | // Add repositories to publish to here. 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/TransferRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.collect.Streams; 4 | import net.minecraft.item.Item; 5 | import net.minecraft.item.ItemGroup; 6 | import org.jetbrains.annotations.ApiStatus; 7 | import org.jetbrains.annotations.NonNls; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Objects; 16 | import java.util.function.BooleanSupplier; 17 | import java.util.stream.Stream; 18 | 19 | /** 20 | * 物品组转移规则,根据一个物品,决定它应该转移到哪个物品组。

21 | * An item group transfer rule, to determine which group to be transferred to according to an item. 22 | */ 23 | @FunctionalInterface 24 | public interface TransferRule { 25 | Logger LOGGER = LoggerFactory.getLogger(TransferRule.class); 26 | 27 | /** 28 | * 根据指定的物品以及已经注册的物品组转移规则,返回它转移的物品组的流。注意这个流可能产生重复元素。 29 | * 30 | * @param item 需要转移物品组的物品。 31 | * @return 返回物品转移后的物品组产生的流,可能为空,也有可能产生重复元素。 32 | */ 33 | static Stream streamTransferredGroupOf(Item item) { 34 | return Internal.RULES.stream().map(TransferRuleContainer::transferRule) 35 | .map(transferRule -> transferRule.getTransferredGroups(item)) 36 | .filter(Objects::nonNull) 37 | .flatMap(Streams::stream); 38 | } 39 | 40 | /** 41 | * 添加一条转移规则。 42 | * 43 | * @param rule 一个物品组转移规则。 44 | */ 45 | static void addTransferRule(TransferRule rule) { 46 | addTransferRule(rule, null); 47 | } 48 | 49 | /** 50 | * 添加一条转移规则。 51 | * 52 | * @param rule 一个物品组转移规则。 53 | * @param name 需要添加的规则的名称,主要用于在调试中显示。 54 | */ 55 | static void addTransferRule(@NotNull TransferRule rule, @Nullable String name) { 56 | Internal.RULES.add(new TransferRuleContainer(rule, name)); 57 | } 58 | 59 | /** 60 | * 添加一个有条件的转移规则,只有在满足条件时,这个规则才会被应用。 61 | * 62 | * @param condition 一个条件。注意只有在转移物品组时,这个条件才会被评估,注册规则时并不会评估条件。 63 | * @param rule 一个物品组转移规则。 64 | */ 65 | static void addConditionalTransferRule(@NotNull BooleanSupplier condition, @NotNull TransferRule rule) { 66 | addConditionalTransferRule(condition, rule, null); 67 | } 68 | 69 | /** 70 | * 添加一个有条件的转移规则,只有在满足条件时,这个规则才会被应用。 71 | * 72 | * @param condition 一个条件。注意只有在转移物品组时,这个条件才会被评估,注册规则时并不会评估条件。 73 | * @param rule 一个物品组转移规则。 74 | * @param name 需要添加的规则的名称,主要用于在调试中显示。 75 | */ 76 | static void addConditionalTransferRule(@NotNull BooleanSupplier condition, @NotNull TransferRule rule, @Nullable String name) { 77 | addTransferRule(new ConditionalTransferRule(condition, rule), name); 78 | } 79 | 80 | /** 81 | * 根据特定物品,决定它应该转移到哪个物品组。可以返回 {@code null},表示不转移。如果返回多个值,则表示转移到了多个物品组。 82 | * 83 | * @param item 需要转移物品组的物品。 84 | * @return 转移后的物品组,可以为 {@code null}。 85 | */ 86 | @Nullable Iterable getTransferredGroups(Item item); 87 | 88 | @ApiStatus.Internal 89 | class Internal { 90 | @ApiStatus.Internal 91 | private static final 92 | List RULES = new ArrayList<>(); 93 | } 94 | 95 | /** 96 | * 用于存储在 {@link Internal#RULES} 中的对象,主要用于与名称一起保存。 97 | * 98 | * @param transferRule 转移规则。 99 | * @param name 名称。 100 | */ 101 | record TransferRuleContainer(@NotNull TransferRule transferRule, @Nullable @NonNls String name) { 102 | @Override 103 | public String toString() { 104 | return "TransferRuleContainer[" + (name == null ? transferRule.toString() : name) + "]"; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/mixin/SimpleRegistryMixin.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod.mixin; 2 | 3 | import com.google.common.collect.Collections2; 4 | import com.mojang.serialization.Lifecycle; 5 | import net.minecraft.util.registry.*; 6 | import org.jetbrains.annotations.Nullable; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | import pers.solid.mod.*; 14 | 15 | import java.util.*; 16 | import java.util.stream.Stream; 17 | 18 | @Mixin(SimpleRegistry.class) 19 | public abstract class SimpleRegistryMixin extends MutableRegistry implements SimpleRegistryExtension { 20 | @Shadow 21 | @Final 22 | private Map> valueToEntry; 23 | 24 | @Shadow 25 | protected abstract List> getEntries(); 26 | 27 | @Shadow 28 | @Nullable 29 | private List> cachedEntries; 30 | 31 | @Shadow 32 | public abstract Optional> getEntry(int rawId); 33 | 34 | @Shadow 35 | public abstract RegistryEntry replace(OptionalInt rawId, RegistryKey key, T newEntry, Lifecycle lifecycle); 36 | 37 | @Shadow 38 | public abstract Optional> getKey(T entry); 39 | 40 | public SimpleRegistryMixin(RegistryKey> registryKey, Lifecycle lifecycle) { 41 | super(registryKey, lifecycle); 42 | } 43 | 44 | @Inject(method = "iterator", at = @At("HEAD"), cancellable = true) 45 | private void reasonableSortedIterator(CallbackInfoReturnable> cir) { 46 | if (!Configs.instance.enableSorting || Configs.instance.sortingInfluenceRange != SortingInfluenceRange.REGISTRY) { 47 | return; 48 | } 49 | if (Configs.instance.sortingCalculationType == SortingCalculationType.REAL_TIME || Configs.instance.sortingCalculationType == SortingCalculationType.SEMI_REAL_TIME) { 50 | final Collection> sortingRules = SortingRule.getSortingRules(getKey()); 51 | if (sortingRules.isEmpty()) return; 52 | 53 | final Stream stream = SortingRule.streamOfRegistry(getKey(), Collections2.transform(getEntries(), RegistryEntry.Reference::value), sortingRules); 54 | if (Configs.instance.debugMode) { 55 | SortingRule.LOGGER.info("The iteration of registry {} is affected by Reasonable Sorting Mode, as the sorting calculation type is set to 'real-time' or 'semi-real-time'.", getKey().getValue()); 56 | } 57 | cir.setReturnValue(stream.iterator()); 58 | cir.cancel(); 59 | } 60 | } 61 | 62 | @Inject(method = "getEntries", at = @At(value = "FIELD", target = "Lnet/minecraft/util/registry/SimpleRegistry;cachedEntries:Ljava/util/List;", ordinal = 1, shift = At.Shift.AFTER)) 63 | private void reasonableSortedCachedEntries(CallbackInfoReturnable>> cir) { 64 | if (!Configs.instance.enableSorting || Configs.instance.sortingInfluenceRange != SortingInfluenceRange.REGISTRY) { 65 | return; 66 | } 67 | // 此处只会在 cachedEntries 不为 null 时执行。 68 | if (Configs.instance.sortingCalculationType == SortingCalculationType.STANDARD) { 69 | 70 | final Collection> sortingRules = SortingRule.getSortingRules(getKey()); 71 | if (sortingRules.isEmpty()) return; 72 | final Stream stream = SortingRule.streamOfRegistry(getKey(), Collections2.transform(getEntries(), RegistryEntry.Reference::value), sortingRules); 73 | if (Configs.instance.debugMode) { 74 | SortingRule.LOGGER.info("Calculated cachedEntries for registry {}. You will not see this info for this registry again until you modify config.", getKey().getValue()); 75 | } 76 | cachedEntries = stream.map(value -> this.valueToEntry.get(value)).toList(); 77 | } 78 | } 79 | 80 | @Override 81 | public void removeCachedEntries() { 82 | cachedEntries = null; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 合理排序 2 | 3 | > **DECLARATION:** The mod , “reasonable-sorting”, is hosted *solely* on GitHub. The author, “SolidBlock-cn”, did not participate in any code-hosting platform except GitHub. The project “reasonable-sorting” and the user profile with the name “SolidBlock-cn” on GitCode, another code-hosting platform affiliated to CSDN, are *plagiarism* and *impersonation*. Please don't be cheated. 4 | > 5 | > **声明**:本模组“reasonable-sorting”**仅**托管在 GitHub 上。模组作者“SolidBlock-cn”从未参与除 GitHub 之外的任何代码托管平台。位于 CSDN 旗下代码托管平台 GitCode 上的“reasonable-sorting”项目以及用户“SolidBlock-cn”个人主页是**剽窃**和**冒充**。请勿上当受骗。 6 | 7 | If you do not understand Chinese, you can read [English version](README-en.md). 8 | 9 | 你是否发现,在创造模式下,物品栏内的物品排序方式特别乱,找到你需要的东西特别不容易?安装本模组,您的创造模式物品栏内就会井然有序!您将会发现,所有的楼梯和台阶排在了基础方块后面,栅栏门转移至装饰性方块并紧随在了对应的栅栏后面。彩色方块也将按照渐变的方式排序。 10 | 11 | 本模组可以在配置中指定自定义的排序规则以及物品组转移规则。 12 | 13 | **本模组依赖 Cloth Config 模组**,如果不安装这些模组将无法运行。此外,对于 Fabric 和 Quilt 版本,**建议安装 Mod Menu 模组**以进行配置。 14 | 15 | 本模组的 Fabric 版本依赖 Fabric API,Quilt 版本(Minecraft 1.18.2 以上)依赖 Quilt Standard Libraries。 16 | 17 | ## 配置 18 | 19 | 本模组可以通过 Mod Menu 进行配置。 20 | 21 | ### 排序 22 | 23 | **启用排序** 24 | 25 | 默认为开启。如果关闭,所有的排序都将会按照原版进行,下面的这些配置也将失效。 26 | 27 | **排序影响范围** 28 | 29 | 决定了本模组在哪些情况下会影响内容排序。如果不明白意思,直接使用默认值即可。支持以下值: 30 | 31 | - 注册表:Minecraft注册表的所有遍历过程都会受到影响。这将会影响更多情况,例如将会影响一些可以列举所有方块或者物品的模组(如 Roughly Enough Items 和 Just Enough Items 模组)。但是,这有可能造成不稳定性,例如加入服务器时可能出现注册表不匹配的情况。 32 | - 仅物品栏:仅影响创造模式物品栏。其他情况不会受到此模组的影响。 33 | 34 | **排序计算类型** 35 | 36 | 决定了本模组在哪些情况下计算排序。如果不明白意思,直接使用默认值即可。支持以下值: 37 | 38 | - 标准:内容(例如物品)的排序会在游戏开始时以及配置加载时计算一次,当配置更改时会重新计算。这是推荐的方式,因为可以减少每次迭代中的不必要的计算。(如果您使用 Forge,且“排序影响范围”设为“注册表”,那么这个选项将不会生效。) 39 | - 半实时:内容组合规则会在游戏开始时、配置加载时、配置更改时计算。但是,排序结果仍会在每次迭代中计算。这可能会造成每次打开创造模式物品栏时出现略微卡顿。 40 | - 实时:组合规则以及排序结果都会在每次注册表迭代时计算。这可能会造成每次打开物品栏时出现卡顿。 41 | 42 | **调试模式** 43 | 44 | 如果启用,那么模组会在日志中输出更多信息。如果你需要研究模组原理或者遇到任何问题,可以启用此选项。 45 | 46 | **启用默认物品排序规则** 47 | 48 | 默认为开启。本模组内置了一些物品排序规则,例如将冰、浮冰、蓝冰排在一起。 49 | 50 | **自定义排序规则** 51 | 52 | 默认为空。你可以通过输入物品id来自定义一些排序规则。在模组配置界面中,点击左边的“+”号,下面将会出现一个文本框(不明显),在其中输入一条规则即可。其语法为:多个物品的id用空格隔开。例如,“`dirt white_wool diamond_block`”表示泥土、白色羊毛和钻石块将会排在一起,其中白色羊毛和钻石块将依次排在泥土后面。 53 | 54 | **紧随基础方块的方块变种** 55 | 56 | Minecraft 中,很多方块都具有其**变种**,例如橡木木板的“楼梯”变种为橡木楼梯,“台阶”变种为橡木台阶等等,也就是说,橡木木板是橡木楼梯、橡木台阶等方块的**基础方块**。你可以指定一些变种类型使之排在基础方块后面。 57 | 58 | 默认语法为多个方块变种名称用空格隔开。可用的方块变种名称会在模组配置界面显示。默认为 `stairs slab`,也就是说所有的楼梯、台阶会依次排在其基础方块后面。 59 | 60 | 需要注意的是,改变物品排序并不会改变物品所在的物品组。如要改变物品组,还需要设置**变种转移规则**。 61 | 62 | **栅栏门紧随栅栏** 63 | 64 | 默认开启。将会使栅栏门方块紧跟在栅栏方块的后面。需要开启**栅栏门移至装饰性方块**,否则这些栅栏门仍会出现在“红石”物品组中,不会达到效果。 65 | 66 | **美观颜色排序** 67 | 68 | 以渐变顺序排序带有颜色的方块和物品(如羊毛、带釉陶瓦、床、旗帜),就像Minecraft 1.19.3中的那样。 69 | 70 | **避免影响方块注册表** 71 | 72 | 默认关闭。若开启,只有物品注册表,包括方块物品(例如创造模式物品栏中的方块)会受到上述选项的影响,而方块注册表(例如调试模式中的方块)则不受影响。这在一定程度上可以避免服务器与客户端之间的方块不匹配。如果“排序影响范围”设为“仅物品栏”,那么这个选项没有影响。 73 | 74 | ### 物品组转移 75 | 76 | **启用物品组转移** 77 | 78 | 默认为开启。如果关闭,所有的物品都会出现在原版物品组,下面的这些配置也将失效。 79 | 80 | **按钮移至装饰性方块** 81 | 82 | **栅栏门移至装饰性方块** 83 | 84 | **剑移至工具** 85 | 86 | **门移至装饰性方块** 87 | 88 | 以上四项设置的意思显而易见。其中,“栅栏门移至装饰性方块”默认为开启,其他的默认关闭。 89 | 90 | **自定义物品转移规则** 91 | 92 | 默认为空。和自定义排序规则一样,一行一条规则,点击“+”新增一条规则。每条规则的语法是:物品id + 空格 + 需要移至的物品组。例如 `redstone_block building_blocks` 就会将红石块移至“建筑方块”(建材)物品组。 93 | 94 | **自定义变种转移规则** 95 | 96 | 默认为空。和上面类似,语法是:变种名称 + 空格 + 需要移至的物品组。例如 `cut transportation` 就会将所有的切制方块移至“交通运输”物品组。 97 | 98 | **自定义正则表达式转移规则** 99 | 100 | 默认为空。和上面类似,语法是:正则表达式 + 空格 + 需要移至的物品组。所有 id 符合该正则表达式的物品都会移至指定的物品组。正则表达式必须语法正确。例如 `.+?button transportation` 就会把所有 id 以 `button` 结尾的物品移至“交通运输”物品组。 101 | 102 | **自定义标签转移规则** 103 | 104 | 默认为空。和上面类似。语法是:标签 + 空格 + 需要转移至的物品组。所有在此标签内的物品都会转移至指定的物品组。例如,`flowers transportation` 就会把拥有标签 `#minecraft:flowers` 的物品转移至“交通运输”物品组。 105 | 106 | ## 技术细节 107 | 108 | 物品排序的实质是“指定一个领队物品和多个跟随物品,这些跟随物品将会跟随在领队物品的后面”。举个例子,对于规则 `dirt white_wool diamond_block`,泥土将会是领队物品,白色羊毛和钻石块将会跟随在泥土后面,而不再出现在原来的位置。 109 | 110 | 一个物品不能同时跟随多个物品,如果有,则只会跟随其中的一个。例如如果同时指定了 `dirt white_wool` 和 `grass_block white_wool` 两个规则,那么白色羊毛只会出现在泥土和草方块二者**其中一个**的后面。也就是说,物品**不会重复出现**。 111 | 112 | 物品**可以嵌套跟随**。例如,默认情况下,根据“紧随基础方块的方块变种”规则,橡木楼梯和橡木台阶会跟随在橡木木板后面,而根据“默认物品排序规则”,石化橡木台阶会跟随在橡木台阶后面。这样,物品栏中将会出现“橡木木板-橡木楼梯-橡木台阶-石化橡木台阶”的组合。 113 | 114 | 物品**不能够互相跟随、循环跟随**。例如,如果同时设置了 `dirt white_wool` 和 `white_wool dirt` 两条规则,则泥土和白色羊毛可能都不会出现(游戏日志中将会记录错误),有可能还会导致死循环。因此,应当避免这种情况。 115 | 116 | 关于物品组转移,转移后的物品将会不再出现在此物品组。但是,一个物品可以转移至多组。 117 | 118 | ## 开发 119 | 120 | 使用 `SortingRule.addSortingRule` 方法可以添加一条排序规则。使用 `SortingRule.addConditionalSortingRule` 可以添加一个只在特定条件下的排序规则。类似地,物品组转移规则可以使用 `TransferRule.addTransferRule` 或 `TransferRule.addConditionalTransferRule` 添加。 -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/TransferRules.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import net.minecraft.block.AbstractButtonBlock; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.block.DoorBlock; 6 | import net.minecraft.block.FenceGateBlock; 7 | import net.minecraft.data.family.BlockFamily; 8 | import net.minecraft.item.BlockItem; 9 | import net.minecraft.item.ItemGroup; 10 | import net.minecraft.item.SwordItem; 11 | import net.minecraft.util.Identifier; 12 | import org.jetbrains.annotations.ApiStatus; 13 | import pers.solid.mod.mixin.BlockFamiliesAccessor; 14 | 15 | import java.util.Collections; 16 | import java.util.LinkedHashSet; 17 | 18 | /** 19 | * 本模组内置的一些物品组转移规则。 20 | */ 21 | public final class TransferRules { 22 | /** 23 | * 将原本位于“红石”中的按钮转移至“装饰性方块”。 24 | */ 25 | public static final TransferRule BUTTON_IN_DECORATIONS = item -> item.getGroup() == ItemGroup.REDSTONE && item instanceof BlockItem blockItem && blockItem.getBlock() instanceof AbstractButtonBlock ? Collections.singleton(ItemGroup.DECORATIONS) : null; 26 | /** 27 | * 将原本位于“红石”中的栅栏门转移至“装饰性方块”。 28 | */ 29 | public static final TransferRule FENCE_GATE_IN_DECORATIONS = item -> item.getGroup() == ItemGroup.REDSTONE && item instanceof BlockItem blockItem && blockItem.getBlock() instanceof FenceGateBlock ? Collections.singleton(ItemGroup.DECORATIONS) : null; 30 | /** 31 | * 将原本位于“红石”中的门转移至“装饰性方块”。 32 | */ 33 | public static final TransferRule DOORS_IN_DECORATIONS = item -> item.getGroup() == ItemGroup.REDSTONE && item instanceof BlockItem blockItem && blockItem.getBlock() instanceof DoorBlock ? Collections.singleton(ItemGroup.DECORATIONS) : null; 34 | /** 35 | * 将原本位于“工具”中的剑转移至“装饰性方块”。 36 | */ 37 | public static final TransferRule SWORDS_IN_TOOLS = item -> item instanceof SwordItem && item.getGroup() == ItemGroup.COMBAT ? Collections.singleton(ItemGroup.TOOLS) : null; 38 | /** 39 | * 自定义的变种转移规则。仅限方块物品。 40 | */ 41 | public static final TransferRule CUSTOM_VARIANT_TRANSFER_RULE = item -> { 42 | if (!(item instanceof BlockItem blockItem)) return null; 43 | final Block block = blockItem.getBlock(); 44 | BlockFamily.Variant variant = null; 45 | final BlockFamily.Variant[] variants = BlockFamily.Variant.values(); 46 | loop1: 47 | for (BlockFamily blockFamily : BlockFamiliesAccessor.getBaseBlocksToFamilies().values()) { 48 | for (BlockFamily.Variant variant1 : variants) { 49 | if (blockFamily.getVariant(variant1) == block) { 50 | variant = variant1; 51 | break loop1; 52 | } 53 | } 54 | } 55 | return variant == null ? null : Configs.CUSTOM_VARIANT_TRANSFER_RULE.get(variant); 56 | }; 57 | /** 58 | * 自定义的正则表达式转移规则。 59 | */ 60 | public static final TransferRule CUSTOM_REGEX_TRANSFER_RULE = item -> { 61 | LinkedHashSet results = new LinkedHashSet<>(); 62 | final Identifier identifier = Bridge.getItemId(item); 63 | Configs.CUSTOM_REGEX_TRANSFER_RULE.forEach((pattern, itemGroup) -> { 64 | if (pattern.matcher(identifier.toString()).matches()) { 65 | results.add(itemGroup); 66 | } 67 | }); 68 | return results.isEmpty() ? null : results; 69 | }; 70 | @ApiStatus.AvailableSince("2.0.1") 71 | public static final TransferRule CUSTOM_TAG_TRANSFER_RULE = item -> { 72 | LinkedHashSet results = new LinkedHashSet<>(); 73 | Configs.CUSTOM_TAG_TRANSFER_RULE.forEach((tag, itemGroup) -> { 74 | if (item.getRegistryEntry().isIn(tag)) { 75 | results.add(itemGroup); 76 | } 77 | }); 78 | return results.isEmpty() ? null : results; 79 | }; 80 | 81 | private TransferRules() { 82 | } 83 | 84 | public static void initialize() { 85 | TransferRule.addConditionalTransferRule(() -> Configs.instance.buttonsInDecorations, BUTTON_IN_DECORATIONS, "buttons in decorations"); 86 | TransferRule.addConditionalTransferRule(() -> Configs.instance.fenceGatesInDecorations, FENCE_GATE_IN_DECORATIONS, "fence gate in decorations"); 87 | TransferRule.addConditionalTransferRule(() -> Configs.instance.doorsInDecorations, DOORS_IN_DECORATIONS, "doors in decorations"); 88 | TransferRule.addConditionalTransferRule(() -> Configs.instance.swordsInTools, SWORDS_IN_TOOLS, "swords in tools"); 89 | TransferRule.addTransferRule(Configs.CUSTOM_TRANSFER_RULE::get, "custom transfer rule"); 90 | TransferRule.addConditionalTransferRule(() -> !Configs.CUSTOM_VARIANT_TRANSFER_RULE.isEmpty(), CUSTOM_VARIANT_TRANSFER_RULE, "custom variant transfer rule"); 91 | TransferRule.addConditionalTransferRule(() -> !Configs.CUSTOM_REGEX_TRANSFER_RULE.isEmpty(), CUSTOM_REGEX_TRANSFER_RULE, "custom regex transfer rule"); 92 | TransferRule.addConditionalTransferRule(() -> !Configs.CUSTOM_TAG_TRANSFER_RULE.isEmpty(), CUSTOM_TAG_TRANSFER_RULE, "custom tag transfer rule"); 93 | 94 | if (Configs.instance.debugMode) { 95 | TransferRule.LOGGER.info("Initializing Transfer Rules. It may happen before or after the registration of mod items, but should take place before loading Reasonable Sorting Configs."); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/reasonable-sorting/lang/zh_hk.json: -------------------------------------------------------------------------------- 1 | { 2 | "modmenu.descriptionTranslation.reasonable-sorting": "较创造模式物品欄下面嘅物品顺序,并修改地方物品所在嘅物品组,从而畀你创造模式物品欄唔再乱。你重可以自啲規則!", 3 | "category.reasonable-sorting.sorting": "排序", 4 | "category.reasonable-sorting.sorting.description": "可以畀创造模式物品欄中嘅物品更加好噉排序。例如,你可以畀樓梯、半砖排喺其基础方塊后面,而唔係好似原版噉一团乱麻。", 5 | "category.reasonable-sorting.transfer": "物品組轉移", 6 | "category.reasonable-sorting.transfer.description": "你可以畀物品從一個組轉移到另一個組。例如,可以將按鈕或者木閘門由「紅石」轉移到「裝飾方塊」。", 7 | "option.reasonable-sorting.buttons_in_decorations": "按鈕移至裝飾方塊", 8 | "option.reasonable-sorting.custom_regex_transfer_rules": "自訂正則轉移規則", 9 | "option.reasonable-sorting.custom_regex_transfer_rules.tooltip": "每一行係一個正則式和物品組名稱,例如,§e .+?bucket transportation§r。\n任何物品若id符合此規則運算式則轉移至該組。", 10 | "option.reasonable-sorting.custom_sorting_rules": "自訂排序規則", 11 | "option.reasonable-sorting.custom_sorting_rules.add": "添加自訂排序規則", 12 | "option.reasonable-sorting.custom_sorting_rules.example": "例如:§e grass_block dirt stone§r。", 13 | "option.reasonable-sorting.custom_sorting_rules.remove": "刪除自訂排序規則。", 14 | "option.reasonable-sorting.custom_sorting_rules.tooltip": "每一行都係一個排序規則,一行內用空格將多個物品嘅id分隔起。", 15 | "option.reasonable-sorting.custom_transfer_rules": "自訂轉移規則", 16 | "option.reasonable-sorting.custom_transfer_rules.add": "添加物品組轉移規則。", 17 | "option.reasonable-sorting.custom_transfer_rules.remove": "移除物品組轉移規則。", 18 | "option.reasonable-sorting.custom_transfer_rules.tooltip": "每一行係一個物品id同埋物品組名稱,例如,§e redstone_block building_blocks§r。", 19 | "option.reasonable-sorting.custom_variant_transfer_rules": "自訂變種轉移規則", 20 | "option.reasonable-sorting.custom_variant_transfer_rules.tooltip": "每一行係一個方塊變種名稱同埋物品組名稱,例如,§e wall building_blocks§r。", 21 | "option.reasonable-sorting.describe_item_groups": "有效嘅物品組名稱: %s。", 22 | "option.reasonable-sorting.describe_variants": "有效嘅變種名稱: %s。", 23 | "option.reasonable-sorting.doors_in_decorations": "門移至裝飾性方塊", 24 | "option.reasonable-sorting.enable_default_item_sorting_rules": "啟用預設物品排序規則。", 25 | "option.reasonable-sorting.enable_default_item_sorting_rules.tooltip": "例如,冰和冰磚、石春和青苔石春會排在一起。", 26 | "option.reasonable-sorting.enable_group_transfer": "啟用物品組轉移", 27 | "option.reasonable-sorting.enable_group_transfer.tooltip": "如果禁用,則所有物品按照原版進行分組,不受此模組影響,下面嘅這些設置也將失效。", 28 | "option.reasonable-sorting.enable_sorting": "啟用排序", 29 | "option.reasonable-sorting.enable_sorting.tooltip": "如果禁用,則所有物品嘅排序按照原版進行,不受此模組影響,下面嘅這些設置也將失效。", 30 | "option.reasonable-sorting.error.group_name_expected": "缺少物品組名稱。", 31 | "option.reasonable-sorting.error.invalid_group_name": "無效物品組名稱: %s。", 32 | "option.reasonable-sorting.error.invalid_identifier": "無效id: %s。", 33 | "option.reasonable-sorting.error.invalid_regex": "無效正則式: %s: %s。", 34 | "option.reasonable-sorting.error.invalid_variant_name": "無效變種名稱: %s。", 35 | "option.reasonable-sorting.error.unexpected_text": "未知文本: %s。", 36 | "option.reasonable-sorting.fence_gate_follows_fence": "木閘門緊隨欄杆", 37 | "option.reasonable-sorting.fence_gate_follows_fence.tooltip": "建議啟用「物品組轉移」中嘅「木閘門移至裝飾方塊」。", 38 | "option.reasonable-sorting.fence_gates_in_decorations": "木閘門移至裝飾方塊", 39 | "option.reasonable-sorting.block_items_only": "避免影響方塊註冊表", 40 | "option.reasonable-sorting.block_items_only.tooltip": "若開啟,只有物品註冊表,包括方塊物品(例如創造模式物品欄中嘅方塊)會受到上述選項嘅影響,而方塊註冊表(例如除錯模式中嘅方塊)則唔受影響。這在一定程度上可以避免伺服器與同用户端之間嘅方塊唔匹配。如果“排序影響範圍”設為“僅物品欄”,那麼這個選項會冇影響。", 41 | "option.reasonable-sorting.swords_in_tools": "劍移至工具", 42 | "option.reasonable-sorting.variants_following_base_blocks": "緊隨基礎方塊嘅方塊變種", 43 | "option.reasonable-sorting.variants_following_base_blocks.tooltip": "這些變種將會緊隨其基礎方塊後面。只支持原版方塊變種。\n請注意某些變種需要設置物品組轉移才能生效。", 44 | "text.reasonable-sorting.disabled": "§c禁用", 45 | "text.reasonable-sorting.enabled": "§a啟用", 46 | "title.reasonable-sorting.config": "合理排序模組配置", 47 | "option.reasonable-sorting.shapes_following_base_blocks": "緊隨基礎方塊嘅擴展方塊形狀", 48 | "option.reasonable-sorting.shapes_following_base_blocks.tooltip": "擴展方塊形狀模組中,這些形狀將會緊隨基礎方塊。§7如果依個選項唔生效,請檢查擴展方塊形狀模組配置嘅「添加至原版物品組」是否開啟。§r", 49 | "option.reasonable-sorting.describe_shapes": "有效嘅形狀名稱: %s。", 50 | "option.reasonable-sorting.error.invalid_shape_name": "無效嘅形狀名稱:%s。", 51 | "option.reasonable-sorting.custom_shape_transfer_rules": "自訂形狀轉移規則", 52 | "option.reasonable-sorting.custom_shape_transfer_rules.tooltip": "將擴展方塊形狀模組中特定形狀嘅方塊轉移至其他組。", 53 | "option.reasonable-sorting.base_blocks_in_building_blocks": "基礎方塊移至建築方塊", 54 | "option.reasonable-sorting.base_blocks_in_building_blocks.tooltip": "將擴展方塊形狀模組中嘅部分基礎方塊,比如蜂巢蜜、菌光體,移至建築方塊。", 55 | "option.reasonable-sorting.sorting_influence_range": "排序影響範圍", 56 | "option.reasonable-sorting.sorting_influence_range.tooltip": "決定了本模組在哪些情況下會影響內容排序。如果不明白意思,直接使用默認值即可。", 57 | "option.reasonable-sorting.sorting_calculation_type": "排序計算類型", 58 | "option.reasonable-sorting.sorting_calculation_type.tooltip": "決定了本模組在哪些情況下計算排序。如果不明白意思,直接使用默認值即可。", 59 | "option.reasonable-sorting.debug_mode": "調試模式", 60 | "option.reasonable-sorting.debug_mode.tooltip": "如果啟用,那麼模組會在日誌中輸出更多資訊。如果你需要研究模組原理或者遇到任何問題,可以啟用此選項。", 61 | "option.reasonable-sorting.fancy_color_sorting": "美觀顏色排序", 62 | "option.reasonable-sorting.fancy_color_sorting.tooltip": "以漸變順序排序帶有顏色的方塊和物品(如羊毛、釉陶、牀、橫額),就像 Minecraft 1.19.3 中的那樣。", 63 | "option.reasonable-sorting.custom_tag_transfer_rules": "自訂標籤轉移規則", 64 | "option.reasonable-sorting.custom_tag_transfer_rules.tooltip": "每一行是一個標籤名稱和物品組名稱,如 \u00a7eflowers building_blocks\u00a7r。", 65 | 66 | "sortingInfluenceRange.registry": "註冊表", 67 | "sortingInfluenceRange.registry.description": "Minecraft 的註冊表的所有遍歷過程都會受到影響。這將會影響更多情況,例如一些可以列舉所有方塊或者物品的模組。但是,這有可能造成不穩定性,例如加入服務器時可能出現註冊表不匹配的情況。", 68 | "sortingInfluenceRange.inventory_only": "僅物品欄", 69 | "sortingInfluenceRange.inventory_only.description": "僅影響創造模式物品欄。其他情況不會受到此模組的影響。", 70 | 71 | "sortingCalculationType.standard": "標準", 72 | "sortingCalculationType.standard.description": "內容(例如物品)的排序會在遊戲開始時以及配置加載時計算一次,當配置被更改時會重新計算。這是推薦的方式,因為可以減少每次迭代中不必要的計算。(如果您使用 Forge,且「排序影響範圍」設為「註冊表」,那麼這個選項不會生效。)", 73 | "sortingCalculationType.semi_real_time": "半實時", 74 | "sortingCalculationType.semi_real_time.description": "內容組合規則會在開始時、配置加載時、配置更改時計算。但是,排序結果仍會在每次遍歷中計算。這可能會造成每次打開創造模式物品欄時出現卡頓。", 75 | "sortingCalculationType.real_time": "實時", 76 | "sortingCalculationType.real_time.description": "組合規則以及排序結果都會在每次註冊表遍歷時計算。這可能會造成每次打開物品欄時出現卡頓。" 77 | } 78 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/reasonable-sorting/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "modmenu.descriptionTranslation.reasonable-sorting": "調整創造模式物品欄下面的物品順序,並修改部分物品所在的物品組,從而讓你的創造模式物品欄不再亂。你還可以自訂一些規則!", 3 | "category.reasonable-sorting.sorting": "排序", 4 | "category.reasonable-sorting.sorting.description": "可以讓創造模式物品欄中的物品更好地排序。比如,你可以讓階梯、半磚排在其基礎方塊後面,而不是像原版那樣一團亂麻。", 5 | "category.reasonable-sorting.transfer": "物品組轉移", 6 | "category.reasonable-sorting.transfer.description": "你可以將物品從一個組轉移到另一個組。例如,可以將按鈕或者柵欄門由「紅石」轉移到「裝飾方塊」。", 7 | "option.reasonable-sorting.buttons_in_decorations": "按鈕移至裝飾方塊", 8 | "option.reasonable-sorting.custom_regex_transfer_rules": "自訂規則運算式轉移規則", 9 | "option.reasonable-sorting.custom_regex_transfer_rules.tooltip": "每一行是一個規則運算式和物品組名稱,例如,§e .+?bucket transportation§r。\n任何物品若id符合此規則運算式則轉移至該組。", 10 | "option.reasonable-sorting.custom_sorting_rules": "自訂排序規則", 11 | "option.reasonable-sorting.custom_sorting_rules.add": "添加自訂排序規則", 12 | "option.reasonable-sorting.custom_sorting_rules.example": "例如:§e grass_block dirt stone§r。", 13 | "option.reasonable-sorting.custom_sorting_rules.remove": "刪除自訂排序規則。", 14 | "option.reasonable-sorting.custom_sorting_rules.tooltip": "每一行都是一個排序規則,一行內用空格將多個物品的id分隔起來。", 15 | "option.reasonable-sorting.custom_transfer_rules": "自訂轉移規則", 16 | "option.reasonable-sorting.custom_transfer_rules.add": "添加物品組轉移規則。", 17 | "option.reasonable-sorting.custom_transfer_rules.remove": "移除物品組轉移規則。", 18 | "option.reasonable-sorting.custom_transfer_rules.tooltip": "每一行是一個物品id和物品組名稱,例如,§e redstone_block building_blocks§r。", 19 | "option.reasonable-sorting.custom_variant_transfer_rules": "自訂變種轉移規則", 20 | "option.reasonable-sorting.custom_variant_transfer_rules.tooltip": "每一行是一個方塊變種名稱和物品組名稱,例如,§e wall building_blocks§r。", 21 | "option.reasonable-sorting.describe_item_groups": "有效的物品組名稱: %s。", 22 | "option.reasonable-sorting.describe_variants": "有效的變種名稱: %s。", 23 | "option.reasonable-sorting.doors_in_decorations": "門移至裝飾性方塊", 24 | "option.reasonable-sorting.enable_default_item_sorting_rules": "啟用預設物品排序規則。", 25 | "option.reasonable-sorting.enable_default_item_sorting_rules.tooltip": "例如,冰和冰磚、鵝卵石和青苔鵝卵石會排在一起。", 26 | "option.reasonable-sorting.enable_group_transfer": "啟用物品組轉移", 27 | "option.reasonable-sorting.enable_group_transfer.tooltip": "如果禁用,則所有物品按照原版進行分組,不受此模組影響,下面的這些設置也將失效。", 28 | "option.reasonable-sorting.enable_sorting": "啟用排序", 29 | "option.reasonable-sorting.enable_sorting.tooltip": "如果禁用,則所有物品的排序按照原版進行,不受此模組影響,下面的這些設置也將失效。", 30 | "option.reasonable-sorting.error.group_name_expected": "缺少物品組名稱。", 31 | "option.reasonable-sorting.error.invalid_group_name": "無效的物品組名稱: %s。", 32 | "option.reasonable-sorting.error.invalid_identifier": "無效的id: %s。", 33 | "option.reasonable-sorting.error.invalid_regex": "無效的規則運算式: %s: %s。", 34 | "option.reasonable-sorting.error.invalid_variant_name": "無效的變種名稱: %s。", 35 | "option.reasonable-sorting.error.unexpected_text": "未知的文本: %s。", 36 | "option.reasonable-sorting.fence_gate_follows_fence": "柵欄門緊隨柵欄", 37 | "option.reasonable-sorting.fence_gate_follows_fence.tooltip": "建議啟用「物品組轉移」中的「柵欄門移至裝飾方塊」。", 38 | "option.reasonable-sorting.fence_gates_in_decorations": "柵欄門移至裝飾方塊", 39 | "option.reasonable-sorting.block_items_only": "避免影響方塊註冊表", 40 | "option.reasonable-sorting.block_items_only.tooltip": "若開啟,只有物品註冊表,包括方塊物品(例如創造模式物品欄中的方塊)會受到上述選項的影響,而方塊註冊表(例如除錯模式中的方塊)則不受影響。這在一定程度上可以避免伺服器與用戶端之間的方塊不匹配。如果“排序影響範圍”設為“僅物品欄”,那麼這個選項沒有影響。", 41 | "option.reasonable-sorting.swords_in_tools": "劍移至工具", 42 | "option.reasonable-sorting.variants_following_base_blocks": "緊隨基礎方塊的方塊變種", 43 | "option.reasonable-sorting.variants_following_base_blocks.tooltip": "這些變種將會緊隨其基礎方塊後面。只支持原版方塊變種。\n請注意某些變種需要設置物品組轉移才能生效。", 44 | "text.reasonable-sorting.disabled": "§c禁用", 45 | "text.reasonable-sorting.enabled": "§a啟用", 46 | "title.reasonable-sorting.config": "合理排序模組配置", 47 | "option.reasonable-sorting.shapes_following_base_blocks": "緊隨基礎方塊的擴展方塊形狀", 48 | "option.reasonable-sorting.shapes_following_base_blocks.tooltip": "擴展方塊形狀模組中,這些形狀將會緊隨基礎方塊。§7如果此選項不生效,請檢查擴展方塊形狀模組配置的「添加至原版物品組」是否開啟。§r", 49 | "option.reasonable-sorting.describe_shapes": "有效的形狀名稱: %s。", 50 | "option.reasonable-sorting.error.invalid_shape_name": "無效的形狀名稱:%s。", 51 | "option.reasonable-sorting.custom_shape_transfer_rules": "自訂形狀轉移規則", 52 | "option.reasonable-sorting.custom_shape_transfer_rules.tooltip": "將擴展方塊形狀模組中特定形狀的方塊轉移至其他組。", 53 | "option.reasonable-sorting.base_blocks_in_building_blocks": "基礎方塊移至建築方塊", 54 | "option.reasonable-sorting.base_blocks_in_building_blocks.tooltip": "將擴展方塊形狀模組中的部分基礎方塊,比如蜂巢塊、蕈光體,移至建築方塊。", 55 | "option.reasonable-sorting.sorting_influence_range": "排序影響範圍", 56 | "option.reasonable-sorting.sorting_influence_range.tooltip": "決定了本模組在哪些情況下會影響內容排序。如果不明白意思,直接使用默認值即可。", 57 | "option.reasonable-sorting.sorting_calculation_type": "排序計算類型", 58 | "option.reasonable-sorting.sorting_calculation_type.tooltip": "決定了本模組在哪些情況下計算排序。如果不明白意思,直接使用默認值即可。", 59 | "option.reasonable-sorting.debug_mode": "除錯模式", 60 | "option.reasonable-sorting.debug_mode.tooltip": "如果啟用,那麼模組會在日誌中輸出更多資訊。如果你需要研究模組原理或者遇到任何問題,可以啟用此選項。", 61 | "option.reasonable-sorting.fancy_color_sorting": "美觀顏色排序", 62 | "option.reasonable-sorting.fancy_color_sorting.tooltip": "以漸變順序排序帶有顏色的方塊和物品(如羊毛、釉陶、床、旗幟),就像 Minecraft 1.19.3 中的那樣。", 63 | "option.reasonable-sorting.custom_tag_transfer_rules": "自訂標籤轉移規則", 64 | "option.reasonable-sorting.custom_tag_transfer_rules.tooltip": "每一行是一個標籤名稱和物品組名稱,如 \u00a7eflowers building_blocks\u00a7r。", 65 | 66 | "sortingInfluenceRange.registry": "註冊表", 67 | "sortingInfluenceRange.registry.description": "Minecraft 的註冊表的所有遍歷過程都會受到影響。這將會影響更多情況,例如一些可以列舉所有方塊或者物品的模組。但是,這有可能造成不穩定性,例如加入服務器時可能出現註冊表不匹配的情況。", 68 | "sortingInfluenceRange.inventory_only": "僅物品欄", 69 | "sortingInfluenceRange.inventory_only.description": "僅影響創造模式物品欄。其他情況不會受到此模組的影響。", 70 | 71 | "sortingCalculationType.standard": "標準", 72 | "sortingCalculationType.standard.description": "內容(例如物品)的排序會在遊戲開始時以及配置加載時計算一次,當配置被更改時會重新計算。這是推薦的方式,因為可以減少每次迭代中不必要的計算。(如果您使用 Forge,且「排序影響範圍」設為「註冊表」,那麼這個選項不會生效。)", 73 | "sortingCalculationType.semi_real_time": "半實時", 74 | "sortingCalculationType.semi_real_time.description": "內容組合規則會在開始時、配置加載時、配置更改時計算。但是,排序結果仍會在每次遍歷中計算。這可能會造成每次打開創造模式物品欄時出現卡頓。", 75 | "sortingCalculationType.real_time": "實時", 76 | "sortingCalculationType.real_time.description": "組合規則以及排序結果都會在每次註冊表遍歷時計算。這可能會造成每次打開物品欄時出現卡頓。" 77 | } 78 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/reasonable-sorting/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "modmenu.descriptionTranslation.reasonable-sorting": "调整创造模式物品栏下面的物品顺序,并修改部分物品所在的物品组,从而让你的创造模式物品栏不再乱。你还可以自定义一些规则!", 3 | "category.reasonable-sorting.sorting": "排序", 4 | "category.reasonable-sorting.sorting.description": "可以让创造模式物品栏中的物品更好地排序。比如,你可以让楼梯、台阶排在其基础方块后面,而不是像原版那样一团乱麻。", 5 | "category.reasonable-sorting.transfer": "物品组转移", 6 | "category.reasonable-sorting.transfer.description": "你可以将物品从一个组转移到另一个组。例如,可以将按钮或者栅栏门由“红石”转移到“装饰性方块”。", 7 | "option.reasonable-sorting.buttons_in_decorations": "按钮移至装饰性方块", 8 | "option.reasonable-sorting.custom_regex_transfer_rules": "自定义正则表达式转移规则", 9 | "option.reasonable-sorting.custom_regex_transfer_rules.tooltip": "每一行是一个正则表达式和物品组名称,例如,§e .+?bucket transportation§r。\n任何物品若id符合此正则表达式则转移至该组。", 10 | "option.reasonable-sorting.custom_sorting_rules": "自定义排序规则", 11 | "option.reasonable-sorting.custom_sorting_rules.add": "添加自定义排序规则", 12 | "option.reasonable-sorting.custom_sorting_rules.example": "例如:§e grass_block dirt stone§r。", 13 | "option.reasonable-sorting.custom_sorting_rules.remove": "删除自定义排序规则。", 14 | "option.reasonable-sorting.custom_sorting_rules.tooltip": "每一行都是一个排序规则,一行内用空格将多个物品的id分隔起来。", 15 | "option.reasonable-sorting.custom_transfer_rules": "自定义转移规则", 16 | "option.reasonable-sorting.custom_transfer_rules.add": "添加物品组转移规则。", 17 | "option.reasonable-sorting.custom_transfer_rules.remove": "移除物品组转移规则。", 18 | "option.reasonable-sorting.custom_transfer_rules.tooltip": "每一行是一个物品id和物品组名称,例如,§e redstone_block building_blocks§r。", 19 | "option.reasonable-sorting.custom_variant_transfer_rules": "自定义变种转移规则", 20 | "option.reasonable-sorting.custom_variant_transfer_rules.tooltip": "每一行是一个方块变种名称和物品组名称,例如,§e wall building_blocks§r。", 21 | "option.reasonable-sorting.describe_item_groups": "有效的物品组名称: %s。", 22 | "option.reasonable-sorting.describe_variants": "有效的变种名称: %s。", 23 | "option.reasonable-sorting.doors_in_decorations": "门移至装饰性方块", 24 | "option.reasonable-sorting.enable_default_item_sorting_rules": "启用默认物品排序规则。", 25 | "option.reasonable-sorting.enable_default_item_sorting_rules.tooltip": "例如,冰和浮冰、圆石和苔石会排在一起。", 26 | "option.reasonable-sorting.enable_group_transfer": "启用物品组转移", 27 | "option.reasonable-sorting.enable_group_transfer.tooltip": "如果禁用,则所有物品按照原版进行分组,不受此模组影响,下面的这些设置也将失效。", 28 | "option.reasonable-sorting.enable_sorting": "启用排序", 29 | "option.reasonable-sorting.enable_sorting.tooltip": "如果禁用,则所有物品的排序按照原版进行,不受此模组影响,下面的这些设置也将失效。", 30 | "option.reasonable-sorting.error.group_name_expected": "缺少物品组名称。", 31 | "option.reasonable-sorting.error.invalid_group_name": "无效的物品组名称: %s。", 32 | "option.reasonable-sorting.error.invalid_identifier": "无效的id: %s。", 33 | "option.reasonable-sorting.error.invalid_regex": "无效的正则表达式: %s: %s。", 34 | "option.reasonable-sorting.error.invalid_variant_name": "无效的变种名称: %s。", 35 | "option.reasonable-sorting.error.unexpected_text": "未知的文本: %s。", 36 | "option.reasonable-sorting.fence_gate_follows_fence": "栅栏门紧随栅栏", 37 | "option.reasonable-sorting.fence_gate_follows_fence.tooltip": "建议启用“物品组转移”中的“栅栏门移至装饰性方块”。", 38 | "option.reasonable-sorting.fence_gates_in_decorations": "栅栏门移至装饰性方块", 39 | "option.reasonable-sorting.block_items_only": "避免影响方块注册表", 40 | "option.reasonable-sorting.block_items_only.tooltip": "若开启,只有物品注册表,包括方块物品(例如创造模式物品栏中的方块)会受到上述选项的影响,而方块注册表(例如调试模式中的方块)则不受影响。这在一定程度上可以避免服务器与客户端之间的方块不匹配。如果“排序影响范围”设为“仅物品栏”,那么这个选项没有影响。", 41 | "option.reasonable-sorting.swords_in_tools": "剑移至工具", 42 | "option.reasonable-sorting.variants_following_base_blocks": "紧随基础方块的方块变种", 43 | "option.reasonable-sorting.variants_following_base_blocks.tooltip": "这些变种将会紧随其基础方块后面。只支持原版方块变种。\n请注意某些变种需要设置物品组转移才能生效。", 44 | "text.reasonable-sorting.disabled": "§c禁用", 45 | "text.reasonable-sorting.enabled": "§a启用", 46 | "title.reasonable-sorting.config": "合理排序模组配置", 47 | "option.reasonable-sorting.shapes_following_base_blocks": "紧随基础方块的扩展方块形状", 48 | "option.reasonable-sorting.shapes_following_base_blocks.tooltip": "扩展方块形状模组中,这些形状将会紧随基础方块。\u00a77如果此选项不生效,请检查扩展方块形状模组配置的“添加至原版物品组”是否开启。\u00a7r", 49 | "option.reasonable-sorting.describe_shapes": "有效的形状名称: %s。", 50 | "option.reasonable-sorting.error.invalid_shape_name": "无效的形状名称:%s。", 51 | "option.reasonable-sorting.custom_shape_transfer_rules": "自定义形状转移规则", 52 | "option.reasonable-sorting.custom_shape_transfer_rules.tooltip": "将扩展方块形状模组中特定形状的方块转移至其他组。", 53 | "option.reasonable-sorting.base_blocks_in_building_blocks": "基础方块移至建筑方块", 54 | "option.reasonable-sorting.base_blocks_in_building_blocks.tooltip": "将扩展方块形状模组中的部分基础方块,比如蜜脾、菌光体,移至建筑方块。", 55 | "option.reasonable-sorting.sorting_influence_range": "排序影响范围", 56 | "option.reasonable-sorting.sorting_influence_range.tooltip": "决定了本模组在哪些情况下会影响内容排序。如果不明白意思,直接使用默认值即可。", 57 | "option.reasonable-sorting.sorting_calculation_type": "排序计算类型", 58 | "option.reasonable-sorting.sorting_calculation_type.tooltip": "决定了本模组在哪些情况下计算排序。如果不明白意思,直接使用默认值即可。", 59 | "option.reasonable-sorting.debug_mode": "调试模式", 60 | "option.reasonable-sorting.debug_mode.tooltip": "如果启用,那么模组会在日志中输出更多信息。如果你需要研究模组原理或者遇到任何问题,可以启用此选项。", 61 | "option.reasonable-sorting.fancy_color_sorting": "美观颜色排序", 62 | "option.reasonable-sorting.fancy_color_sorting.tooltip": "以渐变顺序排序带有颜色的方块和物品(如羊毛、带釉陶瓦、床、旗帜),就像Minecraft 1.19.3中的那样。", 63 | "option.reasonable-sorting.custom_tag_transfer_rules": "自定义标签转移规则", 64 | "option.reasonable-sorting.custom_tag_transfer_rules.tooltip": "每一行是一个标签名称和物品组名称,如 \u00a7eflowers building_blocks\u00a7r。", 65 | 66 | "sortingInfluenceRange.registry": "注册表", 67 | "sortingInfluenceRange.registry.description": "Minecraft注册表的所有遍历过程都会受到影响。这将会影响更多情况,例如一些可以列举所有方块或者物品的模组。但是,这有可能造成不稳定性,例如加入服务器时可能出现注册表不匹配的情况。", 68 | "sortingInfluenceRange.inventory_only": "仅物品栏", 69 | "sortingInfluenceRange.inventory_only.description": "仅影响创造模式物品栏。其他情况不会受到此模组的影响。", 70 | 71 | "sortingCalculationType.standard": "标准", 72 | "sortingCalculationType.standard.description": "内容(例如物品)的排序会在游戏开始时以及配置加载时计算一次,当配置被更改时会重新计算。这是推荐的方式,因为可以减少每次迭代中的不必要的计算。(如果您使用Forge,且“排序影响范围”设为“注册表”,那么这个选项将不会生效。)", 73 | "sortingCalculationType.semi_real_time": "半实时", 74 | "sortingCalculationType.semi_real_time.description": "内容组合规则会在游戏开始时、配置加载时、配置更改时计算。但是,排序结果仍会在每次迭代中计算。这可能会造成每次打开创造模式物品栏时出现略微卡顿。", 75 | "sortingCalculationType.real_time": "实时", 76 | "sortingCalculationType.real_time.description": "组合规则以及排序结果都会在每次注册表迭代时计算。这可能会造成每次打开物品栏时出现卡顿。" 77 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/reasonable-sorting/lang/ru_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "modmenu.descriptionTranslation.reasonable-sorting": "Измените сортировку предметов в творческом инвентаре и настройе их категории, чтобы предметы больше не были в беспорядке! Вы также можете персонализировать некоторые правила.", 3 | "title.reasonable-sorting.config": "Настройки Reasonable Sorting", 4 | "category.reasonable-sorting.sorting": "Сортировка", 5 | "category.reasonable-sorting.sorting.description": "Вы можете улучшить сортировку предметов в творческом инвентаре. Например, разместить лестницы и плиты к рядм с блоками их материалов, а не в хаотичном порядке.", 6 | "category.reasonable-sorting.transfer": "Изменение категорий", 7 | "category.reasonable-sorting.transfer.description": "Вы можете перенести предмет из одной категории предметов в другую. Например, перенести кнопки или калитки из \"Редстоуна\" в \"Декорации\".", 8 | "text.reasonable-sorting.enabled": "\u00a7aВключено", 9 | "text.reasonable-sorting.disabled": "\u00a7cВыключено", 10 | "option.reasonable-sorting.enable_sorting": "Включить сортировку", 11 | "option.reasonable-sorting.enable_sorting.tooltip": "Если отключено, любая сортировка предметов работает как в ванили, без влияния этого мода, и все следующие настройки не будут работать.", 12 | "option.reasonable-sorting.enable_default_item_sorting_rules": "Включить базовые правила сортировки", 13 | "option.reasonable-sorting.enable_default_item_sorting_rules.tooltip": "Например, лёд и плотный лёд или булыжник и замшелый булыжник будут помещены вместе.", 14 | "option.reasonable-sorting.custom_sorting_rules": "Пользовательские правила сортировки", 15 | "option.reasonable-sorting.custom_sorting_rules.add": "Добавить пользовательское правило.", 16 | "option.reasonable-sorting.custom_sorting_rules.remove": "Удалить пользовательское правило.", 17 | "option.reasonable-sorting.custom_sorting_rules.tooltip": "Каждая строка – это правило сортировки с id нескольких предметов, разделённых пробелом.", 18 | "option.reasonable-sorting.custom_sorting_rules.example": "Например:\u00a7e grass_block dirt stone\u00a7r.", 19 | "option.reasonable-sorting.variants_following_base_blocks": "Вариации, помещаемые рядом с основными", 20 | "option.reasonable-sorting.variants_following_base_blocks.tooltip": "Названия групп вариаций блоков, помещаемых рядом с блокоами их материала. Поддерживаются только ванильные группы. \nНекоторые вариации также могут требовать переноса из другой категории.", 21 | "option.reasonable-sorting.fence_gate_follows_fence": "Перенести калитки к забору", 22 | "option.reasonable-sorting.fence_gate_follows_fence.tooltip": "Рекомендуется включить \"Калитки в \"Декорациях\" \" в разделе \"Изменение категорий\".", 23 | "option.reasonable-sorting.enable_group_transfer": "Включить перенос категорий", 24 | "option.reasonable-sorting.enable_group_transfer.tooltip": "Если отключено, предметы группируются как в ванили, без влияния этого мода, и все следующие настройки не будут работать.", 25 | "option.reasonable-sorting.buttons_in_decorations": "Кнопки в \"Декорациях\"", 26 | "option.reasonable-sorting.fence_gates_in_decorations": "Калитки в \"Декорациях\"", 27 | "option.reasonable-sorting.swords_in_tools": "Мечи в \"Инструментах\"", 28 | "option.reasonable-sorting.doors_in_decorations": "Двери в \"Декорациях\"", 29 | "option.reasonable-sorting.describe_item_groups": "Допустимые названия категорий: %s.", 30 | "option.reasonable-sorting.describe_variants": "Допустимые группы: %s.", 31 | "option.reasonable-sorting.custom_transfer_rules": "Пользовательские правила переноса", 32 | "option.reasonable-sorting.custom_transfer_rules.tooltip": "Каждая линия – это id предмета и название категории. Например,\u00a7e redstone_block building_blocks\u00a7r.", 33 | "option.reasonable-sorting.custom_transfer_rules.add": "Добавить правило переноса категории.", 34 | "option.reasonable-sorting.custom_transfer_rules.remove": "Удалить правило переноса категории.", 35 | "option.reasonable-sorting.custom_variant_transfer_rules": "Пользовательские правила переноса вариаций", 36 | "option.reasonable-sorting.custom_variant_transfer_rules.tooltip": "Каждая линия – это название вариации блоков и название категории. Например,\u00a7e wall building_blocks\u00a7r.", 37 | "option.reasonable-sorting.custom_regex_transfer_rules": "Пользовательские правила переноса по выражениям", 38 | "option.reasonable-sorting.custom_regex_transfer_rules.tooltip": "Каждая линия – это регулярное выражение и название категории. Например,\u00a7e .+?bucket transportation\u00a7r. \nВсе предметы, совпадающие с регулярным выражением, будут перенесены в указанную категорию.", 39 | "option.reasonable-sorting.custom_shape_transfer_rules": "Пользовательские правила переноса форм", 40 | "option.reasonable-sorting.custom_shape_transfer_rules.tooltip": "Перенесите указанные формы из мода Extended Block Shapes в другие категории.", 41 | "option.reasonable-sorting.base_blocks_in_building_blocks": "Блоки-источники материала в категории строительных блоков", 42 | "option.reasonable-sorting.base_blocks_in_building_blocks.tooltip": "Блоки-источники в моде Extended Block Shapes, такие как соты и грибосвет будут перемещены к строительным блокам.", 43 | 44 | "option.reasonable-sorting.error.group_name_expected": "Требуется название категории.", 45 | "option.reasonable-sorting.error.unexpected_text": "Неожиданный текст: %s.", 46 | "option.reasonable-sorting.error.invalid_identifier": "Некорректный id: %s.", 47 | "option.reasonable-sorting.error.invalid_group_name": "Некорректное название категории: %s.", 48 | "option.reasonable-sorting.error.invalid_variant_name": "Некорректное название группы: %s.", 49 | "option.reasonable-sorting.error.invalid_regex": "Некорректное выражение: %s: %s.", 50 | "option.reasonable-sorting.shapes_following_base_blocks": "Вариации ExtShape-блоков, помещаемые рядом с основными", 51 | "option.reasonable-sorting.shapes_following_base_blocks.tooltip": "Перенести вариации блоков из мода ExtShape к блокам их материалов. §7If this option has no effect, please check the \"Add to vanilla groups\" option in the configuration of Extended Block Shapes mod.§r", 52 | "option.reasonable-sorting.describe_shapes": "Допустимые названия: %s。", 53 | "option.reasonable-sorting.error.invalid_shape_name": "Некорректное название: %s.", 54 | "option.reasonable-sorting.block_items_only": "Avoid affecting block registries", 55 | "option.reasonable-sorting.block_items_only.tooltip": "When on, only the item registry, including block items (such as blocks in the Creative inventory) are affected by options above, and block registries (such as blocks in the Debug Mode) are not affected. This can to some extend avoid block mismatch if you are joining a server. This option has no effect if you set 'Sorting Influence Range' to 'Inventory only'." 56 | } 57 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/SortingRules.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.base.Predicates; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.collect.ImmutableMultimap; 6 | import com.google.common.collect.Maps; 7 | import net.minecraft.block.Block; 8 | import net.minecraft.block.Blocks; 9 | import net.minecraft.block.FenceBlock; 10 | import net.minecraft.data.family.BlockFamily; 11 | import net.minecraft.item.Item; 12 | import net.minecraft.item.Items; 13 | import net.minecraft.util.DyeColor; 14 | import net.minecraft.util.registry.Registry; 15 | import org.jetbrains.annotations.Unmodifiable; 16 | 17 | import java.util.Collections; 18 | import java.util.Map; 19 | 20 | /** 21 | * 本模组自带的一些排序规则。 22 | */ 23 | public final class SortingRules { 24 | /** 25 | * 此模组默认内置的方块排序规则。 26 | */ 27 | public static final @Unmodifiable ImmutableMultimap DEFAULT_BLOCK_SORTING_RULE = new ImmutableMultimap.Builder() 28 | .put(Blocks.COBBLESTONE, Blocks.MOSSY_COBBLESTONE) 29 | .putAll(Blocks.SAND, Blocks.SANDSTONE, Blocks.CHISELED_SANDSTONE, Blocks.CUT_SANDSTONE, Blocks.SMOOTH_SANDSTONE, Blocks.RED_SAND) 30 | .putAll(Blocks.RED_SAND, Blocks.RED_SANDSTONE, Blocks.CHISELED_RED_SANDSTONE, Blocks.CUT_RED_SANDSTONE, Blocks.SMOOTH_RED_SANDSTONE) 31 | .putAll(Blocks.ICE, Blocks.PACKED_ICE, Blocks.BLUE_ICE) 32 | .putAll(Blocks.NETHER_BRICKS, Blocks.CRACKED_NETHER_BRICKS, Blocks.RED_NETHER_BRICKS) 33 | .putAll(Blocks.QUARTZ_BLOCK, Blocks.SMOOTH_QUARTZ, Blocks.CHISELED_QUARTZ_BLOCK, Blocks.QUARTZ_BRICKS, Blocks.QUARTZ_PILLAR) 34 | .put(Blocks.OAK_SLAB, Blocks.PETRIFIED_OAK_SLAB) 35 | .put(Blocks.SMOOTH_STONE, Blocks.SMOOTH_STONE_SLAB) 36 | .put(Blocks.NETHERITE_BLOCK, Blocks.COPPER_BLOCK) 37 | .build(); 38 | /** 39 | * 此模组默认内置的物品排序规则。 40 | */ 41 | public static final @Unmodifiable ImmutableMultimap DEFAULT_ITEM_SORTING_RULE 42 | = new ImmutableMultimap.Builder() 43 | .putAll((Iterable>) DEFAULT_BLOCK_SORTING_RULE.entries().stream() 44 | .map(entry -> Maps.immutableEntry(entry.getKey().asItem(), entry.getValue().asItem())) 45 | .filter(entry -> entry.getKey() != Items.AIR && entry.getValue() != Items.AIR) 46 | ::iterator) 47 | .put(Items.TORCH, Items.SOUL_TORCH) 48 | .put(Items.BOOK, Items.WRITABLE_BOOK) 49 | .put(Items.PAPER, Items.MAP) 50 | .put(Items.GOLD_NUGGET, Items.IRON_NUGGET) 51 | .put(Items.BRICK, Items.NETHER_BRICK) 52 | .putAll(Items.WHEAT_SEEDS, Items.PUMPKIN_SEEDS, Items.MELON_SEEDS, Items.BEETROOT_SEEDS) 53 | .putAll(Items.SNOWBALL, Items.CLAY_BALL, Items.ENDER_PEARL, Items.ENDER_EYE) 54 | .put(Items.BOW, Items.CROSSBOW) 55 | .putAll(Items.ARROW, Items.TRIDENT, Items.SHIELD, Items.TOTEM_OF_UNDYING) 56 | .putAll(Items.GHAST_TEAR, Items.FERMENTED_SPIDER_EYE, Items.BLAZE_POWDER, Items.MAGMA_CREAM, Items.BREWING_STAND, Items.CAULDRON, Items.GLISTERING_MELON_SLICE, Items.GOLDEN_CARROT, Items.RABBIT_FOOT, Items.PHANTOM_MEMBRANE, Items.GLASS_BOTTLE, Items.DRAGON_BREATH) 57 | .putAll(Items.FLINT, Items.SNOWBALL, Items.LEATHER) 58 | .build(); 59 | /** 60 | * 在 {@link Configs#VARIANTS_FOLLOWING_BASE_BLOCKS} 中的各个变种的方块,应该跟随其基础方块。 61 | */ 62 | public static final BaseToVariantRule VARIANT_FOLLOWS_BASE = new BaseToVariantRule(Predicates.alwaysTrue(), Configs.VARIANTS_FOLLOWING_BASE_BLOCKS); 63 | /** 64 | * {@link #VARIANT_FOLLOWS_BASE} 对应的物品规则,影响方块物品。 65 | */ 66 | public static final SortingRule VARIANT_FOLLOWS_BASE_ITEM = new BlockItemRule(VARIANT_FOLLOWS_BASE); 67 | /** 68 | * 让所有的栅栏门紧随在栅栏的后面。 69 | */ 70 | public static final VariantToVariantRule FENCE_GATE_FOLLOWS_FENCE = new VariantToVariantRule(block -> block instanceof FenceBlock, BlockFamily.Variant.FENCE, Collections.singleton(BlockFamily.Variant.FENCE_GATE)); 71 | /** 72 | * {@link #FENCE_GATE_FOLLOWS_FENCE} 对应的物品规则,影响方块物品。 73 | */ 74 | public static final SortingRule FENCE_GATE_FOLLOWS_FENCE_ITEM = new BlockItemRule(FENCE_GATE_FOLLOWS_FENCE); 75 | public static final ColorSortingRule COLOR_SORTING_RULE = new ColorSortingRule<>(DyeColor.WHITE, ImmutableList.of( 76 | DyeColor.LIGHT_GRAY, 77 | DyeColor.GRAY, 78 | DyeColor.BLACK, 79 | DyeColor.BROWN, 80 | DyeColor.RED, 81 | DyeColor.ORANGE, 82 | DyeColor.YELLOW, 83 | DyeColor.LIME, 84 | DyeColor.GREEN, 85 | DyeColor.CYAN, 86 | DyeColor.LIGHT_BLUE, 87 | DyeColor.BLUE, 88 | DyeColor.PURPLE, 89 | DyeColor.MAGENTA, 90 | DyeColor.PINK 91 | )); 92 | public static final ColorSortingRule COLOR_SORTING_RULE_ITEM = new ColorSortingRule<>(COLOR_SORTING_RULE.baseColor(), COLOR_SORTING_RULE.followingColors()); 93 | 94 | private SortingRules() { 95 | } 96 | 97 | public static void initialize() { 98 | SortingRule.addConditionalSortingRule(Registry.BLOCK_KEY, () -> !Configs.VARIANTS_FOLLOWING_BASE_BLOCKS.isEmpty() && !Configs.instance.blockItemsOnly, VARIANT_FOLLOWS_BASE, 8, "variant follows base"); 99 | SortingRule.addConditionalSortingRule(Registry.ITEM_KEY, () -> !Configs.VARIANTS_FOLLOWING_BASE_BLOCKS.isEmpty(), VARIANT_FOLLOWS_BASE_ITEM, 8, "variant follows base"); 100 | SortingRule.addSortingRule(Registry.BLOCK_KEY, new MultimapSortingRule<>(Configs.CUSTOM_BLOCK_SORTING_RULES), "custom block sorting rules"); 101 | SortingRule.addSortingRule(Registry.ITEM_KEY, new MultimapSortingRule<>(Configs.CUSTOM_ITEM_SORTING_RULES), "custom item sorting rules"); 102 | SortingRule.addConditionalSortingRule(Registry.BLOCK_KEY, () -> Configs.instance.enableDefaultItemSortingRules && !Configs.instance.blockItemsOnly, new MultimapSortingRule<>(DEFAULT_BLOCK_SORTING_RULE), "default block sorting rule"); 103 | SortingRule.addConditionalSortingRule(Registry.ITEM_KEY, () -> Configs.instance.enableDefaultItemSortingRules, new MultimapSortingRule<>(DEFAULT_ITEM_SORTING_RULE), "default item sorting rule"); 104 | SortingRule.addConditionalSortingRule(Registry.BLOCK_KEY, () -> Configs.instance.fenceGateFollowsFence && !Configs.instance.blockItemsOnly, FENCE_GATE_FOLLOWS_FENCE, 1, "fence gate follows fence"); 105 | SortingRule.addConditionalSortingRule(Registry.ITEM_KEY, () -> Configs.instance.fenceGateFollowsFence, FENCE_GATE_FOLLOWS_FENCE_ITEM, 1, "fence gate follows fence"); 106 | SortingRule.addConditionalSortingRule(Registry.BLOCK_KEY, () -> Configs.instance.fancyColorsSorting && !Configs.instance.blockItemsOnly, COLOR_SORTING_RULE, -1, "fancy colors"); 107 | SortingRule.addConditionalSortingRule(Registry.ITEM_KEY, () -> Configs.instance.fancyColorsSorting, COLOR_SORTING_RULE_ITEM, -1, "fancy colors"); 108 | 109 | if (Configs.instance.debugMode) { 110 | SortingRule.LOGGER.info("Initializing Sorting Rules. It may happen before or after the registration of mod items, but should take place before loading Reasonable Sorting Configs."); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | # Reasonable Sorting 2 | 3 | 如果看不懂英文,可以阅读[中文版](README.md)。 4 | 5 | Did you find that, in Creative Mode, it's quite a hard work to find in the creative inventory anything you want, as the sorting of them is so messy? Install this mod, and your creative inventory will be beautifully sorted! You will discover that all stairs and slabs follow their base blocks, and fence gates are transferred to decoration blocks and follow their relevant fence. Colorful blocks are sorted in the gradient color order. 6 | 7 | You may config your custom sorting rules and item group transfer rules. 8 | 9 | The mod **depends on Cloth Config mod**, without which this mod cannot work. Besides, for Fabric and Quilt version, **it's recommended to install Mod Menu** to configure. 10 | 11 | The Fabric version of this mod depends on Fabric API. The Quilt version of this mod (for 1.18.2 and above) depends on Quilt Standard Libraries. 12 | 13 | Since version 2.0.0, this mod does not work with Extended Block Shapes mod of version 1.5.2 or lower (yet still no conflict). Please wait for newer versions of Extended Block Shapes mod. 14 | 15 | The Quilt version of this mod is under construction. 16 | 17 | ## Configuration 18 | 19 | You can configure this mod through Mod Menu. 20 | 21 | ### Sorting 22 | 23 | **Enable sorting** 24 | 25 | ON by default. If OFF, all the sorting works as vanilla, and following configurations will not work. 26 | 27 | **Sorting influence range** 28 | 29 | Defines in what cases the mod has influence. If you don't know the meaning, just remain the default value. Supported values: 30 | 31 | - Registry: All iterations of the Minecraft's registry will be affected. This may affect more situations, such as mods that list blocks or items. However, it may bring out some instabilities, such as potential registry mismatch when you join servers. 32 | - Inventory only: Only affect your Creative Mode inventory. Other situations will not be affected by the mod. 33 | 34 | **Sorting calculation type** 35 | 36 | Defines when to calculate the sorting. If you don't know the meaning, just remain the default value. Supported values: 37 | 38 | - Standard: Sorting of content (such as items) is calculated completely when the game starts or the config is loaded, and will be re-calculated when modifying the configs. This is the preferred way because it reduced unnecessary calculation in each iteration. (This option has no effect if your using Forge and meanwhile the value of "Sorting influence range" is "Registry".) 39 | - Semi real-time: The combination rules of content are calculated when the game starts, the config is loaded, or when modifying configs. However, the sorting result is still calculated in each iteration. It may cause a slight lag when you open your creative inventory. 40 | - Real-time: The combination rules of content and the sorting result is calculated in each iteration of registry. It may cause a lag when you open your creative inventory. 41 | 42 | **Debug mode** 43 | 44 | If enabled, more detailed information of the mod will be logged. You may enable it when you're researching this mod or find any problems. 45 | 46 | **Enable default item sorting rules** 47 | 48 | ON by default. This mod has some installed item sorting rules. For instance, Ice, Packed Ice and Blue Ice are sorted together. 49 | 50 | **Custom sorting rule** 51 | 52 | Empty by default. You can have custom sorting rules by typing item id. In the mod config screen, click the "+" on the left, and you will see an unapparent text box on the below. Type one rule in it. The syntax of a rule is: multiple item ids are separated with a space. For example, `dirt white_wool diamond_block` means that Dirt, White Wool and Diamond Block become together, in which White Wools and Diamond Block follow Dirt. 53 | 54 | **Variants following base blocks** 55 | 56 | In Minecraft, many blocks have their **variants**. For example, of Oak Planks, the "stairs" variant is "Oak Stairs" and "slab" variant is "Oak Slab", which also means, Oak Planks is the **base block** or Oak Stairs, Oak Slab etc. You can specify some variants which will follow their base blocks. 57 | 58 | The default syntax is, multiple block variant names are separated with a space. Available variant names are displayed in the mod config screen. By default `stairs slab`, which means, all stairs and slabs follow their base blocks. 59 | 60 | Take notice that changing item sorting does not affect which item group they belong to. To change item groups you need also to config **Variant transfer rules**. 61 | 62 | **Fence gate follows fence** 63 | 64 | ON by default. Makes all fence gate blocks follow their corresponding fence blocks. Requires **Fence gates in decorations** otherwise their still appear in "Redstone" item group and does not take effect. 65 | 66 | **Fancy color sorting** 67 | 68 | Sort colored blocks and items (such as wool, glazed terracotta, bed, banner) in a gradient order, like what is looks in Minecraft 1.19.3. 69 | 70 | **Avoid affecting block registries** 71 | 72 | OFF by default. When ON, only the item registry, including block items (such as blocks in the Creative inventory) are affected by options above, and block registries (such as blocks in the Debug Mode) are not affected. This can to some extent avoid block mismatch if you are joining a server. This option has no effect if you set 'Sorting Influence Range' to 'Inventory only'. 73 | 74 | Note: If "sorting influence range" is set to "inventory only", this config will take no effect. 75 | 76 | ### Item group transfer 77 | 78 | **Enable item group transfer** 79 | 80 | ON by default. If OFF, all items appear in the item groups they belong to in vanilla, and following configs will not work. 81 | 82 | **Buttons in decorations** 83 | 84 | **Fence gates in decorations** 85 | 86 | **Swords in tools** 87 | 88 | **Doors in decorations** 89 | 90 | The meaning of the four entries above is obvious. By default, "Fence gates in decorations" is ON, and others are OFF. 91 | 92 | **Custom item group transfer rules** 93 | 94 | Empty by default. Similar to custom sorting rules, one rule every line, and add a new rule by clicking "+". The syntax of each rule is: item id, space, the item group to transfer to. For example, `redstone_block building_blocks` will transfer Redstone Block to "Building Blocks" item group. 95 | 96 | **Custom variant transfer rules** 97 | 98 | Empty by default. Similar to above. Syntax: variant name, space, and the item group to transfer to. For example,`cut transportation` transfers all cut blocks to "Transportations". 99 | 100 | **Custom regex transfer rules** 101 | 102 | Empty by default. Similar to above. Syntax: regex, space, and the item group to transfer to. For example, `.+?button transportation` transfers to all items which identifier ends with `button` to "Transportations". 103 | 104 | **Custom tag transfer rules** 105 | 106 | Empty by default. Similar to above. Syntax: tag id, space, and the item group to transfer to. For example, `flowers transportation` will transfer items in `#minecraft:flowers` to "Transportation" item group. 107 | 108 | ## Technical details 109 | 110 | The essence of item sorting is "specify a leading item and multiple following items; the following items will appear right after the leading item". To make an example, for rule `dirt white_wool diamond_block`, the dirt is a leading item, and white wool and diamond block will appear right after the dirt, instead of the place they should have been. 111 | 112 | An item may not follow multiple items, otherwise it only follows one of them. For example, if both the two rules `dirt white_wool` and `grass_block white_wool` are defined, the white wool will right after *one of* the dirt and grass block, which means, items will not duplicate. 113 | 114 | Items can follow recursively. In default situation, for instance, according to the "variants following base blocks" rule, oak stairs and oak slab appear after the oak planks. And according to "default item sorting rule", petrified oak slab appear after the oak slab. Therefore, you may see the combination of "oak planks - oak stairs - oak slab - petrified oak slab". 115 | 116 | Items *may not follow mutually or loopy*. For example, if both `dirt white_wool` and `white_wool dirt` are defined, both dirt and white wool may not disappear (you can find errors in the log). Danger of dead loop may exist. You should avoid this. 117 | 118 | About item group transfer, the transferred items do not appear in the former item groups. However, one item can be transferred to multiple groups. 119 | 120 | ## To develop 121 | 122 | Invoke `SortingRule.addSortingRule` to add a sorting rule, or `SortingRule.addConditionalSortingRule` to add a rule that only applies under specified circumstances. Similarly, you can add transfer rules by `TransferRule.addTransferRule` or `TransferRule.addConditionalTransferRule`. -------------------------------------------------------------------------------- /common/src/main/resources/assets/reasonable-sorting/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "modmenu.descriptionTranslation.reasonable-sorting": "Adjust the sorting of items in creative inventory and change item groups to make items in your creative inventory no longer in a mess! You can also customize some rules!", 3 | "title.reasonable-sorting.config": "Reasonable Sorting Mod configs", 4 | "category.reasonable-sorting.sorting": "Sorting", 5 | "category.reasonable-sorting.sorting.description": "You can make items in your creative inventory sort in a better way. For example, you can make stairs and slabs follow their base block, instead of in a messy order.", 6 | "category.reasonable-sorting.transfer": "Group transfer", 7 | "category.reasonable-sorting.transfer.description": "You can make item transfer from one item group to another. For example, you can transfer buttons or fence gates from Redstone to Decorations.", 8 | "text.reasonable-sorting.enabled": "\u00a7aEnabled", 9 | "text.reasonable-sorting.disabled": "\u00a7cDisabled", 10 | "option.reasonable-sorting.enable_sorting": "Enable sorting", 11 | "option.reasonable-sorting.enable_sorting.tooltip": "If it is disabled, any sorting of items works as vanilla, without the infection of this mod, and all the following options will not work.", 12 | "option.reasonable-sorting.sorting_influence_range": "Sorting influence range", 13 | "option.reasonable-sorting.sorting_influence_range.tooltip": "Defines in what cases the mod has influence. If you don't know the meaning, just remain the default value.", 14 | "option.reasonable-sorting.sorting_calculation_type": "Sorting calculation type", 15 | "option.reasonable-sorting.sorting_calculation_type.tooltip": "Defines when to calculate the sorting. If you don't know the meaning, just remain the default value.", 16 | "option.reasonable-sorting.debug_mode": "Debug mode", 17 | "option.reasonable-sorting.debug_mode.tooltip": "If enabled, more detailed information of the mod will be logged. You may enable it when you're researching this mod or find any problems.", 18 | "option.reasonable-sorting.enable_default_item_sorting_rules": "Enable default item sorting rules.", 19 | "option.reasonable-sorting.enable_default_item_sorting_rules.tooltip": "For example, Ice and Pack Ice, or Cobblestone and Mossy Cobblestone are put together.", 20 | "option.reasonable-sorting.custom_sorting_rules": "Custom sorting rules", 21 | "option.reasonable-sorting.custom_sorting_rules.add": "Add a custom sorting rule.", 22 | "option.reasonable-sorting.custom_sorting_rules.remove": "Delete the custom sorting rule.", 23 | "option.reasonable-sorting.custom_sorting_rules.tooltip": "Each line is a sorting rule, with multiple item ids, split by a space.", 24 | "option.reasonable-sorting.custom_sorting_rules.example": "For example:\u00a7e grass_block dirt stone\u00a7r.", 25 | "option.reasonable-sorting.variants_following_base_blocks": "Variants following base blocks", 26 | "option.reasonable-sorting.variants_following_base_blocks.tooltip": "Name of variants that will follow base blocks. Only vanilla variants are supported. \nSome variants may need item group transfer to follow.", 27 | "option.reasonable-sorting.fence_gate_follows_fence": "Fence gate follows fence", 28 | "option.reasonable-sorting.fence_gate_follows_fence.tooltip": "Enabling \"Fence gates in decorations\" in \"Enable group transfer\" category recommended.", 29 | "option.reasonable-sorting.fancy_color_sorting": "Fancy color sorting", 30 | "option.reasonable-sorting.fancy_color_sorting.tooltip": "Sort colored blocks and items (such as wool, glazed terracotta, bed, banner) in a gradient order, like what is looks in Minecraft 1.19.3.", 31 | "option.reasonable-sorting.block_items_only": "Avoid affecting block registries", 32 | "option.reasonable-sorting.block_items_only.tooltip": "When on, only the item registry, including block items (such as blocks in the Creative inventory) are affected by options above, and block registries (such as blocks in the Debug Mode) are not affected. This can to some extent avoid block mismatch if you are joining a server. This option has no effect if you set 'Sorting Influence Range' to 'Inventory only'.", 33 | "option.reasonable-sorting.enable_group_transfer": "Enable group transfer", 34 | "option.reasonable-sorting.enable_group_transfer.tooltip": "If it is disabled, any grouping of items works as vanilla, without the infection of this mod, and all the following options will not work.", 35 | "option.reasonable-sorting.buttons_in_decorations": "Buttons in decorations", 36 | "option.reasonable-sorting.fence_gates_in_decorations": "Fence gates in decorations", 37 | "option.reasonable-sorting.swords_in_tools": "Swords in tools", 38 | "option.reasonable-sorting.doors_in_decorations": "Doors in decorations", 39 | "option.reasonable-sorting.describe_item_groups": "Valid item group names: %s.", 40 | "option.reasonable-sorting.describe_variants": "Valid variant names: %s.", 41 | "option.reasonable-sorting.custom_transfer_rules": "Custom transfer rules", 42 | "option.reasonable-sorting.custom_transfer_rules.tooltip": "Each line is an item id and a group name, for example,\u00a7e redstone_block building_blocks\u00a7r.", 43 | "option.reasonable-sorting.custom_transfer_rules.add": "Add a group transfer rule.", 44 | "option.reasonable-sorting.custom_transfer_rules.remove": "Remove the group transfer rule.", 45 | "option.reasonable-sorting.custom_variant_transfer_rules": "Custom variant transfer rules", 46 | "option.reasonable-sorting.custom_variant_transfer_rules.tooltip": "Each line is a block variant name and a group name, for example,\u00a7e wall building_blocks\u00a7r.", 47 | "option.reasonable-sorting.custom_tag_transfer_rules": "Custom tag transfer rules", 48 | "option.reasonable-sorting.custom_tag_transfer_rules.tooltip": "Each line is a tag name and a group name, for example,\u00a7eflowers building_blocks\u00a7r.", 49 | "option.reasonable-sorting.custom_regex_transfer_rules": "Custom regex transfer rules", 50 | "option.reasonable-sorting.custom_regex_transfer_rules.tooltip": "Each line is a regex and a group name, for example,\u00a7e .+?bucket transportation\u00a7r. \nAny items with ids matching the regex will be transferred to the group.", 51 | "option.reasonable-sorting.custom_shape_transfer_rules": "Custom Shape Transfer Rules", 52 | "option.reasonable-sorting.custom_shape_transfer_rules.tooltip": "Transfer specified shapes in Extended Block Shape mods to another group.", 53 | "option.reasonable-sorting.base_blocks_in_building_blocks": "Base blocks in building blocks", 54 | "option.reasonable-sorting.base_blocks_in_building_blocks.tooltip": "Base blocks in Extended Block Shapes mod, like honeycomb, shroomlight, are transferred to Building Blocks.", 55 | 56 | "option.reasonable-sorting.error.group_name_expected": "Item group name expected.", 57 | "option.reasonable-sorting.error.unexpected_text": "Unexpected text: %s.", 58 | "option.reasonable-sorting.error.invalid_identifier": "Invalid identifier: %s.", 59 | "option.reasonable-sorting.error.invalid_group_name": "Invalid item group name: %s.", 60 | "option.reasonable-sorting.error.invalid_variant_name": "Invalid variant name: %s.", 61 | "option.reasonable-sorting.error.invalid_regex": "Invalid regex: %s: %s.", 62 | "option.reasonable-sorting.shapes_following_base_blocks": "Shapes following base blocks", 63 | "option.reasonable-sorting.shapes_following_base_blocks.tooltip": "Shapes in extshape mod following base blocks. \u00a77If this option has no effect, please check the \"Add to vanilla groups\" option in the configuration of Extended Block Shapes mod.\u00a7r", 64 | "option.reasonable-sorting.describe_shapes": "Valid shape names: %s。", 65 | "option.reasonable-sorting.error.invalid_shape_name": "Invalid shape name: %s.", 66 | 67 | "sortingInfluenceRange.registry": "Registry", 68 | "sortingInfluenceRange.registry.description": "All iterations of the Minecraft's registry will be affected. This may affect more situations, such as mods that list blocks or items. However, it may bring out some instabilities, such as potential registry mismatch when you join servers.", 69 | "sortingInfluenceRange.inventory_only": "Inventory only", 70 | "sortingInfluenceRange.inventory_only.description": "Only affect your Creative Mode inventory. Other situations will not be affected by the mod.", 71 | 72 | "sortingCalculationType.standard": "Standard", 73 | "sortingCalculationType.standard.description": "Sorting of content (such as items) is calculated completely when the game starts or the config is loaded, and will be re-calculated when modifying the configs. This is the preferred way because it reduced unnecessary calculation in each iteration. (This option has no effect if your using Forge and meanwhile the value of \"Sorting influence range\" is \"Registry\".)", 74 | "sortingCalculationType.semi_real_time": "Semi real-time", 75 | "sortingCalculationType.semi_real_time.description": "The combination rules of content are calculated when the game starts, the config is loaded, or when modifying configs. However, the sorting result is still calculated in each iteration. It may cause a slight lag when you open your creative inventory.", 76 | "sortingCalculationType.real_time": "Real-time", 77 | "sortingCalculationType.real_time.description": "The combination rules of content and the sorting result is calculated in each iteration of registry. It may cause a lag when you open your creative inventory." 78 | } -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/Configs.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.base.Functions; 4 | import com.google.common.collect.ArrayListMultimap; 5 | import com.google.common.collect.ImmutableMap; 6 | import com.google.common.collect.Lists; 7 | import com.google.common.collect.Multimap; 8 | import me.shedaniel.autoconfig.AutoConfig; 9 | import me.shedaniel.autoconfig.ConfigData; 10 | import me.shedaniel.autoconfig.ConfigHolder; 11 | import me.shedaniel.autoconfig.annotation.Config; 12 | import me.shedaniel.autoconfig.event.ConfigSerializeEvent; 13 | import me.shedaniel.autoconfig.serializer.GsonConfigSerializer; 14 | import net.minecraft.block.Block; 15 | import net.minecraft.data.family.BlockFamily; 16 | import net.minecraft.item.Item; 17 | import net.minecraft.item.ItemGroup; 18 | import net.minecraft.tag.TagKey; 19 | import net.minecraft.util.ActionResult; 20 | import org.jetbrains.annotations.ApiStatus; 21 | import org.jetbrains.annotations.Unmodifiable; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | import java.util.regex.Pattern; 29 | 30 | /** 31 | * 此模组的配置储存。考虑到模组在不同的平台对配置的使用有所不同,因此不同平台的模组继承此类,并各自替换 {@link #instance} 字段。本模组使用 Auto Config 进行配置文件的读取与保存,但是配置界面仍是通过 Cloth Config 进行手动创建的。 32 | */ 33 | @Config(name = "reasonable-sorting") 34 | public class Configs implements ConfigData { 35 | /** 36 | * 由方块变种名称到方块变种的不可变映射。模组配置时,可能需要使用方块变种的名称。 37 | */ 38 | public static final @Unmodifiable ImmutableMap NAME_TO_VARIANT = Arrays.stream(BlockFamily.Variant.values()).collect(ImmutableMap.toImmutableMap(BlockFamily.Variant::getName, Functions.identity())); 39 | /** 40 | * 自定义的方块排序规则,目前暂未使用。

41 | * 注意这个字段是静态的,当保存配置时,直接修改这个 Multimap 的内容。 42 | */ 43 | public static final Multimap CUSTOM_BLOCK_SORTING_RULES = ArrayListMultimap.create(); 44 | /** 45 | * 自定义的物品排序规则。这个字段是静态的,当保存配置时,会根据保存的对应配置项(一般是字符串列表)更新这个 Multimap 的内容。 46 | * 47 | * @see #customSortingRules 48 | */ 49 | public static final Multimap CUSTOM_ITEM_SORTING_RULES = ArrayListMultimap.create(); 50 | /** 51 | * 自定义的物品组转移规则。这个字段是静态的,当保存配置时,会根据保存的对应配置项(一般是字符串列表)更新这个 Multimap 的内容。 52 | * 53 | * @see #transferRules 54 | */ 55 | public static final Multimap CUSTOM_TRANSFER_RULE = ArrayListMultimap.create(); 56 | /** 57 | * 自定义的方块变种的物品组转移规则,只影响方块物品。这个字段是静态的,当保存配置时,会根据保存的对应配置项(一般是字符串列表)更新这个 Multimap 的内容。 58 | * 59 | * @see #variantTransferRules 60 | */ 61 | public static final Multimap CUSTOM_VARIANT_TRANSFER_RULE = ArrayListMultimap.create(); 62 | /** 63 | * 自定义正则表达式的物品组转移规则。这个字段是静态的,当保存配置时,会根据保存的对应配置项(一般是字符串列表)更新这个 Multimap 的内容。 64 | * 65 | * @see #regexTransferRules 66 | */ 67 | public static final Multimap CUSTOM_REGEX_TRANSFER_RULE = ArrayListMultimap.create(); 68 | @ApiStatus.AvailableSince("2.0.1") 69 | public static final Multimap, ItemGroup> CUSTOM_TAG_TRANSFER_RULE = ArrayListMultimap.create(); 70 | /** 71 | * 对该数组内的变种应用排序,其他变种不受影响。这个字段是静态的,当保存配置时,会根据保存的对应配置项(一般是字符串列表)更新这个 Multimap 的内容。

72 | * 本字段应当与 {@link Configs#variantsFollowingBaseBlocks} 定义的默认值一致。 73 | * 74 | * @see #variantsFollowingBaseBlocks 75 | */ 76 | public static final ArrayList VARIANTS_FOLLOWING_BASE_BLOCKS = Lists.newArrayList(BlockFamily.Variant.STAIRS, BlockFamily.Variant.SLAB); 77 | public static final ConfigHolder CONFIG_HOLDER = AutoConfig.register(Configs.class, GsonConfigSerializer::new); 78 | 79 | /* 80 | 81 | ===== SORTING PART ===== 82 | 83 | */ 84 | 85 | /** 86 | *

排序部分

87 | *

88 | * 是否启用排序。如果该项为 {@code false},则所有的排序都会按照原版进行。 89 | */ 90 | public boolean enableSorting = true; 91 | /** 92 | * 本模组的排序影响的范围。可以仅影响创造模式物品栏,也可以影响整个注册表。 93 | */ 94 | @ApiStatus.AvailableSince("2.1.0") 95 | public SortingInfluenceRange sortingInfluenceRange = SortingInfluenceRange.INVENTORY_ONLY; 96 | /** 97 | * 排序计算类型,指定在哪些情况下计算排序。 98 | */ 99 | @ApiStatus.AvailableSince("2.1.0") 100 | public SortingCalculationType sortingCalculationType = SortingCalculationType.STANDARD; 101 | /** 102 | * 调试模式。如果启用,则在日志中输出更多内容。 103 | */ 104 | @ApiStatus.AvailableSince("2.1.0") 105 | public boolean debugMode = false; 106 | /** 107 | * 是否启用默认的物品排序规则。 108 | * 109 | * @see SortingRules#DEFAULT_ITEM_SORTING_RULE 110 | */ 111 | public boolean enableDefaultItemSortingRules = true; 112 | /** 113 | * 自定义排序规则。每行一条规则,由多个物品的命名空间 id 组成,用空格隔开。

114 | * 例如:{@code grass_block dirt dirt_path} 表示将草方块、泥土、土径排在一起,以草方块的位置为准。

115 | * 注意:在实际排序时,不是取决于此字段,而是取决于 {@link #CUSTOM_BLOCK_SORTING_RULES},因此加载和保存配置时,应进行更新。 116 | * 117 | * @see #CUSTOM_BLOCK_SORTING_RULES 118 | * @see ConfigsHelper#updateCustomSortingRules 119 | */ 120 | public List customSortingRules = new ArrayList<>(); 121 | 122 | /** 123 | * 受排序规则影响的方块变种。这些变种中的方块物品都会排在其基础方块之后。每次保存、修改配置时,都会对 {@link #VARIANTS_FOLLOWING_BASE_BLOCKS} 进行重写。

124 | * 注意:在实际排序时,不是取决于此字段,而是取决于 {@link #VARIANTS_FOLLOWING_BASE_BLOCKS},因此加载和保存配置时,应进行更新。 125 | * 126 | * @see #VARIANTS_FOLLOWING_BASE_BLOCKS 127 | * @see ConfigsHelper#updateVariantsFollowingBaseBlocks 128 | */ 129 | public String variantsFollowingBaseBlocks = "stairs slab"; 130 | /** 131 | * 受排序规则影响的 ExtShape 模组中的方块变种。本字段内容在 Extended Block Shapes 模组中进行读取,未安装该模组时,本字段仍会正常加载和保存,但不会显示在模组配置屏幕中。 132 | */ 133 | public String shapesFollowingBaseBlocks = "*"; 134 | /** 135 | * 栅栏门紧随栅栏。需要注意的是,该项需要和 {@link #fenceGatesInDecorations} 搭配使用。 136 | * 137 | * @see SortingRules#FENCE_GATE_FOLLOWS_FENCE_ITEM 138 | */ 139 | public boolean fenceGateFollowsFence = true; 140 | /** 141 | * 使用更优美的方式排序各个颜色。 142 | * 143 | * @see SortingRules#COLOR_SORTING_RULE 144 | */ 145 | @ApiStatus.AvailableSince("2.1.0") 146 | public boolean fancyColorsSorting = true; 147 | /** 148 | * 若开启,则上述规则只影响物品(包括物品形式的方块),不影响方块,也就是说,调试模式下的所有方块仍按照原版方式排序,但是创造模式物品栏里面的方块则依然受影响。 149 | */ 150 | public boolean blockItemsOnly = true; 151 | 152 | /* 153 | 154 | ===== TRANSFER PART ===== 155 | 156 | */ 157 | 158 | /** 159 | *

物品组转移部分

160 | *

161 | * 是否启用物品组转移。如果该项为 false,则所有的物品组转移都会按照原版进行。 162 | */ 163 | public boolean enableGroupTransfer = true; 164 | /** 165 | * 按钮移至装饰性方块。 166 | * 167 | * @see TransferRules#BUTTON_IN_DECORATIONS 168 | */ 169 | public boolean buttonsInDecorations = false; 170 | /** 171 | * 栅栏门移至装饰性方块。 172 | * 173 | * @see TransferRules#FENCE_GATE_IN_DECORATIONS 174 | */ 175 | public boolean fenceGatesInDecorations = true; 176 | /** 177 | * 剑移至工具。 178 | * 179 | * @see TransferRules#SWORDS_IN_TOOLS 180 | */ 181 | public boolean swordsInTools = false; 182 | /** 183 | * 门移至装饰性方块。 184 | * 185 | * @see TransferRules#DOORS_IN_DECORATIONS 186 | */ 187 | public boolean doorsInDecorations = false; 188 | /** 189 | * 自定义物品转移规则。注意:在实际排序时,不是取决于此字段,而是取决于 {@link #CUSTOM_TRANSFER_RULE},因此加载和保存配置时,应根据此字段内容进行修改。 190 | * 191 | * @see #CUSTOM_TRANSFER_RULE 192 | * @see ConfigsHelper#updateCustomTransferRule 193 | */ 194 | public List transferRules = new ArrayList<>(); 195 | /** 196 | * 自定义物品变种转移规则。注意:在实际排序时,不是取决于此字段,而是取决于 {@link #CUSTOM_VARIANT_TRANSFER_RULE},因此加载和保存配置时,应根据此字段内容进行修改。 197 | * 198 | * @see #CUSTOM_VARIANT_TRANSFER_RULE 199 | * @see ConfigsHelper#updateCustomVariantTransferRules 200 | */ 201 | public List variantTransferRules = new ArrayList<>(); 202 | /** 203 | * 自定义正则表达式转移规则。注意:在实际排序时,不是取决于此字段,而是取决于 {@link #CUSTOM_REGEX_TRANSFER_RULE},因此加载和保存配置时,应根据此字段内容进行修改。 204 | * 205 | * @see #CUSTOM_REGEX_TRANSFER_RULE 206 | * @see ConfigsHelper#updateCustomRegexTransferRules 207 | */ 208 | public List regexTransferRules = new ArrayList<>(); 209 | /** 210 | * 自定义物品标签转移规则。注意:在实际排序时,不是取决于此字段,而是取决于 {@link #CUSTOM_TAG_TRANSFER_RULE}。 211 | * 212 | * @see #CUSTOM_TAG_TRANSFER_RULE 213 | * @see ConfigsHelper#updateCustomTagTransferRules 214 | */ 215 | @ApiStatus.AvailableSince("2.1.0") 216 | public List tagTransferRules = new ArrayList<>(); 217 | /** 218 | * 用于 Extended Block Shapes 模组。没有安装此模组时,此字段仍会正常加载和保存,但是不会显示在配置屏幕中。 219 | */ 220 | public List shapeTransferRules = new ArrayList<>(); 221 | /** 222 | * 用于 Extended Block Shapes 模组,将蜜脾、菌光体等基础方块移至建筑方块。没有安装此模组时,此字段仍会正常加载和保存,但是不会显示在配置屏幕中。 223 | */ 224 | public boolean baseBlocksInBuildingBlocks = true; 225 | private static final Logger LOGGER = LoggerFactory.getLogger("ReasonableSorting Configs"); 226 | /** 227 | * 这个配置的实例。通常这个值应该为 {@link ConfigHolder#getConfig()} 返回的结果。当被修改时,这个字段也相应修改。初始值为新的实例,然后再会在 {@link #loadAndUpdate()} 中替换。 228 | */ 229 | public static Configs instance = new Configs(); 230 | 231 | public static void loadAndUpdate() { 232 | // 初始化时先手动设置,之后在每次更新和保存时,都会在 listener 中自动更新这个字段的值。 233 | Configs.instance = CONFIG_HOLDER.getConfig(); 234 | 235 | final ConfigSerializeEvent.Load update = (configHolder, configs) -> { 236 | ConfigsHelper.updateCustomSortingRules(configs.customSortingRules, Configs.CUSTOM_ITEM_SORTING_RULES); 237 | ConfigsHelper.updateCustomTransferRule(configs.transferRules, Configs.CUSTOM_TRANSFER_RULE); 238 | ConfigsHelper.updateCustomTagTransferRules(configs.tagTransferRules, Configs.CUSTOM_TAG_TRANSFER_RULE); 239 | ConfigsHelper.updateCustomRegexTransferRules(configs.regexTransferRules, Configs.CUSTOM_REGEX_TRANSFER_RULE); 240 | ConfigsHelper.updateCustomVariantTransferRules(configs.variantTransferRules, Configs.CUSTOM_VARIANT_TRANSFER_RULE); 241 | ConfigsHelper.updateVariantsFollowingBaseBlocks(configs.variantsFollowingBaseBlocks, Configs.VARIANTS_FOLLOWING_BASE_BLOCKS); 242 | ExtShapeBridge.INSTANCE.updateShapeList(configs.shapesFollowingBaseBlocks); 243 | ExtShapeBridge.INSTANCE.updateShapeTransferRules(configs.shapeTransferRules); 244 | 245 | SortingRule.clearValueToFollowersCache(); 246 | SimpleRegistryExtension.removeAllCachedEntries(); 247 | return ActionResult.PASS; 248 | }; 249 | CONFIG_HOLDER.registerLoadListener((configHolder, configs) -> { 250 | Configs.instance = configs; 251 | LOGGER.info("Loading Reasonable Sorting configs."); 252 | update.onLoad(configHolder, configs); 253 | return ActionResult.SUCCESS; 254 | }); 255 | 256 | // ConfigManager 在构造的时候就会调用一次 load,但是当时还是没有注册 loadListener 的,因此需要手动调用一次。 257 | update.onLoad(CONFIG_HOLDER, Configs.instance); 258 | 259 | CONFIG_HOLDER.registerSaveListener((configHolder, configs) -> { 260 | Configs.instance = configs; 261 | LOGGER.info("Saving Reasonable Sorting configs."); 262 | SortingRule.clearValueToFollowersCache(); 263 | SimpleRegistryExtension.removeAllCachedEntries(); 264 | return ActionResult.SUCCESS; 265 | }); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/ConfigsHelper.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Multimap; 5 | import net.minecraft.data.family.BlockFamily; 6 | import net.minecraft.item.Item; 7 | import net.minecraft.item.ItemGroup; 8 | import net.minecraft.tag.TagKey; 9 | import net.minecraft.text.MutableText; 10 | import net.minecraft.text.Text; 11 | import net.minecraft.util.Formatting; 12 | import net.minecraft.util.Identifier; 13 | import net.minecraft.util.registry.Registry; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.jetbrains.annotations.ApiStatus; 16 | import org.jetbrains.annotations.Contract; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.*; 20 | import java.util.regex.Pattern; 21 | import java.util.regex.PatternSyntaxException; 22 | 23 | /** 24 | * 核查模组选项里的字符串形式配置并用于更新相应集合内容的实用类。 25 | */ 26 | public final class ConfigsHelper { 27 | private ConfigsHelper() { 28 | } 29 | 30 | /** 31 | * 根据字符串形式的 id 来返回一个物品组。 32 | * 33 | * @param id 字符串形式的id。若为 null,则返回 null。 34 | * @return 物品组。 35 | */ 36 | @Contract(value = "!null -> _; null -> null", pure = true) 37 | static @Nullable ItemGroup getGroupFromId(String id) { 38 | if (id == null) return null; 39 | for (ItemGroup group : ItemGroup.GROUPS) { 40 | if (id.equals(group.getName())) { 41 | return group; 42 | } 43 | } 44 | return null; 45 | } 46 | 47 | @Contract(mutates = "param2") 48 | public static void updateVariantsFollowingBaseBlocks(String s, List mutableList) { 49 | mutableList.clear(); 50 | Arrays.stream(s.split("\\s+")) 51 | .filter(name -> !name.isEmpty()) 52 | .map(Configs.NAME_TO_VARIANT::get) 53 | .filter(Objects::nonNull) 54 | .forEach(mutableList::add); 55 | } 56 | 57 | @Contract(mutates = "param2") 58 | public static void updateCustomSortingRules(List list, Multimap mutableMap) { 59 | mutableMap.clear(); 60 | for (String s : list) { 61 | final ArrayList split = Lists.newArrayList(s.split("\\s+")); 62 | if (split.size() < 1) { 63 | continue; 64 | } 65 | Identifier key = Identifier.tryParse(split.remove(0)); 66 | if (key == null) { 67 | continue; 68 | } 69 | final Optional item = Bridge.getItemByIdOrWarn(key, SortingRule.LOGGER); 70 | item.ifPresent(value -> mutableMap.putAll( 71 | value, 72 | split.stream() 73 | .map(Identifier::tryParse) 74 | .map((Identifier identifier) -> Bridge.getItemByIdOrWarn(identifier, SortingRule.LOGGER)) 75 | .filter(Optional::isPresent) 76 | .map(Optional::get) 77 | .toList())); 78 | } 79 | } 80 | 81 | @Contract(mutates = "param2") 82 | public static void updateCustomRegexTransferRules(List list, Multimap mutableMap) { 83 | mutableMap.clear(); 84 | for (String s : list) { 85 | try { 86 | final String[] split1 = s.split("\\s+", 2); 87 | if (split1.length < 2) { 88 | continue; 89 | } 90 | final String[] split2 = split1[1].split("\\s+"); 91 | final Pattern compile = Pattern.compile(split1[0]); 92 | Arrays.stream(split2).map(ConfigsHelper::getGroupFromId).filter(Objects::nonNull).forEach(group -> mutableMap.put(compile, group)); 93 | } catch (PatternSyntaxException ignored) { 94 | } 95 | } 96 | } 97 | 98 | @Contract(mutates = "param2") 99 | public static void updateCustomVariantTransferRules(List list, Multimap mutableMap) { 100 | mutableMap.clear(); 101 | for (String s : list) { 102 | final String[] split1 = s.split("\\s+", 2); 103 | if (split1.length < 2) { 104 | continue; 105 | } 106 | final String[] split2 = split1[1].split("\\s+"); 107 | final BlockFamily.Variant variant = Configs.NAME_TO_VARIANT.get(split1[0]); 108 | if (variant == null) continue; 109 | Arrays.stream(split2).map(ConfigsHelper::getGroupFromId).filter(Objects::nonNull).forEach(group -> mutableMap.put(variant, group)); 110 | 111 | } 112 | } 113 | 114 | @ApiStatus.AvailableSince("2.0.1") 115 | @Contract(mutates = "param2") 116 | public static void updateCustomTagTransferRules(List list, Multimap, ItemGroup> mutableMap) { 117 | mutableMap.clear(); 118 | for (String s : list) { 119 | final String[] split1 = s.split("\\s+", 2); 120 | if (split1.length < 2) { 121 | continue; 122 | } 123 | final String[] split2 = split1[1].split("\\s+"); 124 | final Identifier id = Identifier.tryParse(StringUtils.removeStart(split1[0], "#")); 125 | if (id == null) continue; 126 | final TagKey tag = TagKey.of(Registry.ITEM_KEY, id); 127 | Arrays.stream(split2) 128 | .map(ConfigsHelper::getGroupFromId) 129 | .filter(Objects::nonNull) 130 | .forEach(group -> mutableMap.put(tag, group)); 131 | 132 | } 133 | } 134 | 135 | @Contract(mutates = "param2") 136 | public static void updateCustomTransferRule(List list, Multimap mutableMap) { 137 | for (String s : list) { 138 | mutableMap.clear(); 139 | final String[] split1 = s.split("\\s+", 2); 140 | if (split1.length < 2) { 141 | continue; 142 | } 143 | final String[] split2 = split1[1].split("\\s+"); 144 | final Identifier id = Identifier.tryParse(split1[0]); 145 | final Optional item = Bridge.getItemByIdOrWarn(id, TransferRule.LOGGER); 146 | item.ifPresent(value -> Arrays.stream(split2) 147 | .map(ConfigsHelper::getGroupFromId) 148 | .filter(Objects::nonNull) 149 | .forEach(group -> mutableMap.put(value, group))); 150 | } 151 | } 152 | 153 | @Contract(pure = true) 154 | public static Optional validateCustomSortingRule(String s) { 155 | final String[] split = s.split("\\s+"); 156 | final List invalids = 157 | Arrays.stream(split) 158 | .filter(StringUtils::isNotEmpty) 159 | .filter(s1 -> Identifier.tryParse(s1) == null) 160 | .toList(); 161 | return !invalids.isEmpty() 162 | ? Optional.of(Text.translatable("option.reasonable-sorting.error.invalid_identifier", 163 | String.join(" ", invalids))) 164 | : Optional.empty(); 165 | } 166 | 167 | @Contract(pure = true) 168 | public static Optional validateVariantFollowsBaseBlocks(String s) { 169 | List invalidNames = new ArrayList<>(); 170 | Arrays.stream(s.split("\\s+")) 171 | .filter(StringUtils::isNotEmpty) 172 | .filter(name -> !Configs.NAME_TO_VARIANT.containsKey(name)) 173 | .forEach(invalidNames::add); 174 | return invalidNames.isEmpty() 175 | ? Optional.empty() 176 | : Optional.of( 177 | Text.translatable( 178 | "option.reasonable-sorting.error.invalid_variant_name", 179 | String.join(", ", invalidNames))); 180 | } 181 | 182 | @Contract(pure = true) 183 | public static Optional validateShapeFollowsBaseBlocks(String s) { 184 | if ("*".equals(s)) { 185 | return Optional.empty(); 186 | } 187 | final List invalids = 188 | Arrays.stream(s.split("\\s+")) 189 | .filter(StringUtils::isNotEmpty) 190 | .filter(s2 -> !ExtShapeBridge.INSTANCE.isValidShapeName(s2)) 191 | .toList(); 192 | return invalids.isEmpty() 193 | ? Optional.empty() 194 | : Optional.of( 195 | Text.translatable( 196 | "option.reasonable-sorting.error.invalid_shape_name", 197 | String.join(" ", invalids))); 198 | } 199 | 200 | @Contract(pure = true) 201 | public static Optional validateCustomTransferRule(String s) { 202 | final String[] split1 = s.split("\\s+", 2); 203 | if (split1.length < 2) { 204 | return Optional.of( 205 | Text.translatable( 206 | "option.reasonable-sorting.error.group_name_expected")); 207 | } 208 | if (Identifier.tryParse(split1[0]) == null) { 209 | return Optional.of( 210 | Text.translatable( 211 | "option.reasonable-sorting.error.invalid_identifier", split1[0])); 212 | } 213 | return Optional.empty(); 214 | } 215 | 216 | @Contract(pure = true) 217 | public static Optional validateCustomVariantTransferRule(String s) { 218 | if (s.isEmpty()) { 219 | return Optional.empty(); 220 | } 221 | final String[] split1 = s.split("\\s+"); 222 | if (split1.length < 2) { 223 | return Optional.of( 224 | Text.translatable( 225 | "option.reasonable-sorting.error.group_name_expected")); 226 | } 227 | if (!Configs.NAME_TO_VARIANT.containsKey(split1[0])) { 228 | return Optional.of(Text.translatable( 229 | "option.reasonable-sorting.error.invalid_variant_name", split1[0])); 230 | } 231 | return Optional.empty(); 232 | } 233 | 234 | @Contract(pure = true) 235 | public static Optional validateCustomRegexTransferRule(String s) { 236 | if (s.isEmpty()) { 237 | return Optional.empty(); 238 | } 239 | final String[] split = s.split("\\s+"); 240 | if (split.length < 2) { 241 | return Optional.of(Text.translatable( 242 | "option.reasonable-sorting.error.group_name_expected")); 243 | } 244 | final String pattern = split[0]; 245 | try { 246 | Pattern.compile(pattern); 247 | } catch (PatternSyntaxException e) { 248 | final int index = e.getIndex(); 249 | final MutableText msg = Text.translatable( 250 | "option.reasonable-sorting.error.invalid_regex", 251 | e.getDescription(), 252 | Text.literal("") 253 | .append(pattern.substring(0, index)) 254 | .append("»") 255 | .append(Text.literal( 256 | index < pattern.length() 257 | ? pattern.substring(index, index + 1) 258 | : "").formatted(Formatting.DARK_RED)) 259 | .append("«") 260 | .append(Text.literal( 261 | index + 1 < pattern.length() 262 | ? pattern.substring(index + 1) 263 | : ""))); 264 | return Optional.of(msg); 265 | } 266 | return Optional.empty(); 267 | } 268 | 269 | @ApiStatus.AvailableSince("2.0.1") 270 | @Contract(pure = true) 271 | public static Optional validateCustomTagTransferRule(String s) { 272 | if (s.isEmpty()) { 273 | return Optional.empty(); 274 | } 275 | final String[] split1 = s.split("\\s+"); 276 | if (split1.length < 2) { 277 | return Optional.of( 278 | Text.translatable( 279 | "option.reasonable-sorting.error.group_name_expected")); 280 | } 281 | final String id = StringUtils.removeStart(split1[0], "#"); 282 | if (Identifier.tryParse(id) == null) { 283 | return Optional.of( 284 | Text.translatable( 285 | "option.reasonable-sorting.error.invalid_identifier", id)); 286 | } 287 | return Optional.empty(); 288 | } 289 | 290 | @Contract(pure = true) 291 | public static Optional validateCustomShapeTransferRule(String s) { 292 | if (s.isEmpty()) { 293 | return Optional.empty(); 294 | } 295 | final String[] split = s.split("\\s+"); 296 | if (split.length < 2) { 297 | return Optional.of( 298 | net.minecraft.text.Text.translatable( 299 | "option.reasonable-sorting.error.group_name_expected")); 300 | } 301 | if (!ExtShapeBridge.INSTANCE.isValidShapeName(split[0])) { 302 | return Optional.of( 303 | net.minecraft.text.Text.translatable( 304 | "option.reasonable-sorting.error.invalid_shape_name", split[0])); 305 | } 306 | return Optional.empty(); 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/SortingRule.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import com.google.common.base.Predicates; 4 | import com.google.common.collect.*; 5 | import net.minecraft.item.Item; 6 | import net.minecraft.util.registry.Registry; 7 | import net.minecraft.util.registry.RegistryKey; 8 | import net.minecraft.util.registry.SimpleRegistry; 9 | import org.jetbrains.annotations.ApiStatus; 10 | import org.jetbrains.annotations.NonNls; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.*; 17 | import java.util.function.BooleanSupplier; 18 | import java.util.function.Predicate; 19 | import java.util.stream.Stream; 20 | 21 | /** 22 | *

一个排序规则。这种规则的实质是,决定一个对象之后应该由哪些对象跟随。 23 | *

A sorting rule, which is in essence, determining what object or objects should follow a specific object. 24 | *

例如,这个规则可以让石化橡木台阶跟随在橡木台阶后面: 25 | *

For example, the following rule makes a petrified oak slab follows an oak slab: 26 | *

{@code
 27 |  * SortingRule myRule = block -> block == Blocks.OAK_SLAB ? Collections.singleton(Blocks.PETRIFIED_OAK_SLAB) : null}
 28 |  * 
29 | *

这个规则会让橡木台阶返回一个紧随石化橡木台阶的单元素集,也就是说橡木台阶后面紧随着石化橡木台阶。对于其他方块则返回 null,也就是不受影响。 30 | *

The rule makes oak slab returns a singleton set of a petrified oak slab, which means an oak slab should be followed by a petrified oak slab. For other blocks, it returns null, which means no influence. 31 | * 32 | * @param 33 | */ 34 | @FunctionalInterface 35 | public interface SortingRule { 36 | 37 | @ApiStatus.Internal 38 | Logger LOGGER = LoggerFactory.getLogger(SortingRule.class); 39 | 40 | @SuppressWarnings("unchecked") 41 | static Multimap getValueToFollowersCache(RegistryKey> registryKey) { 42 | return (Multimap) Internal.VALUE_TO_FOLLOWER_CACHES.get(registryKey); 43 | } 44 | 45 | static void setValueToFollowersCache(RegistryKey> registryKey, Multimap valueToFollowersCache) { 46 | Internal.VALUE_TO_FOLLOWER_CACHES.put(registryKey, valueToFollowersCache); 47 | } 48 | 49 | static Multimap getOrCreateValueToFollowers(RegistryKey> registryKey, Collection entries) { 50 | Multimap valueToFollowersCache = getValueToFollowersCache(registryKey); 51 | if (valueToFollowersCache == null || Configs.instance.sortingCalculationType == SortingCalculationType.REAL_TIME) { 52 | if (Configs.instance.debugMode) { 53 | LOGGER.info("The value-to-followers cache does not exist in registry key {}. It may happen when you start game or open your inventory at first. Creating a new one for this registry.", registryKey.getValue()); 54 | } 55 | valueToFollowersCache = LinkedListMultimap.create(); 56 | setValueToFollowersCache(registryKey, valueToFollowersCache); 57 | Multimap finalValueToFollowersCache = valueToFollowersCache; 58 | entries.forEach(value -> finalValueToFollowersCache.putAll(value, SortingRule.streamFollowersOf(SortingRule.getSortingRules(registryKey), value).toList())); 59 | if (Configs.instance.debugMode && !valueToFollowersCache.isEmpty()) { 60 | final T exampleKey = valueToFollowersCache.keys().iterator().next(); 61 | LOGGER.info("Built value-to-followers cache for registry {} with {} elements, such as {} -> {}.", registryKey.getValue(), valueToFollowersCache.size(), exampleKey, valueToFollowersCache.get(exampleKey)); 62 | } 63 | } 64 | return valueToFollowersCache; 65 | } 66 | 67 | static void clearValueToFollowersCache() { 68 | if (Configs.instance.debugMode) { 69 | LOGGER.info("Clearing caches of sorting rules of {} registries.", Internal.VALUE_TO_FOLLOWER_CACHES.size()); 70 | } 71 | Internal.VALUE_TO_FOLLOWER_CACHES.clear(); 72 | } 73 | 74 | 75 | /** 76 | * 添加一个规则。迭代注册表时就会应用到此规则。 77 | * 78 | * @param registryKey 该注册表的注册表键。当迭代注册表时,如果注册表的键符合,那么就会使用这个规则。 79 | * @param rule 一个排序规则。它接收一个对象,并返回这个对象应该被哪些对象跟随。如果为 {@code null},那么表示这个对象没有被其他对象跟随。 80 | * @param 对象的类型。 81 | */ 82 | static void addSortingRule(RegistryKey> registryKey, SortingRule rule) { 83 | addSortingRule(registryKey, rule, 0, null); 84 | } 85 | 86 | /** 87 | * 添加一个规则。迭代注册表时就会应用到此规则。 88 | * 89 | * @param registryKey 该注册表的注册表键。当迭代注册表时,如果注册表的键符合,那么就会使用这个规则。 90 | * @param rule 一个排序规则。它接收一个对象,并返回这个对象应该被哪些对象跟随。如果为 {@code null},那么表示这个对象没有被其他对象跟随。 91 | * @param name 规则的名称,主要用于调试。 92 | * @param 对象的类型。 93 | */ 94 | static void addSortingRule(RegistryKey> registryKey, SortingRule rule, @Nullable String name) { 95 | addSortingRule(registryKey, rule, 0, name); 96 | } 97 | 98 | /** 99 | * 添加一个规则。迭代注册表时就会应用到此规则。 100 | * 101 | * @param 对象的类型。 102 | * @param registryKey 该注册表的注册表键。当迭代注册表时,如果注册表的键符合,那么就会使用这个规则。 103 | * @param rule 一个排序规则。它接收一个对象,并返回这个对象应该被哪些对象跟随。如果为 {@code null},那么表示这个对象没有被其他对象跟随。 104 | * @param priority 规则的优先级。较高优先级的规则会最先应用。默认为0。 105 | * @param name 规则的名称,主要用于调试。 106 | */ 107 | static void addSortingRule(RegistryKey> registryKey, SortingRule rule, int priority, @Nullable String name) { 108 | final SortingRuleContainer sortingRuleContainer = new SortingRuleContainer<>(rule, priority, name); 109 | addElement: 110 | // 确保只会把元素添加一次 111 | if (Internal.RULES.containsKey(registryKey)) { 112 | final List> list = Internal.RULES.get(registryKey); 113 | final ListIterator> listIterator = list.listIterator(); 114 | while (listIterator.hasNext()) { 115 | // 将当前的 ruleContainer 添加到 priority 更大的元素之前的最后位置,或者列表末尾。 116 | // insert the current ruleContainer to the last position before elements with higher prioirty, or the end of the list. 117 | final SortingRuleContainer next = listIterator.next(); 118 | if (next.priority() < priority) { 119 | listIterator.previous(); 120 | listIterator.add(sortingRuleContainer); 121 | break addElement; 122 | } 123 | } 124 | listIterator.add(sortingRuleContainer); 125 | } else { 126 | Internal.RULES.put(registryKey, sortingRuleContainer); 127 | } 128 | 129 | SimpleRegistryExtension.removeAllCachedEntries(); 130 | } 131 | 132 | /** 133 | * 添加一个有条件的规则。只有当条件符合时,该规则才会被应用。 134 | * 135 | * @param registryKey 该注册表的注册表键。当迭代时,如果注册表的键符合,那么就会使用这个规则。 136 | * @param condition 应用该排序规则的一个条件。 137 | * @param rule 一个排序规则。 138 | * @param 对象的类型 139 | */ 140 | static void addConditionalSortingRule(RegistryKey> registryKey, BooleanSupplier condition, SortingRule rule) { 141 | addConditionalSortingRule(registryKey, condition, rule, 0, null); 142 | } 143 | 144 | /** 145 | * 添加一个有条件的规则。只有当条件符合时,该规则才会被应用。 146 | * 147 | * @param registryKey 该注册表的注册表键。当迭代时,如果注册表的键符合,那么就会使用这个规则。 148 | * @param condition 应用该排序规则的一个条件。 149 | * @param rule 一个排序规则。 150 | * @param name 规则的名称,主要用于调试。 151 | * @param 对象的类型 152 | */ 153 | static void addConditionalSortingRule(RegistryKey> registryKey, BooleanSupplier condition, SortingRule rule, @Nullable String name) { 154 | addConditionalSortingRule(registryKey, condition, rule, 0, name); 155 | } 156 | 157 | /** 158 | * 添加一个有条件的规则。只有当条件符合时,该规则才会被应用。 159 | * 160 | * @param 对象的类型 161 | * @param registryKey 该注册表的注册表键。当迭代时,如果注册表的键符合,那么就会使用这个规则。 162 | * @param condition 应用该排序规则的一个条件。 163 | * @param rule 一个排序规则。 164 | * @param priority 规则的优先级。较高优先级的规则会最先应用。默认为0。 165 | * @param name 规则的名称,主要用于调试。 166 | */ 167 | static void addConditionalSortingRule(RegistryKey> registryKey, BooleanSupplier condition, SortingRule rule, int priority, @Nullable String name) { 168 | addSortingRule(registryKey, new ConditionalSortingRule<>(condition, rule), priority, name); 169 | } 170 | 171 | /** 172 | * 根据一个注册表键,返回已经注册了的规则的集合。 173 | * 174 | * @param registryKey 注册表键。 175 | * @param 对象的类型。 176 | * @return 规则的集合。 177 | */ 178 | @SuppressWarnings({"unchecked", "rawtypes"}) 179 | static Collection> getSortingRules(RegistryKey> registryKey) { 180 | return Collections2.transform(((Collection>) (Collection) Internal.RULES.get(registryKey)), SortingRuleContainer::sortingRule); 181 | } 182 | 183 | /** 184 | * 根据规则的集合以及可能适用该规则的对象,返回该对象的跟随者的流。若集合中有多个规则,则规则产生的跟随者集合会自动合并。注意该流可能产生重复元素。 185 | * 186 | * @param combinationRules 排序规则集合,一般由 {@link #getSortingRules(RegistryKey)} 返回。 187 | * @param object 可能适用规则并需要查找跟随者的对象。 188 | * @param 对象的类型。 189 | * @return 对象的跟随者的流,可能是空的。 190 | */ 191 | static Stream streamFollowersOf(Collection> combinationRules, T object) { 192 | return combinationRules.stream().map(function -> function.getFollowers(object)).filter(Objects::nonNull).flatMap(Streams::stream); 193 | } 194 | 195 | /** 196 | * 替换一个普通注册表的流,以通过其迭代器应用排序规则。当没有适用的规则时,返回 {@code null}。 197 | * 198 | * @see SimpleRegistry#iterator() 199 | * @see SimpleRegistry#stream() 200 | * @see pers.solid.mod.mixin.SimpleRegistryMixin 201 | */ 202 | static @Nullable Stream streamOfRegistry( 203 | RegistryKey> registryKey, 204 | Collection entries) { 205 | final Collection> ruleSets = getSortingRules(registryKey); 206 | 207 | if (ruleSets.isEmpty()) { 208 | // 如果没有为此注册表设置规则,那么直接返回 null,在 mixin 中表示依然按照原版的迭代方式迭代。 209 | return null; 210 | } 211 | 212 | return streamOfRegistry(registryKey, entries, ruleSets); 213 | } 214 | 215 | 216 | static @NotNull Stream streamOfRegistry(RegistryKey> registryKey, Collection entries, Collection> sortingRules) { 217 | LOGGER.info("{} sorting rules found in the iteration of {}.", sortingRules.size(), registryKey.getValue()); 218 | LinkedHashSet iterated = new LinkedHashSet<>(); 219 | 220 | // 被确认跟随在另一对象之后,不因直接在一级迭代产生,而应在一级迭代产生其他对象时产生的对象。 221 | // 一级迭代时,就应该忽略这些对象。 222 | 223 | // 本集合的键为被跟随的对象,值为跟随者它的对象。 224 | final Multimap valueToFollowers = getOrCreateValueToFollowers(registryKey, entries); 225 | 226 | // 结果流的第一部分。先将内容连同其跟随者都迭代一次,已经迭代过的不重复迭代。但是,这部分可能会丢下一些元素。 227 | final Stream firstStream = entries.stream() 228 | .filter(o -> !valueToFollowers.containsValue(o)) 229 | .flatMap(o -> oneAndItsFollowers(o, valueToFollowers)) 230 | .filter(o -> !iterated.contains(o)) 231 | .peek(iterated::add); 232 | 233 | // 第一次未迭代完成的,在第二次迭代。 234 | final Stream secondStream = entries.stream() 235 | .filter((x -> !iterated.contains(x))) 236 | .peek(o -> LOGGER.info("Object {} not iterated in the first iteration or {}. Iterated in the second iteration.", o, registryKey.getValue())); 237 | 238 | return Stream.concat(firstStream, secondStream); 239 | } 240 | 241 | /** 242 | * 根据一个对象,创建一个它自己及其跟随者的流。跟随者的跟随者也会包含在这里面。 243 | * 244 | * @return 对象自身及其跟随者组成的流。 245 | */ 246 | static Stream oneAndItsFollowers(T o, Multimap valueToFollowers) { 247 | return oneAndItsFollowers(o, valueToFollowers, Predicates.alwaysTrue()); 248 | } 249 | 250 | /** 251 | * 根据一个对象,创建一个它自己及其跟随者的流。跟随者的跟随者也会包含在这里面。 252 | * 253 | * @return 对象自身及其跟随者组成的流。 254 | */ 255 | static Stream oneAndItsFollowers(T o, Multimap valueToFollowers, Predicate followerStreamPredicate) { 256 | final Collection followers = valueToFollowers.get(o); 257 | // 当某个对象的跟随者有跟随者1和跟随者2,且跟随者1的进一步跟随者还有跟随者2,那么优先考虑直接的跟随者。 258 | // 例如,如果规定:A -> B, C, D,且 B -> D, E 259 | // 那么结果应该为:A -> B, E, C, D 而非 A -> B, D, E, C 260 | return Stream.concat(Stream.of(o), followers.stream().flatMap(o1 -> oneAndItsFollowers(o1, valueToFollowers, Predicates.not(followers::contains))).filter(followerStreamPredicate)); 261 | } 262 | 263 | /** 264 | * 此方法主要用于 mixin 中。调用此方法时会检查配置。当配置文件不符合的时候,直接返回参数中的 value。 265 | */ 266 | static Iterator modifyIteratorInInventory(Iterator value, @NonNls String name) { 267 | if (Configs.instance.enableSorting && Configs.instance.sortingInfluenceRange == SortingInfluenceRange.INVENTORY_ONLY) { 268 | if (Configs.instance.sortingCalculationType != SortingCalculationType.STANDARD || Internal.cachedInventoryItems == null) { 269 | if (Configs.instance.debugMode) { 270 | LOGGER.info("Calculating the sorting in the creative inventory or {}. It may cause a slight lag, but will no longer happen until you modify configs.", name); 271 | } 272 | // 如果排序计算类型为实时或者半实时,或者计算类型为标准但是 cachedInventoryItems 为 null,那么迭代一次其中的内容。 273 | final Collection> sortingRules = getSortingRules(Registry.ITEM_KEY); 274 | if (sortingRules.isEmpty()) { 275 | return value; 276 | } 277 | final Stream stream = streamOfRegistry(Registry.ITEM_KEY, Lists.newArrayList(value), sortingRules); 278 | if (Configs.instance.sortingCalculationType == SortingCalculationType.STANDARD) { 279 | // 如果为 standard,保存这次迭代的结果,下次直接使用。 280 | final List list = stream.toList(); 281 | Internal.cachedInventoryItems = list; 282 | return list.iterator(); 283 | } else { 284 | // 如果为 real-time 或 semi-real-time,则直接返回这个流的迭代器,或者原来的迭代器。 285 | return stream.iterator(); 286 | } 287 | } else { 288 | if (Configs.instance.debugMode) { 289 | LOGGER.info("During the iteration in the creative inventory {}, the cached item list is still used, because the 'sorting calculation type' is set to 'standard'.", name); 290 | } 291 | return Internal.cachedInventoryItems.iterator(); 292 | } 293 | } 294 | return value; 295 | } 296 | 297 | /** 298 | *

根据一个 leadingObj 对象,决定它应该被哪些对象跟随。可以返回 null 或空集(表示不影响),或返回由其他对象组成的集合(注意集合不能包含 leadingObj 本身)。 299 | *

With regard to a leadingObj, determine what object or objects should follow it. It may return null or an empty set (indicating no influence), or return a set of other objects (the set cannot contain the leadingObj itself). 300 | * 301 | * @param leadingObj 被紧随的对象。 302 | * @return 跟随该对象的对象集合。 303 | */ 304 | @Nullable Iterable getFollowers(T leadingObj); 305 | 306 | @ApiStatus.Internal 307 | class Internal { 308 | 309 | /** 310 | *

当前已有的排序规则的集。每个注册表键都可以指定排序规则。一个规则可以指定某个对象被接下来的哪些对象跟随,也就是说这几个对象“紧挨在一起”。这个函数应该接收一个对象并返回跟随者的集合(可以为 {@code null}),以让这个对象与它的跟随者“紧挨一起”。 311 | *

一个简单的例子是:对于方块注册表,应用类似如下的规则:

312 | *
橡木木板 -> List.of(橡木楼梯, 橡木台阶)
313 | * 白桦木板 -> List.of(白桦木楼梯, 白桦木台阶)
314 | * ……
315 | *

那么,迭代时,橡木楼梯就会跟随在橡木台阶的后面,以此类推。 316 | *

当然,每个注册表可以指定多个规则,这多个规则集就会合并。 317 | */ 318 | @ApiStatus.Internal 319 | private static final 320 | ListMultimap, SortingRuleContainer> RULES = ArrayListMultimap.create(); 321 | private static final 322 | Map, Multimap> VALUE_TO_FOLLOWER_CACHES = new HashMap<>(); 323 | /** 324 | *

用于物品栏迭代物品迭代时,缓存的物品列表。 325 | *

当配置中的排序影响范围为注册表时,这个字段不会使用。只有当排序影响范围为 {@linkplain SortingInfluenceRange#INVENTORY_ONLY} 时,且排序计算类型为 {@linkplain SortingCalculationType#STANDARD} 时,这个字段才会使用。 326 | *

当游戏初始化时、加载或更改配置时,这个字段会被设为 {@code null},当下一次打开物品栏时,这个字段就会被赋值。 327 | */ 328 | public static @Nullable Iterable cachedInventoryItems; 329 | 330 | private Internal() { 331 | } 332 | } 333 | 334 | /** 335 | * 用于存储在 {@link Internal#RULES} 中的对象,它包含了一条排序规则,以及规则优先级和名称。 336 | * 337 | * @param sortingRule 需要被包含的排序规则。 338 | * @param priority 优先级。 339 | * @param name 规则名称,不会显示,主要用于调试。 340 | */ 341 | record SortingRuleContainer(@NotNull SortingRule sortingRule, int priority, @Nullable @NonNls String name) { 342 | @Override 343 | public String toString() { 344 | return "SortingRuleContainer[" + (name == null ? sortingRule.toString() : name) + ", priority=" + priority + "]"; 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /common/src/main/java/pers/solid/mod/ConfigScreen.java: -------------------------------------------------------------------------------- 1 | package pers.solid.mod; 2 | 3 | import me.shedaniel.clothconfig2.api.ConfigBuilder; 4 | import me.shedaniel.clothconfig2.api.ConfigCategory; 5 | import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; 6 | import net.fabricmc.api.EnvType; 7 | import net.fabricmc.api.Environment; 8 | import net.minecraft.client.gui.screen.Screen; 9 | import net.minecraft.data.family.BlockFamily; 10 | import net.minecraft.item.ItemGroup; 11 | import net.minecraft.text.Text; 12 | import net.minecraft.text.Texts; 13 | import net.minecraft.util.Formatting; 14 | import net.minecraft.util.Identifier; 15 | import org.apache.commons.lang3.StringUtils; 16 | 17 | import java.util.*; 18 | import java.util.stream.Collectors; 19 | 20 | /** 21 | * @author SolidBlock 22 | */ 23 | @Environment(EnvType.CLIENT) 24 | public class ConfigScreen { 25 | public static List formatted(List list) { 26 | List newList = new ArrayList<>(); 27 | for (String s : list) { 28 | newList.add(Arrays.stream(s.split("\\s+")) 29 | .map(Identifier::tryParse) 30 | .filter(Objects::nonNull) 31 | .map(Identifier::toString) 32 | .collect(Collectors.joining(" "))); 33 | } 34 | return newList; 35 | } 36 | 37 | /** 38 | * 模组配置屏幕创建。 39 | * 40 | * @param previousScreen 上层屏幕。 41 | * @return 配置屏幕。 42 | */ 43 | public Screen createScreen(Screen previousScreen) { 44 | final Configs config = Configs.CONFIG_HOLDER.getConfig(); 45 | ConfigBuilder builder = ConfigBuilder.create() 46 | .setParentScreen(previousScreen) 47 | .setSavingRunnable(Configs.CONFIG_HOLDER::save) 48 | .setTitle(Text.translatable("title.reasonable-sorting.config")); 49 | 50 | ConfigEntryBuilder entryBuilder = builder.entryBuilder(); 51 | ConfigCategory categorySorting = 52 | builder.getOrCreateCategory(Text.translatable("category.reasonable-sorting.sorting")); 53 | categorySorting.setDescription( 54 | new Text[]{ 55 | Text.translatable("category.reasonable-sorting.sorting.description") 56 | }); 57 | ConfigCategory categoryTransfer = 58 | builder.getOrCreateCategory(Text.translatable("category.reasonable-sorting.transfer")); 59 | categoryTransfer.setDescription( 60 | new Text[]{ 61 | Text.translatable("category.reasonable-sorting.transfer.description") 62 | }); 63 | 64 | // 排序部分。 65 | categorySorting.addEntry( 66 | entryBuilder 67 | .startTextDescription( 68 | Text.translatable("category.reasonable-sorting.sorting.description")) 69 | .build()); 70 | 71 | categorySorting.addEntry( 72 | entryBuilder 73 | .startBooleanToggle( 74 | Text.translatable("option.reasonable-sorting.enable_sorting"), 75 | config.enableSorting) 76 | .setTooltip(Text.translatable("option.reasonable-sorting.enable_sorting.tooltip")) 77 | .setYesNoTextSupplier( 78 | b -> Text.translatable(b ? "text.reasonable-sorting.enabled" : "text.reasonable-sorting.disabled")) 79 | .setDefaultValue(true) 80 | .setSaveConsumer(b -> config.enableSorting = b) 81 | .build()); 82 | 83 | categorySorting.addEntry( 84 | entryBuilder 85 | .startEnumSelector( 86 | Text.translatable("option.reasonable-sorting.sorting_influence_range"), 87 | SortingInfluenceRange.class, 88 | config.sortingInfluenceRange) 89 | .setTooltip(Text.translatable("option.reasonable-sorting.sorting_influence_range.tooltip").append("\n").append( 90 | Texts.join(Arrays.stream(SortingInfluenceRange.values()) 91 | .map(v -> Text.literal(" - ").append(v.getName().formatted(Formatting.YELLOW)).append(" - ").append(v.getDescription().formatted(Formatting.GRAY))) 92 | .toList(), Text.literal("\n")) 93 | )) 94 | .setEnumNameProvider(anEnum -> ((SortingInfluenceRange) anEnum).getName()) 95 | .setDefaultValue(SortingInfluenceRange.INVENTORY_ONLY) 96 | .setSaveConsumer(b -> config.sortingInfluenceRange = b) 97 | .build()); 98 | categorySorting.addEntry( 99 | entryBuilder 100 | .startEnumSelector( 101 | Text.translatable("option.reasonable-sorting.sorting_calculation_type"), 102 | SortingCalculationType.class, 103 | config.sortingCalculationType) 104 | .setTooltip(Text.translatable("option.reasonable-sorting.sorting_calculation_type.tooltip").append("\n").append( 105 | Texts.join(Arrays.stream(SortingCalculationType.values()) 106 | .map(v -> Text.literal(" - ").append(v.getName().formatted(Formatting.YELLOW)).append(" - ").append(v.getDescription().formatted(Formatting.GRAY))) 107 | .toList(), Text.literal("\n")) 108 | )) 109 | .setEnumNameProvider(anEnum -> ((SortingCalculationType) anEnum).getName()) 110 | .setDefaultValue(SortingCalculationType.STANDARD) 111 | .setSaveConsumer(b -> config.sortingCalculationType = b) 112 | .build()); 113 | 114 | categorySorting.addEntry( 115 | entryBuilder 116 | .startBooleanToggle( 117 | Text.translatable("option.reasonable-sorting.debug_mode"), 118 | config.debugMode) 119 | .setTooltip(Text.translatable("option.reasonable-sorting.debug_mode.tooltip")) 120 | .setYesNoTextSupplier( 121 | b -> Text.translatable(b ? "text.reasonable-sorting.enabled" : "text.reasonable-sorting.disabled")) 122 | .setDefaultValue(false) 123 | .setSaveConsumer(b -> config.debugMode = b) 124 | .build()); 125 | 126 | categorySorting.addEntry( 127 | entryBuilder 128 | .startBooleanToggle( 129 | Text.translatable("option.reasonable-sorting.enable_default_item_sorting_rules"), 130 | config.enableDefaultItemSortingRules) 131 | .setTooltip( 132 | Text.translatable( 133 | "option.reasonable-sorting.enable_default_item_sorting_rules.tooltip")) 134 | .setYesNoTextSupplier( 135 | b -> Text.translatable(b ? "text.reasonable-sorting.enabled" : "text.reasonable-sorting.disabled")) 136 | .setDefaultValue(true) 137 | .setSaveConsumer(b -> config.enableDefaultItemSortingRules = b) 138 | .build()); 139 | 140 | categorySorting.addEntry( 141 | entryBuilder 142 | .startStrList( 143 | Text.translatable("option.reasonable-sorting.custom_sorting_rules"), 144 | config.customSortingRules) 145 | .setTooltip( 146 | Text.translatable("option.reasonable-sorting.custom_sorting_rules.tooltip"), 147 | Text.translatable("option.reasonable-sorting.custom_sorting_rules.example")) 148 | .setInsertInFront(false) 149 | .setExpanded(true) 150 | .setAddButtonTooltip( 151 | Text.translatable("option.reasonable-sorting.custom_sorting_rules.add")) 152 | .setRemoveButtonTooltip( 153 | Text.translatable("option.reasonable-sorting.custom_sorting_rules.remove")) 154 | .setDefaultValue(Collections.emptyList()) 155 | .setCellErrorSupplier(ConfigsHelper::validateCustomSortingRule) 156 | .setSaveConsumer( 157 | list -> { 158 | config.customSortingRules = formatted(list); 159 | ConfigsHelper.updateCustomSortingRules(list, Configs.CUSTOM_ITEM_SORTING_RULES); 160 | }) 161 | .build()); 162 | 163 | categorySorting.addEntry( 164 | entryBuilder 165 | .startTextDescription(Text.translatable( 166 | "option.reasonable-sorting.describe_variants", 167 | Text.literal( 168 | Arrays.stream(BlockFamily.Variant.values()) 169 | .map(BlockFamily.Variant::getName) 170 | .collect(Collectors.joining(StringUtils.SPACE))) 171 | .formatted(Formatting.YELLOW))) 172 | .build()); 173 | 174 | categorySorting.addEntry( 175 | entryBuilder 176 | .startStrField( 177 | Text.translatable("option.reasonable-sorting.variants_following_base_blocks"), 178 | config.variantsFollowingBaseBlocks) 179 | .setDefaultValue("stairs slab") 180 | .setTooltip( 181 | Text.translatable("option.reasonable-sorting.variants_following_base_blocks.tooltip")) 182 | .setErrorSupplier(ConfigsHelper::validateVariantFollowsBaseBlocks) 183 | .setSaveConsumer( 184 | s -> { 185 | config.variantsFollowingBaseBlocks = s; 186 | ConfigsHelper.updateVariantsFollowingBaseBlocks(s, Configs.VARIANTS_FOLLOWING_BASE_BLOCKS); 187 | }) 188 | .build()); 189 | 190 | if (ExtShapeBridge.INSTANCE.modHasLoaded()) { 191 | categorySorting.addEntry( 192 | entryBuilder 193 | .startTextDescription( 194 | Text.translatable( 195 | "option.reasonable-sorting.describe_shapes", 196 | Text.literal(ExtShapeBridge.INSTANCE.getValidShapeNames().collect(Collectors.joining(" "))) 197 | .formatted(Formatting.YELLOW))) 198 | .build()); 199 | categorySorting.addEntry( 200 | entryBuilder 201 | .startStrField( 202 | Text.translatable("option.reasonable-sorting.shapes_following_base_blocks"), 203 | config.shapesFollowingBaseBlocks) 204 | .setDefaultValue("*") 205 | .setTooltip( 206 | Text.translatable( 207 | "option.reasonable-sorting.shapes_following_base_blocks.tooltip")) 208 | .setErrorSupplier(ConfigsHelper::validateShapeFollowsBaseBlocks) 209 | .setSaveConsumer( 210 | s3 -> { 211 | config.shapesFollowingBaseBlocks = s3; 212 | ExtShapeBridge.INSTANCE.updateShapeList(s3); 213 | }) 214 | .build()); 215 | } 216 | 217 | categorySorting.addEntry( 218 | entryBuilder 219 | .startBooleanToggle( 220 | Text.translatable("option.reasonable-sorting.fence_gate_follows_fence"), 221 | config.fenceGateFollowsFence) 222 | .setSaveConsumer(b -> config.fenceGateFollowsFence = b) 223 | .setDefaultValue(true) 224 | .setTooltip( 225 | Text.translatable("option.reasonable-sorting.fence_gate_follows_fence.tooltip")) 226 | .build()); 227 | categorySorting.addEntry( 228 | entryBuilder 229 | .startBooleanToggle( 230 | Text.translatable("option.reasonable-sorting.fancy_color_sorting"), 231 | config.fancyColorsSorting) 232 | .setSaveConsumer(b -> config.fancyColorsSorting = b) 233 | .setDefaultValue(true) 234 | .setTooltip( 235 | Text.translatable("option.reasonable-sorting.fancy_color_sorting.tooltip")) 236 | .build()); 237 | categorySorting.addEntry( 238 | entryBuilder 239 | .startBooleanToggle( 240 | Text.translatable("option.reasonable-sorting.block_items_only"), 241 | config.blockItemsOnly) 242 | .setSaveConsumer(b -> config.blockItemsOnly = b) 243 | .setDefaultValue(false) 244 | .setTooltip( 245 | Text.translatable("option.reasonable-sorting.block_items_only.tooltip")) 246 | .build()); 247 | 248 | // 物品组转移部分。 249 | categoryTransfer.addEntry( 250 | entryBuilder 251 | .startTextDescription( 252 | Text.translatable("category.reasonable-sorting.transfer.description")) 253 | .build()); 254 | 255 | categoryTransfer.addEntry( 256 | entryBuilder 257 | .startBooleanToggle( 258 | Text.translatable("option.reasonable-sorting.enable_group_transfer"), 259 | config.enableGroupTransfer) 260 | .setDefaultValue(true) 261 | .setTooltip( 262 | Text.translatable("option.reasonable-sorting.enable_group_transfer.tooltip")) 263 | .setYesNoTextSupplier( 264 | b -> Text.translatable(b ? "text.reasonable-sorting.enabled" : "text.reasonable-sorting.disabled")) 265 | .setSaveConsumer(b -> config.enableGroupTransfer = b) 266 | .build()); 267 | 268 | categoryTransfer.addEntry( 269 | entryBuilder 270 | .startBooleanToggle( 271 | Text.translatable("option.reasonable-sorting.buttons_in_decorations"), 272 | config.buttonsInDecorations) 273 | .setDefaultValue(false) 274 | .setSaveConsumer(b -> config.buttonsInDecorations = b) 275 | .build()); 276 | categoryTransfer.addEntry( 277 | entryBuilder 278 | .startBooleanToggle( 279 | Text.translatable("option.reasonable-sorting.fence_gates_in_decorations"), 280 | config.fenceGatesInDecorations) 281 | .setDefaultValue(true) 282 | .setSaveConsumer(b -> config.fenceGatesInDecorations = b) 283 | .build()); 284 | categoryTransfer.addEntry( 285 | entryBuilder 286 | .startBooleanToggle( 287 | Text.translatable("option.reasonable-sorting.swords_in_tools"), 288 | config.swordsInTools) 289 | .setDefaultValue(false) 290 | .setSaveConsumer(b -> config.swordsInTools = b) 291 | .build()); 292 | categoryTransfer.addEntry( 293 | entryBuilder 294 | .startBooleanToggle( 295 | Text.translatable("option.reasonable-sorting.doors_in_decorations"), 296 | config.doorsInDecorations) 297 | .setDefaultValue(false) 298 | .setSaveConsumer(b -> config.doorsInDecorations = b) 299 | .build()); 300 | 301 | categoryTransfer.addEntry( 302 | entryBuilder 303 | .startTextDescription( 304 | Text.translatable( 305 | "option.reasonable-sorting.describe_item_groups", 306 | Text.literal(Arrays.stream(ItemGroup.GROUPS).map(ItemGroup::getName).collect(Collectors.joining(" "))) 307 | .formatted(Formatting.YELLOW))) 308 | .build()); 309 | 310 | categoryTransfer.addEntry( 311 | entryBuilder 312 | .startStrList( 313 | Text.translatable("option.reasonable-sorting.custom_transfer_rules"), 314 | config.transferRules) 315 | .setTooltip( 316 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.tooltip")) 317 | .setExpanded(true) 318 | .setInsertInFront(false) 319 | .setAddButtonTooltip( 320 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.add")) 321 | .setRemoveButtonTooltip( 322 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.remove")) 323 | .setCellErrorSupplier(ConfigsHelper::validateCustomTransferRule) 324 | .setDefaultValue(Collections.emptyList()) 325 | .setSaveConsumer( 326 | list -> { 327 | config.transferRules = list; 328 | ConfigsHelper.updateCustomTransferRule(list, Configs.CUSTOM_TRANSFER_RULE); 329 | }) 330 | .build()); 331 | 332 | categoryTransfer.addEntry( 333 | entryBuilder 334 | .startStrList( 335 | Text.translatable("option.reasonable-sorting.custom_variant_transfer_rules"), 336 | config.variantTransferRules) 337 | .setTooltip( 338 | Text.translatable( 339 | "option.reasonable-sorting.custom_variant_transfer_rules.tooltip")) 340 | .setExpanded(true) 341 | .setInsertInFront(false) 342 | .setAddButtonTooltip( 343 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.add")) 344 | .setRemoveButtonTooltip( 345 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.remove")) 346 | .setCellErrorSupplier(ConfigsHelper::validateCustomVariantTransferRule) 347 | .setDefaultValue(Collections.emptyList()) 348 | .setSaveConsumer( 349 | list -> { 350 | config.variantTransferRules = list; 351 | ConfigsHelper.updateCustomVariantTransferRules( 352 | list, Configs.CUSTOM_VARIANT_TRANSFER_RULE); 353 | }) 354 | .build()); 355 | 356 | categoryTransfer.addEntry( 357 | entryBuilder 358 | .startStrList( 359 | Text.translatable("option.reasonable-sorting.custom_regex_transfer_rules"), 360 | config.regexTransferRules) 361 | .setTooltip( 362 | Text.translatable( 363 | "option.reasonable-sorting.custom_regex_transfer_rules.tooltip")) 364 | .setExpanded(true) 365 | .setInsertInFront(false) 366 | .setAddButtonTooltip( 367 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.add")) 368 | .setRemoveButtonTooltip( 369 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.remove")) 370 | .setCellErrorSupplier(ConfigsHelper::validateCustomRegexTransferRule) 371 | .setDefaultValue(Collections.emptyList()) 372 | .setSaveConsumer( 373 | list -> { 374 | config.regexTransferRules = list; 375 | ConfigsHelper.updateCustomRegexTransferRules(list, Configs.CUSTOM_REGEX_TRANSFER_RULE); 376 | }) 377 | .build()); 378 | 379 | categoryTransfer.addEntry( 380 | entryBuilder 381 | .startStrList( 382 | Text.translatable("option.reasonable-sorting.custom_tag_transfer_rules"), 383 | config.tagTransferRules) 384 | .setTooltip( 385 | Text.translatable( 386 | "option.reasonable-sorting.custom_tag_transfer_rules.tooltip")) 387 | .setExpanded(true) 388 | .setInsertInFront(false) 389 | .setAddButtonTooltip( 390 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.add")) 391 | .setRemoveButtonTooltip( 392 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.remove")) 393 | .setCellErrorSupplier(ConfigsHelper::validateCustomTagTransferRule) 394 | .setDefaultValue(Collections.emptyList()) 395 | .setSaveConsumer( 396 | list -> { 397 | config.tagTransferRules = list; 398 | ConfigsHelper.updateCustomTagTransferRules(list, Configs.CUSTOM_TAG_TRANSFER_RULE); 399 | }) 400 | .build()); 401 | 402 | if (ExtShapeBridge.INSTANCE.modHasLoaded()) { 403 | categoryTransfer 404 | .addEntry( 405 | entryBuilder 406 | .startStrList( 407 | Text.translatable("option.reasonable-sorting.custom_shape_transfer_rules"), 408 | config.shapeTransferRules) 409 | .setDefaultValue(Collections.emptyList()) 410 | .setTooltip( 411 | Text.translatable( 412 | "option.reasonable-sorting.custom_shape_transfer_rules.tooltip")) 413 | .setExpanded(true) 414 | .setInsertInFront(false) 415 | .setAddButtonTooltip( 416 | Text.translatable("option.reasonable-sorting.custom_transfer_rules.add")) 417 | .setRemoveButtonTooltip( 418 | Text.translatable( 419 | "option.reasonable-sorting.custom_transfer_rules.remove")) 420 | .setCellErrorSupplier(ConfigsHelper::validateCustomShapeTransferRule) 421 | .setSaveConsumer( 422 | list -> { 423 | config.shapeTransferRules = list; 424 | ExtShapeBridge.INSTANCE.updateShapeTransferRules(list); 425 | }) 426 | .build()) 427 | .addEntry( 428 | entryBuilder 429 | .startBooleanToggle( 430 | Text.translatable( 431 | "option.reasonable-sorting.base_blocks_in_building_blocks"), 432 | config.baseBlocksInBuildingBlocks) 433 | .setDefaultValue(true) 434 | .setTooltip( 435 | Text.translatable( 436 | "option.reasonable-sorting.base_blocks_in_building_blocks.tooltip")) 437 | .setSaveConsumer( 438 | b -> config.baseBlocksInBuildingBlocks = b) 439 | .build()); 440 | } 441 | 442 | return builder.build(); 443 | } 444 | } 445 | --------------------------------------------------------------------------------