├── src └── main │ ├── resources │ ├── config.json │ └── plugin.json │ └── java │ └── uk │ └── haku │ └── idlook │ ├── utils │ ├── StringSimilarity.java │ └── LanguageTools.java │ ├── objects │ ├── QueryResult.java │ └── PluginConfig.java │ ├── IdLookPlugin.java │ └── commands │ └── LookCommand.java ├── .idea ├── vcs.xml ├── .gitignore ├── discord.xml └── misc.xml ├── .gitignore ├── README.md └── pom.xml /src/main/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "scoreTreshold": 40, 3 | "resultLimit": 3, 4 | "defaultLanguage": "EN" 5 | } -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IdLookPlugin", 3 | "description": "IdLookPlugin", 4 | "version": "1.1.0", 5 | "authors": [ "FFauzan" ], 6 | 7 | "mainClass": "uk.haku.idlook.IdLookPlugin" 8 | } -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /src/main/java/uk/haku/idlook/utils/StringSimilarity.java: -------------------------------------------------------------------------------- 1 | package uk.haku.idlook.utils; 2 | 3 | import me.xdrop.fuzzywuzzy.FuzzySearch; 4 | 5 | public class StringSimilarity { 6 | public static double Fuzzy(String x, String y) { 7 | return (FuzzySearch.tokenSetRatio(x.toLowerCase(), y.toLowerCase()) 8 | + FuzzySearch.ratio(x.toLowerCase(), y.toLowerCase())) / 2; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/uk/haku/idlook/utils/LanguageTools.java: -------------------------------------------------------------------------------- 1 | package uk.haku.idlook.utils; 2 | 3 | import emu.grasscutter.utils.Language; 4 | 5 | public class LanguageTools { 6 | private static String[] langList = Language.TextStrings.ARR_LANGUAGES; 7 | 8 | public static boolean IsLanguageExist(String selectedLangCode) { 9 | for (String langCode : langList) { 10 | if (langCode.equalsIgnoreCase(selectedLangCode)) { 11 | return true; 12 | } 13 | } 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/uk/haku/idlook/objects/QueryResult.java: -------------------------------------------------------------------------------- 1 | package uk.haku.idlook.objects; 2 | 3 | 4 | public class QueryResult implements Comparable { 5 | public int Id; 6 | public String Name; 7 | public String ItemType; 8 | public int Score; 9 | 10 | public QueryResult(int id, String Name, String itemType, int score) { 11 | this.Id = id; 12 | this.Name = Name; 13 | this.ItemType = itemType; 14 | this.Score = score; 15 | } 16 | 17 | public int getScore() { 18 | return this.Score; 19 | } 20 | 21 | @Override public int compareTo(QueryResult compareq) { 22 | int compareScore = ((QueryResult)compareq).getScore(); 23 | return compareScore - this.Score; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/uk/haku/idlook/objects/PluginConfig.java: -------------------------------------------------------------------------------- 1 | package uk.haku.idlook.objects; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.io.Reader; 6 | import java.lang.reflect.Type; 7 | 8 | /** 9 | * A data container for the plugin configuration. 10 | * 11 | * This class is used in conjunction with {@link Gson#toJson(Object)} and 12 | * {@link Gson#fromJson(Reader, Type)}. 13 | * With {@link Gson}, it is possible to save and load configuration values from 14 | * a JSON file. 15 | * 16 | * You can set property defaults using `public Object property = (default 17 | * value);`. 18 | * Use {@link Gson#fromJson(Reader, Type)} to load the values set from a 19 | * reader/string into a new instance of this class. 20 | */ 21 | public final class PluginConfig { 22 | public int scoreTreshold = 40; 23 | public int resultLimit = 3; 24 | public String defaultLanguage = "EN"; 25 | 26 | /** 27 | * When saved with {@link Gson#toJson(Object)}, it produces: 28 | * { 29 | * "sendJoinMessage": true, 30 | * "joinMessage": "Welcome to the server!" 31 | * } 32 | */ 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # In-Game GM Handbook Plugin 2 | 3 | ## Preview 4 | 5 | ![](https://s3.fimg.haku.uk/s/idlook110.gif) 6 | 7 | ## 1. Installation 8 | 1. Download IdLook-1.1.0.jar from Release. 9 | 2. Put it to your Grasscutter plugin folder. 10 | > It is advisable to remove the `IdLookPlugin` folder if you are updating from an older version. 11 | 12 | ## 2. Config File Explanations 13 | ``` 14 | { 15 | "scoreTreshold": 40, // Treshold for similarity search. Range 1-100. 16 | "resultLimit": 3, // How many search result should be displayed. 17 | "defaultLanguage": "EN" // Default language for the plugin. 18 | } 19 | ``` 20 | 21 | ## 3. Command Usage 22 | ### Searching for items, avatars, or monsters. 23 | ``` 24 | /gm {your search query} 25 | /l {your search query} 26 | ``` 27 | ### Changing Handbook language 28 | ``` 29 | /gm setlang {language code} 30 | /l setlang {language code} 31 | ``` 32 | 33 | ## 4. Available Languages 34 | ``` 35 | EN,CHS,CHT,JP,KR,DE,ES,FR,ID,PT,RU,TH,VI 36 | ``` 37 | 38 | ## 5. Version Compatibility 39 | 40 | | IdLook | Grasscutter Stable | Grasscutter Development | 41 | |--------|--------------------|--------------------| 42 | | 1.1.0 | | 1.3.2-dev & newer | 43 | | 1.0.3 | | 1.2.3-dev | 44 | | 1.0.2 | | 1.2.2-dev [(949916a and newer)](https://github.com/Grasscutters/Grasscutter/commit/949916ad8060afbd31507b4c5f62427fb6dd59bb) | 45 | | 1.0.1 | 1.2.0 | 1.2.2-dev | 46 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | uk.haku 6 | IdLook 7 | 1.1.0 8 | 9 | 10 | 17 11 | 17 12 | 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-compiler-plugin 19 | 3.8.1 20 | 21 | 17 22 | 17 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-shade-plugin 28 | 3.2.4 29 | 30 | 31 | package 32 | 33 | shade 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | src/main/resources 45 | true 46 | 47 | 48 | 49 | 50 | 51 | 52 | 4benj-maven-releases 53 | 4Benj Maven 54 | https://repo.4benj.com/releases 55 | 56 | 57 | 58 | 59 | 60 | xyz.grasscutters 61 | grasscutter 62 | 1.3.2-dev 63 | provided 64 | 65 | 66 | me.xdrop 67 | fuzzywuzzy 68 | 1.3.0 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/main/java/uk/haku/idlook/IdLookPlugin.java: -------------------------------------------------------------------------------- 1 | package uk.haku.idlook; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import emu.grasscutter.plugin.Plugin; 7 | 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.IOException; 11 | import java.nio.file.Files; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import uk.haku.idlook.commands.*; 16 | import uk.haku.idlook.IdLookPlugin; 17 | import uk.haku.idlook.objects.PluginConfig; 18 | 19 | /** 20 | * The Grasscutter plugin template. 21 | * This is the main class for the plugin. 22 | */ 23 | public final class IdLookPlugin extends Plugin { 24 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 25 | /* Turn the plugin into a singleton. */ 26 | private static IdLookPlugin instance; 27 | 28 | /** 29 | * Gets the plugin instance. 30 | * 31 | * @return A plugin singleton. 32 | */ 33 | public static IdLookPlugin getInstance() { 34 | return instance; 35 | } 36 | 37 | /* The plugin's configuration instance. */ 38 | private PluginConfig configuration; 39 | 40 | /* Player language preference Map */ 41 | private Map playerLang; 42 | 43 | /** 44 | * This method is called immediately after the plugin is first loaded into 45 | * system memory. 46 | */ 47 | @Override 48 | public void onLoad() { 49 | // Set the plugin instance. 50 | instance = this; 51 | 52 | // Get the configuration file. 53 | var configFile = new File(this.getDataFolder(), "config.json"); 54 | if (!configFile.exists()) { 55 | try { 56 | if (!configFile.createNewFile()) 57 | throw new IOException("Failed to create config file."); 58 | Files.write(configFile.toPath(), gson.toJson(new PluginConfig()).getBytes()); 59 | } catch (IOException ignored) { 60 | this.getLogger().error("Unable to save configuration file."); 61 | } 62 | } 63 | 64 | try { // Load configuration file. 65 | this.configuration = gson.fromJson(new FileReader(configFile), PluginConfig.class); 66 | } catch (IOException ignored) { 67 | this.getLogger().error("Unable to load configuration file."); 68 | this.configuration = new PluginConfig(); 69 | } 70 | 71 | // Initiate player languange map 72 | this.playerLang = new HashMap(); 73 | 74 | // Log a plugin status message. 75 | this.getLogger().info("The IdLook plugin has been loaded."); 76 | } 77 | 78 | /** 79 | * This method is called before the servers are started, or when the plugin 80 | * enables. 81 | */ 82 | @Override 83 | public void onEnable() { 84 | // Register commands. 85 | this.getHandle().registerCommand(new LookCommand()); 86 | 87 | // Log a plugin status message. 88 | this.getLogger().info("The IdLook plugin has been enabled."); 89 | } 90 | 91 | /** 92 | * This method is called when the plugin is disabled. 93 | */ 94 | @Override 95 | public void onDisable() { 96 | // Log a plugin status message. 97 | this.getLogger().info("The IdLook plugin has been disabled."); 98 | } 99 | 100 | /** 101 | * Gets the plugin's configuration. 102 | * 103 | * @return A plugin config instance. 104 | */ 105 | public PluginConfig getConfiguration() { 106 | return this.configuration; 107 | } 108 | 109 | /* Get player language map */ 110 | public Map getPlayerLangMap() { 111 | return this.playerLang; 112 | } 113 | 114 | /* Remove player language from map */ 115 | public void removePlayerLang(String accountId) { 116 | this.playerLang.remove(accountId); 117 | } 118 | 119 | /* Add player language to map */ 120 | public void addPlayerLang(String accountId, String lang) { 121 | this.playerLang.put(accountId, lang); 122 | } 123 | 124 | /* Get player language from map */ 125 | public String getPlayerLang(String accountId) { 126 | return this.playerLang.get(accountId); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/uk/haku/idlook/commands/LookCommand.java: -------------------------------------------------------------------------------- 1 | package uk.haku.idlook.commands; 2 | 3 | import java.util.*; 4 | 5 | import org.slf4j.Logger; 6 | 7 | import emu.grasscutter.command.Command; 8 | import emu.grasscutter.command.CommandHandler; 9 | import emu.grasscutter.config.Configuration; 10 | import emu.grasscutter.data.GameData; 11 | import emu.grasscutter.game.player.Player; 12 | import emu.grasscutter.utils.Language; 13 | import emu.grasscutter.utils.Utils; 14 | import uk.haku.idlook.IdLookPlugin; 15 | import uk.haku.idlook.utils.LanguageTools; 16 | import uk.haku.idlook.utils.StringSimilarity; 17 | import uk.haku.idlook.objects.PluginConfig; 18 | import uk.haku.idlook.objects.QueryResult; 19 | 20 | @Command(label = "look", usage = "look ", aliases = { "l", 21 | "gm", "handbook" }, permission = "player.look", targetRequirement = Command.TargetRequirement.NONE) 22 | public final class LookCommand implements CommandHandler { 23 | private static final PluginConfig config = IdLookPlugin.getInstance().getConfiguration(); 24 | private int resultLimit = config.resultLimit; 25 | private int similarityScoreTreshold = config.scoreTreshold; 26 | private String targetLanguage = config.defaultLanguage; 27 | private String availableLangCodeString = String.join(",", Language.TextStrings.ARR_LANGUAGES); 28 | 29 | Logger logger = IdLookPlugin.getInstance().getLogger(); 30 | 31 | @Override 32 | public void execute(Player sender, Player targetPlayer, List args) { 33 | String playerId = ""; 34 | 35 | if (sender != null) { 36 | playerId = sender.getAccount().getId(); 37 | } 38 | 39 | // Set player language preference 40 | if (args.size() < 1) { 41 | usageMessage(targetPlayer); 42 | return; 43 | } 44 | 45 | switch (args.get(0)) { 46 | case "setlang": 47 | if (args.size() != 2) { 48 | setLangUsageMessage(sender); 49 | return; 50 | } 51 | 52 | if (!LanguageTools.IsLanguageExist(args.get(1))) { 53 | CommandHandler.sendMessage(sender, "Available language code: " + availableLangCodeString); 54 | return; 55 | } 56 | 57 | IdLookPlugin.getInstance().addPlayerLang(playerId, args.get(1).toUpperCase()); 58 | CommandHandler.sendMessage(sender, "Handbook language changed to: " + args.get(1).toUpperCase()); 59 | return; 60 | case "getlang": 61 | if (args.size() != 1) { 62 | CommandHandler.sendMessage(sender, "Usage: /gm getlang"); 63 | return; 64 | } 65 | 66 | String playerLang = IdLookPlugin.getInstance().getPlayerLang(playerId); 67 | if (playerLang != null) { 68 | CommandHandler.sendMessage(sender, "Handbook language: " + playerLang); 69 | return; 70 | } else { 71 | CommandHandler.sendMessage(sender, "No Handbook language set"); 72 | setLangUsageMessage(sender); 73 | return; 74 | } 75 | default: 76 | String lookQuery = String.join(" ", args); 77 | ArrayList resultList = new ArrayList(); 78 | String langCode = getLanguage(sender); 79 | 80 | lookFor(lookQuery, resultList, langCode); 81 | 82 | Collections.sort(resultList); 83 | 84 | sendResult(sender, resultList, lookQuery); 85 | return; 86 | } 87 | } 88 | 89 | public void setLangUsageMessage(Player player) { 90 | CommandHandler.sendMessage(player, "/gm setlang {language code}"); 91 | CommandHandler.sendMessage(player, "Available language code: " + availableLangCodeString); 92 | CommandHandler.sendMessage(player, "Example: /gm setlang EN"); 93 | } 94 | 95 | public void usageMessage(Player player) { 96 | CommandHandler.sendMessage(player, "Usage:\n/gm {your query}\nExample: /gm wolf grav"); 97 | CommandHandler.sendMessage(player, 98 | "Changing language:\n/gm setlang {language code}\nAvailable language code: " + availableLangCodeString 99 | + "\nExample: /gm setlang EN"); 100 | } 101 | 102 | public String getLanguage(Player player) { 103 | // Executed from GC console 104 | if (player == null) { 105 | return Utils.getLanguageCode(Configuration.LANGUAGE); 106 | } 107 | 108 | // Get player language from language Map 109 | String playerId = player.getAccount().getId(); 110 | String playerLang = IdLookPlugin.getInstance().getPlayerLang(playerId); 111 | if (playerId != null) { 112 | return playerLang; 113 | } 114 | 115 | // If language in plugin config sets to auto, Get player locale 116 | if (targetLanguage.equals("auto")) { 117 | return Utils.getLanguageCode(player.getAccount().getLocale()); 118 | 119 | } else if (targetLanguage.equals("server")) { 120 | return Configuration.DOCUMENT_LANGUAGE; 121 | } else { 122 | // Check if value of language in plugin config is valid 123 | if (LanguageTools.IsLanguageExist(targetLanguage)) { 124 | return targetLanguage.toUpperCase(); 125 | } 126 | } 127 | 128 | return Configuration.DOCUMENT_LANGUAGE; 129 | } 130 | 131 | public void sendResult(Player player, List lookResult, String query) { 132 | if (lookResult.size() == 0) { 133 | CommandHandler.sendMessage(player, "Cannot find anything, try different keyword"); 134 | return; 135 | } else if (lookResult.size() > resultLimit) { 136 | lookResult = lookResult.subList(0, resultLimit); 137 | } 138 | 139 | CommandHandler.sendMessage(player, "Result for: " + query); 140 | lookResult.forEach((data) -> { 141 | String name = data.Name; 142 | String itemType = data.ItemType; 143 | String responseMsg = "Id: " + data.Id + " | Name: " + name + " | Type: " + itemType; 144 | CommandHandler.sendMessage(player, responseMsg); 145 | }); 146 | return; 147 | } 148 | 149 | public void lookFor(String query, ArrayList lookResult, String langCode) { 150 | lookForAvatar(query, lookResult, langCode); 151 | lookForItem(query, lookResult, langCode); 152 | lookForMonster(query, lookResult, langCode); 153 | return; 154 | } 155 | 156 | public void lookForMonster(String query, ArrayList lookResult, String langCode) { 157 | // Monster 158 | GameData.getMonsterDataMap().forEach((id, data) -> { 159 | Language.TextStrings nameDict = Language.getTextMapKey(data.getNameTextMapHash()); 160 | String name = nameDict.get(langCode); 161 | if (name != null) { 162 | Double similarityScore = StringSimilarity.Fuzzy(query, name); 163 | if (similarityScore > similarityScoreTreshold) { 164 | lookResult.add(new QueryResult(id, name, "Monsters", similarityScore.intValue())); 165 | } 166 | } 167 | }); 168 | return; 169 | } 170 | 171 | public void lookForAvatar(String query, ArrayList lookResult, String langCode) { 172 | // Avatars 173 | GameData.getAvatarDataMap().forEach((id, data) -> { 174 | Language.TextStrings nameDict = Language.getTextMapKey(data.getNameTextMapHash()); 175 | String name = nameDict.get(langCode); 176 | if (name != null) { 177 | Double similarityScore = StringSimilarity.Fuzzy(query, name); 178 | if (similarityScore > similarityScoreTreshold) { 179 | lookResult.add(new QueryResult(id, name, "Avatars", similarityScore.intValue())); 180 | } 181 | } 182 | }); 183 | return; 184 | } 185 | 186 | public void lookForItem(String query, ArrayList lookResult, String langCode) { 187 | // Item 188 | GameData.getItemDataMap().forEach((id, data) -> { 189 | Language.TextStrings nameDict = Language.getTextMapKey(data.getNameTextMapHash()); 190 | String name = nameDict.get(langCode); 191 | if (name != null) { 192 | Double similarityScore = StringSimilarity.Fuzzy(query, name); 193 | if (similarityScore > similarityScoreTreshold) { 194 | lookResult.add(new QueryResult(id, name, data.getItemType().name(), similarityScore.intValue())); 195 | } 196 | } 197 | }); 198 | return; 199 | } 200 | } 201 | --------------------------------------------------------------------------------