diff --git a/patches/server/0004-Leaves-Server-Config-And-Command.patch b/patches/server/0004-Leaves-Server-Config-And-Command.patch index 019fe150..cfcb04fc 100644 --- a/patches/server/0004-Leaves-Server-Config-And-Command.patch +++ b/patches/server/0004-Leaves-Server-Config-And-Command.patch @@ -129,10 +129,10 @@ index e1c99d941c7bb954bf3ac83d5002dbf58fd833b0..4760b943da08771a42fcb22eba4d586d .withRequiredArg() diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..aad482669709aad45d803a43c0669478f2ec5b9b +index 0000000000000000000000000000000000000000..da736450a6abc10d800f7d2ef97bfc33b2b9b814 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java -@@ -0,0 +1,998 @@ +@@ -0,0 +1,1003 @@ +package top.leavesmc.leaves; + +import com.destroystokyo.paper.util.SneakyThrow; @@ -956,6 +956,11 @@ index 0000000000000000000000000000000000000000..aad482669709aad45d803a43c0669478 + public static void registerLeavesFeatures() { + } + ++ public static boolean hopperCounter = false; ++ private static void hopperCounter() { ++ hopperCounter = getBoolean("settings.modify.hopper-counter", hopperCounter); ++ } ++ + public static final class WorldConfig { + + public final String worldName; diff --git a/patches/server/0077-Bladeren-mspt-sync-protocol.patch b/patches/server/0077-Bladeren-mspt-sync-protocol.patch index 57d645b0..627710b9 100644 --- a/patches/server/0077-Bladeren-mspt-sync-protocol.patch +++ b/patches/server/0077-Bladeren-mspt-sync-protocol.patch @@ -41,7 +41,7 @@ index 47b27712c6ed11fc5c2cd7de04482870207545e7..93054a59f14fee933a908944e93b462b public boolean getCommandBlockOverride(String command) { diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java -index e14aeb52a17ac174c22f95294708129db84a521b..a89cb107ed8c405548172f720b4481c2bf6950a8 100644 +index f87479eb24fb244c5f2c4ee2f307b43187a0f33c..8356766f2bc1b1f53f4912feb507bab61328c0d3 100644 --- a/src/main/java/top/leavesmc/leaves/LeavesConfig.java +++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java @@ -845,6 +845,7 @@ public final class LeavesConfig { @@ -51,7 +51,7 @@ index e14aeb52a17ac174c22f95294708129db84a521b..a89cb107ed8c405548172f720b4481c2 + LeavesFeatureSet.register(LeavesFeature.of("mspt_sync", msptSyncProtocol)); } - public static final class WorldConfig { + public static boolean hopperCounter = false; diff --git a/src/main/java/top/leavesmc/leaves/protocol/bladeren/MsptSyncProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/bladeren/MsptSyncProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..5c206ecc6aba1ef632467f85ea83f909486ced29 diff --git a/patches/server/0081-Lava-riptide.patch b/patches/server/0081-Lava-riptide.patch index 5a97741d..3bb3cee5 100644 --- a/patches/server/0081-Lava-riptide.patch +++ b/patches/server/0081-Lava-riptide.patch @@ -27,7 +27,7 @@ index 8078f127ff4b6e0aafb5804b9c02e237f79445b5..801f066878d6ffe5dabe01d20513db90 } else { user.startUsingItem(hand); diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java -index 1ce04475e600d527aaf31fab6cfa6bdb1a5ac2fb..7641ecc09a9795d48049731f1f0f0013981fcae1 100644 +index 3573a86cf287d01cb3f8ca195d8a8de2df9e981f..62c7e7e2a655a3b06dd8b2b1bb41aa981e40a812 100644 --- a/src/main/java/top/leavesmc/leaves/LeavesConfig.java +++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java @@ -849,6 +849,7 @@ public final class LeavesConfig { @@ -37,4 +37,4 @@ index 1ce04475e600d527aaf31fab6cfa6bdb1a5ac2fb..7641ecc09a9795d48049731f1f0f0013 + LeavesFeatureSet.register(LeavesFeature.of("lava_riptide", lavaRiptide)); } - public static final class WorldConfig { + public static boolean hopperCounter = false; diff --git a/patches/server/0122-Wool-Hopper-Counter.patch b/patches/server/0122-Wool-Hopper-Counter.patch new file mode 100644 index 00000000..d155177f --- /dev/null +++ b/patches/server/0122-Wool-Hopper-Counter.patch @@ -0,0 +1,591 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Mon, 4 Sep 2023 00:16:09 +0800 +Subject: [PATCH] Wool Hopper Counter + +This patch is Powered by fabric-carpet(https://github.com/gnembon/fabric-carpet) + +diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +index cd7ea0c16f9ddcb84b5d7e8a2533e6e84f3879c7..2eea998e771f2cc63c31f9c78bfc19a3c3782d00 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java ++++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +@@ -33,7 +33,7 @@ import net.minecraft.world.level.ItemLike; + public final class Ingredient implements Predicate { + + public static final Ingredient EMPTY = new Ingredient(Stream.empty()); +- private final Ingredient.Value[] values; ++ public final Ingredient.Value[] values; // Leaves - private -> public + @Nullable + public ItemStack[] itemStacks; + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index fa19bc9f9ac90fb6c0e687c72c253324a312fd51..47089c6f79133e5adbe500b3b589cb632cf1915b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -405,6 +405,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + // Paper end + + private static boolean ejectItems(Level world, BlockPos blockposition, BlockState iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit ++ // Leaves start - hopper counter ++ if (top.leavesmc.leaves.util.HopperCounter.isEnabled()) { ++ if (woolHopperCounter(world, blockposition, iblockdata, iinventory, hopper)) { ++ return true; ++ } ++ } ++ // Leaves end - hopper counter + Container iinventory1 = HopperBlockEntity.getAttachedContainer(world, blockposition, iblockdata); + + if (iinventory1 == null) { +@@ -462,6 +469,23 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + ++ // Leaves start - hopper counter ++ private static boolean woolHopperCounter(Level level, BlockPos blockPos, BlockState state, Container container, HopperBlockEntity hopper) { ++ net.minecraft.world.item.DyeColor woolColor = top.leavesmc.leaves.util.WoolUtils.getWoolColorAtPosition(level, blockPos.relative(state.getValue(HopperBlock.FACING))); ++ if (woolColor != null) { ++ for (int i = 0; i < container.getContainerSize(); ++i) { ++ if (!container.getItem(i).isEmpty()) { ++ ItemStack itemstack = container.getItem(i); ++ top.leavesmc.leaves.util.HopperCounter.getCounter(woolColor).add(level.getServer(), itemstack); ++ container.setItem(i, ItemStack.EMPTY); ++ } ++ } ++ return true; ++ } ++ return false; ++ } ++ // Leaves end - hopper counter ++ + private static IntStream getSlots(Container inventory, Direction side) { + return inventory instanceof WorldlyContainer ? IntStream.of(((WorldlyContainer) inventory).getSlotsForFace(side)) : IntStream.range(0, inventory.getContainerSize()); + } +diff --git a/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java b/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java +index 0259a1472bba36b5bb903e559a99f791e8a9317a..258c6c97eb8cc86ee53569377c3c472f87293334 100644 +--- a/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java ++++ b/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java +@@ -12,6 +12,7 @@ import org.bukkit.permissions.PermissionDefault; + import org.bukkit.plugin.PluginManager; + import org.checkerframework.checker.nullness.qual.Nullable; + import org.jetbrains.annotations.NotNull; ++import top.leavesmc.leaves.command.subcommands.CounterCommand; + import top.leavesmc.leaves.command.subcommands.PeacefulModeSwitchCommand; + import top.leavesmc.leaves.command.subcommands.TickCommand; + import top.leavesmc.leaves.command.subcommands.UpdateCommand; +@@ -37,6 +38,7 @@ public final class LeavesCommand extends Command { + commands.put(Set.of("update"), new UpdateCommand()); + commands.put(Set.of("peaceful"), new PeacefulModeSwitchCommand()); + commands.put(Set.of("tick"), new TickCommand()); ++ commands.put(Set.of("counter"), new CounterCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/top/leavesmc/leaves/command/subcommands/CounterCommand.java b/src/main/java/top/leavesmc/leaves/command/subcommands/CounterCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..24eecf862f3f5fab678813de7de8d3ba7af467fb +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/command/subcommands/CounterCommand.java +@@ -0,0 +1,121 @@ ++package top.leavesmc.leaves.command.subcommands; ++ ++import io.papermc.paper.command.CommandUtil; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.item.DyeColor; ++import org.bukkit.command.CommandSender; ++import org.jetbrains.annotations.NotNull; ++import top.leavesmc.leaves.LeavesConfig; ++import top.leavesmc.leaves.command.LeavesSubcommand; ++import top.leavesmc.leaves.util.HopperCounter; ++ ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++ ++public class CounterCommand implements LeavesSubcommand { ++ ++ @Override ++ public boolean execute(CommandSender sender, String subCommand, String[] args) { ++ if (!LeavesConfig.hopperCounter) { ++ return false; ++ } ++ ++ if (args.length < 1) { ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Hopper Counter: ", NamedTextColor.GRAY), ++ Component.text(HopperCounter.isEnabled(), HopperCounter.isEnabled() ? NamedTextColor.AQUA : NamedTextColor.GRAY) ++ )); ++ return true; ++ } ++ ++ if (!HopperCounter.isEnabled()) { ++ if (args[0].equals("enable")) { ++ HopperCounter.setEnabled(true); ++ sender.sendMessage(Component.text("Hopper Counter now is enabled", NamedTextColor.AQUA)); ++ } else { ++ sender.sendMessage(Component.text("Hopper Counter is not enabled", NamedTextColor.RED)); ++ } ++ return true; ++ } ++ ++ DyeColor color = DyeColor.byName(args[0], null); ++ if (color != null) { ++ HopperCounter counter = HopperCounter.getCounter(color); ++ if (args.length < 2) { ++ displayCounter(sender, counter, false); ++ } else { ++ switch (args[1]) { ++ case "reset" -> { ++ counter.reset(MinecraftServer.getServer()); ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Restarted "), ++ Component.text(color.getName(), TextColor.color(color.getTextColor())), ++ Component.text(" counter") ++ )); ++ } ++ case "realtime" -> displayCounter(sender, counter, true); ++ } ++ } ++ return true; ++ } ++ ++ switch (args[0]) { ++ case "reset" -> { ++ HopperCounter.resetAll(MinecraftServer.getServer(), false); ++ sender.sendMessage(Component.text("Restarted all counters")); ++ } ++ case "disable" -> { ++ HopperCounter.setEnabled(false); ++ HopperCounter.resetAll(MinecraftServer.getServer(), true); ++ sender.sendMessage(Component.text("Hopper Counter now is disabled", NamedTextColor.GRAY)); ++ } ++ } ++ ++ return true; ++ } ++ ++ private void displayCounter(CommandSender sender, @NotNull HopperCounter counter, boolean realTime) { ++ for (Component component : counter.format(MinecraftServer.getServer(), realTime)) { ++ sender.sendMessage(component); ++ } ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String subCommand, String[] args) { ++ if (!LeavesConfig.hopperCounter) { ++ return Collections.emptyList(); ++ } ++ ++ switch (args.length) { ++ case 1 -> { ++ if (!HopperCounter.isEnabled()) { ++ return Collections.singletonList("enable"); ++ } ++ ++ List list = new ArrayList<>(Arrays.stream(DyeColor.values()).map(DyeColor::getName).toList()); ++ list.add("reset"); ++ list.add("disable"); ++ return CommandUtil.getListMatchingLast(sender, args, list); ++ } ++ ++ case 2 -> { ++ if (DyeColor.byName(args[0], null) != null) { ++ return CommandUtil.getListMatchingLast(sender, args, "reset", "realtime"); ++ } ++ } ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean tabCompletes() { ++ return LeavesConfig.hopperCounter; ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/util/HopperCounter.java b/src/main/java/top/leavesmc/leaves/util/HopperCounter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2733cc3c97e877f8c9ee58aa9c81d1a8f87caa20 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/util/HopperCounter.java +@@ -0,0 +1,332 @@ ++package top.leavesmc.leaves.util; ++ ++import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2LongMap; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.TextComponent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.Style; ++import net.kyori.adventure.text.format.TextColor; ++import net.kyori.adventure.text.format.TextDecoration; ++import net.minecraft.core.Registry; ++import net.minecraft.core.RegistryAccess; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.item.BlockItem; ++import net.minecraft.world.item.DyeColor; ++import net.minecraft.world.item.DyeItem; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.item.crafting.Ingredient; ++import net.minecraft.world.item.crafting.Recipe; ++import net.minecraft.world.item.crafting.RecipeManager; ++import net.minecraft.world.item.crafting.RecipeType; ++import net.minecraft.world.level.block.AbstractBannerBlock; ++import net.minecraft.world.level.block.BeaconBeamBlock; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.material.MapColor; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.EnumMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import java.util.stream.Collectors; ++ ++import static java.util.Map.entry; ++ ++public class HopperCounter { ++ ++ private static boolean enabled = false; ++ private static final Map COUNTERS; ++ ++ static { ++ EnumMap counterMap = new EnumMap<>(DyeColor.class); ++ for (DyeColor color : DyeColor.values()) { ++ counterMap.put(color, new HopperCounter(color)); ++ } ++ COUNTERS = Collections.unmodifiableMap(counterMap); ++ } ++ ++ public final DyeColor color; ++ private final TextComponent coloredName; ++ private final Object2LongMap counter = new Object2LongLinkedOpenHashMap<>(); ++ private long startTick; ++ private long startMillis; ++ ++ private HopperCounter(DyeColor color) { ++ this.startTick = -1; ++ this.color = color; ++ this.coloredName = Component.text(color.getName(), TextColor.color(color.getTextColor())); ++ } ++ ++ public void add(MinecraftServer server, ItemStack stack) { ++ if (startTick < 0) { ++ startTick = server.overworld().getGameTime(); ++ startMillis = System.currentTimeMillis(); ++ } ++ Item item = stack.getItem(); ++ counter.put(item, counter.getLong(item) + stack.getCount()); ++ } ++ ++ public void reset(MinecraftServer server) { ++ counter.clear(); ++ startTick = server.overworld().getGameTime(); ++ startMillis = System.currentTimeMillis(); ++ } ++ ++ public List format(MinecraftServer server, boolean realTime) { ++ long ticks = Math.max(realTime ? (System.currentTimeMillis() - startMillis) / 50 : server.overworld().getGameTime() - startTick, -1); ++ ++ if (startTick < 0 || ticks == -1) { ++ return Collections.singletonList(Component.text().append(coloredName, Component.text(" hasn't started counting yet")).build()); ++ } ++ ++ long total = getTotalItems(); ++ if (total <= 0) { ++ return Collections.singletonList(Component.text() ++ .append(Component.text("No items for "), coloredName) ++ .append(Component.text(" yet ("), Component.text(String.format("%.2f ", ticks / (20.0 * 60.0)), Style.style(TextDecoration.BOLD))) ++ .append(Component.text("min"), Component.text(realTime ? " - real time" : ""), Component.text(")")) ++ .build()); ++ } ++ ++ List items = new ArrayList<>(); ++ items.add(Component.text() ++ .append(Component.text("Items for "), coloredName, Component.text(" ")) ++ .append(Component.text("("), Component.text(String.format("%.2f ", ticks * 1.0 / (20 * 60)), Style.style(TextDecoration.BOLD))) ++ .append(Component.text("min"), Component.text(realTime ? " - real time" : ""), Component.text("), ")) ++ .append(Component.text("total: "), Component.text(total, Style.style(TextDecoration.BOLD)), Component.text(", ")) ++ .append(Component.text("("), Component.text(String.format("%.1f", total * 1.0 * (20 * 60 * 60) / ticks), Style.style(TextDecoration.BOLD))) ++ .append(Component.text("/h):")) ++ .build()); ++ ++ counter.object2LongEntrySet().forEach(entry -> { ++ Item item = entry.getKey(); ++ Component name = Component.translatable(item.getDescriptionId()); ++ TextColor textColor = guessColor(server, item); ++ ++ if (textColor != null) { ++ name = name.style(name.style().merge(Style.style(textColor))); ++ } else { ++ name = name.style(name.style().merge(Style.style(TextDecoration.ITALIC))); ++ } ++ ++ long count = entry.getLongValue(); ++ items.add(Component.text() ++ .append(Component.text("- ", NamedTextColor.GRAY)) ++ .append(name) ++ .append(Component.text(": ", NamedTextColor.GRAY)) ++ .append(Component.text(count, Style.style(TextDecoration.BOLD)), Component.text(", ", NamedTextColor.GRAY)) ++ .append(Component.text(String.format("%.1f", count * (20.0 * 60.0 * 60.0) / ticks), Style.style(TextDecoration.BOLD))) ++ .append(Component.text("/h")) ++ .build()); ++ }); ++ return items; ++ } ++ ++ private static final Map DEFAULTS = Map.ofEntries( ++ entry(Items.DANDELION, Blocks.YELLOW_WOOL), ++ entry(Items.POPPY, Blocks.RED_WOOL), ++ entry(Items.BLUE_ORCHID, Blocks.LIGHT_BLUE_WOOL), ++ entry(Items.ALLIUM, Blocks.MAGENTA_WOOL), ++ entry(Items.AZURE_BLUET, Blocks.SNOW_BLOCK), ++ entry(Items.RED_TULIP, Blocks.RED_WOOL), ++ entry(Items.ORANGE_TULIP, Blocks.ORANGE_WOOL), ++ entry(Items.WHITE_TULIP, Blocks.SNOW_BLOCK), ++ entry(Items.PINK_TULIP, Blocks.PINK_WOOL), ++ entry(Items.OXEYE_DAISY, Blocks.SNOW_BLOCK), ++ entry(Items.CORNFLOWER, Blocks.BLUE_WOOL), ++ entry(Items.WITHER_ROSE, Blocks.BLACK_WOOL), ++ entry(Items.LILY_OF_THE_VALLEY, Blocks.WHITE_WOOL), ++ entry(Items.BROWN_MUSHROOM, Blocks.BROWN_MUSHROOM_BLOCK), ++ entry(Items.RED_MUSHROOM, Blocks.RED_MUSHROOM_BLOCK), ++ entry(Items.STICK, Blocks.OAK_PLANKS), ++ entry(Items.GOLD_INGOT, Blocks.GOLD_BLOCK), ++ entry(Items.IRON_INGOT, Blocks.IRON_BLOCK), ++ entry(Items.DIAMOND, Blocks.DIAMOND_BLOCK), ++ entry(Items.NETHERITE_INGOT, Blocks.NETHERITE_BLOCK), ++ entry(Items.SUNFLOWER, Blocks.YELLOW_WOOL), ++ entry(Items.LILAC, Blocks.MAGENTA_WOOL), ++ entry(Items.ROSE_BUSH, Blocks.RED_WOOL), ++ entry(Items.PEONY, Blocks.PINK_WOOL), ++ entry(Items.CARROT, Blocks.ORANGE_WOOL), ++ entry(Items.APPLE, Blocks.RED_WOOL), ++ entry(Items.WHEAT, Blocks.HAY_BLOCK), ++ entry(Items.PORKCHOP, Blocks.PINK_WOOL), ++ entry(Items.RABBIT, Blocks.PINK_WOOL), ++ entry(Items.CHICKEN, Blocks.WHITE_TERRACOTTA), ++ entry(Items.BEEF, Blocks.NETHERRACK), ++ entry(Items.ENCHANTED_GOLDEN_APPLE, Blocks.GOLD_BLOCK), ++ entry(Items.COD, Blocks.WHITE_TERRACOTTA), ++ entry(Items.SALMON, Blocks.ACACIA_PLANKS), ++ entry(Items.ROTTEN_FLESH, Blocks.BROWN_WOOL), ++ entry(Items.PUFFERFISH, Blocks.YELLOW_TERRACOTTA), ++ entry(Items.TROPICAL_FISH, Blocks.ORANGE_WOOL), ++ entry(Items.POTATO, Blocks.WHITE_TERRACOTTA), ++ entry(Items.MUTTON, Blocks.RED_WOOL), ++ entry(Items.BEETROOT, Blocks.NETHERRACK), ++ entry(Items.MELON_SLICE, Blocks.MELON), ++ entry(Items.POISONOUS_POTATO, Blocks.SLIME_BLOCK), ++ entry(Items.SPIDER_EYE, Blocks.NETHERRACK), ++ entry(Items.GUNPOWDER, Blocks.GRAY_WOOL), ++ entry(Items.SCUTE, Blocks.LIME_WOOL), ++ entry(Items.FEATHER, Blocks.WHITE_WOOL), ++ entry(Items.FLINT, Blocks.BLACK_WOOL), ++ entry(Items.LEATHER, Blocks.SPRUCE_PLANKS), ++ entry(Items.GLOWSTONE_DUST, Blocks.GLOWSTONE), ++ entry(Items.PAPER, Blocks.WHITE_WOOL), ++ entry(Items.BRICK, Blocks.BRICKS), ++ entry(Items.INK_SAC, Blocks.BLACK_WOOL), ++ entry(Items.SNOWBALL, Blocks.SNOW_BLOCK), ++ entry(Items.WATER_BUCKET, Blocks.WATER), ++ entry(Items.LAVA_BUCKET, Blocks.LAVA), ++ entry(Items.MILK_BUCKET, Blocks.WHITE_WOOL), ++ entry(Items.CLAY_BALL, Blocks.CLAY), ++ entry(Items.COCOA_BEANS, Blocks.COCOA), ++ entry(Items.BONE, Blocks.BONE_BLOCK), ++ entry(Items.COD_BUCKET, Blocks.BROWN_TERRACOTTA), ++ entry(Items.PUFFERFISH_BUCKET, Blocks.YELLOW_TERRACOTTA), ++ entry(Items.SALMON_BUCKET, Blocks.PINK_TERRACOTTA), ++ entry(Items.TROPICAL_FISH_BUCKET, Blocks.ORANGE_TERRACOTTA), ++ entry(Items.SUGAR, Blocks.WHITE_WOOL), ++ entry(Items.BLAZE_POWDER, Blocks.GOLD_BLOCK), ++ entry(Items.ENDER_PEARL, Blocks.WARPED_PLANKS), ++ entry(Items.NETHER_STAR, Blocks.DIAMOND_BLOCK), ++ entry(Items.PRISMARINE_CRYSTALS, Blocks.SEA_LANTERN), ++ entry(Items.PRISMARINE_SHARD, Blocks.PRISMARINE), ++ entry(Items.RABBIT_HIDE, Blocks.OAK_PLANKS), ++ entry(Items.CHORUS_FRUIT, Blocks.PURPUR_BLOCK), ++ entry(Items.SHULKER_SHELL, Blocks.SHULKER_BOX), ++ entry(Items.NAUTILUS_SHELL, Blocks.BONE_BLOCK), ++ entry(Items.HEART_OF_THE_SEA, Blocks.CONDUIT), ++ entry(Items.HONEYCOMB, Blocks.HONEYCOMB_BLOCK), ++ entry(Items.NAME_TAG, Blocks.BONE_BLOCK), ++ entry(Items.TOTEM_OF_UNDYING, Blocks.YELLOW_TERRACOTTA), ++ entry(Items.TRIDENT, Blocks.PRISMARINE), ++ entry(Items.GHAST_TEAR, Blocks.WHITE_WOOL), ++ entry(Items.PHANTOM_MEMBRANE, Blocks.BONE_BLOCK), ++ entry(Items.EGG, Blocks.BONE_BLOCK), ++ entry(Items.COPPER_INGOT, Blocks.COPPER_BLOCK), ++ entry(Items.AMETHYST_SHARD, Blocks.AMETHYST_BLOCK) ++ ); ++ ++ @Nullable ++ public static TextColor guessColor(@NotNull MinecraftServer server, Item item) { ++ RegistryAccess registryAccess = server.registryAccess(); ++ TextColor direct = fromItem(item, registryAccess); ++ if (direct != null) { ++ return direct; ++ } ++ ++ ResourceLocation id = registryAccess.registryOrThrow(Registries.ITEM).getKey(item); ++ for (RecipeType type : registryAccess.registryOrThrow(Registries.RECIPE_TYPE)) { ++ for (Recipe r : getAllMatching(server.getRecipeManager(), type, id, registryAccess)) { ++ for (Ingredient ingredient : r.getIngredients()) { ++ for (Collection stacks : getRecipeStacks(ingredient)) { ++ for (ItemStack itemStack : stacks) { ++ TextColor textColor = fromItem(itemStack.getItem(), registryAccess); ++ if (textColor != null) { ++ return textColor; ++ } ++ } ++ } ++ } ++ } ++ } ++ return null; ++ } ++ ++ @Nullable ++ public static TextColor fromItem(Item item, RegistryAccess registryAccess) { ++ if (DEFAULTS.containsKey(item)) { ++ return TextColor.color(appropriateColor(DEFAULTS.get(item).defaultMapColor().col)); ++ } ++ if (item instanceof DyeItem dye) { ++ return TextColor.color(appropriateColor(dye.getDyeColor().getMapColor().col)); ++ } ++ ++ Block block = null; ++ final Registry itemRegistry = registryAccess.registryOrThrow(Registries.ITEM); ++ final Registry blockRegistry = registryAccess.registryOrThrow(Registries.BLOCK); ++ ResourceLocation id = itemRegistry.getKey(item); ++ if (item instanceof BlockItem blockItem) { ++ block = blockItem.getBlock(); ++ } else if (blockRegistry.getOptional(id).isPresent()) { ++ block = blockRegistry.get(id); ++ } ++ ++ if (block != null) { ++ if (block instanceof AbstractBannerBlock) { ++ return TextColor.color(appropriateColor(((AbstractBannerBlock) block).getColor().getMapColor().col)); ++ } else if (block instanceof BeaconBeamBlock) { ++ return TextColor.color(appropriateColor(((BeaconBeamBlock) block).getColor().getMapColor().col)); ++ } ++ return TextColor.color(appropriateColor(block.defaultMapColor().col)); ++ } ++ return null; ++ } ++ ++ public static List> getAllMatching(@NotNull RecipeManager manager, RecipeType type, ResourceLocation output, final RegistryAccess registryAccess) { ++ Map> typeRecipes = manager.recipes.get(type); ++ if (typeRecipes == null) { ++ return Collections.emptyList(); ++ } ++ if (typeRecipes.containsKey(output)) { ++ return Collections.singletonList(typeRecipes.get(output)); ++ } ++ final Registry regs = registryAccess.registryOrThrow(Registries.ITEM); ++ return typeRecipes.values().stream().filter( ++ r -> Objects.equals(regs.getKey(r.getResultItem(registryAccess).getItem()), output)).collect(Collectors.toList()); ++ } ++ ++ public static List> getRecipeStacks(@NotNull Ingredient ingredient) { ++ return Arrays.stream(ingredient.values).map(Ingredient.Value::getItems).toList(); ++ } ++ ++ public static int appropriateColor(int color) { ++ if (color == 0) { ++ return MapColor.SNOW.col; ++ } ++ int r = (color >> 16 & 255); ++ int g = (color >> 8 & 255); ++ int b = (color & 255); ++ if (r < 70) r = 70; ++ if (g < 70) g = 70; ++ if (b < 70) b = 70; ++ return (r << 16) + (g << 8) + b; ++ } ++ ++ public long getTotalItems() { ++ return counter.isEmpty() ? 0 : counter.values().longStream().sum(); ++ } ++ ++ public static void resetAll(MinecraftServer server, boolean fresh) { ++ for (HopperCounter counter : COUNTERS.values()) { ++ counter.reset(server); ++ if (fresh) { ++ counter.startTick = -1; ++ } ++ } ++ } ++ ++ public static HopperCounter getCounter(DyeColor color) { ++ return COUNTERS.get(color); ++ } ++ ++ public static void setEnabled(boolean is) { ++ enabled = is; ++ } ++ ++ public static boolean isEnabled() { ++ return enabled; ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/util/WoolUtils.java b/src/main/java/top/leavesmc/leaves/util/WoolUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a314af297795f6a6ac83b161e39c0c2c7ee58fb2 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/util/WoolUtils.java +@@ -0,0 +1,38 @@ ++package top.leavesmc.leaves.util; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.item.DyeColor; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++ ++import java.util.Map; ++ ++import static java.util.Map.entry; ++ ++public class WoolUtils { ++ private static final Map WOOL_BLOCK_TO_DYE = Map.ofEntries( ++ entry(Blocks.WHITE_WOOL, DyeColor.WHITE), ++ entry(Blocks.ORANGE_WOOL, DyeColor.ORANGE), ++ entry(Blocks.MAGENTA_WOOL, DyeColor.MAGENTA), ++ entry(Blocks.LIGHT_BLUE_WOOL, DyeColor.LIGHT_BLUE), ++ entry(Blocks.YELLOW_WOOL, DyeColor.YELLOW), ++ entry(Blocks.LIME_WOOL, DyeColor.LIME), ++ entry(Blocks.PINK_WOOL, DyeColor.PINK), ++ entry(Blocks.GRAY_WOOL, DyeColor.GRAY), ++ entry(Blocks.LIGHT_GRAY_WOOL, DyeColor.LIGHT_GRAY), ++ entry(Blocks.CYAN_WOOL, DyeColor.CYAN), ++ entry(Blocks.PURPLE_WOOL, DyeColor.PURPLE), ++ entry(Blocks.BLUE_WOOL, DyeColor.BLUE), ++ entry(Blocks.BROWN_WOOL, DyeColor.BROWN), ++ entry(Blocks.GREEN_WOOL, DyeColor.GREEN), ++ entry(Blocks.RED_WOOL, DyeColor.RED), ++ entry(Blocks.BLACK_WOOL, DyeColor.BLACK) ++ ); ++ ++ public static DyeColor getWoolColorAtPosition(Level worldIn, BlockPos pos) { ++ BlockState state = worldIn.getBlockState(pos); ++ return WOOL_BLOCK_TO_DYE.get(state.getBlock()); ++ } ++}