package net.minecraft.server; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonIOException; 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.JsonOps; import java.io.IOException; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.function.BiConsumer; import net.minecraft.FileUtil; import net.minecraft.advancements.Advancement; import net.minecraft.advancements.AdvancementHolder; import net.minecraft.advancements.AdvancementNode; import net.minecraft.advancements.AdvancementProgress; import net.minecraft.advancements.AdvancementTree; import net.minecraft.advancements.Criterion; import net.minecraft.advancements.CriterionProgress; import net.minecraft.advancements.CriterionTrigger; import net.minecraft.advancements.CriterionTriggerInstance; import net.minecraft.advancements.CriterionTrigger.Listener; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.protocol.game.ClientboundSelectAdvancementsTabPacket; import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.advancements.AdvancementVisibilityEvaluator; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.PlayerList; import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.world.level.GameRules; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class PlayerAdvancements { private static final Logger LOGGER = LogUtils.getLogger(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private final PlayerList playerList; private final Path playerSavePath; private AdvancementTree tree; private final Map progress = new LinkedHashMap(); private final Set visible = new HashSet(); private final Set progressChanged = new HashSet(); private final Set rootsToUpdate = new HashSet(); private ServerPlayer player; @Nullable private AdvancementHolder lastSelectedTab; private boolean isFirstPacket = true; private final Codec codec; public PlayerAdvancements(DataFixer dataFixer, PlayerList playerList, ServerAdvancementManager manager, Path playerSavePath, ServerPlayer player) { this.playerList = playerList; this.playerSavePath = playerSavePath; this.player = player; this.tree = manager.tree(); int i = 1343; this.codec = DataFixTypes.ADVANCEMENTS.wrapCodec(PlayerAdvancements.Data.CODEC, dataFixer, 1343); this.load(manager); } public void setPlayer(ServerPlayer player) { this.player = player; } public void stopListening() { for (CriterionTrigger criterionTrigger : BuiltInRegistries.TRIGGER_TYPES) { criterionTrigger.removePlayerListeners(this); } } public void reload(ServerAdvancementManager manager) { this.stopListening(); this.progress.clear(); this.visible.clear(); this.rootsToUpdate.clear(); this.progressChanged.clear(); this.isFirstPacket = true; this.lastSelectedTab = null; this.tree = manager.tree(); this.load(manager); } private void registerListeners(ServerAdvancementManager manager) { for (AdvancementHolder advancementHolder : manager.getAllAdvancements()) { this.registerListeners(advancementHolder); } } private void checkForAutomaticTriggers(ServerAdvancementManager manager) { for (AdvancementHolder advancementHolder : manager.getAllAdvancements()) { Advancement advancement = advancementHolder.value(); if (advancement.criteria().isEmpty()) { this.award(advancementHolder, ""); advancement.rewards().grant(this.player); } } } private void load(ServerAdvancementManager manager) { if (Files.isRegularFile(this.playerSavePath, new LinkOption[0])) { try { JsonReader jsonReader = new JsonReader(Files.newBufferedReader(this.playerSavePath, StandardCharsets.UTF_8)); try { jsonReader.setLenient(false); JsonElement jsonElement = Streams.parse(jsonReader); PlayerAdvancements.Data data = this.codec.parse(JsonOps.INSTANCE, jsonElement).getOrThrow(JsonParseException::new); this.applyFrom(manager, data); } catch (Throwable var6) { try { jsonReader.close(); } catch (Throwable var5) { var6.addSuppressed(var5); } throw var6; } jsonReader.close(); } catch (JsonIOException | IOException var7) { LOGGER.error("Couldn't access player advancements in {}", this.playerSavePath, var7); } catch (JsonParseException var8) { LOGGER.error("Couldn't parse player advancements in {}", this.playerSavePath, var8); } } this.checkForAutomaticTriggers(manager); this.registerListeners(manager); } public void save() { JsonElement jsonElement = this.codec.encodeStart(JsonOps.INSTANCE, this.asData()).getOrThrow(); try { FileUtil.createDirectoriesSafe(this.playerSavePath.getParent()); Writer writer = Files.newBufferedWriter(this.playerSavePath, StandardCharsets.UTF_8); try { GSON.toJson(jsonElement, GSON.newJsonWriter(writer)); } catch (Throwable var6) { if (writer != null) { try { writer.close(); } catch (Throwable var5) { var6.addSuppressed(var5); } } throw var6; } if (writer != null) { writer.close(); } } catch (JsonIOException | IOException var7) { LOGGER.error("Couldn't save player advancements to {}", this.playerSavePath, var7); } } private void applyFrom(ServerAdvancementManager advancementManager, PlayerAdvancements.Data data) { data.forEach((resourceLocation, advancementProgress) -> { AdvancementHolder advancementHolder = advancementManager.get(resourceLocation); if (advancementHolder == null) { LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", resourceLocation, this.playerSavePath); } else { this.startProgress(advancementHolder, advancementProgress); this.progressChanged.add(advancementHolder); this.markForVisibilityUpdate(advancementHolder); } }); } private PlayerAdvancements.Data asData() { Map map = new LinkedHashMap(); this.progress.forEach((advancementHolder, advancementProgress) -> { if (advancementProgress.hasProgress()) { map.put(advancementHolder.id(), advancementProgress); } }); return new PlayerAdvancements.Data(map); } public boolean award(AdvancementHolder advancement, String criterionKey) { boolean bl = false; AdvancementProgress advancementProgress = this.getOrStartProgress(advancement); boolean bl2 = advancementProgress.isDone(); if (advancementProgress.grantProgress(criterionKey)) { this.unregisterListeners(advancement); this.progressChanged.add(advancement); bl = true; if (!bl2 && advancementProgress.isDone()) { advancement.value().rewards().grant(this.player); advancement.value().display().ifPresent(displayInfo -> { if (displayInfo.shouldAnnounceChat() && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { this.playerList.broadcastSystemMessage(displayInfo.getType().createAnnouncement(advancement, this.player), false); } }); } } if (!bl2 && advancementProgress.isDone()) { this.markForVisibilityUpdate(advancement); } return bl; } public boolean revoke(AdvancementHolder advancement, String criterionKey) { boolean bl = false; AdvancementProgress advancementProgress = this.getOrStartProgress(advancement); boolean bl2 = advancementProgress.isDone(); if (advancementProgress.revokeProgress(criterionKey)) { this.registerListeners(advancement); this.progressChanged.add(advancement); bl = true; } if (bl2 && !advancementProgress.isDone()) { this.markForVisibilityUpdate(advancement); } return bl; } private void markForVisibilityUpdate(AdvancementHolder advancement) { AdvancementNode advancementNode = this.tree.get(advancement); if (advancementNode != null) { this.rootsToUpdate.add(advancementNode.root()); } } private void registerListeners(AdvancementHolder advancement) { AdvancementProgress advancementProgress = this.getOrStartProgress(advancement); if (!advancementProgress.isDone()) { for (Entry> entry : advancement.value().criteria().entrySet()) { CriterionProgress criterionProgress = advancementProgress.getCriterion((String)entry.getKey()); if (criterionProgress != null && !criterionProgress.isDone()) { this.registerListener(advancement, (String)entry.getKey(), (Criterion)entry.getValue()); } } } } private void registerListener(AdvancementHolder advancement, String criterionKey, Criterion criterion) { criterion.trigger().addPlayerListener(this, new Listener<>(criterion.triggerInstance(), advancement, criterionKey)); } private void unregisterListeners(AdvancementHolder advancement) { AdvancementProgress advancementProgress = this.getOrStartProgress(advancement); for (Entry> entry : advancement.value().criteria().entrySet()) { CriterionProgress criterionProgress = advancementProgress.getCriterion((String)entry.getKey()); if (criterionProgress != null && (criterionProgress.isDone() || advancementProgress.isDone())) { this.removeListener(advancement, (String)entry.getKey(), (Criterion)entry.getValue()); } } } private void removeListener(AdvancementHolder advancement, String criterionKey, Criterion criterion) { criterion.trigger().removePlayerListener(this, new Listener<>(criterion.triggerInstance(), advancement, criterionKey)); } public void flushDirty(ServerPlayer player, boolean showAdvancements) { if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) { Map map = new HashMap(); Set set = new HashSet(); Set set2 = new HashSet(); for (AdvancementNode advancementNode : this.rootsToUpdate) { this.updateTreeVisibility(advancementNode, set, set2); } this.rootsToUpdate.clear(); for (AdvancementHolder advancementHolder : this.progressChanged) { if (this.visible.contains(advancementHolder)) { map.put(advancementHolder.id(), (AdvancementProgress)this.progress.get(advancementHolder)); } } this.progressChanged.clear(); if (!map.isEmpty() || !set.isEmpty() || !set2.isEmpty()) { player.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, set, set2, map, showAdvancements)); } } this.isFirstPacket = false; } public void setSelectedTab(@Nullable AdvancementHolder advancement) { AdvancementHolder advancementHolder = this.lastSelectedTab; if (advancement != null && advancement.value().isRoot() && advancement.value().display().isPresent()) { this.lastSelectedTab = advancement; } else { this.lastSelectedTab = null; } if (advancementHolder != this.lastSelectedTab) { this.player.connection.send(new ClientboundSelectAdvancementsTabPacket(this.lastSelectedTab == null ? null : this.lastSelectedTab.id())); } } public AdvancementProgress getOrStartProgress(AdvancementHolder advancement) { AdvancementProgress advancementProgress = (AdvancementProgress)this.progress.get(advancement); if (advancementProgress == null) { advancementProgress = new AdvancementProgress(); this.startProgress(advancement, advancementProgress); } return advancementProgress; } private void startProgress(AdvancementHolder advancement, AdvancementProgress advancementProgress) { advancementProgress.update(advancement.value().requirements()); this.progress.put(advancement, advancementProgress); } private void updateTreeVisibility(AdvancementNode root, Set advancementOutput, Set idOutput) { AdvancementVisibilityEvaluator.evaluateVisibility( root, advancementNode -> this.getOrStartProgress(advancementNode.holder()).isDone(), (advancementNode, bl) -> { AdvancementHolder advancementHolder = advancementNode.holder(); if (bl) { if (this.visible.add(advancementHolder)) { advancementOutput.add(advancementHolder); if (this.progress.containsKey(advancementHolder)) { this.progressChanged.add(advancementHolder); } } } else if (this.visible.remove(advancementHolder)) { idOutput.add(advancementHolder.id()); } } ); } record Data(Map map) { public static final Codec CODEC = Codec.unboundedMap(ResourceLocation.CODEC, AdvancementProgress.CODEC) .xmap(PlayerAdvancements.Data::new, PlayerAdvancements.Data::map); public void forEach(BiConsumer action) { this.map .entrySet() .stream() .sorted(Entry.comparingByValue()) .forEach(entry -> action.accept((ResourceLocation)entry.getKey(), (AdvancementProgress)entry.getValue())); } } }