package net.minecraft.stats; 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.internal.Streams; import com.google.gson.stream.JsonReader; import com.mojang.datafixers.DataFixer; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.Dynamic; import com.mojang.serialization.JsonOps; 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.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.protocol.game.ClientboundAwardStatsPacket; 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 static final Codec, Integer>> STATS_CODEC = Codec.dispatchedMap( BuiltInRegistries.STAT_TYPE.byNameCodec(), Util.memoize(ServerStatsCounter::createTypedStatsCodec) ) .xmap(map -> { Map, Integer> map2 = new HashMap(); map.forEach((statType, map2x) -> map2.putAll(map2x)); return map2; }, map -> (Map)map.entrySet().stream().collect(Collectors.groupingBy(entry -> ((Stat)entry.getKey()).getType(), Util.toMap()))); private final MinecraftServer server; private final File file; private final Set> dirty = Sets.>newHashSet(); private static Codec, Integer>> createTypedStatsCodec(StatType type) { Codec codec = type.getRegistry().byNameCodec(); Codec> codec2 = codec.flatComapMap( type::get, stat -> stat.getType() == type ? DataResult.success(stat.getValue()) : DataResult.error(() -> "Expected type " + type + ", but got " + stat.getType()) ); return Codec.unboundedMap(codec2, Codec.INT); } 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)); label35: { try { jsonReader.setLenient(false); JsonElement jsonElement = Streams.parse(jsonReader); if (!jsonElement.isJsonNull()) { Dynamic dynamic = new Dynamic<>(JsonOps.INSTANCE, jsonElement); dynamic = DataFixTypes.STATS.updateToCurrentVersion(fixerUpper, dynamic, NbtUtils.getDataVersion(dynamic, 1343)); this.stats .putAll( (Map)STATS_CODEC.parse(dynamic.get("stats").orElseEmptyMap()) .resultOrPartial(string -> LOGGER.error("Failed to parse statistics for {}: {}", this.file, string)) .orElse(Map.of()) ); break label35; } LOGGER.error("Unable to parse Stat data from {}", this.file); } catch (Throwable var7) { try { jsonReader.close(); } catch (Throwable var6) { var7.addSuppressed(var6); } throw var7; } jsonReader.close(); return; } jsonReader.close(); } catch (IOException | JsonParseException var8) { LOGGER.error("Unable to parse Stat data from {}", this.file, var8); } } protected String toJson() { JsonObject jsonObject = new JsonObject(); jsonObject.add("stats", STATS_CODEC.encodeStart(JsonOps.INSTANCE, this.stats).getOrThrow()); jsonObject.addProperty("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion()); return jsonObject.toString(); } 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)); } }