From 176fa1f5b5c5e42fca2c189ee462688bbf016031 Mon Sep 17 00:00:00 2001 From: SoundLogic Date: Mon, 22 Jun 2015 13:17:16 -0500 Subject: [PATCH] Item Autocomplete system --- .../botania/api/corporea/CorporeaHelper.java | 20 +- .../ICorporeaAutoCompleteController.java | 31 +++ .../handler/CorporeaAutoCompleteHandler.java | 186 ++++++++++++++++++ .../client/core/proxy/ClientProxy.java | 8 + .../tile/corporea/TileCorporeaIndex.java | 13 +- .../botania/common/lib/LibObfuscation.java | 4 + 6 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 src/main/java/vazkii/botania/api/corporea/ICorporeaAutoCompleteController.java create mode 100644 src/main/java/vazkii/botania/client/core/handler/CorporeaAutoCompleteHandler.java diff --git a/src/main/java/vazkii/botania/api/corporea/CorporeaHelper.java b/src/main/java/vazkii/botania/api/corporea/CorporeaHelper.java index f2961029f..0411ed199 100644 --- a/src/main/java/vazkii/botania/api/corporea/CorporeaHelper.java +++ b/src/main/java/vazkii/botania/api/corporea/CorporeaHelper.java @@ -29,7 +29,8 @@ public final class CorporeaHelper { private static final List empty = Collections.unmodifiableList(new ArrayList()); private static final WeakHashMap, List> cachedNetworks = new WeakHashMap(); - + private static final List autoCompleteControllers = new ArrayList(); + public static final String[] WILDCARD_STRINGS = new String[] { "...", "~", "+", "?" , "*" }; @@ -319,4 +320,21 @@ public final class CorporeaHelper { public static boolean equalOrContain(String s1, String s2, boolean contain) { return contain ? s1.contains(s2) : s1.equals(s2); } + + /** + * Registers a ICorporeaAutoCompleteController + */ + public static void registerAutoCompleteController(ICorporeaAutoCompleteController controller) { + autoCompleteControllers.add(controller); + } + + /** + * Returns if the auto complete helper should run + */ + public static boolean shouldAutoComplete() { + for(ICorporeaAutoCompleteController controller : autoCompleteControllers) + if(controller.shouldAutoComplete()) + return true; + return false; + } } diff --git a/src/main/java/vazkii/botania/api/corporea/ICorporeaAutoCompleteController.java b/src/main/java/vazkii/botania/api/corporea/ICorporeaAutoCompleteController.java new file mode 100644 index 000000000..78749d3b3 --- /dev/null +++ b/src/main/java/vazkii/botania/api/corporea/ICorporeaAutoCompleteController.java @@ -0,0 +1,31 @@ +/** + * This class was created by . It's distributed as + * part of the Botania Mod. Get the Source Code in github: + * https://github.com/Vazkii/Botania + * + * Botania is Open Source and distributed under the + * Botania License: http://botaniamod.net/license.php + * + * File Created @ [June 8, 2015, 1:04:05 PM (GMT)] + */ +package vazkii.botania.api.corporea; + +import java.util.List; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +/** + * An interface for systems which may control if the item auto complete system should be active + */ +public interface ICorporeaAutoCompleteController { + + /** + * Return true if auto completion should be enabled. + */ + @SideOnly(Side.CLIENT) + public boolean shouldAutoComplete(); + +} diff --git a/src/main/java/vazkii/botania/client/core/handler/CorporeaAutoCompleteHandler.java b/src/main/java/vazkii/botania/client/core/handler/CorporeaAutoCompleteHandler.java new file mode 100644 index 000000000..a17f8454f --- /dev/null +++ b/src/main/java/vazkii/botania/client/core/handler/CorporeaAutoCompleteHandler.java @@ -0,0 +1,186 @@ +/** + * This class was created by . It's distributed as + * part of the Botania Mod. Get the Source Code in github: + * https://github.com/Vazkii/Botania + * + * Botania is Open Source and distributed under the + * Botania License: http://botaniamod.net/license.php + * + * File Created @ [June 8, 2015, 12:55:20 AM (GMT)] + */ +package vazkii.botania.client.core.handler; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiChat; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ChatComponentText; + +import org.lwjgl.input.Keyboard; + +import vazkii.botania.api.corporea.CorporeaHelper; +import vazkii.botania.common.lib.LibObfuscation; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent; +import cpw.mods.fml.common.gameevent.TickEvent.Phase; +import cpw.mods.fml.relauncher.ReflectionHelper; + +public class CorporeaAutoCompleteHandler { + + boolean isAutoCompleted = false; + String originalString = ""; + List completions = new ArrayList(); + int position; + + static TreeSet itemNames = new TreeSet( + new Comparator() { + @Override + public int compare(String arg0, String arg1) { + return arg0.compareToIgnoreCase(arg1); + } + }); + + private boolean tabLastTick = false; + + public static void updateItemList() { + itemNames.clear(); + Iterator iterator = Item.itemRegistry.iterator(); + ArrayList curList = new ArrayList(); + + while(iterator.hasNext()) { + Item item = iterator.next(); + + if(item != null && item.getCreativeTab() != null) { + curList.clear(); + try { + item.getSubItems(item, (CreativeTabs) null, curList); + for(ItemStack stack : curList) + itemNames.add(stack.getDisplayName().trim()); + } + catch (Exception e) {} + } + } + } + + @SubscribeEvent + public void onTick(ClientTickEvent event) { + if(event.phase != Phase.END) + return; + GuiScreen screen = Minecraft.getMinecraft().currentScreen; + if(!(screen instanceof GuiChat)) { + isAutoCompleted = false; + return; + } + GuiChat chat = (GuiChat) screen; + if(isAutoCompleted) { + boolean valid = ReflectionHelper.getPrivateValue(GuiChat.class, chat, LibObfuscation.COMPLETE_FLAG); + if(!valid) + isAutoCompleted = false; + } + if(Keyboard.isKeyDown(15)) { + if(tabLastTick) + return; + tabLastTick = true; + } else { + tabLastTick = false; + return; + } + + if(!CorporeaHelper.shouldAutoComplete()) + return; + + GuiTextField inputField = ReflectionHelper.getPrivateValue(GuiChat.class, chat, LibObfuscation.INPUT_FIELD); + if(!isAutoCompleted && GuiScreen.isShiftKeyDown()) + buildAutoCompletes(inputField, chat); + if(isAutoCompleted && !completions.isEmpty()) + advanceAutoComplete(inputField, chat); + } + + private void advanceAutoComplete(GuiTextField inputField, GuiChat chat) { + this.position++; + if(this.position >= completions.size()) + this.position -= completions.size(); + CompletionData data = completions.get(position); + String str = originalString.substring(0, originalString.length() - data.prefixLength) + data.string; + inputField.setText(str); + } + + private void buildAutoCompletes(GuiTextField inputField, GuiChat chat) { + String leftOfCursor; + if(inputField.getCursorPosition() == 0) + leftOfCursor = ""; + else + leftOfCursor = inputField.getText().substring(0, inputField.getCursorPosition()); + if(leftOfCursor.length() == 0 || leftOfCursor.charAt(0) == '/') + return; + this.completions = getNames(leftOfCursor); + if(completions.isEmpty()) + return; + this.position = -1; + ReflectionHelper.setPrivateValue(GuiChat.class, chat, true, LibObfuscation.COMPLETE_FLAG); + StringBuilder stringbuilder = new StringBuilder(); + CompletionData data; + for(Iterator iterator = completions.iterator(); iterator.hasNext(); stringbuilder.append(data.string)) { + data = iterator.next(); + if(stringbuilder.length() > 0) + stringbuilder.append(", "); + } + + Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessageWithOptionalDeletion(new ChatComponentText(stringbuilder.toString()), 1); + isAutoCompleted = true; + originalString = inputField.getText(); + } + + private ArrayList getNames(String prefix) { + TreeSet result = new TreeSet(); + String[] words = prefix.split(" "); + int i = words.length - 1; + String curPrefix = words[i]; + while(i >= 0) { + result.addAll(getNamesStartingWith(curPrefix.toLowerCase())); + i--; + if(i >= 0) + curPrefix = words[i] + " " + curPrefix; + } + return new ArrayList(result); + } + + private List getNamesStartingWith(String prefix) { + ArrayList result = new ArrayList(); + int length = prefix.length(); + SortedSet after = itemNames.tailSet(prefix); + for(String str : after) { + if(str.toLowerCase().startsWith(prefix)) + result.add(new CompletionData(str, length)); + else return result; + } + return result; + } + + private static class CompletionData implements Comparable { + + private String string; + private int prefixLength; + + public CompletionData(String string, int prefixLength) { + this.string = string; + this.prefixLength = prefixLength; + } + + @Override + public int compareTo(CompletionData arg0) { + return string.compareTo(arg0.string); + } + } + +} diff --git a/src/main/java/vazkii/botania/client/core/proxy/ClientProxy.java b/src/main/java/vazkii/botania/client/core/proxy/ClientProxy.java index 97bccfdb4..3f63d3eb9 100644 --- a/src/main/java/vazkii/botania/client/core/proxy/ClientProxy.java +++ b/src/main/java/vazkii/botania/client/core/proxy/ClientProxy.java @@ -39,6 +39,7 @@ import vazkii.botania.client.core.handler.BaubleRenderHandler; import vazkii.botania.client.core.handler.BotaniaPlayerController; import vazkii.botania.client.core.handler.BoundTileRenderer; import vazkii.botania.client.core.handler.ClientTickHandler; +import vazkii.botania.client.core.handler.CorporeaAutoCompleteHandler; import vazkii.botania.client.core.handler.DebugHandler; import vazkii.botania.client.core.handler.HUDHandler; import vazkii.botania.client.core.handler.LightningHandler; @@ -143,6 +144,7 @@ import cpw.mods.fml.client.registry.RenderingRegistry; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Optional; import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.relauncher.ReflectionHelper; public class ClientProxy extends CommonProxy { @@ -162,6 +164,7 @@ public class ClientProxy extends CommonProxy { MinecraftForge.EVENT_BUS.register(new BaubleRenderHandler()); MinecraftForge.EVENT_BUS.register(new DebugHandler()); MinecraftForge.EVENT_BUS.register(new SubTileRadiusRenderHandler()); + FMLCommonHandler.instance().bus().register(new CorporeaAutoCompleteHandler()); if(ConfigHandler.versionCheckEnabled) new VersionChecker().init(); @@ -174,6 +177,11 @@ public class ClientProxy extends CommonProxy { initRenderers(); } + @Override + public void postInit(FMLPostInitializationEvent event) { + super.postInit(event); + CorporeaAutoCompleteHandler.updateItemList(); + } private void initRenderers() { LibRenderIDs.idAltar = RenderingRegistry.getNextAvailableRenderId(); diff --git a/src/main/java/vazkii/botania/common/block/tile/corporea/TileCorporeaIndex.java b/src/main/java/vazkii/botania/common/block/tile/corporea/TileCorporeaIndex.java index 273850dc9..9e9f197d8 100644 --- a/src/main/java/vazkii/botania/common/block/tile/corporea/TileCorporeaIndex.java +++ b/src/main/java/vazkii/botania/common/block/tile/corporea/TileCorporeaIndex.java @@ -20,6 +20,7 @@ import java.util.WeakHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import net.minecraft.client.Minecraft; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; @@ -32,6 +33,7 @@ import net.minecraftforge.event.ServerChatEvent; import org.apache.commons.lang3.text.WordUtils; import vazkii.botania.api.corporea.CorporeaHelper; +import vazkii.botania.api.corporea.ICorporeaAutoCompleteController; import vazkii.botania.api.corporea.ICorporeaSpark; import vazkii.botania.common.achievement.ModAchievements; import vazkii.botania.common.core.helper.MathHelper; @@ -213,8 +215,12 @@ public class TileCorporeaIndex extends TileCorporeaBase { } } - public static final class InputHandler { + public static final class InputHandler implements ICorporeaAutoCompleteController { + public InputHandler() { + CorporeaHelper.registerAutoCompleteController(this); + } + @SubscribeEvent(priority = EventPriority.HIGHEST) public void onChatMessage(ServerChatEvent event) { List nearbyIndexes = getNearbyIndexes(event.player); @@ -270,6 +276,11 @@ public class TileCorporeaIndex extends TileCorporeaBase { return indexList; } + @Override + public boolean shouldAutoComplete() { + return !getNearbyIndexes(Minecraft.getMinecraft().thePlayer).isEmpty(); + } + } public static interface IRegexStacker { diff --git a/src/main/java/vazkii/botania/common/lib/LibObfuscation.java b/src/main/java/vazkii/botania/common/lib/LibObfuscation.java index 4ecc24e74..c81e472d8 100644 --- a/src/main/java/vazkii/botania/common/lib/LibObfuscation.java +++ b/src/main/java/vazkii/botania/common/lib/LibObfuscation.java @@ -72,5 +72,9 @@ public final class LibObfuscation { // GuiContainer public static final String[] THE_SLOT = new String[] { "theSlot", "field_147006_u", "u" }; + + // GuiChat + public static final String[] INPUT_FIELD = new String[] { "inputField", "field_146415_a", "a" }; + public static final String[] COMPLETE_FLAG = new String[] { "field_146414_r", "r" }; }