minecraft-src/net/minecraft/stats/ServerStatsCounter.java
2025-07-04 03:45:38 +03:00

157 lines
5.4 KiB
Java

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<Map<Stat<?>, Integer>> STATS_CODEC = Codec.dispatchedMap(
BuiltInRegistries.STAT_TYPE.byNameCodec(), Util.memoize(ServerStatsCounter::createTypedStatsCodec)
)
.xmap(map -> {
Map<Stat<?>, 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<Stat<?>> dirty = Sets.<Stat<?>>newHashSet();
private static <T> Codec<Map<Stat<?>, Integer>> createTypedStatsCodec(StatType<T> type) {
Codec<T> codec = type.getRegistry().byNameCodec();
Codec<Stat<?>> 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<Stat<?>> getDirty() {
Set<Stat<?>> set = Sets.<Stat<?>>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<JsonElement> 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<Stat<?>> object2IntMap = new Object2IntOpenHashMap<>();
for (Stat<?> stat : this.getDirty()) {
object2IntMap.put(stat, this.getValue(stat));
}
player.connection.send(new ClientboundAwardStatsPacket(object2IntMap));
}
}