package net.minecraft.stats; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; import com.google.gson.internal.Streams; import com.google.gson.stream.JsonReader; import com.mojang.datafixers.DataFixer; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.util.Iterator; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.Map.Entry; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.protocol.game.ClientboundAwardStatsPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.world.entity.player.Player; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; /** * Server-side implementation of {@link net.minecraft.stats.StatsCounter}; handles counting, serialising, and de-serialising statistics, as well as sending them to connected clients via the {@linkplain net.minecraft.network.protocol.game.ClientboundAwardStatsPacket award stats packet}. */ public class ServerStatsCounter extends StatsCounter { private static final Logger LOGGER = LogUtils.getLogger(); private final MinecraftServer server; private final File file; private final Set> dirty = Sets.>newHashSet(); public ServerStatsCounter(MinecraftServer server, File file) { this.server = server; this.file = file; if (file.isFile()) { try { this.parseLocal(server.getFixerUpper(), FileUtils.readFileToString(file)); } catch (IOException var4) { LOGGER.error("Couldn't read statistics file {}", file, var4); } catch (JsonParseException var5) { LOGGER.error("Couldn't parse statistics file {}", file, var5); } } } public void save() { try { FileUtils.writeStringToFile(this.file, this.toJson()); } catch (IOException var2) { LOGGER.error("Couldn't save stats", (Throwable)var2); } } @Override public void setValue(Player player, Stat stat, int value) { super.setValue(player, stat, value); this.dirty.add(stat); } private Set> getDirty() { Set> set = Sets.>newHashSet(this.dirty); this.dirty.clear(); return set; } public void parseLocal(DataFixer fixerUpper, String json) { try { JsonReader jsonReader = new JsonReader(new StringReader(json)); label47: { try { jsonReader.setLenient(false); JsonElement jsonElement = Streams.parse(jsonReader); if (!jsonElement.isJsonNull()) { CompoundTag compoundTag = fromJson(jsonElement.getAsJsonObject()); compoundTag = DataFixTypes.STATS.updateToCurrentVersion(fixerUpper, compoundTag, NbtUtils.getDataVersion(compoundTag, 1343)); if (!compoundTag.contains("stats", 10)) { break label47; } CompoundTag compoundTag2 = compoundTag.getCompound("stats"); Iterator var7 = compoundTag2.getAllKeys().iterator(); while (true) { if (!var7.hasNext()) { break label47; } String string = (String)var7.next(); if (compoundTag2.contains(string, 10)) { Util.ifElse( BuiltInRegistries.STAT_TYPE.getOptional(ResourceLocation.parse(string)), statType -> { CompoundTag compoundTag2x = compoundTag2.getCompound(string); for (String string2 : compoundTag2x.getAllKeys()) { if (compoundTag2x.contains(string2, 99)) { Util.ifElse( this.getStat(statType, string2), stat -> this.stats.put(stat, compoundTag2x.getInt(string2)), () -> LOGGER.warn("Invalid statistic in {}: Don't know what {} is", this.file, string2) ); } else { LOGGER.warn("Invalid statistic value in {}: Don't know what {} is for key {}", this.file, compoundTag2x.get(string2), string2); } } }, () -> LOGGER.warn("Invalid statistic type in {}: Don't know what {} is", this.file, string) ); } } } LOGGER.error("Unable to parse Stat data from {}", this.file); } catch (Throwable var10) { try { jsonReader.close(); } catch (Throwable var9) { var10.addSuppressed(var9); } throw var10; } jsonReader.close(); return; } jsonReader.close(); } catch (IOException | JsonParseException var11) { LOGGER.error("Unable to parse Stat data from {}", this.file, var11); } } private Optional> getStat(StatType type, String location) { return Optional.ofNullable(ResourceLocation.tryParse(location)).flatMap(type.getRegistry()::getOptional).map(type::get); } private static CompoundTag fromJson(JsonObject json) { CompoundTag compoundTag = new CompoundTag(); for (Entry entry : json.entrySet()) { JsonElement jsonElement = (JsonElement)entry.getValue(); if (jsonElement.isJsonObject()) { compoundTag.put((String)entry.getKey(), fromJson(jsonElement.getAsJsonObject())); } else if (jsonElement.isJsonPrimitive()) { JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive(); if (jsonPrimitive.isNumber()) { compoundTag.putInt((String)entry.getKey(), jsonPrimitive.getAsInt()); } } } return compoundTag; } protected String toJson() { Map, JsonObject> map = Maps., JsonObject>newHashMap(); for (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry> entry : this.stats.object2IntEntrySet()) { Stat stat = (Stat)entry.getKey(); ((JsonObject)map.computeIfAbsent(stat.getType(), statType -> new JsonObject())).addProperty(getKey(stat).toString(), entry.getIntValue()); } JsonObject jsonObject = new JsonObject(); for (Entry, JsonObject> entry2 : map.entrySet()) { jsonObject.add(BuiltInRegistries.STAT_TYPE.getKey((StatType)entry2.getKey()).toString(), (JsonElement)entry2.getValue()); } JsonObject jsonObject2 = new JsonObject(); jsonObject2.add("stats", jsonObject); jsonObject2.addProperty("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion()); return jsonObject2.toString(); } private static ResourceLocation getKey(Stat stat) { return stat.getType().getRegistry().getKey(stat.getValue()); } public void markAllDirty() { this.dirty.addAll(this.stats.keySet()); } public void sendStats(ServerPlayer player) { Object2IntMap> object2IntMap = new Object2IntOpenHashMap<>(); for (Stat stat : this.getDirty()) { object2IntMap.put(stat, this.getValue(stat)); } player.connection.send(new ClientboundAwardStatsPacket(object2IntMap)); } }