diff --git a/patches/minecraft/net/minecraft/command/EntitySelector.java.patch b/patches/minecraft/net/minecraft/command/EntitySelector.java.patch index c2dbe4168..dd80d47d1 100644 --- a/patches/minecraft/net/minecraft/command/EntitySelector.java.patch +++ b/patches/minecraft/net/minecraft/command/EntitySelector.java.patch @@ -1,6 +1,18 @@ --- ../src-base/minecraft/net/minecraft/command/EntitySelector.java +++ ../src-work/minecraft/net/minecraft/command/EntitySelector.java -@@ -153,6 +153,7 @@ +@@ -121,6 +121,11 @@ + + public static List func_179656_b(ICommandSender p_179656_0_, String p_179656_1_, Class p_179656_2_) throws CommandException + { ++ return net.minecraftforge.common.command.SelectorHandlerManager.matchEntities(p_179656_0_, p_179656_1_, p_179656_2_); ++ } ++ ++ public static List matchEntitiesDefault(ICommandSender p_179656_0_, String p_179656_1_, Class p_179656_2_) throws CommandException ++ { + Matcher matcher = field_82389_a.matcher(p_179656_1_); + + if (matcher.matches() && p_179656_0_.func_70003_b(1, "@")) +@@ -153,6 +158,7 @@ list2.addAll(func_184951_f(map)); list2.addAll(func_180698_a(map, vec3d)); list2.addAll(func_179662_g(map)); @@ -8,3 +20,27 @@ if ("s".equalsIgnoreCase(s)) { +@@ -786,6 +792,11 @@ + + public static boolean func_82377_a(String p_82377_0_) throws CommandException + { ++ return net.minecraftforge.common.command.SelectorHandlerManager.matchesMultiplePlayers(p_82377_0_); ++ } ++ ++ public static boolean matchesMultiplePlayersDefault(String p_82377_0_) throws CommandException ++ { + Matcher matcher = field_82389_a.matcher(p_82377_0_); + + if (!matcher.matches()) +@@ -803,6 +814,11 @@ + + public static boolean func_82378_b(String p_82378_0_) + { ++ return net.minecraftforge.common.command.SelectorHandlerManager.isSelector(p_82378_0_); ++ } ++ ++ public static boolean isSelectorDefault(String p_82378_0_) ++ { + return field_82389_a.matcher(p_82378_0_).matches(); + } + diff --git a/src/main/java/net/minecraftforge/common/command/SelectorHandler.java b/src/main/java/net/minecraftforge/common/command/SelectorHandler.java new file mode 100644 index 000000000..9c0eb2f05 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/command/SelectorHandler.java @@ -0,0 +1,52 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.command; + +import java.util.List; + +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.Entity; + +/** + * Handler for custom types of selectors registered with {@link SelectorHandlerManager} + */ +public interface SelectorHandler +{ + /** + * Returns a {@link List} of {@link Entity Entities} of class {@code targetClass} ({@code T}) represented by {@code token}
+ * Note: If {@code token} does not match the overall syntax defined by {@link #isSelector}, this method should return an empty list. + * For any other error, an exception should be thrown + * + * @param sender The {@link ICommandSender} that initiated the query + */ + public List matchEntities(ICommandSender sender, String token, Class targetClass) throws CommandException; + + /** + * Returns whether the selector string potentially matches multiple entities + */ + public boolean matchesMultiplePlayers(String selectorStr) throws CommandException; + + /** + * Returns whether the string matches the overall syntax of the selector
+ * Note: If this returns {@code false}, {@link #matchEntities} should return an empty list + */ + public boolean isSelector(String selectorStr); +} diff --git a/src/main/java/net/minecraftforge/common/command/SelectorHandlerManager.java b/src/main/java/net/minecraftforge/common/command/SelectorHandlerManager.java new file mode 100644 index 000000000..b7db6d0b5 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/command/SelectorHandlerManager.java @@ -0,0 +1,141 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.command; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.TreeMap; + +import org.apache.commons.lang3.ArrayUtils; + +import net.minecraft.command.CommandException; +import net.minecraft.command.EntitySelector; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.Entity; +import net.minecraftforge.fml.common.Loader; + +/** + * Allows registration of custom selector types by assigning a {@link SelectorHandler} to a prefix + * This class handles calls to the {@link EntitySelector} methods {@link EntitySelector#matchEntities matchEntities}, + * {@link EntitySelector#matchesMultiplePlayers matchesMultiplePlayers} and {@link EntitySelector#isSelector isSelector}.
+ * The calls are delegated to the handler with the longest matching prefix.
+ *
+ * Note: If you register a {@link SelectorHandler} to a broader domain (not just a single selector), you should take care of possible shadowing conflicts yourself. + * For this you can use the information provided by {@link #selectorHandlers} and {@link #registeringMods}. + */ +public class SelectorHandlerManager +{ + private SelectorHandlerManager() + { + } + + //the ordering is reversed such that longer prefixes appear before their shorter substrings + public static final NavigableMap selectorHandlers = new TreeMap(Collections. reverseOrder()); + public static final NavigableMap registeringMods = new TreeMap(Collections. reverseOrder()); + + private static final SelectorHandler vanillaHandler = new SelectorHandler() + { + @Override + public List matchEntities(final ICommandSender sender, final String token, final Class targetClass) throws CommandException + { + return EntitySelector.matchEntitiesDefault(sender, token, targetClass); + } + + @Override + public boolean matchesMultiplePlayers(final String selectorStr) throws CommandException + { + return EntitySelector.matchesMultiplePlayersDefault(selectorStr); + } + + @Override + public boolean isSelector(final String selectorStr) + { + return EntitySelector.isSelectorDefault(selectorStr); + } + }; + + static + { + for (final String prefix : ArrayUtils.toArray("@p", "@a", "@r", "@e", "@s")) + { + selectorHandlers.put(prefix, vanillaHandler); + registeringMods.put(prefix, "minecraft"); + } + } + + /** + * Registers a new {@link SelectorHandler} for {@code prefix}.
+ * + * @param prefix The domain the specified {@code handler} is registered for. + * If you want to register just a single selector, {@code prefix} has the form '@{selectorName}' + */ + public static void register(final String prefix, final SelectorHandler handler) + { + if (prefix.isEmpty()) + { + throw new IllegalArgumentException("Prefix must not be empty"); + } + + final String modId = Loader.instance().activeModContainer().getModId(); + + selectorHandlers.put(prefix, handler); + registeringMods.put(prefix, modId); + } + + /** + * Returns the best matching handler for the given string. Defaults to the vanilla handler if no prefix applies + */ + public static SelectorHandler getHandler(final String selectorStr) + { + if (!selectorStr.isEmpty()) + { + for (final Entry handler : selectorHandlers.subMap(selectorStr, true, selectorStr.substring(0, 1), true).entrySet()) + { + if (selectorStr.startsWith(handler.getKey())) + { + return handler.getValue(); + } + } + } + + return vanillaHandler; + } + + //These methods are called by the vanilla methods + + public static List matchEntities(final ICommandSender sender, final String token, final Class targetClass) throws CommandException + { + return getHandler(token).matchEntities(sender, token, targetClass); + } + + public static boolean matchesMultiplePlayers(final String selectorStr) throws CommandException + { + return getHandler(selectorStr).matchesMultiplePlayers(selectorStr); + } + + public static boolean isSelector(final String selectorStr) + { + return getHandler(selectorStr).isSelector(selectorStr); + } +} diff --git a/src/main/resources/forge.exc b/src/main/resources/forge.exc index 6220bc7a8..405c818a5 100644 --- a/src/main/resources/forge.exc +++ b/src/main/resources/forge.exc @@ -61,3 +61,7 @@ net/minecraft/world/storage/loot/LootEntryEmpty.(II[Lnet/minecraft/world/s net/minecraft/world/chunk/BlockStateContainer.setBits(IZ)V=|p_186012_1_,forceBits net/minecraft/village/Village.getPlayerReputation(Ljava/util/UUID;)I=|p_82684_1_ net/minecraft/village/Village.modifyPlayerReputation(Ljava/util/UUID;I)I=|p_82688_1_,p_82688_2_ + +net/minecraft/command/EntitySelector.matchEntitiesDefault(Lnet/minecraft/command/ICommandSender;Ljava/lang/String;Ljava/lang/Class;)Ljava/util/List;=|p_179656_0_,p_179656_1_,p_179656_2_ +net/minecraft/command/EntitySelector.matchesMultiplePlayersDefault(Ljava/lang/String;)Z=|p_82377_0_ +net/minecraft/command/EntitySelector.isSelectorDefault(Ljava/lang/String;)Z=|p_82378_0_ diff --git a/src/test/java/net/minecraftforge/test/SelectorHandlerTest.java b/src/test/java/net/minecraftforge/test/SelectorHandlerTest.java new file mode 100644 index 000000000..56674506f --- /dev/null +++ b/src/test/java/net/minecraftforge/test/SelectorHandlerTest.java @@ -0,0 +1,50 @@ +package net.minecraftforge.test; + +import java.util.Collections; +import java.util.List; + +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.Entity; +import net.minecraftforge.common.command.SelectorHandler; +import net.minecraftforge.common.command.SelectorHandlerManager; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; + +@Mod(modid = "selectorhandlertest", name = "Selector Handler Test", version = "0.0.0") +public class SelectorHandlerTest +{ + @EventHandler + public void init(final FMLInitializationEvent event) + { + SelectorHandlerManager.register(Handler.name, new Handler()); + } + + private static class Handler implements SelectorHandler + { + protected static final String name = "@self"; + + @SuppressWarnings("unchecked") + @Override + public List matchEntities(final ICommandSender sender, final String token, final Class targetClass) throws CommandException + { + final Entity senderEntity = sender.getCommandSenderEntity(); + return senderEntity != null && targetClass.isAssignableFrom(senderEntity.getClass()) && name.equals(token) + ? Collections.singletonList((T) sender.getCommandSenderEntity()) + : Collections. emptyList(); + } + + @Override + public boolean matchesMultiplePlayers(final String selectorStr) throws CommandException + { + return false; + } + + @Override + public boolean isSelector(final String selectorStr) + { + return name.equals(selectorStr); + } + } +}