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.Map.Entry; 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; 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(p_390196_ -> { Map, Integer> map = new HashMap<>(); p_390196_.forEach((p_390199_, p_390200_) -> map.putAll((Map, ? extends Integer>)p_390200_)); return map; }, p_390195_ -> p_390195_.entrySet().stream().collect(Collectors.groupingBy(p_390201_ -> p_390201_.getKey().getType(), Util.toMap()))); private final MinecraftServer server; private final File file; private final Set> dirty = Sets.newHashSet(); private static Codec, Integer>> createTypedStatsCodec(StatType p_395191_) { Codec codec = p_395191_.getRegistry().byNameCodec(); Codec> codec1 = codec.flatComapMap( p_395191_::get, p_390205_ -> p_390205_.getType() == p_395191_ ? DataResult.success((T)p_390205_.getValue()) : DataResult.error(() -> "Expected type " + p_395191_ + ", but got " + p_390205_.getType()) ); return Codec.unboundedMap(codec1, Codec.INT); } public ServerStatsCounter(MinecraftServer p_12816_, File p_12817_) { this.server = p_12816_; this.file = p_12817_; if (p_12817_.isFile()) { try { this.parseLocal(p_12816_.getFixerUpper(), FileUtils.readFileToString(p_12817_)); } catch (IOException ioexception) { LOGGER.error("Couldn't read statistics file {}", p_12817_, ioexception); } catch (JsonParseException jsonparseexception) { LOGGER.error("Couldn't parse statistics file {}", p_12817_, jsonparseexception); } } } public void save() { try { FileUtils.writeStringToFile(this.file, this.toJson()); } catch (IOException ioexception) { LOGGER.error("Couldn't save stats", (Throwable)ioexception); } } @Override public void setValue(Player p_12827_, Stat p_12828_, int p_12829_) { super.setValue(p_12827_, p_12828_, p_12829_); this.dirty.add(p_12828_); } private Set> getDirty() { Set> set = Sets.newHashSet(this.dirty); this.dirty.clear(); return set; } public void parseLocal(DataFixer p_12833_, String p_12834_) { try { try (JsonReader jsonreader = new JsonReader(new StringReader(p_12834_))) { jsonreader.setLenient(false); JsonElement jsonelement = Streams.parse(jsonreader); if (!jsonelement.isJsonNull()) { Dynamic dynamic = new Dynamic<>(JsonOps.INSTANCE, jsonelement); dynamic = DataFixTypes.STATS.updateToCurrentVersion(p_12833_, dynamic, NbtUtils.getDataVersion(dynamic, 1343)); this.stats .putAll( STATS_CODEC.parse(dynamic.get("stats").orElseEmptyMap()) .resultOrPartial(p_390197_ -> LOGGER.error("Failed to parse statistics for {}: {}", this.file, p_390197_)) .orElse(Map.of()) ); return; } LOGGER.error("Unable to parse Stat data from {}", this.file); } } catch (IOException | JsonParseException jsonparseexception) { LOGGER.error("Unable to parse Stat data from {}", this.file, jsonparseexception); } } 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 p_12820_) { Object2IntMap> object2intmap = new Object2IntOpenHashMap<>(); for (Stat stat : this.getDirty()) { object2intmap.put(stat, this.getValue(stat)); } p_12820_.connection.send(new ClientboundAwardStatsPacket(object2intmap)); } }