├── 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 |
5 |
6 |
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 |
16 |
17 |
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 | 
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 |
--------------------------------------------------------------------------------