diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java index efc71ae8..045552b6 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java @@ -92,7 +92,7 @@ public class CustomFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null") || data.isEmpty()) { + if (data == null || data.trim().equalsIgnoreCase("null") || data.isEmpty()) { closed = true; return; } @@ -108,7 +108,7 @@ public class CustomFormWindow extends FormWindow { List componentResponses = new ArrayList<>(); try { - componentResponses = new ObjectMapper().readValue(data, new TypeReference>(){}); + componentResponses = new ObjectMapper().readValue(data.trim(), new TypeReference>(){}); } catch (IOException e) { } for (String response : componentResponses) { diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java index 7c1acc26..3101f5fb 100644 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java @@ -72,14 +72,14 @@ public class SimpleFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { + if (data == null || data.trim().equalsIgnoreCase("null")) { closed = true; return; } int buttonID; try { - buttonID = Integer.parseInt(data); + buttonID = Integer.parseInt(data.trim()); } catch (Exception ex) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index afa75503..2b35424a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -52,6 +52,7 @@ public abstract class CommandManager { registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand")); registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump")); registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version")); + registerCommand(new StatisticsCommand(connector, "statistics", LanguageUtils.getLocaleStringLog("geyser.commands.statistics.desc"), "geyser.command.statistics")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java new file mode 100644 index 00000000..ed9db58f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.command.defaults; + +import com.github.steveice10.mc.protocol.data.game.ClientRequest; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; + +public class StatisticsCommand extends GeyserCommand { + + private final GeyserConnector connector; + + public StatisticsCommand(GeyserConnector connector, String name, String description, String permission) { + super(name, description, permission); + + this.connector = connector; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (sender.isConsole()) { + return; + } + + // Make sure the sender is a Bedrock edition client + GeyserSession session = null; + if (sender instanceof GeyserSession) { + session = (GeyserSession) sender; + } else { + // Needed for Spigot - sender is not an instance of GeyserSession + for (GeyserSession otherSession : connector.getPlayers()) { + if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) { + session = otherSession; + break; + } + } + } + if (session == null) return; + session.setWaitingForStatistics(true); + ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); + session.sendDownstreamPacket(clientRequestPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index f76d64ed..f99abbe5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.ResourcePackManifest; import org.geysermc.connector.utils.SettingsUtils; +import org.geysermc.connector.utils.StatisticsUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -141,6 +142,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(ModalFormResponsePacket packet) { if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) { return SettingsUtils.handleSettingsForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_MENU_FORM_ID) { + return StatisticsUtils.handleMenuForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_LIST_FORM_ID) { + return StatisticsUtils.handleListForm(session, packet.getFormData()); } return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index bb7602f3..a8a4adb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; @@ -55,6 +56,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; @@ -275,6 +277,18 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + /** + * Stores a map of all statistics sent from the server. + * The server only sends new statistics back to us, so in order to show all statistics we need to cache existing ones. + */ + private final Map statistics = new HashMap<>(); + + /** + * Whether we're expecting statistics to be sent back to us. + */ + @Setter + private boolean waitingForStatistics = false; + @Setter private List selectedEmotes = new ArrayList<>(); private final Set emotes = new HashSet<>(); @@ -776,6 +790,15 @@ public class GeyserSession implements CommandSender { sendUpstreamPacket(adventureSettingsPacket); } + /** + * Used for updating statistic values since we only get changes from the server + * + * @param statistics Updated statistics values + */ + public void updateStatistics(@NonNull Map statistics) { + this.statistics.putAll(statistics); + } + public void refreshEmotes(List emotes) { this.selectedEmotes = emotes; this.emotes.addAll(emotes); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java new file mode 100644 index 00000000..9a80254b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerStatisticsPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.StatisticsUtils; + +@Translator(packet = ServerStatisticsPacket.class) +public class JavaStatisticsTranslator extends PacketTranslator { + + @Override + public void translate(ServerStatisticsPacket packet, GeyserSession session) { + session.updateStatistics(packet.getStatistics()); + + if (session.isWaitingForStatistics()) { + session.setWaitingForStatistics(false); + session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index e5f8d6aa..5314292a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -67,6 +67,11 @@ public class BlockTranslator { public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + /** + * Java numeric ID to java unique identifier, used for block names in the statistics screen + */ + public static final Int2ObjectMap JAVA_ID_TO_JAVA_IDENTIFIER_MAP = new Int2ObjectOpenHashMap<>(); + /** * Runtime command block ID, used for fixing command block minecart appearances */ @@ -124,6 +129,7 @@ public class BlockTranslator { int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; + int uniqueJavaId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; @@ -166,6 +172,11 @@ public class BlockTranslator { String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + if (!JAVA_ID_TO_JAVA_IDENTIFIER_MAP.containsValue(cleanJavaIdentifier)) { + uniqueJavaId++; + JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier); + } + if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java new file mode 100644 index 00000000..3c42182d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.protocol.data.MagicValues; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.github.steveice10.mc.protocol.data.game.statistic.*; +import org.geysermc.common.window.SimpleFormWindow; +import org.geysermc.common.window.button.FormButton; +import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +import java.util.Map; + +public class StatisticsUtils { + + // Used in UpstreamPacketHandler.java + public static final int STATISTICS_MENU_FORM_ID = 1339; + public static final int STATISTICS_LIST_FORM_ID = 1340; + + /** + * Build a form for the given session with all statistic categories + * + * @param session The session to build the form for + */ + public static SimpleFormWindow buildMenuForm(GeyserSession session) { + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language))); + + return window; + } + + /** + * Handle the menu form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleMenuForm(GeyserSession session, String response) { + SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); + menuForm.setResponse(response); + SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); + + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + if (formResponse != null && formResponse.getClickedButton() != null) { + String title; + StringBuilder content = new StringBuilder(); + + switch (formResponse.getClickedButtonId()) { + case 0: + title = LocaleUtils.getLocaleString("stat.generalButton", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof GenericStatistic) { + content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 1: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakBlockStatistic) { + String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); + block = block.replace("minecraft:", "block.minecraft."); + block = LocaleUtils.getLocaleString(block, language); + content.append(block + ": " + entry.getValue() + "\n"); + } + } + break; + case 2: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 3: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof CraftItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 4: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof UseItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 5: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof PickupItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 6: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof DropItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 7: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KillEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + case 8: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KilledByEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + default: + return false; + } + + if (content.length() == 0) { + content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); + } + + SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); + session.sendForm(window, STATISTICS_LIST_FORM_ID); + } + + return true; + } + + /** + * Handle the list form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleListForm(GeyserSession session, String response) { + SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); + listForm.setResponse(response); + + if (!listForm.isClosed()) { + session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); + } + + return true; + } + + /** + * Finds the item translation key from the Java locale. + * + * @param item the namespaced item to search for. + * @param language the language to search in + * @return the full name of the item + */ + private static String getItemTranslateKey(String item, String language) { + item = item.replace("minecraft:", "item.minecraft."); + String translatedItem = LocaleUtils.getLocaleString(item, language); + if (translatedItem.equals(item)) { + // Didn't translate; must be a block + translatedItem = LocaleUtils.getLocaleString(item.replace("item.", "block."), language); + } + return translatedItem; + } +} diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 5f217922..a4125be9 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 5f21792264a364e32425014e0be79db93593da1e +Subproject commit a4125be98fefea6cefd43dc52ccb2ade4e70573e