diff --git a/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/SignalBlockSerializer.java b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/SignalBlockSerializer.java new file mode 100644 index 00000000..14c12ad3 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/SignalBlockSerializer.java @@ -0,0 +1,15 @@ +package tools.redstone.redstonetools.features.arguments.serializers; + +import tools.redstone.redstonetools.utils.SignalBlock; + +public class SignalBlockSerializer extends EnumSerializer { + private static final SignalBlockSerializer INSTANCE = new SignalBlockSerializer(); + + private SignalBlockSerializer() { + super(SignalBlock.class); + } + + public static SignalBlockSerializer signalBlock() { + return INSTANCE; + } +} diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/SignalStrengthBlockFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/SignalStrengthBlockFeature.java new file mode 100644 index 00000000..066bbcba --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/features/commands/SignalStrengthBlockFeature.java @@ -0,0 +1,53 @@ +package tools.redstone.redstonetools.features.commands; + +import com.google.auto.service.AutoService; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.minecraft.item.ItemStack; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; +import tools.redstone.redstonetools.features.AbstractFeature; +import tools.redstone.redstonetools.features.Feature; +import tools.redstone.redstonetools.features.arguments.Argument; +import tools.redstone.redstonetools.features.arguments.serializers.SignalBlockSerializer; +import tools.redstone.redstonetools.features.feedback.Feedback; +import tools.redstone.redstonetools.utils.SignalBlock; + +import java.util.Random; + +import static tools.redstone.redstonetools.features.arguments.serializers.IntegerSerializer.integer; + +@AutoService(AbstractFeature.class) +@Feature(name = "Signal Strength Block", description = "Creates a block with the specified signal strength.", command = "ssb") +public class SignalStrengthBlockFeature extends CommandFeature { + + public static final Argument signalStrength = Argument + .ofType(integer(0)); + + public static final Argument block = Argument + .ofType(SignalBlockSerializer.signalBlock()) + .withDefault(SignalBlock.AUTO); + + @Override + protected Feedback execute(ServerCommandSource source) throws CommandSyntaxException { + try { + ItemStack itemStack = block.getValue().getItemStack(signalStrength.getValue()); + source.getPlayer().giveItemStack(itemStack); + } catch (IllegalArgumentException | IllegalStateException e) { + return Feedback.error(e.getMessage()); + } + + //funny + if(signalStrength.getValue() == 0) { + String[] funny = { + "Why would you want this??", "Wtf are you going to use this for?", "What for?", + "... Ok, if you're sure.", "I'm 99% sure you could just use any other block.", + "This seems unnecessary.", "Is that a typo?", "Do you just like the glint?", + "Wow, what a fancy but otherwise useless barrel.", "For decoration?"}; + return Feedback.success(funny[new Random().nextInt(funny.length)]); + } + + return Feedback.none(); + } + +} diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/SsBarrelFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/SsBarrelFeature.java deleted file mode 100644 index a901c905..00000000 --- a/src/main/java/tools/redstone/redstonetools/features/commands/SsBarrelFeature.java +++ /dev/null @@ -1,69 +0,0 @@ -package tools.redstone.redstonetools.features.commands; - -import com.google.auto.service.AutoService; -import tools.redstone.redstonetools.features.AbstractFeature; -import tools.redstone.redstonetools.features.Feature; -import tools.redstone.redstonetools.features.arguments.Argument; -import tools.redstone.redstonetools.features.feedback.Feedback; -import tools.redstone.redstonetools.utils.RedstoneUtils; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.minecraft.enchantment.Enchantment; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtList; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.text.Text; -import net.minecraft.util.registry.Registry; -import java.util.Random; - -import static tools.redstone.redstonetools.features.arguments.serializers.IntegerSerializer.integer; - -@AutoService(AbstractFeature.class) -@Feature(name = "Signal Strength Barrel", description = "Creates a barrel with the specified signal strength.", command = "ss") -public class SsBarrelFeature extends CommandFeature { - private static final int BARREL_CONTAINER_SLOTS = 27; - - public static final Argument signalStrength = Argument - .ofType(integer(0, 15)) - .withDefault(15); - - @Override - protected Feedback execute(ServerCommandSource source) throws CommandSyntaxException { - var stack = new ItemStack(Items.BARREL); - - // {BlockEntityTag:{Items:[{Slot:0,id:redstone,Count:3},{Slot:1,id:redstone,Count:61}]}} - var items = new NbtList(); - - for (int i = 0; i < RedstoneUtils.signalStrengthToNonStackableItemCount(signalStrength.getValue(), BARREL_CONTAINER_SLOTS); i++) { - var item = new NbtCompound(); - item.putByte("Slot", (byte) i); - item.putString("id", Registry.ITEM.getId(Items.TOTEM_OF_UNDYING).toString()); - item.putByte("Count", (byte) 1); - items.add(item); - } - - stack.getOrCreateSubNbt("BlockEntityTag").put("Items", items); - stack.setCustomName(Text.of(signalStrength.getValue().toString())); - stack.addEnchantment(Enchantment.byRawId(0),0); - stack.getOrCreateNbt().putBoolean("HideFlags", true); - - source.getPlayer().giveItemStack(stack); - - //funny - if(signalStrength.getValue() == 0) - - { - String[] funny = { - "Why would you want this??", "Wtf are you going to use this for?", "What for?", - "... Ok, if you're sure.", "I'm 99% sure you could just use any other block.", - "This seems unnecessary.", "Is that a typo?", "Do you just like the glint?", - "Wow, what a fancy but otherwise useless barrel.", "For decoration?"}; - return Feedback.success(funny[new Random( - - ).nextInt(funny.length)]); - } - - return Feedback.none(); - } -} diff --git a/src/main/java/tools/redstone/redstonetools/utils/SignalBlock.java b/src/main/java/tools/redstone/redstonetools/utils/SignalBlock.java new file mode 100644 index 00000000..a8b839ff --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/utils/SignalBlock.java @@ -0,0 +1,47 @@ +package tools.redstone.redstonetools.utils; + +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.item.ItemStack; + +public enum SignalBlock { + COMPOSTER(Blocks.COMPOSTER, SignalBlockSupplier.composter()), + BARREL(Blocks.BARREL, SignalBlockSupplier.container(27)), + CHEST(Blocks.CHEST, SignalBlockSupplier.container(27)), + SHULKER_BOX(Blocks.SHULKER_BOX, SignalBlockSupplier.container(27)), + DISPENSER(Blocks.DISPENSER, SignalBlockSupplier.container(9)), + DROPPER(Blocks.DROPPER, SignalBlockSupplier.container(9)), + HOPPER(Blocks.HOPPER, SignalBlockSupplier.container(5)), + BREWING_STAND(Blocks.BREWING_STAND, SignalBlockSupplier.container(5)), + FURNACE(Blocks.FURNACE, SignalBlockSupplier.container(3)), + SMOKER(Blocks.SMOKER, SignalBlockSupplier.container(3)), + BLAST_FURNACE(Blocks.BLAST_FURNACE, SignalBlockSupplier.container(3)), + COMMAND_BLOCK(Blocks.COMMAND_BLOCK, SignalBlockSupplier.commandBlock()), + AUTO(null, null); + + private final Block block; + private final SignalBlockSupplier supplier; + + SignalBlock(Block block, SignalBlockSupplier supplier) { + this.block = block; + this.supplier = supplier; + } + + public static SignalBlock getBestBlock(int signal) { + return signal < 1780 + ? BARREL + : COMMAND_BLOCK; + } + + public ItemStack getItemStack(int signal) { + if (block == null || supplier == null) + return getBestBlock(signal).getItemStack(signal); + + return supplier.getItemStack(block, signal); + } + + @Override + public String toString() { + return this.name().toLowerCase(); + } +} diff --git a/src/main/java/tools/redstone/redstonetools/utils/SignalBlockSupplier.java b/src/main/java/tools/redstone/redstonetools/utils/SignalBlockSupplier.java new file mode 100644 index 00000000..53934de1 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/utils/SignalBlockSupplier.java @@ -0,0 +1,131 @@ +package tools.redstone.redstonetools.utils; + +import net.minecraft.block.Block; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.text.LiteralText; +import net.minecraft.text.MutableText; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.registry.Registry; + +@FunctionalInterface +public interface SignalBlockSupplier { + + NbtCompound createNbt(int signalStrength); + + default ItemStack getItemStack(Block block, int signalStrength) { + ItemStack item = new ItemStack(block); + setCompoundNbt(item, this.createNbt(signalStrength)); + setItemName(item, signalStrength); + return item; + } + + static SignalBlockSupplier container(int slots) { + return signalStrength -> { + if (isInvalidSignalStrength(signalStrength, 1779)) + throw new IllegalArgumentException("Container signal must be 0-1779"); + + NbtCompound tags = new NbtCompound(); + NbtList itemsTag = new NbtList(); + + Item item = getBestItem(signalStrength, slots); + int stackSize = getStackSize(signalStrength, item); + int itemsNeeded = signalStrength == 1 + ? 1 + : (int) Math.ceil(slots * (signalStrength - 1) / 14D * item.getMaxCount()); + String itemId = Registry.ITEM.getId(item).toString(); + + // Check that the calculated number of items is correct. + // This is to prevent problems with items that have a maximum stack size of 1 but stackSize > 1. + // TODO: This can be improved by removing an item and adding stackable items up to the desired signal strength. + // Even with the improvement, this will still fail for inventories with no available slots. + if (calculateComparatorOutput(itemsNeeded, slots, item.getMaxCount()) != signalStrength) + throw new IllegalStateException("This signal strength cannot be achieved with the selected container"); + + for (int slot = 0, count = itemsNeeded; count > 0; slot++, count -= stackSize) { + NbtCompound slotTag = new NbtCompound(); + slotTag.putByte("Slot", (byte) slot); + slotTag.putString("id", itemId); + slotTag.putByte("Count", (byte) Math.min(stackSize, count)); + itemsTag.add(slotTag); + } + + NbtCompound tag = new NbtCompound(); + tag.put("Items", itemsTag); + tags.put("BlockEntityTag", tag); + + return tags; + }; + } + + static SignalBlockSupplier composter() { + return signalStrength -> { + if (signalStrength == 7 || isInvalidSignalStrength(signalStrength, 8)) + throw new IllegalArgumentException("Composter signal must be 0-6 or 8"); + + NbtCompound tags = new NbtCompound(); + NbtCompound tag = new NbtCompound(); + tag.putInt("level", signalStrength); + tags.put("BlockStateTag", tag); + return tags; + }; + } + + static SignalBlockSupplier commandBlock() { + return signalStrength -> { + if (isInvalidSignalStrength(signalStrength, Integer.MAX_VALUE)) + throw new IllegalArgumentException("Command block signal must be positive"); + + NbtCompound tags = new NbtCompound(); + NbtCompound tag = new NbtCompound(); + tag.putInt("SuccessCount", signalStrength); + tags.put("BlockEntityTag", tag); + return tags; + }; + } + + private static boolean isInvalidSignalStrength(int signalStrength, int maxSignalStrength) { + return signalStrength < 0 || signalStrength > maxSignalStrength; + } + + private static int calculateComparatorOutput(int items, int slots, int item$getMaxCount) { + float f = (float) items / (float) item$getMaxCount; + return MathHelper.floor((f /= (float)slots) * 14.0f) + (items > 0 ? 1 : 0); + } + + private static Item getBestItem(int signalStrength, int slots) { + if (signalStrength > 15) + return Items.WHITE_SHULKER_BOX; + else if (slots >= 15) + return Items.WOODEN_SHOVEL; + else + return Items.STICK; + } + + private static int getStackSize(int signalStrength, Item item) { + if (signalStrength > 897) + return 127; + else if (signalStrength > 15) + return 64; + else + return item.getMaxCount(); + } + + private static void setCompoundNbt(ItemStack item, NbtCompound nbt) { + nbt.putBoolean("HideFlags", true); + item.setNbt(nbt); + item.addEnchantment(Enchantment.byRawId(0), 0); + } + + private static void setItemName(ItemStack item, int signalStrength) { + MutableText text = new LiteralText(String.valueOf(signalStrength)); + text.setStyle(text.getStyle().withColor(Formatting.RED)); + item.setCustomName(text); + } + +}