diff --git a/gradle.properties b/gradle.properties index fe88d66e..4c0cdb5e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ org.gradle.parallel=true loader_version=0.14.6 # Mod Properties - mod_version = 1.18.2-1.2.0 + mod_version = 1.18.2-2.0.0 maven_group = tools.redstone archives_base_name = redstonetools diff --git a/src/main/java/tools/redstone/redstonetools/RedstoneToolsClient.java b/src/main/java/tools/redstone/redstonetools/RedstoneToolsClient.java index 7a651e9e..2207c32b 100644 --- a/src/main/java/tools/redstone/redstonetools/RedstoneToolsClient.java +++ b/src/main/java/tools/redstone/redstonetools/RedstoneToolsClient.java @@ -2,17 +2,22 @@ import net.fabricmc.api.ClientModInitializer; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rip.hippo.inject.Doctor; import rip.hippo.inject.Injector; -import tools.redstone.redstonetools.macros.WorldlessCommandHelper; +import tools.redstone.redstonetools.utils.DependencyLookup; import tools.redstone.redstonetools.utils.ReflectionUtils; +import java.nio.file.Path; + public class RedstoneToolsClient implements ClientModInitializer { + public static final String MOD_ID = "redstonetools"; public static final String MOD_VERSION = "v" + FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().getMetadata().getVersion().getFriendlyString(); public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static final Path CONFIG_DIR = FabricLoader.getInstance().getConfigDir().resolve("redstonetools"); public static final Injector INJECTOR = Doctor.createInjector(ReflectionUtils.getModules()); @Override @@ -24,12 +29,14 @@ public void onInitializeClient() { // Register features ReflectionUtils.getFeatures().forEach(feature -> { - LOGGER.trace("Registering feature {}", feature); + LOGGER.trace("Registering feature {}", feature.getClass().getName()); + if (feature.requiresWorldEdit() && !DependencyLookup.WORLDEDIT_PRESENT) { + LOGGER.warn("Feature {} requires WorldEdit, but WorldEdit is not loaded. Skipping registration.", feature.getName()); + return; + } feature.register(); }); - - // should call the "static" block - WorldlessCommandHelper.dummyNetworkHandler.getCommandDispatcher(); } + } diff --git a/src/main/java/tools/redstone/redstonetools/RedstoneToolsGameRules.java b/src/main/java/tools/redstone/redstonetools/RedstoneToolsGameRules.java index 7befef6e..73a9e2f8 100644 --- a/src/main/java/tools/redstone/redstonetools/RedstoneToolsGameRules.java +++ b/src/main/java/tools/redstone/redstonetools/RedstoneToolsGameRules.java @@ -3,6 +3,7 @@ import net.fabricmc.fabric.api.gamerule.v1.GameRuleFactory; import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry; import net.minecraft.world.GameRules; +import tools.redstone.redstonetools.utils.DependencyLookup; public class RedstoneToolsGameRules { @@ -14,6 +15,9 @@ private RedstoneToolsGameRules() { public static void register() { DO_CONTAINER_DROPS = GameRuleRegistry.register("doContainerDrops", GameRules.Category.DROPS, GameRuleFactory.createBooleanRule(true)); - DO_BLOCK_UPDATES_AFTER_EDIT = GameRuleRegistry.register("doBlockUpdatesAfterEdit", GameRules.Category.UPDATES, GameRuleFactory.createBooleanRule(false)); + + if (DependencyLookup.WORLDEDIT_PRESENT) { + DO_BLOCK_UPDATES_AFTER_EDIT = GameRuleRegistry.register("doBlockUpdatesAfterEdit", GameRules.Category.UPDATES, GameRuleFactory.createBooleanRule(false)); + } } } diff --git a/src/main/java/tools/redstone/redstonetools/features/AbstractFeature.java b/src/main/java/tools/redstone/redstonetools/features/AbstractFeature.java index 74846035..e8e6f1af 100644 --- a/src/main/java/tools/redstone/redstonetools/features/AbstractFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/AbstractFeature.java @@ -5,26 +5,47 @@ import net.minecraft.server.command.ServerCommandSource; public abstract class AbstractFeature { - private final Feature feature; + + private final Feature featureInfo; + private final String id; { - feature = getClass().getAnnotation(Feature.class); + featureInfo = getClass().getAnnotation(Feature.class); - if (feature == null) { + if (featureInfo == null) { throw new IllegalStateException("Feature " + getClass() + " is not annotated with @Feature"); } + + String id = featureInfo.id(); + if (id.isEmpty()) { + // derive id from name + // Air Place -> airplace + id = featureInfo.name() + .toLowerCase() + .replace(" ", ""); + } + + this.id = id; + } + + public String getID() { + return id; } public String getName() { - return feature.name(); + return featureInfo.name(); } public String getDescription() { - return feature.description(); + return featureInfo.description(); } public String getCommand() { - return feature.command(); + return featureInfo.command(); + } + + public boolean requiresWorldEdit() { + return featureInfo.worldedit(); } /** @@ -35,4 +56,5 @@ public void register() { } protected abstract void registerCommands(CommandDispatcher dispatcher, boolean dedicated); + } diff --git a/src/main/java/tools/redstone/redstonetools/features/Feature.java b/src/main/java/tools/redstone/redstonetools/features/Feature.java index ae3cbe53..14979af7 100644 --- a/src/main/java/tools/redstone/redstonetools/features/Feature.java +++ b/src/main/java/tools/redstone/redstonetools/features/Feature.java @@ -8,7 +8,9 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Feature { + String id() default ""; String name(); String description(); String command(); + boolean worldedit() default false; } diff --git a/src/main/java/tools/redstone/redstonetools/features/arguments/Argument.java b/src/main/java/tools/redstone/redstonetools/features/arguments/Argument.java index 9dc6df45..683536c0 100644 --- a/src/main/java/tools/redstone/redstonetools/features/arguments/Argument.java +++ b/src/main/java/tools/redstone/redstonetools/features/arguments/Argument.java @@ -7,7 +7,7 @@ public class Argument { private String name; private final TypeSerializer type; private boolean optional = false; - private T value; + private volatile T value; private T defaultValue; private Argument(TypeSerializer type) { @@ -21,10 +21,15 @@ public static Argument ofType(TypeSerializer type) { public Argument withDefault(T defaultValue) { optional = true; this.defaultValue = defaultValue; + this.value = defaultValue; // for options, temporary return this; } + public T getDefaultValue() { + return defaultValue; + } + public Argument named(String name) { this.name = name; @@ -52,7 +57,7 @@ public boolean isOptional() { } @SuppressWarnings("unchecked") - public void setValue(CommandContext context) { + public void updateValue(CommandContext context) { try { value = (T) context.getArgument(name, Object.class); } catch (IllegalArgumentException e) { @@ -64,6 +69,10 @@ public void setValue(CommandContext context) { } } + public void setValue(T value) { + this.value = value; + } + public T getValue() { return value; } diff --git a/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/ColoredBlockTypeSerializer.java b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/ColoredBlockTypeSerializer.java new file mode 100644 index 00000000..3b7b6e99 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/ColoredBlockTypeSerializer.java @@ -0,0 +1,15 @@ +package tools.redstone.redstonetools.features.arguments.serializers; + +import tools.redstone.redstonetools.utils.ColoredBlockType; + +public class ColoredBlockTypeSerializer extends EnumSerializer { + private static final ColoredBlockTypeSerializer INSTANCE = new ColoredBlockTypeSerializer(); + + private ColoredBlockTypeSerializer() { + super(ColoredBlockType.class); + } + + public static ColoredBlockTypeSerializer coloredBlockType() { + return INSTANCE; + } +} diff --git a/src/main/java/tools/redstone/redstonetools/features/arguments/DirectionSerializer.java b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/DirectionSerializer.java similarity index 73% rename from src/main/java/tools/redstone/redstonetools/features/arguments/DirectionSerializer.java rename to src/main/java/tools/redstone/redstonetools/features/arguments/serializers/DirectionSerializer.java index ecdea684..9a2f3b36 100644 --- a/src/main/java/tools/redstone/redstonetools/features/arguments/DirectionSerializer.java +++ b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/DirectionSerializer.java @@ -1,6 +1,5 @@ -package tools.redstone.redstonetools.features.arguments; +package tools.redstone.redstonetools.features.arguments.serializers; -import tools.redstone.redstonetools.features.arguments.serializers.EnumSerializer; import tools.redstone.redstonetools.utils.DirectionArgument; public class DirectionSerializer extends EnumSerializer { diff --git a/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/IntLikeSerializer.java b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/IntLikeSerializer.java index 6ecccb3a..0cfc6974 100644 --- a/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/IntLikeSerializer.java +++ b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/IntLikeSerializer.java @@ -53,25 +53,28 @@ public T deserialize(String serialized) { } private T deserializeUnchecked(String serialized) { + boolean isNegative = false; if (serialized.length() == 1) { return tryParse(serialized); } + if(serialized.charAt(0) == '-' && serialized.chars().filter(ch -> ch == '-').count() == 1){ + isNegative = true; + serialized = serialized.replace("-",""); + } + if (serialized.charAt(0) == '0') { - var prefixedBase = serialized.substring(0, 2); - var number = serialized.substring(2); - - // TODO(Refactor): Write a NumberBase.fromCharacter method instead of this that iterates of the NumberBases (add the char to the NumberBase constructor) - var numberBase = switch (prefixedBase.charAt(1)) { - case 'b' -> NumberBase.BINARY; - case 'o' -> NumberBase.OCTAL; - case 'd' -> NumberBase.DECIMAL; - case 'x' -> NumberBase.HEXADECIMAL; - default -> null; - }; - - if (numberBase != null) { - return tryParse(number, numberBase.toInt()); + if(serialized.length() > 1) { + var prefixedBase = serialized.substring(0, 2); + var number = serialized.substring(2); + + var numberBase = NumberBase.fromPrefix(prefixedBase).orElse(null); + + if (numberBase != null) { + return isNegative ? tryParse("-" + number, numberBase.toInt()) : tryParse(number, numberBase.toInt()); + } + } else { + return tryParse(serialized,10); } } @@ -90,10 +93,10 @@ private T deserializeUnchecked(String serialized) { throw new IllegalArgumentException("Invalid base '" + parts[1] + "'."); } - return tryParse(number, base); + return isNegative ? tryParse("-"+number, base) : tryParse(number, base); } - return tryParse(serialized); + return isNegative ? tryParse("-"+serialized) : tryParse(serialized); } private T tryParse(String string) { diff --git a/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/NumberSerializer.java b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/NumberSerializer.java new file mode 100644 index 00000000..320eb217 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/NumberSerializer.java @@ -0,0 +1,34 @@ +package tools.redstone.redstonetools.features.arguments.serializers; + +import tools.redstone.redstonetools.utils.NumberArg; + +import java.util.Optional; + +public class NumberSerializer extends IntLikeSerializer { + private static final NumberSerializer INSTANCE = new NumberSerializer(null,null); + + public static NumberSerializer numberArg(){ + return INSTANCE; + } + + public static NumberSerializer numberArg(NumberArg min) { + return new NumberSerializer(min, null); + } + + public static NumberSerializer numberArg(NumberArg min, NumberArg max) { + return new NumberSerializer(min, max); + } + + private NumberSerializer(NumberArg min, NumberArg max){ + super(NumberArg.class,min, max); + } + + @Override + protected Optional tryParseOptional(String string, int radix) { + try { + return Optional.of(new NumberArg(string, radix)); + } catch (NumberFormatException ignored) { + return Optional.empty(); + } + } +} 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/arguments/serializers/TypeSerializer.java b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/TypeSerializer.java index d0731ac8..9796c052 100644 --- a/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/TypeSerializer.java +++ b/src/main/java/tools/redstone/redstonetools/features/arguments/serializers/TypeSerializer.java @@ -25,6 +25,10 @@ protected TypeSerializer(Class clazz) { this.clazz = clazz; } + public Class getTypeClass() { + return clazz; + } + /* ArgumentType impl */ @Override public final T parse(StringReader reader) throws CommandSyntaxException { diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/BaseConvertFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/BaseConvertFeature.java index 51a75927..818d4203 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/BaseConvertFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/BaseConvertFeature.java @@ -6,26 +6,27 @@ 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.NumberArg; -import java.math.BigInteger; -import static tools.redstone.redstonetools.features.arguments.serializers.BigIntegerSerializer.bigInteger; import static tools.redstone.redstonetools.features.arguments.serializers.NumberBaseSerializer.numberBase; +import static tools.redstone.redstonetools.features.arguments.serializers.NumberSerializer.numberArg; @AutoService(AbstractFeature.class) @Feature(name = "Base Convert", description = "Converts a number from one base to another.", command = "base") public class BaseConvertFeature extends CommandFeature { - public static final Argument number = Argument - .ofType(bigInteger()); + public static final Argument inputNum = Argument + .ofType(numberArg()); public static final Argument toBase = Argument .ofType(numberBase()) .withDefault(10); @Override protected Feedback execute(ServerCommandSource source) { - var output = number.getValue().toString(toBase.getValue()); + var input = inputNum.getValue().toPrefixedString(); + var output = inputNum.getValue().toPrefixedString(toBase.getValue()); - return Feedback.success(output); + return Feedback.success("{} = {}", input, output); } } diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/BinaryBlockReadFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/BinaryBlockReadFeature.java index c85d1bee..808f5d10 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/BinaryBlockReadFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/BinaryBlockReadFeature.java @@ -22,7 +22,7 @@ import static tools.redstone.redstonetools.features.arguments.serializers.NumberBaseSerializer.numberBase; @AutoService(AbstractFeature.class) -@Feature(name = "Binary Block Read", description = "Interprets your WorldEdit selection as a binary number.", command = "/read") +@Feature(name = "Binary Block Read", description = "Interprets your WorldEdit selection as a binary number.", command = "/read", worldedit = true) public class BinaryBlockReadFeature extends CommandFeature { private static final BlockStateArgument LIT_LAMP_ARG = new BlockStateArgument( Blocks.REDSTONE_LAMP.getDefaultState().with(RedstoneLampBlock.LIT, true), @@ -67,7 +67,7 @@ protected Feedback execute(ServerCommandSource source) throws CommandSyntaxExcep var spacingVector = direction.multiply(offset.getValue()); if (direction.getBlockX() + direction.getBlockY() + direction.getBlockZ() > 1) { - return Feedback.invalidUsage("The selection must have 2 axis the same"); + return Feedback.invalidUsage("The selection must have 2 axis the same."); } var bits = new StringBuilder(); @@ -95,7 +95,7 @@ protected Feedback execute(ServerCommandSource source) throws CommandSyntaxExcep } var output = Integer.toString(Integer.parseInt(bits.toString(), 2), toBase.getValue()); - return Feedback.success(output); + return Feedback.success("{}.", output); } } \ No newline at end of file diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/BlockRaycastFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/BlockRaycastFeature.java index 3b081772..d8c1b842 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/BlockRaycastFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/BlockRaycastFeature.java @@ -15,12 +15,12 @@ public abstract class BlockRaycastFeature extends CommandFeature { protected Feedback execute(ServerCommandSource source) throws CommandSyntaxException { MinecraftClient client = MinecraftClient.getInstance(); if (client.player == null || client.world == null) { - throw new CommandSyntaxException(null, Text.of("This command is client-side only")); + throw new CommandSyntaxException(null, Text.of("This command is client-side only.")); } if (client.crosshairTarget == null || client.crosshairTarget.getType() != HitResult.Type.BLOCK) { if (requiresBlock()) { - return Feedback.invalidUsage("You must be looking at a block to use this command"); + return Feedback.invalidUsage("You must be looking at a block to use this command."); } else { return execute(source, null); } diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/ColorCodeFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/ColorCodeFeature.java index 68460b00..00742feb 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/ColorCodeFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/ColorCodeFeature.java @@ -24,7 +24,7 @@ import static tools.redstone.redstonetools.features.arguments.serializers.BlockColorSerializer.blockColor; @AutoService(AbstractFeature.class) -@Feature(name = "Color Code", description = "Color codes all color-able blocks in your WorldEdit selection.", command = "/colorcode") +@Feature(name = "Color Code", description = "Color codes all color-able blocks in your WorldEdit selection.", command = "/colorcode", worldedit = true) public class ColorCodeFeature extends CommandFeature { public static final Argument color = Argument .ofType(blockColor()); @@ -104,9 +104,9 @@ public BaseBlock applyBlock(BlockVector3 position) { // call remember to allow undo playerSession.remember(session); - return Feedback.success("Successfully colored " + blocksColored + " blocks " + color.getValue()); + return Feedback.success("Successfully colored {} block(s) {}.", blocksColored, color.getValue()); } catch (Exception e) { - return Feedback.error("An error occurred while coloring the blocks."); + return Feedback.error("An error occurred while coloring the block(s)."); } } diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/ColoredFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/ColoredFeature.java new file mode 100644 index 00000000..d7505c29 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/features/commands/ColoredFeature.java @@ -0,0 +1,38 @@ +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.BlockColor; +import tools.redstone.redstonetools.utils.BlockInfo; +import com.mojang.datafixers.util.Either; +import net.minecraft.item.ItemStack; +import net.minecraft.server.command.ServerCommandSource; +import tools.redstone.redstonetools.utils.ColoredBlockType; + +import javax.annotation.Nullable; + +import static tools.redstone.redstonetools.features.arguments.serializers.ColoredBlockTypeSerializer.coloredBlockType; + +@AutoService(AbstractFeature.class) +@Feature(name = "Colored", description = "Gives the player specified variant of block being looked at, with the same color. Default is White.", command = "colored") +public class ColoredFeature extends PickBlockFeature { + public static final Argument blockType = Argument.ofType(coloredBlockType()); + @Override + protected boolean requiresBlock() { + return false; + } + + @Override + protected Either getItemStack(ServerCommandSource source, @Nullable BlockInfo blockInfo) { + var color = blockInfo == null + ? BlockColor.WHITE + : BlockColor.fromBlock(blockInfo.block); + + var coloredBlock = blockType.getValue().withColor(color); + + return Either.left(new ItemStack(coloredBlock.toBlock())); + } +} diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/CommandFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/CommandFeature.java index 5a4d4883..cc5f9412 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/CommandFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/CommandFeature.java @@ -1,15 +1,12 @@ package tools.redstone.redstonetools.features.commands; -import com.mojang.brigadier.context.CommandContext; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import tools.redstone.redstonetools.features.AbstractFeature; import tools.redstone.redstonetools.features.feedback.AbstractFeedbackSender; import tools.redstone.redstonetools.features.feedback.Feedback; -import tools.redstone.redstonetools.utils.CommandSourceUtils; import tools.redstone.redstonetools.utils.CommandUtils; import tools.redstone.redstonetools.utils.ReflectionUtils; import com.mojang.brigadier.CommandDispatcher; @@ -18,7 +15,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; import static tools.redstone.redstonetools.RedstoneToolsClient.INJECTOR; @@ -64,13 +60,12 @@ protected void registerCommands(CommandDispatcher dispatche arguments, context -> { for (var argument : arguments) { - argument.setValue(context); + argument.updateValue(context); } var feedback = execute(context.getSource()); INJECTOR.getInstance(AbstractFeedbackSender.class) - .sendFeedback(context.getSource(), feedback); return feedback.getType().getCode(); diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/CopyStateFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/CopyStateFeature.java index 7d3c2757..3e0a7cc3 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/CopyStateFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/CopyStateFeature.java @@ -5,9 +5,6 @@ import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtList; -import net.minecraft.nbt.NbtString; import net.minecraft.server.command.ServerCommandSource; import tools.redstone.redstonetools.features.AbstractFeature; import tools.redstone.redstonetools.features.Feature; @@ -16,6 +13,10 @@ import tools.redstone.redstonetools.utils.BlockInfo; import tools.redstone.redstonetools.utils.BlockStateNbtUtil; + +import static tools.redstone.redstonetools.utils.ItemUtils.addExtraNBTText; + + @AutoService(AbstractFeature.class) @Feature(name = "Copy State", description = "Gives you a copy of the block you're looking at with its BlockState.", command = "copystate") public class CopyStateFeature extends PickBlockFeature { @@ -38,23 +39,10 @@ protected Either getItemStack(ServerCommandSource source, B } private int addBlockStateNbt(ItemStack itemStack, BlockState blockState) { - addBlockStateText(itemStack); + addExtraNBTText(itemStack, "BlockState"); BlockStateNbtUtil.putPlacement(itemStack, blockState); return 1; } - private void addBlockStateText(ItemStack itemStack) { - NbtCompound displayNbt = itemStack.getSubNbt("display"); - NbtList loreList = new NbtList(); - if (displayNbt == null) { - displayNbt = new NbtCompound(); - } else { - loreList = (NbtList) displayNbt.get("Lore"); - } - - loreList.add(NbtString.of("\"(+BlockState)\"")); - displayNbt.put("Lore", loreList); - itemStack.setSubNbt("display", displayNbt); - } } diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/GlassFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/GlassFeature.java deleted file mode 100644 index cd7b4f92..00000000 --- a/src/main/java/tools/redstone/redstonetools/features/commands/GlassFeature.java +++ /dev/null @@ -1,68 +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.feedback.Feedback; -import tools.redstone.redstonetools.utils.BlockColor; -import tools.redstone.redstonetools.utils.BlockInfo; -import tools.redstone.redstonetools.utils.ColoredBlock; -import com.mojang.datafixers.util.Either; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.item.ItemStack; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.util.Identifier; -import net.minecraft.util.registry.Registry; - -import javax.annotation.Nullable; - -@AutoService(AbstractFeature.class) -@Feature(name = "Glass", description = "Converts colored blocks to their glass variant and glass to wool.", command = "glass") -public class GlassFeature extends PickBlockFeature { - @Override - protected boolean requiresBlock() { - return false; - } - - @Override - protected Either getItemStack(ServerCommandSource source, @Nullable BlockInfo blockInfo) { - if (blockInfo == null) { - return Either.left(new ItemStack(Blocks.GLASS)); - } - - var coloredBlock = getColoredGlassOrWoolVariant(blockInfo.block); - - return Either.left(coloredBlock == null - ? new ItemStack(Blocks.GLASS) - : new ItemStack(coloredBlock.toBlock())); - } - - private ColoredBlock getColoredGlassOrWoolVariant(Block block) { - var blockId = Registry.BLOCK.getId(block).toString(); - - var coloredBlock = ColoredBlock.fromBlockId(blockId); - if (coloredBlock == null) return null; - - return (isGlass(coloredBlock) - ? getColoredWool() - : getColoredGlass() - ).withColor(coloredBlock.color); - } - - private boolean isGlass(ColoredBlock coloredBlock) { - var whiteVariant = coloredBlock.withColor(BlockColor.WHITE).toBlockId(); - - var whiteVariantId = Identifier.tryParse(whiteVariant); - - return Registry.BLOCK.get(whiteVariantId).equals(Blocks.WHITE_STAINED_GLASS); - } - - private ColoredBlock getColoredWool() { - return ColoredBlock.fromBlock(Blocks.WHITE_WOOL); - } - - private ColoredBlock getColoredGlass() { - return ColoredBlock.fromBlock(Blocks.WHITE_STAINED_GLASS); - } -} diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/ItemBindFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/ItemBindFeature.java new file mode 100644 index 00000000..4403353b --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/features/commands/ItemBindFeature.java @@ -0,0 +1,53 @@ +package tools.redstone.redstonetools.features.commands; + + +import com.google.auto.service.AutoService; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.client.MinecraftClient; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtString; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import tools.redstone.redstonetools.features.AbstractFeature; +import tools.redstone.redstonetools.features.Feature; +import tools.redstone.redstonetools.features.feedback.Feedback; +import tools.redstone.redstonetools.utils.ItemUtils; + +@AutoService(AbstractFeature.class) +@Feature(command = "itembind", description = "Allows you to bind command to a specific item", name = "Item Bind") +public class ItemBindFeature extends CommandFeature{ + public static boolean waitingForCommand = false; + private static ServerPlayerEntity player; + + + @Override + protected Feedback execute(ServerCommandSource source) throws CommandSyntaxException { + + player = source.getPlayer(); + waitingForCommand = true; + + return Feedback.success("Please run any command and hold the item you want the command be bound to"); + } + + public static Feedback addCommand(String command) { + if (!waitingForCommand || MinecraftClient.getInstance().getServer() == null) return null; + + if (player == null || MinecraftClient.getInstance().getServer().getPlayerManager().getPlayer(player.getUuid()) != player) { + waitingForCommand = false; + return null; + } + + ItemStack mainHandStack = player.getMainHandStack(); + if (mainHandStack == null || mainHandStack.getItem() == Items.AIR) { + return Feedback.error("You need to be holding an item!"); + } + + mainHandStack.getOrCreateNbt().put("command", NbtString.of(command)); + ItemUtils.addExtraNBTText(mainHandStack,"Command"); + + waitingForCommand = false; + + return Feedback.success("Successfully bound command: '{}' to this item ({})!", command, mainHandStack.getItem()); + } +} diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/MacroFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/MacroFeature.java index cbef6164..6afa69ba 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/MacroFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/MacroFeature.java @@ -22,7 +22,7 @@ protected Feedback execute(ServerCommandSource source) throws CommandSyntaxExcep if (macroObj == null) { - return Feedback.invalidUsage("Macro \"%s\" does not exist".formatted(macro.getValue())); + return Feedback.invalidUsage("Macro \"{}\" does not exist.", macro.getValue()); } macroObj.run(); diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/MinSelectionFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/MinSelectionFeature.java index 4829a018..388da842 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/MinSelectionFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/MinSelectionFeature.java @@ -21,7 +21,7 @@ import java.util.List; @AutoService(AbstractFeature.class) -@Feature(command = "/minsel", description = "Removes all air-only layers from a selection", name = "Minimize Selection") +@Feature(command = "/minsel", description = "Removes all air-only layers from a selection", name = "Minimize Selection", worldedit = true) public class MinSelectionFeature extends CommandFeature { @Override @@ -50,7 +50,7 @@ protected Feedback execute(ServerCommandSource source) throws CommandSyntaxExcep } if (isEmpty) { - return Feedback.invalidUsage("could not minimize the selection because the selection is empty."); + return Feedback.invalidUsage("Cannot minimize empty selections."); } diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/PickBlockFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/PickBlockFeature.java index 54eaa1f7..756563e3 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/PickBlockFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/PickBlockFeature.java @@ -18,7 +18,7 @@ public abstract class PickBlockFeature extends BlockRaycastFeature { protected final Feedback execute(ServerCommandSource source, @Nullable BlockInfo blockInfo) throws CommandSyntaxException { MinecraftClient client = MinecraftClient.getInstance(); if (client.player == null) { - return Feedback.error("Failed to get player"); + return Feedback.error("Failed to get player."); } var stackOrFeedback = getItemStack(source, blockInfo); @@ -33,7 +33,7 @@ protected final Feedback execute(ServerCommandSource source, @Nullable BlockInfo playerInventory.addPickBlock(stack); if (client.interactionManager == null) { - throw new CommandSyntaxException(null, Text.of("Failed to get interaction manager")); + throw new CommandSyntaxException(null, Text.of("Failed to get interaction manager.")); } client.interactionManager.clickCreativeStack(client.player.getStackInHand(Hand.MAIN_HAND), 36 + playerInventory.selectedSlot); diff --git a/src/main/java/tools/redstone/redstonetools/features/commands/RStackFeature.java b/src/main/java/tools/redstone/redstonetools/features/commands/RStackFeature.java index 650a0ef0..d06452a5 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/RStackFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/RStackFeature.java @@ -20,13 +20,13 @@ import net.minecraft.server.command.ServerCommandSource; import org.jetbrains.annotations.Nullable; -import static tools.redstone.redstonetools.features.arguments.DirectionSerializer.direction; +import static tools.redstone.redstonetools.features.arguments.serializers.DirectionSerializer.direction; import static tools.redstone.redstonetools.features.arguments.serializers.IntegerSerializer.integer; import static tools.redstone.redstonetools.utils.DirectionUtils.directionToBlock; import static tools.redstone.redstonetools.utils.DirectionUtils.matchDirection; @AutoService(AbstractFeature.class) -@Feature(name = "RStack", description = "Stacks with custom distance", command = "/rstack") +@Feature(name = "RStack", description = "Stacks with custom distance", command = "/rstack", worldedit = true) public class RStackFeature extends CommandFeature { public static final Argument count = Argument .ofType(integer()) @@ -93,6 +93,6 @@ public Mask2D toMask2D() { throw new RuntimeException(e); } - return Feedback.success("Stacked " + count.getValue() + " time(s)"); + return Feedback.success("Stacked {} time(s).", count.getValue()); } } 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/features/commands/update/RegionUpdater.java b/src/main/java/tools/redstone/redstonetools/features/commands/update/RegionUpdater.java index 145fc97a..3f23d3d3 100644 --- a/src/main/java/tools/redstone/redstonetools/features/commands/update/RegionUpdater.java +++ b/src/main/java/tools/redstone/redstonetools/features/commands/update/RegionUpdater.java @@ -22,7 +22,7 @@ public static Feedback updateRegion(World world, BlockVector3 minPos, BlockVecto } } } - return Feedback.success("Successfully forced block updates for " + blockCount + " blocks"); + return Feedback.success("Successfully forced block updates for {} block(s).", blockCount); } } diff --git a/src/main/java/tools/redstone/redstonetools/features/feedback/Feedback.java b/src/main/java/tools/redstone/redstonetools/features/feedback/Feedback.java index ad10e86e..c3d6df18 100644 --- a/src/main/java/tools/redstone/redstonetools/features/feedback/Feedback.java +++ b/src/main/java/tools/redstone/redstonetools/features/feedback/Feedback.java @@ -1,24 +1,50 @@ package tools.redstone.redstonetools.features.feedback; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.command.ServerCommandSource; import net.minecraft.util.Formatting; +import tools.redstone.redstonetools.RedstoneToolsClient; import javax.annotation.Nullable; public abstract class Feedback { private final @Nullable String message; + private final @Nullable Object[] values; - protected Feedback(@Nullable String message) { + protected Feedback(@Nullable String message, @Nullable Object... values) { this.message = message; + this.values = values; } private String formatMessage(String message) { - return "[RST] " + message; + return String.format("%s[%sRST%s]%s ", Formatting.GRAY, Formatting.RED, Formatting.GRAY, Formatting.RESET) + message; } public final String getMessage() { - return formatMessage(message != null - ? message - : getDefaultMessage()); + if (message != null) { + String sentMessage = message; + for (Object value : values) { + sentMessage = sentMessage.replaceFirst("\\{\\}", Formatting.RED + value.toString() + getFormatting()); + } + + return formatMessage(sentMessage); + } else { + return formatMessage(getDefaultMessage()); + } + } + + /** Returns the status code. */ + public int send(ServerCommandSource source) { + RedstoneToolsClient.INJECTOR + .getInstance(FeedbackSender.class) + .sendFeedback(source, this); + + return getType().getCode(); + } + + /** Returns the status code. */ + public int send(CommandContext context) { + return send(context.getSource()); } public abstract Formatting getFormatting(); @@ -29,7 +55,7 @@ public static None none() { return new None(); } - private static class None extends Feedback { + public static class None extends Feedback { public None() { super(null); } @@ -50,13 +76,13 @@ public FeedbackType getType() { } } - public static Success success(@Nullable String message) { - return new Success(message); + public static Success success(@Nullable String message, @Nullable Object... values) { + return new Success(message, values); } - private static class Success extends Feedback { - public Success(@Nullable String message) { - super(message); + public static class Success extends Feedback { + public Success(@Nullable String message, @Nullable Object... values) { + super(message, values); } @Override @@ -75,13 +101,13 @@ public FeedbackType getType() { } } - public static Warning warning(@Nullable String message) { - return new Warning(message); + public static Warning warning(@Nullable String message, @Nullable Object... values) { + return new Warning(message, values); } - private static class Warning extends Feedback { - public Warning(@Nullable String message) { - super(message); + public static class Warning extends Feedback { + public Warning(@Nullable String message, @Nullable Object... values) { + super(message, values); } @Override @@ -100,13 +126,13 @@ public FeedbackType getType() { } } - public static Error error(@Nullable String message) { - return new Error(message); + public static Error error(@Nullable String message, @Nullable Object... values) { + return new Error(message, values); } - private static class Error extends Feedback { - public Error(@Nullable String message) { - super(message); + public static class Error extends Feedback { + public Error(@Nullable String message, @Nullable Object... values) { + super(message, values); } @Override @@ -125,13 +151,13 @@ public FeedbackType getType() { } } - public static InvalidUsage invalidUsage(@Nullable String message) { - return new InvalidUsage(message); + public static InvalidUsage invalidUsage(@Nullable String message, @Nullable Object... values) { + return new InvalidUsage(message, values); } - private static class InvalidUsage extends Feedback { - public InvalidUsage(@Nullable String message) { - super(message); + public static class InvalidUsage extends Feedback { + public InvalidUsage(@Nullable String message, @Nullable Object... values) { + super(message, values); } @Override diff --git a/src/main/java/tools/redstone/redstonetools/features/toggleable/AirPlaceFeature.java b/src/main/java/tools/redstone/redstonetools/features/toggleable/AirPlaceFeature.java index efc3766f..db0eba6a 100644 --- a/src/main/java/tools/redstone/redstonetools/features/toggleable/AirPlaceFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/toggleable/AirPlaceFeature.java @@ -1,10 +1,125 @@ package tools.redstone.redstonetools.features.toggleable; import com.google.auto.service.AutoService; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; 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.BoolSerializer; +import tools.redstone.redstonetools.mixin.accessors.WorldRendererAccessor; +import tools.redstone.redstonetools.utils.ItemUtils; +import tools.redstone.redstonetools.utils.RaycastUtils; + +import static tools.redstone.redstonetools.features.arguments.serializers.FloatSerializer.floatArg; @AutoService(AbstractFeature.class) @Feature(name = "Air Place", description = "Allows you to place blocks in the air.", command = "airplace") public class AirPlaceFeature extends ToggleableFeature { + + public static boolean canAirPlace(PlayerEntity player) { + ItemStack itemStack = ItemUtils.getMainItem(player); + + // empty slot + if (itemStack == null || itemStack.getItem() == Items.AIR) + return false; + + // rocket boost for elytra + if (itemStack.getItem() == Items.FIREWORK_ROCKET && + player.getEquippedStack(EquipmentSlot.CHEST).getItem() == Items.ELYTRA && + player.isFallFlying()) + return false; + + return true; + } + + public static HitResult findAirPlacePosition(MinecraftClient client) { + if (client.player == null) + return null; + ClientPlayerEntity player = client.player; + + float reach = AirPlaceFeature.reach.getValue(); + return player.raycast(reach, 0, false); + } + + public static BlockHitResult findAirPlaceBlockHit(PlayerEntity playerEntity) { + var hit = RaycastUtils.rayCastFromEye(playerEntity, reach.getValue()); + return new BlockHitResult(hit.getPos(), hit.getSide(), hit.getBlockPos(), false); + } + + public static final Argument reach = Argument + .ofType(floatArg(3.0f)) + .withDefault(5.0f); + + public static final Argument showOutline = Argument + .ofType(BoolSerializer.bool()) + .withDefault(true); + + private static final BlockState FULL_BLOCK_STATE = Blocks.BEDROCK.getDefaultState(); + + { + // register ghost block renderer + WorldRenderEvents.BEFORE_BLOCK_OUTLINE.register((context, blockOutlineContext) -> { + if (!isEnabled()) + return true; + if (showOutline.getValue() != Boolean.TRUE) + return true; + + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null || client.interactionManager == null) + return true; + if (blockOutlineContext.getType() != HitResult.Type.MISS) + return true; + + if (!canAirPlace(client.player)) + return true; + + HitResult hitResult = findAirPlacePosition(client); + if (hitResult == null) + return true; + BlockPos blockPos = new BlockPos(hitResult.getPos()); + + BlockState blockState = ItemUtils.getUseState(client.player, + ItemUtils.getMainItem(client.player), + reach.getValue()); + if (blockState == null) + return true; + + /* render block outline */ + Camera camera = client.gameRenderer.getCamera(); + Vec3d camPos = camera.getPos(); + + try { + VertexConsumer consumer = context.consumers().getBuffer(RenderLayer.getLines()); + + ((WorldRendererAccessor)context.worldRenderer()).invokeDrawBlockOutline( + context.matrixStack(), + consumer, + client.player, + camPos.x, camPos.y, camPos.z, + blockPos, + blockState + ); + } catch (Throwable t) { + throw new IllegalStateException(t); + } + + return true; + }); + } + } diff --git a/src/main/java/tools/redstone/redstonetools/features/toggleable/AutoDustFeature.java b/src/main/java/tools/redstone/redstonetools/features/toggleable/AutoDustFeature.java index 15060a4f..9a9e1c7a 100644 --- a/src/main/java/tools/redstone/redstonetools/features/toggleable/AutoDustFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/toggleable/AutoDustFeature.java @@ -7,4 +7,5 @@ @AutoService(AbstractFeature.class) @Feature(name = "Auto Dust", description = "Automatically places redstone on top of colored blocks.", command = "autodust") public class AutoDustFeature extends ToggleableFeature { + } diff --git a/src/main/java/tools/redstone/redstonetools/features/toggleable/BigDustFeature.java b/src/main/java/tools/redstone/redstonetools/features/toggleable/BigDustFeature.java new file mode 100644 index 00000000..6f6490c8 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/features/toggleable/BigDustFeature.java @@ -0,0 +1,16 @@ +package tools.redstone.redstonetools.features.toggleable; + +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.arguments.serializers.IntegerSerializer; + +@AutoService(AbstractFeature.class) +@Feature(name = "Big Dust", description = "Change the size of redstone's hitbox.", command = "bigdust") +public class BigDustFeature extends ToggleableFeature { + + public static final Argument heightInPixels = Argument.ofType(IntegerSerializer.integer(1, 16)) + .withDefault(1); + +} diff --git a/src/main/java/tools/redstone/redstonetools/features/toggleable/ToggleableFeature.java b/src/main/java/tools/redstone/redstonetools/features/toggleable/ToggleableFeature.java index f55474a0..2ace5531 100644 --- a/src/main/java/tools/redstone/redstonetools/features/toggleable/ToggleableFeature.java +++ b/src/main/java/tools/redstone/redstonetools/features/toggleable/ToggleableFeature.java @@ -1,51 +1,257 @@ package tools.redstone.redstonetools.features.toggleable; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; import tools.redstone.redstonetools.features.AbstractFeature; import tools.redstone.redstonetools.features.Feature; import tools.redstone.redstonetools.features.feedback.Feedback; import tools.redstone.redstonetools.features.feedback.FeedbackSender; import tools.redstone.redstonetools.utils.ReflectionUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.minecraft.server.command.ServerCommandSource; +import tools.redstone.redstonetools.RedstoneToolsClient; +import tools.redstone.redstonetools.features.AbstractFeature; +import tools.redstone.redstonetools.features.arguments.Argument; +import tools.redstone.redstonetools.features.feedback.Feedback; +import tools.redstone.redstonetools.utils.ReflectionUtils; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.ArrayList; +import java.util.List; import static tools.redstone.redstonetools.RedstoneToolsClient.INJECTOR; + +import static net.minecraft.server.command.CommandManager.argument; import static net.minecraft.server.command.CommandManager.literal; public abstract class ToggleableFeature extends AbstractFeature { - private boolean enabled; - private Feature info; + + private static final List keyBindings = new ArrayList<>(); + + @Override + public void register() { + super.register(); + + // load user settings + // and register save hook + loadConfig(); + ClientLifecycleEvents.CLIENT_STOPPING.register(client -> { + saveConfig(); + }); + + var containsRequiredArguments = ReflectionUtils.getArguments(getClass()).stream() + .anyMatch(a -> !a.isOptional()); + if (containsRequiredArguments) { + return; + } + + var info = ReflectionUtils.getFeatureInfo(getClass()); + var keyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding( + info.name(), + InputUtil.Type.KEYSYM, + -1, + "Redstone Tools" + )); + + keyBindings.add(keyBinding); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + while (keyBinding.wasPressed()) { + assert client.player != null; + client.player.sendChatMessage("/" + info.command()); + } + }); + } + + private static final Executor IO_EXECUTOR = Executors.newSingleThreadExecutor(); + + private static final Gson GSON = new GsonBuilder() + .setPrettyPrinting() + .create(); + + private volatile boolean enabled; // volatile for thread safety + + private final List> arguments = ReflectionUtils.getArguments(getClass()); + + private final Path configFile = RedstoneToolsClient.CONFIG_DIR + .resolve("features").resolve(getID() + ".json"); + + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override protected void registerCommands(CommandDispatcher dispatcher, boolean dedicated) { - info = ReflectionUtils.getFeatureInfo(getClass()); + var baseCommand = literal(getCommand()) + .executes(this::toggle); + + // add option configurations + for (Argument argument : arguments) { + String name = argument.getName(); + baseCommand.then(literal(name) + .executes(context -> { + Object value = argument.getValue(); + return Feedback.success("Option {} of feature {} is set to: {}", name, getName(), argument.getType().serialize(value)).send(context); + }) + .then(argument("value", argument.getType()).executes(context -> { + Object value = context.getArgument("value", argument.getType().getTypeClass()); - dispatcher.register(literal(info.command()) - .executes(this::toggle)); + argument.setValue(value); + + if (!enabled) { + enable(context); + } + + IO_EXECUTOR.execute(this::saveConfig); + + return Feedback.success("Set {} to {} for feature {}", name, value, getName()).send(context); + })) + ); + } + + dispatcher.register(baseCommand); } public boolean isEnabled() { return enabled; } - private int toggle(CommandContext context) throws CommandSyntaxException { - enabled = !enabled; + public int toggle(CommandContext context) throws CommandSyntaxException { + return toggle(context.getSource()); + } + + public int toggle(ServerCommandSource source) throws CommandSyntaxException { + return !enabled ? enable(source) : disable(source); + } + + public void setEnabled(boolean status) { + if (status == enabled) + return; // no work to do - return enabled ? onEnable(context.getSource()) : onDisable(context.getSource()); + if (status) { + enable(); + } else { + disable(); + } } - //TODO: these need to be replaced when the sendMessage util gets made. - protected int onEnable(ServerCommandSource source) throws CommandSyntaxException { - INJECTOR.getInstance(FeedbackSender.class).sendFeedback(source, Feedback.success(info.name() + " has been enabled.")); + public void enable() { + enabled = true; + onEnable(); + } + public int enable(ServerCommandSource source) throws CommandSyntaxException { + enable(); + Feedback.success("Enabled feature {}", getName()).send(source); return 0; } - protected int onDisable(ServerCommandSource source) throws CommandSyntaxException { - INJECTOR.getInstance(FeedbackSender.class).sendFeedback(source, Feedback.success(info.name() + " has been disabled.")); + public int enable(CommandContext context) throws CommandSyntaxException { + return enable(context.getSource()); + } + public void disable() { + enabled = false; + onDisable(); + } + + public int disable(ServerCommandSource source) throws CommandSyntaxException { + disable(); + Feedback.success("Disabled feature {}", getName()).send(source); return 0; } + + public int disable(CommandContext context) throws CommandSyntaxException { + return disable(context.getSource()); + } + + protected void onEnable() { } + protected void onDisable() { } + + // todo: right now the configuration methods are assuming every + // type is serialized to a string, this should be fixed in the future + // but for now it works because every type right now serializes to a string + // + it will probably be refactored soon + + /** Reloads the configuration from the disk. */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void loadConfig() { + try { + boolean enabled = false; + if (Files.exists(configFile)) { + JsonObject object = GSON.fromJson(new BufferedReader( + new InputStreamReader(Files.newInputStream(configFile), StandardCharsets.UTF_8)), + JsonObject.class); + + enabled = Boolean.parseBoolean(object.get("enabled").getAsString()); + + // read options + for (Argument argument : arguments) { + if (!object.has(argument.getName())) + continue; + + String valueString = object.get(argument.getName()).getAsString(); + Object value = argument.getType().deserialize(valueString); + + argument.setValue(value); + } + } + + setEnabled(enabled); + + RedstoneToolsClient.LOGGER.info("Loaded configuration for feature " + getID() + " file(" + configFile + ")"); + } catch (Exception e) { + RedstoneToolsClient.LOGGER.error("Failed to load configuration for feature " + getID() + " file(" + configFile + ")"); + e.printStackTrace(); + } + } + + /** Saves the configuration to the disk. */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void saveConfig() { + try { + if (!Files.exists(configFile)) { + if (!Files.exists(configFile.getParent())) + Files.createDirectories(configFile.getParent()); + Files.createFile(configFile); + } + + // serialize configuration + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("enabled", Boolean.toString(enabled)); + for (Argument argument : arguments) { + Object value = argument.getValue(); + String valueSerialized = (String) argument.getType().serialize(value); + + jsonObject.addProperty(argument.getName(), valueSerialized); + } + + // write json document + String json = GSON.toJson(jsonObject); + OutputStream outputStream = Files.newOutputStream(configFile); + outputStream.write(json.getBytes(StandardCharsets.UTF_8)); + outputStream.close(); + + RedstoneToolsClient.LOGGER.info("Saved configuration for feature " + getID() + " file(" + configFile + ")"); + } catch (Exception e) { + RedstoneToolsClient.LOGGER.error("Failed to save configuration for feature " + getID() + " file(" + configFile + ")"); + e.printStackTrace(); + } + } + } diff --git a/src/main/java/tools/redstone/redstonetools/macros/KeyBindingMixin.java b/src/main/java/tools/redstone/redstonetools/macros/KeyBindingMixin.java index e471d3b2..3e0f1cda 100644 --- a/src/main/java/tools/redstone/redstonetools/macros/KeyBindingMixin.java +++ b/src/main/java/tools/redstone/redstonetools/macros/KeyBindingMixin.java @@ -1,9 +1,12 @@ package tools.redstone.redstonetools.macros; import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil.Key; public interface KeyBindingMixin { void removeKeybinding(KeyBinding keyBinding); + KeyBinding getBindingFromKey(Key key); + } diff --git a/src/main/java/tools/redstone/redstonetools/macros/Macro.java b/src/main/java/tools/redstone/redstonetools/macros/Macro.java index 20974ad9..2e30e36e 100644 --- a/src/main/java/tools/redstone/redstonetools/macros/Macro.java +++ b/src/main/java/tools/redstone/redstonetools/macros/Macro.java @@ -1,5 +1,6 @@ package tools.redstone.redstonetools.macros; +import net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry; import tools.redstone.redstonetools.macros.actions.Action; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.minecraft.client.MinecraftClient; @@ -85,10 +86,12 @@ public void setKey(Key key) { } public void changeKeyBindingKeyCode() { + if (this.keyBinding != null) { MinecraftClient.getInstance().options.setKeyCode(keyBinding,key); KeyBinding.updateKeysByCode(); } + } public Key getKey(){ diff --git a/src/main/java/tools/redstone/redstonetools/macros/MacroManager.java b/src/main/java/tools/redstone/redstonetools/macros/MacroManager.java index 27cd7093..95b1f0cf 100644 --- a/src/main/java/tools/redstone/redstonetools/macros/MacroManager.java +++ b/src/main/java/tools/redstone/redstonetools/macros/MacroManager.java @@ -130,6 +130,7 @@ private List getDefaultMacros() { "/gamerule doMobSpawning false", "/gamerule doContainerDrops false", "/time set noon", + "/weather clear" }) ); } diff --git a/src/main/java/tools/redstone/redstonetools/macros/WorldlessCommandHelper.java b/src/main/java/tools/redstone/redstonetools/macros/WorldlessCommandHelper.java deleted file mode 100644 index 6de3f240..00000000 --- a/src/main/java/tools/redstone/redstonetools/macros/WorldlessCommandHelper.java +++ /dev/null @@ -1,42 +0,0 @@ -package tools.redstone.redstonetools.macros; - -import tools.redstone.redstonetools.macros.gui.commandsuggestor.WorldlessCommandSource; -import com.mojang.authlib.GameProfile; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.network.ClientConnection; -import net.minecraft.network.NetworkSide; -import net.minecraft.server.command.CommandManager; -import net.minecraft.tag.BlockTags; -import net.minecraft.util.registry.RegistryEntry; -import net.minecraft.world.Difficulty; -import net.minecraft.world.dimension.DimensionType; - -import java.util.OptionalLong; - -import static net.minecraft.world.dimension.DimensionType.OVERWORLD_ID; - - -public class WorldlessCommandHelper { - - public static final ClientPlayNetworkHandler dummyNetworkHandler; - public static final WorldlessCommandSource commandSource; - - public static final ClientPlayerEntity dummyPlayer; - - public static boolean registered = false; - - - static { - dummyNetworkHandler = new ClientPlayNetworkHandler(MinecraftClient.getInstance(), null,new ClientConnection(NetworkSide.CLIENTBOUND) , new GameProfile(null, "Player0"), null); - commandSource = new WorldlessCommandSource(); - - new CommandManager(CommandManager.RegistrationEnvironment.DEDICATED); //registers commands using CommandDispatcherMixin - registered = true; - - dummyPlayer = new ClientPlayerEntity(MinecraftClient.getInstance(),new ClientWorld(dummyNetworkHandler,new ClientWorld.Properties(Difficulty.EASY,false,false),null,new RegistryEntry.Direct<>(DimensionType.create(OptionalLong.empty(), true, false, false, true, 1.0, false, false, true, false, true, -64, 384, 384, BlockTags.INFINIBURN_OVERWORLD, OVERWORLD_ID, 0.0F)),0,0,null,null,true,0 ), dummyNetworkHandler, null,null,false,false); - } - -} diff --git a/src/main/java/tools/redstone/redstonetools/macros/gui/commandsuggestor/WorldlessCommandSuggestor.java b/src/main/java/tools/redstone/redstonetools/macros/gui/MaroCommandSuggestor.java similarity index 53% rename from src/main/java/tools/redstone/redstonetools/macros/gui/commandsuggestor/WorldlessCommandSuggestor.java rename to src/main/java/tools/redstone/redstonetools/macros/gui/MaroCommandSuggestor.java index 05647618..9e4b9314 100644 --- a/src/main/java/tools/redstone/redstonetools/macros/gui/commandsuggestor/WorldlessCommandSuggestor.java +++ b/src/main/java/tools/redstone/redstonetools/macros/gui/MaroCommandSuggestor.java @@ -1,4 +1,4 @@ -package tools.redstone.redstonetools.macros.gui.commandsuggestor; +package tools.redstone.redstonetools.macros.gui; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; @@ -8,12 +8,12 @@ import java.util.HashMap; -public class WorldlessCommandSuggestor extends CommandSuggestor { - private static final HashMap yMap = new HashMap<>(); +public class MaroCommandSuggestor extends CommandSuggestor { + private static final HashMap yMap = new HashMap<>(); - public WorldlessCommandSuggestor(MinecraftClient client, Screen owner, TextFieldWidget textField, TextRenderer textRenderer, boolean slashOptional, boolean suggestingWhenEmpty, int y, int maxSuggestionSize, int color) { + public MaroCommandSuggestor(MinecraftClient client, Screen owner, TextFieldWidget textField, TextRenderer textRenderer, boolean slashOptional, boolean suggestingWhenEmpty, int y, int maxSuggestionSize, int color) { super(client, owner, textField, textRenderer, slashOptional, suggestingWhenEmpty, 0, maxSuggestionSize, false, color); yMap.put(this,y); @@ -24,7 +24,7 @@ public void close(){ } public static boolean instance(Object object) { - return object instanceof WorldlessCommandSuggestor; + return object instanceof MaroCommandSuggestor; } public static int getY(Object object){ @@ -33,10 +33,9 @@ public static int getY(Object object){ @Override public void refresh() { - try{ - super.refresh(); - } catch (NullPointerException ignored) { - } + if (MinecraftClient.getInstance().player == null) return; + super.refresh(); + } diff --git a/src/main/java/tools/redstone/redstonetools/macros/gui/commandsuggestor/WorldlessCommandSource.java b/src/main/java/tools/redstone/redstonetools/macros/gui/commandsuggestor/WorldlessCommandSource.java deleted file mode 100644 index dce42ae0..00000000 --- a/src/main/java/tools/redstone/redstonetools/macros/gui/commandsuggestor/WorldlessCommandSource.java +++ /dev/null @@ -1,55 +0,0 @@ -package tools.redstone.redstonetools.macros.gui.commandsuggestor; - -import com.google.common.collect.Lists; -import net.minecraft.client.MinecraftClient; -import net.minecraft.recipe.RecipeManager; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.util.Identifier; -import net.minecraft.util.registry.RegistryKey; -import net.minecraft.world.World; - -import java.util.Collection; -import java.util.Set; -import java.util.stream.Stream; - -public class WorldlessCommandSource extends ServerCommandSource { - - private static final RecipeManager recipeManager = new RecipeManager(); - - - public WorldlessCommandSource() { - super(null,null,null,null,0,null,null, null,null); - } - - @Override - public boolean hasPermissionLevel(int level) { - return true; - } - - @Override - public Collection getPlayerNames() { - return Lists.newArrayList(); - } - - @Override - public Collection getTeamNames() { - return Lists.newArrayList(); - } - - @Override - public Collection getSoundIds() { - return MinecraftClient.getInstance().getSoundManager().getKeys(); - } - - @Override - public Stream getRecipeIds() { - return recipeManager.keys(); - } - - @Override - public Set> getWorldKeys() { - return Set.of(); - } - - -} diff --git a/src/main/java/tools/redstone/redstonetools/macros/gui/screen/CommandEditScreen.java b/src/main/java/tools/redstone/redstonetools/macros/gui/screen/CommandEditScreen.java index 3c036ca1..4e64fd5f 100644 --- a/src/main/java/tools/redstone/redstonetools/macros/gui/screen/CommandEditScreen.java +++ b/src/main/java/tools/redstone/redstonetools/macros/gui/screen/CommandEditScreen.java @@ -1,7 +1,6 @@ package tools.redstone.redstonetools.macros.gui.screen; -import org.lwjgl.glfw.GLFW; -import tools.redstone.redstonetools.macros.gui.commandsuggestor.WorldlessCommandSuggestor; +import tools.redstone.redstonetools.macros.gui.MaroCommandSuggestor; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.option.GameOptionsScreen; @@ -15,18 +14,18 @@ public class CommandEditScreen extends GameOptionsScreen { private final TextFieldWidget commandField; - private final WorldlessCommandSuggestor commandWorldlessCommandSuggestor; + private final MaroCommandSuggestor commandMaroCommandSuggestor; private boolean changed = false; public CommandEditScreen(Screen parent, GameOptions gameOptions, TextFieldWidget commandField) { super(parent, gameOptions, Text.of("")); this.commandField = commandField; client = MinecraftClient.getInstance(); - this.commandWorldlessCommandSuggestor = new WorldlessCommandSuggestor(client, parent, commandField,client.textRenderer,true,false, commandField.y -20,5,-805306368); + this.commandMaroCommandSuggestor = new MaroCommandSuggestor(client, parent, commandField,client.textRenderer,true,false, commandField.y -20,5,-805306368); commandField.setChangedListener((s) -> changed = true); - commandWorldlessCommandSuggestor.setWindowActive(true); - commandWorldlessCommandSuggestor.refresh(); + commandMaroCommandSuggestor.setWindowActive(true); + commandMaroCommandSuggestor.refresh(); } @Override @@ -37,9 +36,9 @@ public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { commandField.render(matrices, mouseX, mouseY, delta); - commandWorldlessCommandSuggestor.render(matrices, mouseX, mouseY); + commandMaroCommandSuggestor.render(matrices, mouseX, mouseY); if (changed) { - commandWorldlessCommandSuggestor.refresh(); + commandMaroCommandSuggestor.refresh(); changed = false; } @@ -63,15 +62,15 @@ public void close() { super.close(); commandField.setTextFieldFocused(false); commandField.setChangedListener(null); - commandWorldlessCommandSuggestor.setWindowActive(false); - commandWorldlessCommandSuggestor.refresh(); - commandWorldlessCommandSuggestor.close(); + commandMaroCommandSuggestor.setWindowActive(false); + commandMaroCommandSuggestor.refresh(); + commandMaroCommandSuggestor.close(); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (!commandField.mouseClicked(mouseX, mouseY, button)) { - if (!commandWorldlessCommandSuggestor.mouseClicked(mouseX, mouseY, button)) { + if (!commandMaroCommandSuggestor.mouseClicked(mouseX, mouseY, button)) { close(); } else { commandField.setTextFieldFocused(true); @@ -83,7 +82,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - return commandWorldlessCommandSuggestor.mouseScrolled(amount); + return commandMaroCommandSuggestor.mouseScrolled(amount); } @Override @@ -97,7 +96,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { close(); return true; } - commandWorldlessCommandSuggestor.keyPressed(keyCode, scanCode, modifiers); + commandMaroCommandSuggestor.keyPressed(keyCode, scanCode, modifiers); return commandField.keyPressed(keyCode, scanCode, modifiers); } diff --git a/src/main/java/tools/redstone/redstonetools/macros/gui/screen/MacroEditScreen.java b/src/main/java/tools/redstone/redstonetools/macros/gui/screen/MacroEditScreen.java index d48cb60d..8ad466c9 100644 --- a/src/main/java/tools/redstone/redstonetools/macros/gui/screen/MacroEditScreen.java +++ b/src/main/java/tools/redstone/redstonetools/macros/gui/screen/MacroEditScreen.java @@ -24,6 +24,7 @@ import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import tools.redstone.redstonetools.utils.KeyBindingUtils; import java.util.List; @@ -31,7 +32,6 @@ public class MacroEditScreen extends GameOptionsScreen { - private final MacroListWidget macroListWidget; private final Macro macro; @@ -92,6 +92,7 @@ public void init() { Key keyCode = macro.getKey(); Text text = keyCode.getLocalizedText(); if (keyCode == InputUtil.UNKNOWN_KEY) text = Text.of(""); + if ( KeyBindingUtils.isKeyAlreadyBound(keyCode) ) { text = new LiteralText(text.getString()).formatted(Formatting.RED); } keyBindButton = new ButtonWidget(this.width / 2 + 26, 55, 75, 20, text, (button) -> { detectingKeycodeKey = true; @@ -244,6 +245,7 @@ private boolean updateKeybinding(Key key) { detectingKeycodeKey = false; Text text = key.getLocalizedText(); if (key == InputUtil.UNKNOWN_KEY) text = Text.of(""); + if ( KeyBindingUtils.isKeyAlreadyBound(key) ) { text = new LiteralText(text.getString()).formatted(Formatting.RED); } keyBindButton.setMessage(text); macro.setKey(key); diff --git a/src/main/java/tools/redstone/redstonetools/macros/gui/widget/commandlist/CommandEntry.java b/src/main/java/tools/redstone/redstonetools/macros/gui/widget/commandlist/CommandEntry.java index 0b8ca91e..afb75e28 100644 --- a/src/main/java/tools/redstone/redstonetools/macros/gui/widget/commandlist/CommandEntry.java +++ b/src/main/java/tools/redstone/redstonetools/macros/gui/widget/commandlist/CommandEntry.java @@ -1,6 +1,6 @@ package tools.redstone.redstonetools.macros.gui.widget.commandlist; -import tools.redstone.redstonetools.macros.gui.commandsuggestor.WorldlessCommandSuggestor; +import tools.redstone.redstonetools.macros.gui.MaroCommandSuggestor; import tools.redstone.redstonetools.macros.gui.widget.IconButtonWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.widget.ButtonWidget; @@ -22,17 +22,17 @@ public CommandEntry(MinecraftClient client, CommandListWidget owner, String text this.owner = owner; command = new TextFieldWidget(client.textRenderer, 0, 0, 300, 20, Text.of("")); - command.setMaxLength(32500); + command.setMaxLength(255); command.setText(text); deleteButton = new IconButtonWidget(IconButtonWidget.CROSS_ICON,0, 0, 20, 20, Text.of(""), (button) -> { this.owner.removeCommand(this); }); - WorldlessCommandSuggestor commandWorldlessCommandSuggestor = new WorldlessCommandSuggestor(client, owner.getParent(), command,client.textRenderer,true,false, 0,0,0); - commandWorldlessCommandSuggestor.setWindowActive(false); - commandWorldlessCommandSuggestor.refresh(); - commandWorldlessCommandSuggestor.close(); + MaroCommandSuggestor commandMaroCommandSuggestor = new MaroCommandSuggestor(client, owner.getParent(), command,client.textRenderer,true,false, 0,0,0); + commandMaroCommandSuggestor.setWindowActive(false); + commandMaroCommandSuggestor.refresh(); + commandMaroCommandSuggestor.close(); } diff --git a/src/main/java/tools/redstone/redstonetools/mixin/accessors/MinecraftClientAccessor.java b/src/main/java/tools/redstone/redstonetools/mixin/accessors/MinecraftClientAccessor.java index f3632cdf..6996cf54 100644 --- a/src/main/java/tools/redstone/redstonetools/mixin/accessors/MinecraftClientAccessor.java +++ b/src/main/java/tools/redstone/redstonetools/mixin/accessors/MinecraftClientAccessor.java @@ -8,6 +8,8 @@ @Mixin(MinecraftClient.class) public interface MinecraftClientAccessor { + @Invoker ItemStack invokeAddBlockEntityNbt(ItemStack stack, BlockEntity blockEntity); + } diff --git a/src/main/java/tools/redstone/redstonetools/mixin/accessors/WorldRendererAccessor.java b/src/main/java/tools/redstone/redstonetools/mixin/accessors/WorldRendererAccessor.java new file mode 100644 index 00000000..88ff872a --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/mixin/accessors/WorldRendererAccessor.java @@ -0,0 +1,18 @@ +package tools.redstone.redstonetools.mixin.accessors; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(WorldRenderer.class) +public interface WorldRendererAccessor { + + @Invoker + void invokeDrawBlockOutline(MatrixStack matrices, VertexConsumer vertexConsumer, Entity entity, double d, double e, double f, BlockPos pos, BlockState state); + +} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/blocks/RedstoneHitboxMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/blocks/RedstoneHitboxMixin.java new file mode 100644 index 00000000..663852fc --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/mixin/blocks/RedstoneHitboxMixin.java @@ -0,0 +1,46 @@ +package tools.redstone.redstonetools.mixin.blocks; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.RedstoneWireBlock; +import net.minecraft.block.ShapeContext; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.BlockView; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import tools.redstone.redstonetools.RedstoneToolsClient; +import tools.redstone.redstonetools.features.toggleable.BigDustFeature; + +import java.util.HashMap; +import java.util.Map; + +@Pseudo +@Mixin(RedstoneWireBlock.class) +public class RedstoneHitboxMixin { + + private static BigDustFeature bigDustFeature; + + // use array for better performance + private static final VoxelShape[] SHAPES = new VoxelShape[16]; + + @Inject(method="getOutlineShape", at = @At("HEAD"), cancellable = true) + public void getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context, CallbackInfoReturnable cir) { + if (bigDustFeature == null) { + bigDustFeature = RedstoneToolsClient.INJECTOR.getInstance(BigDustFeature.class); + } + + if (bigDustFeature.isEnabled()) { + cir.setReturnValue(SHAPES[BigDustFeature.heightInPixels.getValue() - 1]); + } + } + + static { + for (int i = 1; i <= 16; i++) { + SHAPES[i - 1] = Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, i, 16.0); + } + } + +} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/features/AirplaceMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/features/AirPlaceClientMixin.java similarity index 71% rename from src/main/java/tools/redstone/redstonetools/mixin/features/AirplaceMixin.java rename to src/main/java/tools/redstone/redstonetools/mixin/features/AirPlaceClientMixin.java index f936465d..9236718d 100644 --- a/src/main/java/tools/redstone/redstonetools/mixin/features/AirplaceMixin.java +++ b/src/main/java/tools/redstone/redstonetools/mixin/features/AirPlaceClientMixin.java @@ -7,7 +7,6 @@ import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; -import net.minecraft.util.math.Vec3d; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -17,9 +16,10 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import tools.redstone.redstonetools.RedstoneToolsClient; import tools.redstone.redstonetools.features.toggleable.AirPlaceFeature; +import tools.redstone.redstonetools.utils.RaycastUtils; @Mixin(MinecraftClient.class) -public class AirplaceMixin { +public class AirPlaceClientMixin { private final AirPlaceFeature airPlaceFeature = RedstoneToolsClient.INJECTOR.getInstance(AirPlaceFeature.class); @Shadow @@ -27,27 +27,26 @@ public class AirplaceMixin { @Inject(method = "doItemUse", at = @At(value = "HEAD"), locals = LocalCapture.CAPTURE_FAILHARD) public void doItemUse(CallbackInfo callbackInfo) { - if (!isAirplaceAllowed()) { + if (!isAirPlaceAllowed()) { return; } - var hitResult = getAirplaceHitResult(); - - crosshairTarget = new BlockHitResult(hitResult, Direction.UP, new BlockPos(hitResult), false); + crosshairTarget = AirPlaceFeature.findAirPlaceBlockHit(getPlayer()); } @Inject(method = "doAttack", at = @At(value = "HEAD"), locals = LocalCapture.CAPTURE_FAILHARD) public void doAttack(CallbackInfoReturnable cir) { - if (!isAirplaceAllowed()) { + if (!isAirPlaceAllowed()) { return; } - //Call interactionManager directly because the block is air, with which the player cannot interact - getInteractionManager().attackBlock(new BlockPos(getAirplaceHitResult()), Direction.UP); + // Call interactionManager directly because the block is air, with which the player cannot interact + var hit = AirPlaceFeature.findAirPlaceBlockHit(getPlayer()); + getInteractionManager().attackBlock(hit.getBlockPos(), hit.getSide()); } - private boolean isAirplaceAllowed() { - // If airplace is disabled + private boolean isAirPlaceAllowed() { + // If air place is disabled if (!airPlaceFeature.isEnabled()) { return false; } @@ -62,18 +61,13 @@ private boolean isAirplaceAllowed() { return false; } - return true; - } - - private Vec3d getAirplaceHitResult() { - // Asserting values previously tested by isAirplaceAllowed() - assert getInteractionManager() != null; - assert getPlayer() != null; - - var reach = getInteractionManager().getReachDistance(); - var hitResult = getPlayer().raycast(reach, 0, false); + // If air place isn't possible with the current + // player equipment and state + if (!AirPlaceFeature.canAirPlace(getPlayer())) { + return false; + } - return hitResult.getPos(); + return true; } private MinecraftClient getMinecraftClient() { @@ -87,4 +81,5 @@ private ClientPlayerEntity getPlayer() { private ClientPlayerInteractionManager getInteractionManager() { return getMinecraftClient().interactionManager; } + } diff --git a/src/main/java/tools/redstone/redstonetools/mixin/features/AirPlaceServerMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/features/AirPlaceServerMixin.java new file mode 100644 index 00000000..5b12380e --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/mixin/features/AirPlaceServerMixin.java @@ -0,0 +1,27 @@ +package tools.redstone.redstonetools.mixin.features; + +import net.minecraft.server.network.ServerPlayNetworkHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import tools.redstone.redstonetools.RedstoneToolsClient; +import tools.redstone.redstonetools.features.toggleable.AirPlaceFeature; + +@Mixin(ServerPlayNetworkHandler.class) +public class AirPlaceServerMixin { + + private final AirPlaceFeature airPlaceFeature = RedstoneToolsClient.INJECTOR.getInstance(AirPlaceFeature.class); + + @ModifyConstant(method = "onPlayerInteractBlock", constant = @Constant(doubleValue = 64.0) ) + private double modifyConstant(double originalValue) { + + if (airPlaceFeature.isEnabled()) { + float reach5 = AirPlaceFeature.reach.getValue() + 5; + return reach5 * reach5; + } else { + return originalValue; + } + + } + +} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/features/ItemBindMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/features/ItemBindMixin.java new file mode 100644 index 00000000..ee194cba --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/mixin/features/ItemBindMixin.java @@ -0,0 +1,78 @@ +package tools.redstone.redstonetools.mixin.features; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtString; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import tools.redstone.redstonetools.features.commands.ItemBindFeature; +import tools.redstone.redstonetools.features.feedback.Feedback; +import tools.redstone.redstonetools.features.feedback.FeedbackSender; + +import static tools.redstone.redstonetools.RedstoneToolsClient.INJECTOR; + + +public abstract class ItemBindMixin { + + @Mixin(ItemStack.class) + private abstract static class ItemStackMixin { + + @Shadow + public abstract @Nullable NbtCompound getNbt(); + + @Inject(method = "use", at = @At("HEAD"), cancellable = true) + public void checkCommandNBT(World world, PlayerEntity user, Hand hand, CallbackInfoReturnable> cir) { + if (tryToExecuteNBTCommand(hand, world)) { + cir.setReturnValue(TypedActionResult.pass((ItemStack) ((Object) this))); + } + } + + @Inject(method = "useOnBlock", at = @At("HEAD"), cancellable = true) + public void checkCommandNBT(ItemUsageContext context, CallbackInfoReturnable cir) { + if (tryToExecuteNBTCommand(context.getHand(), context.getWorld())) { + cir.setReturnValue(ActionResult.PASS); + } + } + + private boolean tryToExecuteNBTCommand(Hand hand, World world) { + if (hand == Hand.OFF_HAND || world.isClient) return false; + NbtCompound nbt = getNbt(); + if (nbt == null || !nbt.contains("command")) return false; + NbtString command = (NbtString) nbt.get("command"); + MinecraftClient.getInstance().player.sendChatMessage(command.asString()); + + return true; + } + } + + @Mixin(ClientPlayerEntity.class) + private abstract static class PlayerMixin { + + @Inject(method = "sendChatMessage", at = @At("HEAD"), cancellable = true) + public void injectCommand(String message, CallbackInfo ci) { + if (!message.startsWith("/") || !ItemBindFeature.waitingForCommand) return; + + Feedback addCommandFeedback = ItemBindFeature.addCommand(message); + if (addCommandFeedback != null) { + INJECTOR.getInstance(FeedbackSender.class).sendFeedback(((Entity) ((Object)this)).getCommandSource(),addCommandFeedback); + ci.cancel(); + } + } + } + + +} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/gamerules/DoContainerDropsMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/gamerules/DoContainerDropsMixin.java index 007e49d2..48280e65 100644 --- a/src/main/java/tools/redstone/redstonetools/mixin/gamerules/DoContainerDropsMixin.java +++ b/src/main/java/tools/redstone/redstonetools/mixin/gamerules/DoContainerDropsMixin.java @@ -1,7 +1,8 @@ package tools.redstone.redstonetools.mixin.gamerules; -import net.minecraft.item.ItemStack; +import net.minecraft.inventory.Inventory; import net.minecraft.util.ItemScatterer; +import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -12,8 +13,8 @@ @Mixin(ItemScatterer.class) public class DoContainerDropsMixin { - @Inject(method = "spawn(Lnet/minecraft/world/World;DDDLnet/minecraft/item/ItemStack;)V", at = @At("HEAD"), cancellable = true) - private static void spawn(World world, double x, double y, double z, ItemStack stack, CallbackInfo ci) { + @Inject(method = "spawn(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/inventory/Inventory;)V", at = @At("HEAD"), cancellable = true) + private static void spawn(World world, BlockPos pos, Inventory inventory, CallbackInfo ci) { if (!world.getGameRules().getBoolean(DO_CONTAINER_DROPS)) ci.cancel(); } } diff --git a/src/main/java/tools/redstone/redstonetools/mixin/macros/KeyBindingMixinImpl.java b/src/main/java/tools/redstone/redstonetools/mixin/macros/KeyBindingMixinImpl.java index 677cc486..8ac71b89 100644 --- a/src/main/java/tools/redstone/redstonetools/mixin/macros/KeyBindingMixinImpl.java +++ b/src/main/java/tools/redstone/redstonetools/mixin/macros/KeyBindingMixinImpl.java @@ -22,4 +22,9 @@ public void removeKeybinding(KeyBinding keyBinding) { KEY_TO_BINDINGS.entrySet().removeIf(entry -> entry.getValue().equals(this)); } + @Override + public KeyBinding getBindingFromKey(InputUtil.Key key) { + return KEY_TO_BINDINGS.get(key); + } + } diff --git a/src/main/java/tools/redstone/redstonetools/mixin/macros/autocomplete/CommandDispatcherMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/macros/autocomplete/CommandDispatcherMixin.java deleted file mode 100644 index 862d666f..00000000 --- a/src/main/java/tools/redstone/redstonetools/mixin/macros/autocomplete/CommandDispatcherMixin.java +++ /dev/null @@ -1,44 +0,0 @@ -package tools.redstone.redstonetools.mixin.macros.autocomplete; - -import tools.redstone.redstonetools.macros.WorldlessCommandHelper; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.tree.LiteralCommandNode; -import net.minecraft.command.CommandSource; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - - -@org.spongepowered.asm.mixin.Mixin(CommandDispatcher.class) -public class CommandDispatcherMixin { - - - @Inject(method = "register", at = @At("HEAD"), remap = false) - private void register(LiteralArgumentBuilder command, CallbackInfoReturnable> cir) { - if (!WorldlessCommandHelper.registered && !WorldlessCommandHelper.dummyNetworkHandler.getCommandDispatcher().equals(this)){ - WorldlessCommandHelper.dummyNetworkHandler.getCommandDispatcher().register((LiteralArgumentBuilder) command); - } - } - - @ModifyVariable(method = "getSmartUsage(Lcom/mojang/brigadier/tree/CommandNode;Ljava/lang/Object;)Ljava/util/Map;", at = @At("HEAD"), ordinal = 0, argsOnly = true, remap = false) - public Object getSmartUsageSource(Object source) { - return getSourceOrDefault(source); - } - - @ModifyVariable(method = "parse(Lcom/mojang/brigadier/StringReader;Ljava/lang/Object;)Lcom/mojang/brigadier/ParseResults;", at = @At("HEAD"), ordinal = 0, argsOnly = true, remap = false) - public Object parseSource(Object source) { - return getSourceOrDefault(source); - } - - private Object getSourceOrDefault(Object def) { - if (WorldlessCommandHelper.dummyNetworkHandler.getCommandDispatcher().equals(this)) { - return WorldlessCommandHelper.commandSource; - } - - return def; - } - - -} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/macros/autocomplete/CommandSuggestorMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/macros/autocomplete/CommandSuggestorMixin.java index 0efcdd54..c88c08b4 100644 --- a/src/main/java/tools/redstone/redstonetools/mixin/macros/autocomplete/CommandSuggestorMixin.java +++ b/src/main/java/tools/redstone/redstonetools/mixin/macros/autocomplete/CommandSuggestorMixin.java @@ -1,10 +1,8 @@ package tools.redstone.redstonetools.mixin.macros.autocomplete; import com.mojang.brigadier.suggestion.Suggestions; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.CommandSuggestor; import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.util.math.MatrixStack; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; @@ -14,9 +12,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import tools.redstone.redstonetools.macros.ClientPlayerEntityMixin; -import tools.redstone.redstonetools.macros.WorldlessCommandHelper; -import tools.redstone.redstonetools.macros.gui.commandsuggestor.WorldlessCommandSuggestor; +import tools.redstone.redstonetools.macros.gui.MaroCommandSuggestor; import java.util.concurrent.CompletableFuture; @@ -24,8 +20,6 @@ @Mixin(CommandSuggestor.class) public class CommandSuggestorMixin{ - @Shadow @Final - MinecraftClient client; @Shadow @Final TextFieldWidget textField; @Shadow private @Nullable CompletableFuture pendingSuggestions; @@ -33,64 +27,9 @@ public class CommandSuggestorMixin{ int maxSuggestionSize; - private ClientPlayNetworkHandler networkHandler; - - - - @Inject(method = "refresh", at = @At("HEAD")) - public void refreshHead(CallbackInfo ci){ - if (WorldlessCommandSuggestor.instance(this)) { - if (client.player == null || client.player.equals(WorldlessCommandHelper.dummyPlayer)) { - client.player = WorldlessCommandHelper.dummyPlayer; - } else { - if (client.player.networkHandler != WorldlessCommandHelper.dummyNetworkHandler) networkHandler = client.player.networkHandler; - ((ClientPlayerEntityMixin)client.player).setNetworkHandler(WorldlessCommandHelper.dummyNetworkHandler); - } - } - } - - @Inject(method = "refresh", at = @At("TAIL")) - public void refreshTail(CallbackInfo ci){ - if (WorldlessCommandSuggestor.instance(this)) { - - if (client.player == null || client.player.equals(WorldlessCommandHelper.dummyPlayer)) { - client.player = null; - } else if (networkHandler != null){ - ((ClientPlayerEntityMixin)client.player).setNetworkHandler(networkHandler); - } - } - } - - @Inject(method = "showUsages", at = @At("HEAD")) - public void showUsagesHead(CallbackInfo ci){ - if (WorldlessCommandSuggestor.instance(this)) { - - if (client.player == null || client.player.equals(WorldlessCommandHelper.dummyPlayer)) { - client.player = WorldlessCommandHelper.dummyPlayer; - } else { - if (client.player.networkHandler != WorldlessCommandHelper.dummyNetworkHandler) networkHandler = client.player.networkHandler; - ((ClientPlayerEntityMixin)client.player).setNetworkHandler(WorldlessCommandHelper.dummyNetworkHandler); - } - - } - } - - @Inject(method = "showUsages", at = @At("TAIL")) - public void showUsagesTail(CallbackInfo ci){ - if (WorldlessCommandSuggestor.instance(this)) { - - if (client.player == null || client.player.equals(WorldlessCommandHelper.dummyPlayer) ) { - client.player = null; - } else if (networkHandler != null){ - ((ClientPlayerEntityMixin)client.player).setNetworkHandler(networkHandler); - } - - } - } - @ModifyVariable(method = "showSuggestions", at = @At("STORE"), ordinal = 1) public int suggestionWindXPos(int j){ - if (WorldlessCommandSuggestor.instance(this)) { + if (MaroCommandSuggestor.instance(this)) { Suggestions suggestions = this.pendingSuggestions.join(); return this.textField.getCharacterX(suggestions.getRange().getStart())+4; } @@ -99,10 +38,10 @@ public int suggestionWindXPos(int j){ @ModifyVariable(method = "showSuggestions", at = @At("STORE"), ordinal = 2) public int suggestionWindYPos(int k){ - if (WorldlessCommandSuggestor.instance(this)) { + if (MaroCommandSuggestor.instance(this)) { Suggestions suggestions = this.pendingSuggestions.join(); - int y = WorldlessCommandSuggestor.getY(this)-2; + int y = MaroCommandSuggestor.getY(this)-2; return y +20 - Math.min(suggestions.getList().size(), this.maxSuggestionSize) * 12; } return k; @@ -118,8 +57,8 @@ public void render(MatrixStack matrices, int mouseX, int mouseY, CallbackInfo ci @ModifyVariable(method = "render", at = @At("STORE"), ordinal = 3) public int messageYPos(int j) { - if (WorldlessCommandSuggestor.instance(this)) { - int y = WorldlessCommandSuggestor.getY(this); + if (MaroCommandSuggestor.instance(this)) { + int y = MaroCommandSuggestor.getY(this); i++; return y - 12*(i-1)+43; } diff --git a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/InitializeTelemetryClientMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/telemetry/InitializeTelemetryClientMixin.java deleted file mode 100644 index 5c924b8d..00000000 --- a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/InitializeTelemetryClientMixin.java +++ /dev/null @@ -1,18 +0,0 @@ -package tools.redstone.redstonetools.mixin.telemetry; - -import tools.redstone.redstonetools.telemetry.TelemetryClient; -import net.minecraft.client.MinecraftClient; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import static tools.redstone.redstonetools.RedstoneToolsClient.INJECTOR; - -@Mixin(MinecraftClient.class) -public class InitializeTelemetryClientMixin { - @Inject(method = "", at = @At("TAIL")) - public void init(CallbackInfo ci) { - INJECTOR.getInstance(TelemetryClient.class); - } -} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/SendCommandMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/telemetry/SendCommandMixin.java deleted file mode 100644 index 78316884..00000000 --- a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/SendCommandMixin.java +++ /dev/null @@ -1,31 +0,0 @@ -package tools.redstone.redstonetools.mixin.telemetry; - -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.minecraft.client.MinecraftClient; -import net.minecraft.server.network.ServerPlayerEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import tools.redstone.redstonetools.utils.TelemetryUtils; - -import java.util.Objects; - -@Mixin(net.minecraft.server.command.CommandManager.class) -public class SendCommandMixin { - @Inject(method = "execute", at = @At("HEAD")) - private void execute(net.minecraft.server.command.ServerCommandSource source, String command, CallbackInfoReturnable cir) { - ServerPlayerEntity player; - try { - player = source.getPlayer(); - } catch (CommandSyntaxException ignored) { - return; - } - - if (!player.getUuid().equals(Objects.requireNonNull(MinecraftClient.getInstance().player).getUuid())) { - return; - } - - TelemetryUtils.sendCommand(command); - } -} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/SendCrashReportMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/telemetry/SendCrashReportMixin.java deleted file mode 100644 index 3878d572..00000000 --- a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/SendCrashReportMixin.java +++ /dev/null @@ -1,17 +0,0 @@ -package tools.redstone.redstonetools.mixin.telemetry; - -import tools.redstone.redstonetools.utils.TelemetryUtils; -import net.minecraft.client.MinecraftClient; -import net.minecraft.util.crash.CrashReport; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MinecraftClient.class) -public class SendCrashReportMixin { - @Inject(method = "printCrashReport", at = @At("TAIL")) - private static void printCrashReport(CrashReport report, CallbackInfo ci) { - TelemetryUtils.sendCrash(report); - } -} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/ShowTelemetryPopupMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/telemetry/ShowTelemetryPopupMixin.java deleted file mode 100644 index 2639b213..00000000 --- a/src/main/java/tools/redstone/redstonetools/mixin/telemetry/ShowTelemetryPopupMixin.java +++ /dev/null @@ -1,44 +0,0 @@ -package tools.redstone.redstonetools.mixin.telemetry; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import tools.redstone.redstonetools.telemetry.TelemetryManager; - -import static tools.redstone.redstonetools.RedstoneToolsClient.INJECTOR; - -@Mixin(TitleScreen.class) -public class ShowTelemetryPopupMixin extends Screen { - private static final String TELEMETRY_PROMPT = "Redstone Tools includes an optional telemetry feature that collects anonymous usage data to help us improve the mod. By enabling telemetry, you can help us better understand how the mod is being used and identify areas for improvement.\n\n\n\nWould you like to help us improve Redstone Tools by enabling anonymous usage data?"; // TODO: Add "You can turn this off at any time in the settings menu." once you can actually turn it off in the settings menu - - public ShowTelemetryPopupMixin(Text title) { - super(title); - } - - @Inject(method = "init", at = @At("TAIL")) - public void init(CallbackInfo ci) { - var manager = INJECTOR.getInstance(TelemetryManager.class); - - if (!manager.showTelemetryPrompt) { - return; - } - - var parentScreen = MinecraftClient.getInstance().currentScreen; - var popup = new ConfirmScreen(accepted -> { - manager.showTelemetryPrompt = false; - manager.telemetryEnabled = accepted; - - manager.saveChanges(); - - MinecraftClient.getInstance().setScreen(parentScreen); - }, Text.of("Telemetry"), Text.of(TELEMETRY_PROMPT)); - - MinecraftClient.getInstance().setScreen(popup); - } -} diff --git a/src/main/java/tools/redstone/redstonetools/mixin/update/CheckUpdateMixin.java b/src/main/java/tools/redstone/redstonetools/mixin/update/CheckUpdateMixin.java index 72eba267..d2642ae4 100644 --- a/src/main/java/tools/redstone/redstonetools/mixin/update/CheckUpdateMixin.java +++ b/src/main/java/tools/redstone/redstonetools/mixin/update/CheckUpdateMixin.java @@ -2,11 +2,10 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ConfirmScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.text.Text; +import net.minecraft.client.gui.widget.PressableTextWidget; +import net.minecraft.text.*; import net.minecraft.util.Util; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -24,14 +23,16 @@ @Mixin(TitleScreen.class) public class CheckUpdateMixin extends Screen { - public boolean updateChecked = false; + private static boolean updateChecked = false; - public CheckUpdateMixin(Text title) { - super(title); + private static MutableText updateStatus = (MutableText) Text.of("Redstone Tools Version: " + MOD_VERSION + "(Bug found, report on Github)"); + private static URI uri; + public CheckUpdateMixin() { + super(Text.of("UpdateText(Bug found, report on Github)")); } @Inject(method = "init", at = @At("TAIL")) - public void init(CallbackInfo ci) { + public void checkUpdate(CallbackInfo ci) { if (updateChecked) return; @@ -55,7 +56,7 @@ public void init(CallbackInfo ci) { Gson gson = new Gson(); JsonObject release = gson.fromJson(responseBody, JsonObject.class); - URI uri = new URI(release.get("html_url").getAsString()); + uri = new URI(release.get("html_url").getAsString()); String newVersion = release.get("tag_name").getAsString(); LOGGER.info("Found latest version: " + newVersion); @@ -64,23 +65,17 @@ public void init(CallbackInfo ci) { return; } + Style underline = Style.EMPTY; if (RedstoneToolsClient.MOD_VERSION.equals(newVersion)) { LOGGER.info("Already up to date, current version: " + MOD_VERSION); - return; + updateStatus = (MutableText) Text.of("Redstone Tools " + MOD_VERSION); + } else { + LOGGER.info("Found newer version, current version: " + RedstoneToolsClient.MOD_VERSION + ", new version: " + newVersion); + updateStatus = (MutableText) Text.of("Redstone Tools " + MOD_VERSION + " ("); + updateStatus.append(Text.of("Click to Update").getWithStyle(underline.withUnderline(true)).get(0)); + updateStatus.append(")"); } - LOGGER.info("Found newer version, current version: " + RedstoneToolsClient.MOD_VERSION + ", new version: " + newVersion); - - var parentScreen = MinecraftClient.getInstance().currentScreen; - var popup = new ConfirmScreen(confirmed -> { - MinecraftClient.getInstance().setScreen(parentScreen); - - if (confirmed) { - Util.getOperatingSystem().open(uri); - } - }, Text.of("Update Available"), Text.of("An update is available for redstone tools! You are on version " + MOD_VERSION + " but version " + newVersion + " is available."), Text.of("Go to release"), Text.of("Ignore")); - - MinecraftClient.getInstance().setScreen(popup); } catch (Exception e) { LOGGER.warn("Failed to check for RedstoneTools updates"); e.printStackTrace(); @@ -88,4 +83,9 @@ public void init(CallbackInfo ci) { updateChecked = true; } } + + @Inject(method="init", at = @At("HEAD")) + public void updateTextInjection(CallbackInfo ci){ + this.addDrawableChild(new PressableTextWidget(4,4, textRenderer.getWidth(updateStatus), textRenderer.fontHeight,updateStatus,button -> {Util.getOperatingSystem().open(uri);},textRenderer)); + } } diff --git a/src/main/java/tools/redstone/redstonetools/telemetry/TelemetryClient.java b/src/main/java/tools/redstone/redstonetools/telemetry/TelemetryClient.java deleted file mode 100644 index 60cd0d69..00000000 --- a/src/main/java/tools/redstone/redstonetools/telemetry/TelemetryClient.java +++ /dev/null @@ -1,227 +0,0 @@ -package tools.redstone.redstonetools.telemetry; - -import com.google.gson.Gson; -import it.unimi.dsi.fastutil.Pair; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; -import tools.redstone.redstonetools.telemetry.dto.TelemetryAuth; -import tools.redstone.redstonetools.telemetry.dto.TelemetryCommand; -import tools.redstone.redstonetools.telemetry.dto.TelemetryException; - -import javax.inject.Singleton; -import java.io.IOException; -import java.net.ConnectException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Instant; -import java.util.Objects; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import static tools.redstone.redstonetools.RedstoneToolsClient.INJECTOR; -import static tools.redstone.redstonetools.RedstoneToolsClient.LOGGER; - -@Singleton -public class TelemetryClient { - private static final String BASE_URL = FabricLoader.getInstance().isDevelopmentEnvironment() - ? "https://localhost/api/telemetry/v1" - : "https://redstone.tools/api/telemetry/v1"; - private static final int SESSION_REFRESH_TIME_SECONDS = 60 * 4; // 4 minutes - private static final int REQUEST_SEND_TIME_MILLISECONDS = 50; - private static final int REQUEST_VALID_FOR_SECONDS = 15; - - private final TelemetryManager manager = INJECTOR.getInstance(TelemetryManager.class); - - private static volatile Instant lastAuthorization = Instant.MIN; - - private final Gson gson = new Gson(); - private final HttpClient httpClient; - private final Queue> requestQueue = new ConcurrentLinkedQueue<>(); - - private volatile String token; - - public TelemetryClient() { - LOGGER.info("Initializing telemetry client"); - - httpClient = HttpClient.newBuilder() - .build(); - - Executors.newSingleThreadExecutor() - .execute(this::refreshSessionThread); - - Executors.newSingleThreadExecutor() - .execute(this::sendQueuedRequestsAsync); - } - - public void sendCommand(TelemetryCommand command) { - if (manager.telemetryEnabled) { - addRequest(createRequest("/command", command)); - } - } - - public void sendException(TelemetryException exception) { - if (manager.telemetryEnabled) { - addRequest(createRequest("/exception", exception)); - } - } - - private void addRequest(HttpRequest.Builder request) { - requestQueue.add(Pair.of(request, Instant.now())); - } - - public synchronized void waitForQueueToEmpty() { - while (!requestQueue.isEmpty()) { - try { - TimeUnit.MILLISECONDS.sleep(REQUEST_SEND_TIME_MILLISECONDS); - } catch (InterruptedException ignored) { - } - } - } - - private HttpRequest.Builder createRequest(String path, Object body) { - final String bodyData = body == null ? "" : gson.toJson(body); - return HttpRequest.newBuilder() - .header("Content-Type", "application/json") - .uri(URI.create(BASE_URL + path)) - .POST(HttpRequest.BodyPublishers.ofString(bodyData)); - } - - private boolean isSuccessful(HttpResponse response) { - if (response == null) return false; - return response.statusCode() >= 200 && response.statusCode() < 300; - } - - private synchronized CompletableFuture sendQueuedRequestsAsync() { - return CompletableFuture.runAsync(() -> { - while (true) { - while (!requestQueue.isEmpty()) { - try { - TimeUnit.MILLISECONDS.sleep(REQUEST_SEND_TIME_MILLISECONDS); - } catch (InterruptedException ignored) { - } - - var pair = Objects.requireNonNull(requestQueue.peek()); - var request = pair.first(); - var queuedAt = pair.second(); - - if (queuedAt.plusSeconds(REQUEST_VALID_FOR_SECONDS).isBefore(Instant.now())) { - requestQueue.remove(); - - continue; - } - - if (token != null) { - request.header("Authorization", token); - } - - var response = sendPostRequest(request); - - if (response == null || !isSuccessful(response)) { - if (response != null && responseIsUnauthorized(response)) { - lastAuthorization = Instant.MIN; - } - - try { - TimeUnit.SECONDS.sleep(3); - } catch (InterruptedException ignored) { - } - - continue; - } - - requestQueue.remove(); - } - - try { - TimeUnit.MILLISECONDS.sleep(REQUEST_SEND_TIME_MILLISECONDS); - } catch (InterruptedException ignored) { - } - } - }); - } - - private synchronized HttpResponse sendPostRequest(HttpRequest.Builder request) { - LOGGER.trace("Sending telemetry request to " + request.build().uri()); - - // this doesnt have to be async as the only place this is - // ever called is from another thread - -// return CompletableFuture.supplyAsync(() -> { - try { - return httpClient.send(request.build() - /* the json as a string */, - HttpResponse.BodyHandlers.ofString()); - } catch (ConnectException e) { - // Either the server is down or the user isn't connected to the internet - LOGGER.debug("Failed to send telemetry request: " + e.getMessage()); - } catch (InterruptedException | IOException e) { - LOGGER.error("Failed to send telemetry request", e); - } - - return null; -// }); - } - - private void refreshSessionThread() { - while (true) { - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException ignored) { - } - - var nextAuthorization = lastAuthorization.plusSeconds(SESSION_REFRESH_TIME_SECONDS); - if (Instant.now().isAfter(nextAuthorization)) { - refreshSessionAsync().join(); - } - } - } - - private synchronized CompletableFuture refreshSessionAsync() { - return CompletableFuture.supplyAsync(() -> { - var request = createRequest(token == null ? "/session/create" : "/session/refresh", getAuth()); - - if (token != null) { - request.header("Authorization", token); - } - - var response = sendPostRequest(request); - - if (response == null) - return false; - if (!isSuccessful(response)) { - LOGGER.warn("Failed to refresh telemetry session, response code: " + response.statusCode() + ", message: " + response.body()); - if (responseIsUnauthorized(response)) { - token = null; - } - - return false; - } - - token = response.body(); - - LOGGER.info("Refreshed telemetry session"); - lastAuthorization = Instant.now(); - return true; - }); - } - - private TelemetryAuth getAuth() { - var session = MinecraftClient.getInstance().getSession(); - - return new TelemetryAuth( - session.getUuid(), - session.getProfile().getId().toString(), - session.getAccessToken() - ); - } - - private static boolean responseIsUnauthorized(HttpResponse response) { - return response.statusCode() == 401 - || response.statusCode() == 403; - } -} diff --git a/src/main/java/tools/redstone/redstonetools/telemetry/TelemetryManager.java b/src/main/java/tools/redstone/redstonetools/telemetry/TelemetryManager.java deleted file mode 100644 index 72cfdb28..00000000 --- a/src/main/java/tools/redstone/redstonetools/telemetry/TelemetryManager.java +++ /dev/null @@ -1,67 +0,0 @@ -package tools.redstone.redstonetools.telemetry; - -import net.minecraft.client.MinecraftClient; - -import javax.inject.Singleton; -import javax.json.Json; -import javax.json.JsonObject; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -@Singleton -public class TelemetryManager { - private final Path telemetryFilePath; - public boolean telemetryEnabled = false; - public boolean showTelemetryPrompt = true; - - public TelemetryManager() { - telemetryFilePath = MinecraftClient.getInstance().runDirectory.toPath() - .resolve("config") - .resolve("redstonetools") - .resolve("telemetry.json"); - - JsonObject telemetryJson = null; - try { - Files.createDirectories(telemetryFilePath.getParent()); - if (Files.exists(telemetryFilePath)) { - var reader = Json.createReader(new FileReader(telemetryFilePath.toFile())); - telemetryJson = reader.readObject(); - reader.close(); - - loadSettingsFromJson(telemetryJson); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void loadSettingsFromJson(JsonObject json) { - telemetryEnabled = json.getBoolean("telemetryEnabled"); - showTelemetryPrompt = json.getBoolean("showTelemetryPrompt"); - - saveChanges(); - } - - public void saveChanges() { - // Write %appdata%/.minecraft/config/redstonetools/telemetry.json - try { - Files.createDirectories(telemetryFilePath.getParent()); - } catch (IOException e) { - e.printStackTrace(); - } - - var telemetryJson = Json.createObjectBuilder() - .add("telemetryEnabled", telemetryEnabled) - .add("showTelemetryPrompt", showTelemetryPrompt) - .build(); - - try (var writer = Json.createWriter(new FileWriter(telemetryFilePath.toFile()))) { - writer.writeObject(telemetryJson); - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryAuth.java b/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryAuth.java deleted file mode 100644 index 7d399f6a..00000000 --- a/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryAuth.java +++ /dev/null @@ -1,4 +0,0 @@ -package tools.redstone.redstonetools.telemetry.dto; - -public record TelemetryAuth(String serverId, String selectedProfile, String accessToken) { -} diff --git a/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryCommand.java b/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryCommand.java deleted file mode 100644 index a0e6b177..00000000 --- a/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryCommand.java +++ /dev/null @@ -1,4 +0,0 @@ -package tools.redstone.redstonetools.telemetry.dto; - -public record TelemetryCommand(String command) { -} diff --git a/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryException.java b/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryException.java deleted file mode 100644 index bfd02f0a..00000000 --- a/src/main/java/tools/redstone/redstonetools/telemetry/dto/TelemetryException.java +++ /dev/null @@ -1,4 +0,0 @@ -package tools.redstone.redstonetools.telemetry.dto; - -public record TelemetryException(String stackTrace, boolean isFatal) { -} diff --git a/src/main/java/tools/redstone/redstonetools/utils/BlockColor.java b/src/main/java/tools/redstone/redstonetools/utils/BlockColor.java index de7c8fef..62dee464 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/BlockColor.java +++ b/src/main/java/tools/redstone/redstonetools/utils/BlockColor.java @@ -1,5 +1,7 @@ package tools.redstone.redstonetools.utils; +import net.minecraft.block.Block; + import java.util.Arrays; public enum BlockColor { @@ -33,6 +35,14 @@ public static BlockColor fromString(String name) { .orElseThrow(); } + public static BlockColor fromBlock(Block block) { + var coloredBlock = ColoredBlock.fromBlock(block); + + return coloredBlock == null + ? BlockColor.WHITE + : coloredBlock.color; + } + @Override public String toString() { return name; diff --git a/src/main/java/tools/redstone/redstonetools/utils/ColoredBlockType.java b/src/main/java/tools/redstone/redstonetools/utils/ColoredBlockType.java new file mode 100644 index 00000000..701447c7 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/utils/ColoredBlockType.java @@ -0,0 +1,39 @@ +package tools.redstone.redstonetools.utils; + +import net.minecraft.block.Block; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public enum ColoredBlockType { + // TODO: Merge some things with the ColoredBlock class so we dont have to repeat the formats and stuff + WOOL("wool", "minecraft:%s_wool"), + GLASS("glass", "minecraft:%s_stained_glass"), + CONCRETE("concrete", "minecraft:%s_concrete"), + TERRACOTTA("terracotta", "minecraft:%s_terracotta"); + + private final String displayName; + private final String blockIdFormat; + + ColoredBlockType(String displayName, String blockIdFormat) { + this.displayName = displayName; + this.blockIdFormat = blockIdFormat; + } + + @Override + public String toString() { + return displayName; + } + + public ColoredBlock withColor(BlockColor color) { + return ColoredBlock.fromBlock(toBlock()) + .withColor(color); + } + + public String toBlockId() { + return String.format(blockIdFormat, BlockColor.WHITE); + } + + public Block toBlock() { + return Registry.BLOCK.get(Identifier.tryParse(toBlockId())); + } +} diff --git a/src/main/java/tools/redstone/redstonetools/utils/CommandUtils.java b/src/main/java/tools/redstone/redstonetools/utils/CommandUtils.java index 211764d0..5d42ef6f 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/CommandUtils.java +++ b/src/main/java/tools/redstone/redstonetools/utils/CommandUtils.java @@ -1,5 +1,6 @@ package tools.redstone.redstonetools.utils; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import tools.redstone.redstonetools.features.arguments.Argument; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; @@ -14,7 +15,7 @@ public class CommandUtils { private CommandUtils() { } - public static void register(String name, List> arguments, Command executor, CommandDispatcher dispatcher, boolean dedicated) { + public static LiteralArgumentBuilder build(String name, List> arguments, Command executor) { var base = CommandManager.literal(name); if (arguments.stream().allMatch(Argument::isOptional)) { @@ -27,6 +28,11 @@ public static void register(String name, List> arguments, Command> arguments, Command executor, CommandDispatcher dispatcher, boolean dedicated) { + var base = build(name, arguments, executor); dispatcher.register(base); } diff --git a/src/main/java/tools/redstone/redstonetools/utils/DependencyLookup.java b/src/main/java/tools/redstone/redstonetools/utils/DependencyLookup.java new file mode 100644 index 00000000..a316d3b8 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/utils/DependencyLookup.java @@ -0,0 +1,25 @@ +package tools.redstone.redstonetools.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DependencyLookup { + private static final Logger LOGGER = + LoggerFactory.getLogger(DependencyLookup.class); + public static final boolean WORLDEDIT_PRESENT = + require("com.sk89q.worldedit.WorldEdit"); + + private static boolean require(String... classNames) { + for (String className : classNames) { + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + return false; + } catch (Throwable t) { + LOGGER.warn("Unexpected error while checking for dependency {}", className, t); + return false; + } + } + return true; + } +} diff --git a/src/main/java/tools/redstone/redstonetools/utils/DirectionUtils.java b/src/main/java/tools/redstone/redstonetools/utils/DirectionUtils.java index dbaf26c4..2eb14654 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/DirectionUtils.java +++ b/src/main/java/tools/redstone/redstonetools/utils/DirectionUtils.java @@ -1,5 +1,6 @@ package tools.redstone.redstonetools.utils; + import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.Direction; import net.minecraft.command.CommandException; @@ -34,26 +35,26 @@ public static DirectionArgument relativeToAbsolute(DirectionArgument direction, }; case LEFT -> switch (firstOrdinal(playerFacing)) { case UP, DOWN -> throw new CommandException(Text.of("Can't determine direction")); - case NORTH -> DirectionArgument.EAST; - case EAST -> DirectionArgument.SOUTH; - case SOUTH -> DirectionArgument.WEST; - case WEST -> DirectionArgument.NORTH; - case NORTHEAST -> DirectionArgument.SOUTHEAST; - case NORTHWEST -> DirectionArgument.NORTHEAST; - case SOUTHWEST -> DirectionArgument.NORTHWEST; - case SOUTHEAST -> DirectionArgument.SOUTHWEST; + case NORTH -> DirectionArgument.WEST; + case EAST -> DirectionArgument.NORTH; + case SOUTH -> DirectionArgument.EAST; + case WEST -> DirectionArgument.SOUTH; + case NORTHWEST -> DirectionArgument.SOUTHWEST; + case NORTHEAST -> DirectionArgument.NORTHWEST; + case SOUTHEAST -> DirectionArgument.NORTHEAST; + case SOUTHWEST -> DirectionArgument.SOUTHEAST; default -> null; }; case RIGHT -> switch (firstOrdinal(playerFacing)) { case UP, DOWN -> throw new CommandException(Text.of("Can't determine direction")); - case NORTH -> DirectionArgument.WEST; + case NORTH -> DirectionArgument.EAST; case EAST -> DirectionArgument.SOUTH; - case SOUTH -> DirectionArgument.EAST; + case SOUTH -> DirectionArgument.WEST; case WEST -> DirectionArgument.NORTH; - case NORTHWEST -> DirectionArgument.NORTHEAST; case NORTHEAST -> DirectionArgument.SOUTHEAST; - case SOUTHEAST -> DirectionArgument.SOUTHWEST; + case NORTHWEST -> DirectionArgument.NORTHEAST; case SOUTHWEST -> DirectionArgument.NORTHWEST; + case SOUTHEAST -> DirectionArgument.SOUTHWEST; default -> null; }; case BACK -> switch (firstOrdinal(playerFacing)) { diff --git a/src/main/java/tools/redstone/redstonetools/utils/ItemUtils.java b/src/main/java/tools/redstone/redstonetools/utils/ItemUtils.java index 1675984d..652b9081 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/ItemUtils.java +++ b/src/main/java/tools/redstone/redstonetools/utils/ItemUtils.java @@ -1,23 +1,68 @@ package tools.redstone.redstonetools.utils; -import net.minecraft.item.Item; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.*; import net.minecraft.util.registry.Registry; +import net.minecraft.world.BlockStateRaycastContext; +import net.minecraft.world.RaycastContext; -import java.util.HashMap; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.nbt.NbtString; public class ItemUtils { + private ItemUtils(){ } - private static final HashMap ITEM_MAP = new HashMap<>(); + public static void addExtraNBTText(ItemStack itemStack, String text) { + NbtCompound displayNbt = itemStack.getSubNbt("display"); + NbtList loreList = new NbtList(); - static { - for (Item item : Registry.ITEM) { - ITEM_MAP.put(item.toString(), item); + if (displayNbt == null) { + displayNbt = new NbtCompound(); + } else { + loreList = (NbtList) displayNbt.get("Lore"); } + String lore = "\"(+"+text +")\""; + + if (loreList != null && !loreList.contains(NbtString.of(lore))) loreList.add(NbtString.of(lore)); + displayNbt.put("Lore", loreList); + itemStack.setSubNbt("display", displayNbt); + } + + public static boolean isEmpty(ItemStack itemStack) { + return itemStack == null || itemStack.getItem() == Items.AIR || itemStack.getCount() == 0; + } + + public static ItemStack getMainItem(PlayerEntity player) { + ItemStack stack = player.getMainHandStack(); + return isEmpty(stack) ? player.getOffHandStack() : stack; + } + + public static BlockState getPlacementState(ClientPlayerEntity player, ItemStack stack, float reach) { + Item type = stack.getItem(); + if (!(type instanceof BlockItem blockItem)) + return null; + return blockItem.getBlock().getPlacementState(new ItemPlacementContext( + player, + player.getActiveHand(), + stack, + RaycastUtils.rayCastFromEye(player, reach) + )); } - public static Item getItemByName(String itemName) { - return ITEM_MAP.getOrDefault(itemName,null); + public static BlockState getUseState(ClientPlayerEntity player, ItemStack stack, float reach) { + if (stack == null || stack.getItem() == Items.AIR) + return null; + BlockState state = getPlacementState(player, stack, reach); + if (state == null) + state = Blocks.BEDROCK.getDefaultState(); + return state; } + } diff --git a/src/main/java/tools/redstone/redstonetools/utils/KeyBindingUtils.java b/src/main/java/tools/redstone/redstonetools/utils/KeyBindingUtils.java index 91b9e7a8..56e752eb 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/KeyBindingUtils.java +++ b/src/main/java/tools/redstone/redstonetools/utils/KeyBindingUtils.java @@ -1,6 +1,8 @@ package tools.redstone.redstonetools.utils; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil.Key; import tools.redstone.redstonetools.macros.KeyBindingMixin; public class KeyBindingUtils { @@ -9,4 +11,9 @@ public static void removeKeyBinding(KeyBinding keyBinding) { ((KeyBindingMixin)keyBinding).removeKeybinding(keyBinding); } + public static boolean isKeyAlreadyBound(Key key) { + KeyBinding foundKeyBinding = ( (KeyBindingMixin)MinecraftClient.getInstance().options.attackKey ).getBindingFromKey(key); + return foundKeyBinding != null; + } + } diff --git a/src/main/java/tools/redstone/redstonetools/utils/NumberArg.java b/src/main/java/tools/redstone/redstonetools/utils/NumberArg.java new file mode 100644 index 00000000..130ddfeb --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/utils/NumberArg.java @@ -0,0 +1,62 @@ +package tools.redstone.redstonetools.utils; + +import org.jetbrains.annotations.NotNull; + +import java.math.BigInteger; + +public class NumberArg implements Comparable { + public final BigInteger numValue; + + public final Integer originalBase; + + public NumberArg(String num, int base){ + this.numValue = new BigInteger(num, base); + this.originalBase = base; + } + + public BigInteger getNum(){ + return this.numValue; + } + public Integer getBase(){ + return this.originalBase; + } + + /** + * Returns the number in a string format without a base prefix. + * @return Formatted String + */ + public String toString(){ + return this.toString(this.originalBase); + } + + /** + * Returns the number in a string format converted to another radix and without a base prefix. + * @param radix The Radix to turn use on the number + * @return Formatted String + */ + public String toString(int radix){ + return this.numValue.toString(radix); + } + + /** + * Returns the number in a string format with a base prefix. + * @return Formatted String + */ + public String toPrefixedString(){ + return toPrefixedString(this.originalBase); + } + + /** + * Returns the number in a string format converted to another radix and with a base prefix. + * @param radix The Radix to turn use on the number + * @return Formatted String + */ + public String toPrefixedString(int radix){ + return NumberBase.formatNumber(this.numValue.toString(radix),radix); + } + + @Override + public int compareTo(@NotNull NumberArg o) { + return this.numValue.compareTo(o.numValue); + } +} diff --git a/src/main/java/tools/redstone/redstonetools/utils/NumberBase.java b/src/main/java/tools/redstone/redstonetools/utils/NumberBase.java index 394ebc8c..bb8c57ff 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/NumberBase.java +++ b/src/main/java/tools/redstone/redstonetools/utils/NumberBase.java @@ -4,35 +4,98 @@ import java.util.Optional; public enum NumberBase { - BINARY(2), - OCTAL(8), - DECIMAL(10), - HEXADECIMAL(16); + BINARY(2,'b'), + OCTAL(8,'o'), + DECIMAL(10,'d'), + HEXADECIMAL(16,'x'); private final int base; - NumberBase(int base) { + private final char prefixChar; + + NumberBase(int base,char prefixChar) { this.base = base; + this.prefixChar = prefixChar; } public int toInt() { return base; } + public char getPrefixChar() {return prefixChar;} + + public String getPrefix(){ + return "0" + prefixChar; + } + @Override public String toString() { return super.toString().toLowerCase(); } + /** + * Returns an Optional for a NumberBase. Returns empty Optional if not found. + * @param string Search input for the NumberBase + * @return Optional for a NumberBase + */ public static Optional fromString(String string) { return Arrays.stream(values()) .filter(b -> b.toString().equalsIgnoreCase(string)) .findFirst(); } + /** + * Returns an Optional for a NumberBase. Returns empty Optional if not found. + * @param i Search input for the NumberBase + * @return Optional for a NumberBase + */ public static Optional fromInt(int i) { return Arrays.stream(values()) .filter(b -> b.toInt() == i) .findFirst(); } + + /** + * Returns an Optional for a NumberBase. Returns empty Optional if not found. + * @param c Search input for the NumberBase + * @return Optional for a NumberBase + */ + public static Optional fromCharacter(char c) { + return Arrays.stream(values()) + .filter(b -> b.getPrefixChar() == c) + .findFirst(); + } + + /** + * Returns an Optional for a NumberBase. Returns empty Optional if not found. + * @param prefix Search input for the NumberBase + * @return Optional for a NumberBase + */ + public static Optional fromPrefix(String prefix) { + return Arrays.stream(values()) + .filter(b -> b.getPrefix().equalsIgnoreCase(prefix)) + .findFirst(); + } + + /** + * Returns a formatted String that includes the base and the number prefix together + * @param num Number to be formatted + * @param base Base to be paired with the number + * @return Formatted String + */ + public static String formatNumber(String num, int base) { + String negsign = ""; + if(Integer.parseInt(num,base) < 0){ + num = num.substring(1); + negsign = "-"; + } + num = num.toUpperCase(); + return negsign + switch (base) { + case 2 -> "0b" + num; + case 8 -> "0o" + num; + case 10 -> "0d" + num; + case 16 -> "0x" + num; + default -> num + "_" + base; + }; + } } diff --git a/src/main/java/tools/redstone/redstonetools/utils/RaycastUtils.java b/src/main/java/tools/redstone/redstonetools/utils/RaycastUtils.java index f3276ba8..abaf26a2 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/RaycastUtils.java +++ b/src/main/java/tools/redstone/redstonetools/utils/RaycastUtils.java @@ -1,6 +1,8 @@ package tools.redstone.redstonetools.utils; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.RaycastContext; public class RaycastUtils { private RaycastUtils() { @@ -13,4 +15,14 @@ public static BlockHitResult getBlockHitNeighbor(BlockHitResult hit) { return hit.withBlockPos(newBlockPos); } + + public static BlockHitResult rayCastFromEye(PlayerEntity player, float reach) { + return player.getWorld().raycast(new RaycastContext( + player.getEyePos(), + player.getEyePos().add(player.getRotationVector().multiply(reach)), + RaycastContext.ShapeType.COLLIDER, + RaycastContext.FluidHandling.NONE, + player + )); + } } diff --git a/src/main/java/tools/redstone/redstonetools/utils/ReflectionUtils.java b/src/main/java/tools/redstone/redstonetools/utils/ReflectionUtils.java index 47d37586..e80d7ceb 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/ReflectionUtils.java +++ b/src/main/java/tools/redstone/redstonetools/utils/ReflectionUtils.java @@ -1,22 +1,25 @@ package tools.redstone.redstonetools.utils; +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import rip.hippo.inject.DoctorModule; +import sun.misc.Unsafe; import tools.redstone.redstonetools.features.AbstractFeature; import tools.redstone.redstonetools.features.Feature; import tools.redstone.redstonetools.features.arguments.Argument; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.io.IOException; import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.List; -import java.util.ServiceLoader; -import java.util.Set; +import java.net.URL; +import java.util.*; import java.util.stream.Collectors; public class ReflectionUtils { - private static final ServiceLoader modulesLoader = - ServiceLoader.load(DoctorModule.class); - private static final ServiceLoader featuresLoader = - ServiceLoader.load(AbstractFeature.class); + private static final Logger LOGGER = LogManager.getLogger(); private static DoctorModule[] modules; private static Set features; @@ -24,24 +27,99 @@ private ReflectionUtils() { throw new IllegalStateException("Utility class"); } + private static final MethodHandles.Lookup INTERNAL_LOOKUP; + private static final Unsafe unsafe; + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (Throwable t) { + t.printStackTrace(); + throw new ExceptionInInitializerError(t); + } + + try { + // get lookup + Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + MethodHandles.publicLookup(); + INTERNAL_LOOKUP = (MethodHandles.Lookup) + unsafe.getObject( + unsafe.staticFieldBase(field), + unsafe.staticFieldOffset(field) + ); + } catch (Exception e) { + e.printStackTrace(); + throw new ExceptionInInitializerError(e); + } + } + + public static MethodHandles.Lookup getInternalLookup() { + return INTERNAL_LOOKUP; + } + public static DoctorModule[] getModules() { if (modules == null) { - modules = modulesLoader.stream() - .map(ServiceLoader.Provider::get) - .toArray(DoctorModule[]::new); + try { + modules = serviceLoad(DoctorModule.class) + .toArray(DoctorModule[]::new); + } catch (IOException e) { + throw new RuntimeException("Failed to load modules", e); + } } return modules; } public static Set getFeatures() { if (features == null) { - features = featuresLoader.stream() - .map(ServiceLoader.Provider::get) - .collect(Collectors.toSet()); + try { + features = serviceLoad(AbstractFeature.class); + } catch (IOException e) { + throw new RuntimeException("Failed to load features", e); + } } return features; } + private static Set serviceLoad(Class clazz) throws IOException { + ClassLoader cl = ReflectionUtils.class.getClassLoader(); + Enumeration serviceFiles = cl.getResources("META-INF/services/" + clazz.getName()); + Set classNames = new HashSet<>(); + while (serviceFiles.hasMoreElements()) { + URL serviceFile = serviceFiles.nextElement(); + try (var reader = serviceFile.openStream()) { + classNames.addAll(IOUtils.readLines(reader, "UTF-8")); + } + } + return classNames.stream() + .filter(it -> !it.isEmpty() && !it.isBlank()) + .map(ReflectionUtils::loadClass) + .filter(Objects::nonNull) + .filter(clazz::isAssignableFrom) + .map(it -> { + try { + return it.getDeclaredConstructor().newInstance(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to instantiate " + it, e); + } + }) + .map(clazz::cast) + .collect(Collectors.toSet()); + } + + @SuppressWarnings("unchecked") + private static @Nullable Class loadClass(String className) { + try { + return (Class) Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Failed to load class " + className, e); + } catch (NoClassDefFoundError e) { + LOGGER.warn("Failed to load class {}, required {}", className, e.getMessage()); + } + return null; + } + public static List> getArguments(Class featureClass) { return Arrays.stream(featureClass.getFields()) .filter(field -> Argument.class.isAssignableFrom(field.getType())) 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..aa83bc12 --- /dev/null +++ b/src/main/java/tools/redstone/redstonetools/utils/SignalBlock.java @@ -0,0 +1,48 @@ +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..0b0d2558 --- /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 = Math.max(0, 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); + } + +} diff --git a/src/main/java/tools/redstone/redstonetools/utils/TelemetryUtils.java b/src/main/java/tools/redstone/redstonetools/utils/TelemetryUtils.java deleted file mode 100644 index 4867c720..00000000 --- a/src/main/java/tools/redstone/redstonetools/utils/TelemetryUtils.java +++ /dev/null @@ -1,35 +0,0 @@ -package tools.redstone.redstonetools.utils; - -import tools.redstone.redstonetools.telemetry.TelemetryClient; -import tools.redstone.redstonetools.telemetry.dto.TelemetryCommand; -import tools.redstone.redstonetools.telemetry.dto.TelemetryException; -import net.minecraft.util.crash.CrashReport; - -import static tools.redstone.redstonetools.RedstoneToolsClient.INJECTOR; - -public class TelemetryUtils { - private TelemetryUtils() { - } - - public static void sendCommand(String command) { - INJECTOR.getInstance(TelemetryClient.class).sendCommand(new TelemetryCommand(command)); - } - - public static void sendException(Throwable throwable) { - // Write the stack trace to a string - var writer = new java.io.StringWriter(); - var printWriter = new java.io.PrintWriter(writer); - throwable.printStackTrace(printWriter); - printWriter.close(); - - INJECTOR.getInstance(TelemetryClient.class).sendException(new TelemetryException(writer.toString(), false)); - } - - public static void sendCrash(CrashReport report) { - var client = INJECTOR.getInstance(TelemetryClient.class); - - client.sendException(new TelemetryException(report.asString(), true)); - - client.waitForQueueToEmpty(); - } -} diff --git a/src/main/java/tools/redstone/redstonetools/utils/WorldEditUtils.java b/src/main/java/tools/redstone/redstonetools/utils/WorldEditUtils.java index 95967ff7..29e8ece7 100644 --- a/src/main/java/tools/redstone/redstonetools/utils/WorldEditUtils.java +++ b/src/main/java/tools/redstone/redstonetools/utils/WorldEditUtils.java @@ -28,6 +28,10 @@ public class WorldEditUtils { */ public static void forEachBlockInRegion(Region region, Consumer consumer) { + if (!DependencyLookup.WORLDEDIT_PRESENT) { + throw new IllegalStateException("WorldEdit is not loaded."); + } + CuboidRegion bb = region.getBoundingBox(); BlockVector3 min = bb.getMinimumPoint(); BlockVector3 max = bb.getMaximumPoint(); @@ -44,6 +48,10 @@ public static void forEachBlockInRegion(Region region, } public static Either getSelection(ServerPlayerEntity player) { + if (!DependencyLookup.WORLDEDIT_PRESENT) { + throw new IllegalStateException("WorldEdit is not loaded."); + } + var actor = FabricAdapter.adaptPlayer(player); var localSession = WorldEdit.getInstance() @@ -55,7 +63,7 @@ public static Either getSelection(ServerPlayerEntity player) { try { return Either.left(localSession.getSelection(selectionWorld)); } catch (IncompleteRegionException ex) { - return Either.right(Feedback.invalidUsage("Please make a selection with worldedit first.")); + return Either.right(Feedback.invalidUsage("Please make a selection with WorldEdit first")); } } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7ba8c5ae..740381b0 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -32,10 +32,12 @@ ], "depends": { - "fabricloader": ">=0.14.6", + "fabricloader": ">=0.13.0", "fabric": "*", "minecraft": "~1.18.2", - "java": ">=17", - "worldedit": "7.2.10" + "java": ">=17" + }, + "recommends": { + "worldedit": "*" } } diff --git a/src/main/resources/redstonetools.mixins.json b/src/main/resources/redstonetools.mixins.json index 5aaa9e1e..e896d6b0 100644 --- a/src/main/resources/redstonetools.mixins.json +++ b/src/main/resources/redstonetools.mixins.json @@ -7,23 +7,23 @@ "defaultRequire": 1 }, "mixins": [ + "blocks.RedstoneHitboxMixin", + "features.AirPlaceServerMixin", "features.AutoDustMixin", "features.CopyStateMixin", + "features.ItemBindMixin$ItemStackMixin", "gamerules.DoContainerDropsMixin", - "telemetry.SendCommandMixin", - "telemetry.ShowTelemetryPopupMixin" ], "client": [ "accessors.MinecraftClientAccessor", - "features.AirplaceMixin", + "accessors.WorldRendererAccessor", + "features.AirPlaceClientMixin", + "features.ItemBindMixin$PlayerMixin", "macros.AddMacroButtonMixin", "macros.InitializeMacroManagerMixin", "macros.KeyBindingMixinImpl", "macros.autocomplete.ClientPlayerEntityMixinImpl", - "macros.autocomplete.CommandDispatcherMixin", "macros.autocomplete.CommandSuggestorMixin", - "telemetry.InitializeTelemetryClientMixin", - "telemetry.SendCrashReportMixin", "update.CheckUpdateMixin" ] }