minecraft-src/net/minecraft/server/PlayerAdvancements.java
2025-07-04 02:00:41 +03:00

363 lines
13 KiB
Java

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<AdvancementHolder, AdvancementProgress> progress = new LinkedHashMap();
private final Set<AdvancementHolder> visible = new HashSet();
private final Set<AdvancementHolder> progressChanged = new HashSet();
private final Set<AdvancementNode> rootsToUpdate = new HashSet();
private ServerPlayer player;
@Nullable
private AdvancementHolder lastSelectedTab;
private boolean isFirstPacket = true;
private final Codec<PlayerAdvancements.Data> 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<ResourceLocation, AdvancementProgress> 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<String, Criterion<?>> 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 <T extends CriterionTriggerInstance> void registerListener(AdvancementHolder advancement, String criterionKey, Criterion<T> criterion) {
criterion.trigger().addPlayerListener(this, new Listener<>(criterion.triggerInstance(), advancement, criterionKey));
}
private void unregisterListeners(AdvancementHolder advancement) {
AdvancementProgress advancementProgress = this.getOrStartProgress(advancement);
for (Entry<String, Criterion<?>> 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 <T extends CriterionTriggerInstance> void removeListener(AdvancementHolder advancement, String criterionKey, Criterion<T> criterion) {
criterion.trigger().removePlayerListener(this, new Listener<>(criterion.triggerInstance(), advancement, criterionKey));
}
public void flushDirty(ServerPlayer serverPlayer) {
if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) {
Map<ResourceLocation, AdvancementProgress> map = new HashMap();
Set<AdvancementHolder> set = new HashSet();
Set<ResourceLocation> 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()) {
serverPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, set, set2, map));
}
}
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<AdvancementHolder> advancementOutput, Set<ResourceLocation> 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<ResourceLocation, AdvancementProgress> map) {
public static final Codec<PlayerAdvancements.Data> CODEC = Codec.unboundedMap(ResourceLocation.CODEC, AdvancementProgress.CODEC)
.xmap(PlayerAdvancements.Data::new, PlayerAdvancements.Data::map);
public void forEach(BiConsumer<ResourceLocation, AdvancementProgress> action) {
this.map
.entrySet()
.stream()
.sorted(Entry.comparingByValue())
.forEach(entry -> action.accept((ResourceLocation)entry.getKey(), (AdvancementProgress)entry.getValue()));
}
}
}