diff --git a/src/main/java/slimeknights/mantle/Mantle.java b/src/main/java/slimeknights/mantle/Mantle.java index 6ea1361..290cdab 100644 --- a/src/main/java/slimeknights/mantle/Mantle.java +++ b/src/main/java/slimeknights/mantle/Mantle.java @@ -13,6 +13,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import slimeknights.mantle.command.MantleCommand; import slimeknights.mantle.config.Config; import slimeknights.mantle.loot.AddEntryLootModifier; import slimeknights.mantle.loot.MantleLoot; @@ -55,6 +56,7 @@ public class Mantle { private void commonSetup(final FMLCommonSetupEvent event) { MantleNetwork.registerPackets(); + MantleCommand.init(); } private void registerRecipeSerializers(final RegistryEvent.Register> event) { diff --git a/src/main/java/slimeknights/mantle/command/DumpTagCommand.java b/src/main/java/slimeknights/mantle/command/DumpTagCommand.java new file mode 100644 index 0000000..0547fee --- /dev/null +++ b/src/main/java/slimeknights/mantle/command/DumpTagCommand.java @@ -0,0 +1,102 @@ +package slimeknights.mantle.command; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; +import net.minecraft.command.CommandSource; +import net.minecraft.command.Commands; +import net.minecraft.command.arguments.ResourceLocationArgument; +import net.minecraft.resources.IResource; +import net.minecraft.resources.IResourceManager; +import net.minecraft.tags.ITag; +import net.minecraft.util.JSONUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import org.apache.commons.io.IOUtils; +import slimeknights.mantle.Mantle; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** Command that dumps a tag into a JSON object */ +public class DumpTagCommand { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Dynamic2CommandExceptionType ERROR_READING_TAG = new Dynamic2CommandExceptionType((type, name) -> new TranslationTextComponent("command.mantle.dump_tag.read_error", type, name)); + + /** + * Registers this sub command with the root command + * @param subCommand Command builder + */ + public static void register(LiteralArgumentBuilder subCommand) { + subCommand.requires(sender -> sender.hasPermissionLevel(MantleCommand.PERMISSION_EDIT_SPAWN)) + .then(Commands.argument("type", TagCollectionArgument.collection()) + .then(Commands.argument("name", ResourceLocationArgument.resourceLocation()) + .executes(DumpTagCommand::run))); + } + + /** + * Runs the view-tag command + * @param context Tag context + * @return Integer return + * @throws CommandSyntaxException If invalid values are passed + */ + private static int run(CommandContext context) throws CommandSyntaxException { + TagCollectionArgument.Result type = context.getArgument("type", TagCollectionArgument.Result.class); + ResourceLocation name = context.getArgument("name", ResourceLocation.class); + IResourceManager manager = context.getSource().getServer().getDataPackRegistries().getResourceManager(); + + ResourceLocation path = new ResourceLocation(name.getNamespace(), "tags/" + type.getTagFolder() + "/" + name.getPath() + ".json"); + try { + // fetch tags from all datapacks for this, logic mostly based on vanilla tag loader + List resources = manager.getAllResources(path); + // simply create a tag builder + ITag.Builder builder = ITag.Builder.create(); + int tagsProcessed = 0; + for (IResource resource : resources) { + try ( + InputStream inputstream = resource.getInputStream(); + Reader reader = new BufferedReader(new InputStreamReader(inputstream, StandardCharsets.UTF_8)); + ) { + JsonObject json = JSONUtils.fromJson(GSON, reader, JsonObject.class); + if (json == null) { + // no json + Mantle.logger.error("Couldn't load {} tag list {} from {} in data pack {} as it is empty or null", type.getName(), name, path, resource.getPackName()); + } else { + builder.deserialize(json, resource.getPackName()); + tagsProcessed++; + } + } catch (RuntimeException | IOException ex) { + // failed to parse + Mantle.logger.error("Couldn't read {} tag list {} from {} in data pack {}", type.getName(), name, path, resource.getPackName(), ex); + } finally { + IOUtils.closeQuietly(resource); + } + } + + // builder done, ready to dump + ITextComponent message = new TranslationTextComponent("command.mantle.dump_tag.success", type.getName(), name); + context.getSource().sendFeedback(message, true); + Mantle.logger.info("Tag dump of {} tag '{}':\n{}", type.getName(), name, GSON.toJson(builder.serialize())); + return tagsProcessed; + } catch (IOException ex) { + // if the tag does not exist in the collect, probably an invalid tag name + if (type.getCollection().get(name) == null) { + throw ViewTagCommand.TAG_NOT_FOUND.create(type.getName(), name); + } else { + // tag exists and we still could not read it? something went wrong + Mantle.logger.error("Couldn't read {} tag list {} from {}", type.getName(), name, path, ex); + throw ERROR_READING_TAG.create(type.getName(), name); + } + } + } +} diff --git a/src/main/java/slimeknights/mantle/command/MantleCommand.java b/src/main/java/slimeknights/mantle/command/MantleCommand.java new file mode 100644 index 0000000..9ec6364 --- /dev/null +++ b/src/main/java/slimeknights/mantle/command/MantleCommand.java @@ -0,0 +1,67 @@ +package slimeknights.mantle.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.command.CommandSource; +import net.minecraft.command.Commands; +import net.minecraft.command.arguments.ArgumentSerializer; +import net.minecraft.command.arguments.ArgumentTypes; +import net.minecraft.world.GameRules; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.RegisterCommandsEvent; + +import java.util.function.Consumer; + +/** + * Root command for all commands in mantle + */ +public class MantleCommand { + /** Permission level that allows a user to build in spawn protected areas */ + public static final int PERMISSION_EDIT_SPAWN = 1; + /** Permission level that can run standard game commands, used by command blocks and functions */ + public static final int PERMISSION_GAME_COMMANDS = 2; + /** Standard permission level for server operators */ + public static final int PERMISSION_PLAYER_COMMANDS = 3; + /** Permission level for the server owner, server console, or the player in single player */ + public static final int PERMISSION_OWNER = 4; + + /** Registers all Mantle command related content */ + public static void init() { + // register arguments + ArgumentTypes.register("mantle:tag_collection", TagCollectionArgument.class, new ArgumentSerializer<>(TagCollectionArgument::collection)); + // add command listener + MinecraftForge.EVENT_BUS.addListener(MantleCommand::registerCommand); + } + + /** Registers a sub command for the root Mantle command */ + private static void register(LiteralArgumentBuilder root, String name, Consumer> consumer) { + LiteralArgumentBuilder subCommand = Commands.literal(name); + consumer.accept(subCommand); + root.then(subCommand); + } + + /** Event listener to register the Mantle command */ + private static void registerCommand(RegisterCommandsEvent event) { + LiteralArgumentBuilder builder = Commands.literal("mantle"); + + // sub commands + register(builder, "view_tag", ViewTagCommand::register); + register(builder, "dump_tag", DumpTagCommand::register); + register(builder, "tags_for", TagsForCommand::register); + + // register final command + event.getDispatcher().register(builder); + } + + /* Helpers */ + + /** + * Returns true if the source either does not have reduced debug info or they have the proper level + * Allows limiting a command that prints debug info to not work in reduced debug info + * @param source Command source + * @param reducedDebugLevel Level to use when reduced debug info is true + * @return True if the command can be run + */ + public static boolean requiresDebugInfoOrOp(CommandSource source, int reducedDebugLevel) { + return !source.getWorld().getGameRules().getBoolean(GameRules.REDUCED_DEBUG_INFO) || source.hasPermissionLevel(reducedDebugLevel); + } +} diff --git a/src/main/java/slimeknights/mantle/command/TagCollectionArgument.java b/src/main/java/slimeknights/mantle/command/TagCollectionArgument.java new file mode 100644 index 0000000..800b0b2 --- /dev/null +++ b/src/main/java/slimeknights/mantle/command/TagCollectionArgument.java @@ -0,0 +1,143 @@ +package slimeknights.mantle.command; + +import com.google.common.collect.AbstractIterator; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import net.minecraft.command.ISuggestionProvider; +import net.minecraft.tags.ITagCollection; +import net.minecraft.tags.TagCollectionManager; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraftforge.common.ForgeTagHandler; +import net.minecraftforge.registries.ForgeRegistry; +import net.minecraftforge.registries.RegistryManager; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +/** + * Argument type that supports any tag collection + */ +@NoArgsConstructor(staticName = "collection") +public class TagCollectionArgument implements ArgumentType { + private static final Collection EXAMPLES = Arrays.asList("minecraft:blocks", "minecraft:enchantments"); + private static final Iterable ALL_COLLECTIONS_ITERABLE = CollectionIterator::new; + // errors + /* Tag collection name is invalid */ + private static final DynamicCommandExceptionType TAG_COLLECTION_NOT_FOUND = new DynamicCommandExceptionType(name -> new TranslationTextComponent("command.mantle.tag_collection.not_found", name)); + + /** + * Gets the tag type for a vanilla tag + * @param name Name + * @return Tag type + */ + @Nullable + private static Result getVanillaTags(ForgeRegistry registry, ResourceLocation name) { + for (VanillaTagType type : VanillaTagType.values()) { + if (name.equals(type.getName())) { + return Result.of(name, name.getPath() + "s", registry, type.getCollection()); + } + } + return null; + } + + @Override + public Result parse(StringReader reader) throws CommandSyntaxException { + ResourceLocation name = ResourceLocation.read(reader); + // first, find the proper registry, if no registry the tag is probably the wrong type + ForgeRegistry registry = RegistryManager.ACTIVE.getRegistry(name); + if (registry != null) { + // try vanilla tags next + Result result = getVanillaTags(registry, name); + if (result != null) { + return result; + } + + // then forge tags + ITagCollection collection = ForgeTagHandler.getCustomTagTypes().get(name); + String tagFolder = registry.getTagFolder(); + if (collection != null && tagFolder != null) { + return Result.of(name, tagFolder, registry, collection); + } + } + throw TAG_COLLECTION_NOT_FOUND.create(name); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return ISuggestionProvider.suggestIterable(ALL_COLLECTIONS_ITERABLE, builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + /** Data class for result, to ensure its unique to the mod */ + @Data(staticConstructor = "of") + public static class Result { + private final ResourceLocation name; + private final String tagFolder; + private final ForgeRegistry registry; + private final ITagCollection collection; + } + + /** Enum containing all vanilla tag collection types */ + @AllArgsConstructor + private enum VanillaTagType { + BLOCK(Registry.BLOCK_KEY.getLocation(), () -> TagCollectionManager.getManager().getBlockTags()), + ITEM(Registry.ITEM_KEY.getLocation(), () -> TagCollectionManager.getManager().getItemTags()), + FLUID(Registry.FLUID_KEY.getLocation(), () -> TagCollectionManager.getManager().getFluidTags()), + ENTITY_TYPE(Registry.ENTITY_TYPE_KEY.getLocation(), () -> TagCollectionManager.getManager().getEntityTypeTags()); + + @Getter + private final ResourceLocation name; + private final Supplier> supplier; + + public ITagCollection getCollection() { + return supplier.get(); + } + } + + /** Iterator for all possible collection names */ + private static class CollectionIterator extends AbstractIterator { + private VanillaTagType nextVanilla = VanillaTagType.BLOCK; + private final Iterator forgeIterator = ForgeTagHandler.getCustomTagTypeNames().iterator(); + + @Override + protected ResourceLocation computeNext() { + // iterate vanilla tags first + if (nextVanilla != null) { + ResourceLocation result = nextVanilla.getName(); + // simply increase index by 1 through enum + int nextIndex = nextVanilla.ordinal() + 1; + VanillaTagType[] values = VanillaTagType.values(); + if (nextIndex < values.length) { + nextVanilla = values[nextIndex]; + } else { + // null means time for forge tags + nextVanilla = null; + } + return result; + // for forge, just redirect to the collection iterator + } else if (forgeIterator.hasNext()) { + return forgeIterator.next(); + } + return endOfData(); + } + } +} diff --git a/src/main/java/slimeknights/mantle/command/TagsForCommand.java b/src/main/java/slimeknights/mantle/command/TagsForCommand.java new file mode 100644 index 0000000..79798b1 --- /dev/null +++ b/src/main/java/slimeknights/mantle/command/TagsForCommand.java @@ -0,0 +1,274 @@ +package slimeknights.mantle.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.command.CommandSource; +import net.minecraft.command.Commands; +import net.minecraft.command.arguments.ResourceLocationArgument; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.ProjectileHelper; +import net.minecraft.fluid.Fluid; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.SpawnEggItem; +import net.minecraft.potion.Potion; +import net.minecraft.potion.PotionUtils; +import net.minecraft.potion.Potions; +import net.minecraft.tags.ITagCollection; +import net.minecraft.tags.TagCollectionManager; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockRayTraceResult; +import net.minecraft.util.math.EntityRayTraceResult; +import net.minecraft.util.math.RayTraceContext.FluidMode; +import net.minecraft.util.math.RayTraceResult.Type; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.text.IFormattableTextComponent; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; +import net.minecraftforge.common.ForgeMod; +import net.minecraftforge.common.ForgeTagHandler; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; +import net.minecraftforge.fluids.capability.templates.EmptyFluidHandler; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** + * Command to list all tags for an entry + */ +public class TagsForCommand { + /** Tag type cannot be found */ + protected static final Dynamic2CommandExceptionType VALUE_NOT_FOUND = new Dynamic2CommandExceptionType((type, name) -> new TranslationTextComponent("command.mantle.tags_for.not_found", type, name)); + + /* Missing target errors */ + private static final ITextComponent NO_HELD_BLOCK = new TranslationTextComponent("command.mantle.view_tags.no_held_block"); + private static final ITextComponent NO_HELD_ENTITY = new TranslationTextComponent("command.mantle.view_tags.no_held_entity"); + private static final ITextComponent NO_HELD_POTION = new TranslationTextComponent("command.mantle.view_tags.no_held_potion"); + private static final ITextComponent NO_HELD_FLUID = new TranslationTextComponent("command.mantle.view_tags.no_held_fluid"); + private static final ITextComponent NO_HELD_ENCHANTMENT = new TranslationTextComponent("command.mantle.view_tags.no_held_enchantment"); + private static final ITextComponent NO_TARGETED_ENTITY = new TranslationTextComponent("command.mantle.view_tags.no_targeted_entity"); + private static final ITextComponent NO_TARGETED_TILE_ENTITY = new TranslationTextComponent("command.mantle.view_tags.no_targeted_tile_entity"); + /** Value has no tags */ + private static final ITextComponent NO_TAGS = new TranslationTextComponent("command.mantle.view_tags.no_tags"); + + /** + * Registers this sub command with the root command + * @param subCommand Command builder + */ + public static void register(LiteralArgumentBuilder subCommand) { + subCommand.requires(source -> MantleCommand.requiresDebugInfoOrOp(source, MantleCommand.PERMISSION_GAME_COMMANDS)) + // by registry ID + .then(Commands.literal("id") + .then(Commands.argument("type", TagCollectionArgument.collection()) + .then(Commands.argument("name", ResourceLocationArgument.resourceLocation()) + .executes(TagsForCommand::runForId)))) + // held item + .then(Commands.literal("held") + .then(Commands.literal("item").executes(TagsForCommand::heldItem)) + .then(Commands.literal("block").executes(TagsForCommand::heldBlock)) + .then(Commands.literal("enchantments").executes(TagsForCommand::heldEnchantments)) + .then(Commands.literal("fluid").executes(TagsForCommand::heldFluid)) + .then(Commands.literal("entity").executes(TagsForCommand::heldEntity)) + .then(Commands.literal("potion").executes(TagsForCommand::heldPotion))) + // targeted + .then(Commands.literal("targeted") + .then(Commands.literal("tile_entity").executes(TagsForCommand::targetedTileEntity)) + .then(Commands.literal("entity").executes(TagsForCommand::targetedEntity))); + } + + /** + * Prints the final list of owning tags + * @param context Command context + * @param collection Tag collection + * @param typeName Tag name + * @param name Value name + * @param value Value to print + * @param Collection type + * @return Number of tags printed + */ + private static int printOwningTags(CommandContext context, ITagCollection collection, ResourceLocation typeName, ResourceLocation name, Object value) { + // TODO: not sure if there is a better way to do this, it did come out of that registry + IFormattableTextComponent output = new TranslationTextComponent("command.mantle.tags_for.success", typeName, name); + Collection tags = collection.getOwningTags((T)value); + if (tags.isEmpty()) { + output.appendString("\n* ").append(NO_TAGS); + } else { + tags.stream() + .sorted(ResourceLocation::compareNamespaced) + .forEach(tag -> output.appendString("\n* " + tag)); + } + context.getSource().sendFeedback(output, true); + return tags.size(); + } + + + /* Standard way: by ID */ + + /** Run the registry ID subcommand */ + private static int runForId(CommandContext context) throws CommandSyntaxException { + TagCollectionArgument.Result result = context.getArgument("type", TagCollectionArgument.Result.class); + ResourceLocation name = context.getArgument("name", ResourceLocation.class); + + // first, fetch value + if (!result.getRegistry().containsKey(name)) { + throw VALUE_NOT_FOUND.create(result.getName(), name); + } + Object value = result.getRegistry().getValue(name); + if (value == null) { + throw VALUE_NOT_FOUND.create(result.getName(), name); + } + return printOwningTags(context, result.getCollection(), result.getName(), name, value); + } + + + /* Held item, can extract some data from the stack */ + + /** Item tags for held item */ + private static int heldItem(CommandContext context) throws CommandSyntaxException { + Item item = context.getSource().asPlayer().getHeldItemMainhand().getItem(); + return printOwningTags(context, TagCollectionManager.getManager().getItemTags(), Registry.ITEM_KEY.getLocation(), Objects.requireNonNull(item.getRegistryName()), item); + } + + /** Block tags for held item */ + private static int heldBlock(CommandContext context) throws CommandSyntaxException { + CommandSource source = context.getSource(); + Item item = source.asPlayer().getHeldItemMainhand().getItem(); + Block block = Block.getBlockFromItem(item); + if (block != Blocks.AIR) { + return printOwningTags(context, TagCollectionManager.getManager().getBlockTags(), Registry.BLOCK_KEY.getLocation(), Objects.requireNonNull(block.getRegistryName()), block); + } + source.sendFeedback(NO_HELD_BLOCK, true); + return 0; + } + + /** Fluid tags for held item */ + private static int heldFluid(CommandContext context) throws CommandSyntaxException { + CommandSource source = context.getSource(); + ItemStack stack = source.asPlayer().getHeldItemMainhand(); + LazyOptional capability = stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY); + if (capability.isPresent()) { + IFluidHandler handler = capability.map(h -> (IFluidHandler) h).orElse(EmptyFluidHandler.INSTANCE); + if (handler.getTanks() > 0) { + FluidStack fluidStack = handler.getFluidInTank(0); + if (!fluidStack.isEmpty()) { + Fluid fluid = fluidStack.getFluid(); + return printOwningTags(context, TagCollectionManager.getManager().getFluidTags(), Registry.FLUID_KEY.getLocation(), Objects.requireNonNull(fluid.getRegistryName()), fluid); + } + } + } + source.sendFeedback(NO_HELD_FLUID, true); + return 0; + } + + /** Potion tags for held item */ + private static int heldPotion(CommandContext context) throws CommandSyntaxException { + CommandSource source = context.getSource(); + ItemStack stack = source.asPlayer().getHeldItemMainhand(); + Potion potion = PotionUtils.getPotionFromItem(stack); + if (potion != Potions.EMPTY) { + ResourceLocation registry = Registry.POTION_KEY.getLocation(); + return printOwningTags(context, ForgeTagHandler.getCustomTagTypes().get(registry), registry, Objects.requireNonNull(potion.getRegistryName()), potion); + } + source.sendFeedback(NO_HELD_POTION, true); + return 0; + } + + /** Block tags for held item */ + private static int heldEnchantments(CommandContext context) throws CommandSyntaxException { + CommandSource source = context.getSource(); + ItemStack stack = source.asPlayer().getHeldItemMainhand(); + Map enchantments = EnchantmentHelper.getEnchantments(stack); + if (!enchantments.isEmpty()) { + int totalTags = 0; + ResourceLocation registry = Registry.ENCHANTMENT_KEY.getLocation(); + ITagCollection enchantmentTags = ForgeTagHandler.getCustomTagTypes().get(registry); + // print tags for each contained enchantment + for (Enchantment enchantment : enchantments.keySet()) { + totalTags += printOwningTags(context, enchantmentTags, registry, Objects.requireNonNull(enchantment.getRegistryName()), enchantment); + } + return totalTags; + } + source.sendFeedback(NO_HELD_ENCHANTMENT, true); + return 0; + } + + /** Entity tags for held item */ + private static int heldEntity(CommandContext context) throws CommandSyntaxException { + CommandSource source = context.getSource(); + ItemStack stack = source.asPlayer().getHeldItemMainhand(); + if (stack.getItem() instanceof SpawnEggItem) { + EntityType type = ((SpawnEggItem) stack.getItem()).getType(stack.getTag()); + return printOwningTags(context, TagCollectionManager.getManager().getEntityTypeTags(), Registry.ENTITY_TYPE_KEY.getLocation(), Objects.requireNonNull(type.getRegistryName()), type); + } + source.sendFeedback(NO_HELD_ENTITY, true); + return 0; + } + + + /* Targeted, based on look vector. Leaves out anything on the debug screen */ + + /** + * Gets the tags for the fluid being looked at + * @param context Context + * @return Tags for the looked at block or entity + * @throws CommandSyntaxException For command errors + */ + private static int targetedTileEntity(CommandContext context) throws CommandSyntaxException { + CommandSource source = context.getSource(); + PlayerEntity player = source.asPlayer(); + World world = source.getWorld(); + BlockRayTraceResult blockTrace = Item.rayTrace(world, player, FluidMode.NONE); + if (blockTrace.getType() == Type.BLOCK) { + TileEntity te = world.getTileEntity(blockTrace.getPos()); + if (te != null) { + TileEntityType type = te.getType(); + ResourceLocation registry = Registry.BLOCK_ENTITY_TYPE_KEY.getLocation(); + return printOwningTags(context, ForgeTagHandler.getCustomTagTypes().get(registry), registry, Objects.requireNonNull(type.getRegistryName()), type); + } + } + // failed + source.sendFeedback(NO_TARGETED_TILE_ENTITY, true); + return 0; + } + + /** + * Gets the tags for the entity being looked at + * @param context Context + * @return Tags for the looked at block or entity + * @throws CommandSyntaxException For command errors + */ + private static int targetedEntity(CommandContext context) throws CommandSyntaxException { + CommandSource source = context.getSource(); + PlayerEntity player = source.asPlayer(); + Vector3d start = player.getEyePosition(1F); + Vector3d look = player.getLookVec(); + double range = Objects.requireNonNull(player.getAttribute(ForgeMod.REACH_DISTANCE.get())).getValue(); + Vector3d direction = start.add(look.x * range, look.y * range, look.z * range); + AxisAlignedBB bb = player.getBoundingBox().expand(look.x * range, look.y * range, look.z * range).expand(1, 1, 1); + EntityRayTraceResult entityTrace = ProjectileHelper.rayTraceEntities(source.getWorld(), player, start, direction, bb, e -> true); + if (entityTrace != null) { + EntityType target = entityTrace.getEntity().getType(); + return printOwningTags(context, TagCollectionManager.getManager().getEntityTypeTags(), Registry.ENTITY_TYPE_KEY.getLocation(), Objects.requireNonNull(target.getRegistryName()), target); + } + // failed + source.sendFeedback(NO_TARGETED_ENTITY, true); + return 0; + } +} diff --git a/src/main/java/slimeknights/mantle/command/ViewTagCommand.java b/src/main/java/slimeknights/mantle/command/ViewTagCommand.java new file mode 100644 index 0000000..8fcaf93 --- /dev/null +++ b/src/main/java/slimeknights/mantle/command/ViewTagCommand.java @@ -0,0 +1,68 @@ +package slimeknights.mantle.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; +import net.minecraft.command.CommandSource; +import net.minecraft.command.Commands; +import net.minecraft.command.arguments.ResourceLocationArgument; +import net.minecraft.tags.ITag; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.IFormattableTextComponent; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraftforge.registries.IForgeRegistryEntry; + +import java.util.Collection; +import java.util.Objects; + +/** Command that lists all values in a tag */ +public class ViewTagCommand { + /** Tag has no values */ + private static final ITextComponent EMPTY = new TranslationTextComponent("command.mantle.tag.empty"); + /** Tag type cannot be found */ + protected static final Dynamic2CommandExceptionType TAG_NOT_FOUND = new Dynamic2CommandExceptionType((type, name) -> new TranslationTextComponent("command.mantle.tag.not_found", type, name)); + + /** + * Registers this sub command with the root command + * @param subCommand Command builder + */ + public static void register(LiteralArgumentBuilder subCommand) { + subCommand.requires(source -> MantleCommand.requiresDebugInfoOrOp(source, MantleCommand.PERMISSION_GAME_COMMANDS)) + .then(Commands.argument("type", TagCollectionArgument.collection()) + .then(Commands.argument("name", ResourceLocationArgument.resourceLocation()) + .executes(ViewTagCommand::run))); + } + + /** + * Runs the view-tag command + * @param context Tag context + * @return Integer return + * @throws CommandSyntaxException If invalid values are passed + */ + private static int run(CommandContext context) throws CommandSyntaxException { + TagCollectionArgument.Result result = context.getArgument("type", TagCollectionArgument.Result.class); + ResourceLocation name = context.getArgument("name", ResourceLocation.class); + ITag tag = result.getCollection().get(name); + if (tag != null) { + // start building output message + IFormattableTextComponent output = new TranslationTextComponent("command.mantle.view_tag.success", result.getName(), name); + Collection values = tag.getAllElements(); + + // if no values, print empty + if (values.isEmpty()) { + output.appendString("\n* ").append(EMPTY); + } else { + values.stream() + .filter(value -> value instanceof IForgeRegistryEntry) + .map(value -> (IForgeRegistryEntry) value) + .sorted((a, b) -> Objects.requireNonNull(a.getRegistryName()).compareNamespaced(Objects.requireNonNull(b.getRegistryName()))) + .forEach(value -> output.appendString("\n* " + Objects.requireNonNull(value.getRegistryName()).toString())); + } + context.getSource().sendFeedback(output, true); + return values.size(); + } + throw TAG_NOT_FOUND.create(result.getName(), name); + } +} diff --git a/src/main/java/slimeknights/mantle/command/package-info.java b/src/main/java/slimeknights/mantle/command/package-info.java new file mode 100644 index 0000000..4c53bad --- /dev/null +++ b/src/main/java/slimeknights/mantle/command/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package slimeknights.mantle.command; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 6b14cf0..bf9802a 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -44,3 +44,6 @@ public net.minecraft.world.gen.feature.template.Template field_204769_a # blocks # DimensionType public net.minecraft.world.DimensionType field_236004_h_ # OVERWORLD_TYPE + +# Ray trace for tags command +public net.minecraft.item.Item func_219968_a(Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/math/RayTraceContext$FluidMode;)Lnet/minecraft/util/math/BlockRayTraceResult; # rayTrace diff --git a/src/main/resources/assets/mantle/lang/en_us.json b/src/main/resources/assets/mantle/lang/en_us.json index 6da92f5..d1dc97d 100644 --- a/src/main/resources/assets/mantle/lang/en_us.json +++ b/src/main/resources/assets/mantle/lang/en_us.json @@ -1,4 +1,24 @@ { - "mantle:tooltip.cooktime": "Cook Time: %ds", - "config.mantle.extraHeartRenderer": "Enable Extra Heart Renderer" -} \ No newline at end of file + "mantle:tooltip.cooktime": "Cook Time: %ds", + "config.mantle.extraHeartRenderer": "Enable Extra Heart Renderer", + + "command.mantle.view_tags.success": "Values for %s tag '%s':", + "command.mantle.view_tags.not_found": "Unknown %s registry value '%s'", + "command.mantle.view_tags.no_held_block": "Held item is not a block", + "command.mantle.view_tags.no_held_entity": "Held item is not a spawn egg", + "command.mantle.view_tags.no_held_potion": "Held item is not a potion", + "command.mantle.view_tags.no_held_fluid": "Unable to get fluid from the held item", + "command.mantle.view_tags.no_held_enchantment": "Held item is not enchanted", + "command.mantle.view_tags.no_targeted_entity": "No entity within range", + "command.mantle.view_tags.no_targeted_tile_entity": "No fluid within range", + "command.mantle.view_tags.no_tags": "No tags", + + "command.mantle.dump_tag.success": "Printed combined %s tag '%s' to console log", + "command.mantle.dump_tag.read_error": "Error reading %s tag '%s':", + + "command.mantle.tags_for.success": "%s tags for '%s':", + + "command.mantle.tag.empty": "Empty", + "command.mantle.tag_collection.not_found": "Unknown tag collection '%s'", + "command.mantle.tag.not_found": "Unknown %s tag '%s'" +}