diff --git a/core/pom.xml b/core/pom.xml index e3189a1cb..ec41b6d72 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -155,7 +155,7 @@ com.github.GeyserMC MCProtocolLib - 6970991 + a991afe compile diff --git a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java index 870828f44..98d3aa341 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java @@ -61,6 +61,12 @@ public class BlockRegistries { */ public static final MappedRegistry> JAVA_IDENTIFIERS = MappedRegistry.create(RegistryLoaders.empty(Object2IntBiMap::new)); + /** + * A registry which stores unique Java IDs to its clean identifier + * This is used in the statistics form. + */ + public static final ArrayRegistry CLEAN_JAVA_IDENTIFIERS = ArrayRegistry.create(RegistryLoaders.empty(() -> new String[] {})); + /** * A registry containing all the waterlogged blockstates. */ diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index a81903e8e..ce63c2c5b 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent; import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; @@ -61,6 +62,7 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.IntFunction; /** * Holds all the common registries in Geyser. diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index 301dfa1da..fef5b32aa 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -46,10 +46,7 @@ import org.geysermc.geyser.util.BlockUtils; import java.io.DataInputStream; import java.io.InputStream; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.BiFunction; import java.util.zip.GZIPInputStream; @@ -216,7 +213,7 @@ public class BlockRegistryPopulator { BlockRegistries.JAVA_BLOCKS.set(new BlockMapping[blocksJson.size()]); // Set array size to number of blockstates - Set cleanIdentifiers = new HashSet<>(); + Deque cleanIdentifiers = new ArrayDeque<>(); int javaRuntimeId = -1; int bellBlockId = -1; @@ -281,7 +278,7 @@ public class BlockRegistryPopulator { String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(entry.getKey()); String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); - if (!cleanIdentifiers.contains(cleanJavaIdentifier)) { + if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) { uniqueJavaId++; cleanIdentifiers.add(cleanJavaIdentifier.intern()); } @@ -360,6 +357,8 @@ public class BlockRegistryPopulator { } BlockStateValues.JAVA_WATER_ID = waterRuntimeId; + BlockRegistries.CLEAN_JAVA_IDENTIFIERS.set(cleanIdentifiers.toArray(new String[0])); + BLOCKS_JSON = blocksJson; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 6a730010e..71036b792 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -442,7 +442,7 @@ public class ItemRegistryPopulator { entries.put("geysermc:furnace_minecart", new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true)); mappings.put(javaFurnaceMinecartId, ItemMapping.builder() - .javaIdentifier("geysermc:furnace_minecart") + .javaIdentifier("minecraft:furnace_minecart") .bedrockIdentifier("geysermc:furnace_minecart") .javaId(javaFurnaceMinecartId) .bedrockId(furnaceMinecartId) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 0011381cf..a58d57503 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -39,6 +39,7 @@ import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientIntentionPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket; @@ -1464,6 +1465,12 @@ public class GeyserSession implements GeyserConnection, CommandSender { * @param statistics Updated statistics values */ public void updateStatistics(@NonNull Map statistics) { + if (this.statistics.isEmpty()) { + // Initialize custom statistics to 0, so that they appear in the form + for (CustomStatistic customStatistic : CustomStatistic.values()) { + this.statistics.put(customStatistic, 0); + } + } this.statistics.putAll(statistics); } diff --git a/core/src/main/java/org/geysermc/geyser/util/StatisticFormatters.java b/core/src/main/java/org/geysermc/geyser/util/StatisticFormatters.java new file mode 100644 index 000000000..f8f12fe79 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/StatisticFormatters.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2021 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.geyser.util; + +import com.github.steveice10.mc.protocol.data.game.statistic.StatisticFormat; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Map; +import java.util.function.IntFunction; + +public class StatisticFormatters { + + private static final Map> FORMATTERS = new Object2ObjectOpenHashMap<>(); + private static final DecimalFormat FORMAT = new DecimalFormat("###,###,##0.00"); + + public static final IntFunction INTEGER = NumberFormat.getIntegerInstance(Locale.US)::format; + + static { + FORMATTERS.put(StatisticFormat.INTEGER, INTEGER); + FORMATTERS.put(StatisticFormat.TENTHS, value -> FORMAT.format(value / 10d)); + FORMATTERS.put(StatisticFormat.DISTANCE, centimeter -> { + double meter = centimeter / 100d; + double kilometer = meter / 1000d; + if (kilometer > 0.5) { + return FORMAT.format(kilometer) + " km"; + } else if (meter > 0.5) { + return FORMAT.format(meter) + " m"; + } else { + return centimeter + " cm"; + } + }); + FORMATTERS.put(StatisticFormat.TIME, ticks -> { + double seconds = ticks / 20d; + double minutes = seconds / 60d; + double hours = minutes / 60d; + double days = hours / 24d; + double years = days / 365d; + if (years > 0.5) { + return FORMAT.format(years) + " y"; + } else if (days > 0.5) { + return FORMAT.format(days) + " d"; + } else if (hours > 0.5) { + return FORMAT.format(hours) + " h"; + } else if (minutes > 0.5) { + return FORMAT.format(minutes) + " m"; + } else { + return FORMAT.format(seconds) + " s"; + } + }); + } + + public static IntFunction get(StatisticFormat format) { + return FORMATTERS.getOrDefault(format, INTEGER); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java b/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java index aa5893462..6a03495db 100644 --- a/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/StatisticsUtils.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.util; -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.geyser.session.GeyserSession; import org.geysermc.geyser.registry.BlockRegistries; @@ -37,7 +35,11 @@ import org.geysermc.cumulus.util.FormImage; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.function.IntFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -59,12 +61,12 @@ public class StatisticsUtils { .title("gui.stats") .button("stat.generalButton", FormImage.Type.PATH, "textures/ui/World") .button("stat.itemsButton - stat_type.minecraft.mined", FormImage.Type.PATH, "textures/items/iron_pickaxe") - .button("stat.itemsButton - stat_type.minecraft.broken", FormImage.Type.PATH, "textures/item/record_11") + .button("stat.itemsButton - stat_type.minecraft.broken", FormImage.Type.PATH, "textures/items/record_11") .button("stat.itemsButton - stat_type.minecraft.crafted", FormImage.Type.PATH, "textures/blocks/crafting_table_side") .button("stat.itemsButton - stat_type.minecraft.used", FormImage.Type.PATH, "textures/ui/Wrenches1") .button("stat.itemsButton - stat_type.minecraft.picked_up", FormImage.Type.PATH, "textures/blocks/chest_front") .button("stat.itemsButton - stat_type.minecraft.dropped", FormImage.Type.PATH, "textures/ui/trash_default") - .button("stat.mobsButton - geyser.statistics.killed", FormImage.Type.PATH, "textures/items/diamon_sword") + .button("stat.mobsButton - geyser.statistics.killed", FormImage.Type.PATH, "textures/items/diamond_sword") .button("stat.mobsButton - geyser.statistics.killed_by", FormImage.Type.PATH, "textures/ui/wither_heart_flash") .responseHandler((form, responseData) -> { SimpleFormResponse response = form.parseResponse(responseData); @@ -76,7 +78,7 @@ public class StatisticsUtils { SimpleForm.builder() .translator(StatisticsUtils::translate, language); - StringBuilder content = new StringBuilder(); + List content = new ArrayList<>(); ItemMappings mappings = session.getItemMappings(); switch (response.getClickedButtonId()) { @@ -84,9 +86,10 @@ public class StatisticsUtils { builder.title("stat.generalButton"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof GenericStatistic) { - String statName = ((GenericStatistic) entry.getKey()).name().toLowerCase(); - content.append("stat.minecraft.").append(statName).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof CustomStatistic statistic) { + String statName = statistic.name().toLowerCase(Locale.ROOT); + IntFunction formatter = StatisticFormatters.get(statistic.getFormat()); + content.add("stat.minecraft." + statName + ": " + formatter.apply(entry.getValue())); } } break; @@ -94,10 +97,12 @@ public class StatisticsUtils { builder.title("stat.itemsButton - stat_type.minecraft.mined"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakBlockStatistic) { - String block = BlockRegistries.JAVA_BLOCKS.get(((BreakBlockStatistic) entry.getKey()).getId()).getJavaIdentifier(); - block = block.replace("minecraft:", "block.minecraft."); - content.append(block).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof BreakBlockStatistic statistic) { + String identifier = BlockRegistries.CLEAN_JAVA_IDENTIFIERS.get(statistic.getId()); + if (identifier != null) { + String block = identifier.replace("minecraft:", "block.minecraft."); + content.add(block + ": " + entry.getValue()); + } } } break; @@ -105,9 +110,9 @@ public class StatisticsUtils { builder.title("stat.itemsButton - stat_type.minecraft.broken"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakItemStatistic) { - String item = mappings.getMapping(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof BreakItemStatistic statistic) { + String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); + content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); } } break; @@ -115,9 +120,9 @@ public class StatisticsUtils { builder.title("stat.itemsButton - stat_type.minecraft.crafted"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof CraftItemStatistic) { - String item = mappings.getMapping(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof CraftItemStatistic statistic) { + String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); + content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); } } break; @@ -125,9 +130,9 @@ public class StatisticsUtils { builder.title("stat.itemsButton - stat_type.minecraft.used"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof UseItemStatistic) { - String item = mappings.getMapping(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof UseItemStatistic statistic) { + String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); + content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); } } break; @@ -135,9 +140,9 @@ public class StatisticsUtils { builder.title("stat.itemsButton - stat_type.minecraft.picked_up"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof PickupItemStatistic) { - String item = mappings.getMapping(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof PickupItemStatistic statistic) { + String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); + content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); } } break; @@ -145,9 +150,9 @@ public class StatisticsUtils { builder.title("stat.itemsButton - stat_type.minecraft.dropped"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof DropItemStatistic) { - String item = mappings.getMapping(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof DropItemStatistic statistic) { + String item = mappings.getMapping(statistic.getId()).getJavaIdentifier(); + content.add(getItemTranslateKey(item, language) + ": " + entry.getValue()); } } break; @@ -155,9 +160,9 @@ public class StatisticsUtils { builder.title("stat.mobsButton - geyser.statistics.killed"); for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KillEntityStatistic) { - String entityName = MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); - content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof KillEntityStatistic statistic) { + String entityName = statistic.getEntity().name().toLowerCase(Locale.ROOT); + content.add("entity.minecraft." + entityName + ": " + entry.getValue()); } } break; @@ -166,9 +171,9 @@ public class StatisticsUtils { for (Map.Entry entry : session .getStatistics().entrySet()) { - if (entry.getKey() instanceof KilledByEntityStatistic) { - String entityName = MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); - content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + if (entry.getKey() instanceof KilledByEntityStatistic statistic) { + String entityName = statistic.getEntity().name().toLowerCase(Locale.ROOT); + content.add("entity.minecraft." + entityName + ": " + entry.getValue()); } } break; @@ -176,12 +181,26 @@ public class StatisticsUtils { return; } - if (content.length() == 0) { - content = new StringBuilder("geyser.statistics.none"); + StringBuilder assembledContent = new StringBuilder(); + if (content.size() == 0) { + assembledContent.append("geyser.statistics.none"); + } else { + content.replaceAll(x -> translate(x, language)); + // Sort statistics alphabetically + content.sort(String::compareTo); + for (int i = 0; i < content.size(); i++) { + assembledContent.append(content.get(i)); + // Make every other line gray + if (i % 2 == 0) { + assembledContent.append("\u00a77\n"); + } else { + assembledContent.append("\u00a7r\n"); + } + } } session.sendForm( - builder.content(content.toString()) + builder.content(assembledContent.toString()) .button("gui.back", FormImage.Type.PATH, "textures/gui/newgui/undo") .responseHandler((form1, subFormResponseData) -> { SimpleFormResponse response1 = form.parseResponse(subFormResponseData); @@ -212,7 +231,7 @@ public class StatisticsUtils { private static String translate(String keys, String locale) { Matcher matcher = CONTENT_PATTERN.matcher(keys); - StringBuffer buffer = new StringBuffer(); + StringBuilder buffer = new StringBuilder(); while (matcher.find()) { String group = matcher.group(); matcher.appendReplacement(buffer, translateEntry(group.substring(0, group.length() - 1), locale) + ":");