327 lines
14 KiB
Java
327 lines
14 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.google.gson.Gson
|
|
* com.google.gson.GsonBuilder
|
|
* com.google.gson.JsonElement
|
|
* com.google.gson.JsonIOException
|
|
* com.google.gson.JsonParseException
|
|
* com.mojang.datafixers.DataFixer
|
|
* com.mojang.logging.LogUtils
|
|
* com.mojang.serialization.Codec
|
|
* com.mojang.serialization.DynamicOps
|
|
* com.mojang.serialization.JsonOps
|
|
* org.jspecify.annotations.Nullable
|
|
* org.slf4j.Logger
|
|
*/
|
|
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.mojang.datafixers.DataFixer;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.serialization.Codec;
|
|
import com.mojang.serialization.DynamicOps;
|
|
import com.mojang.serialization.JsonOps;
|
|
import java.io.BufferedReader;
|
|
import java.io.BufferedWriter;
|
|
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.OpenOption;
|
|
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.function.BiConsumer;
|
|
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.core.registries.BuiltInRegistries;
|
|
import net.minecraft.network.protocol.game.ClientboundSelectAdvancementsTabPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;
|
|
import net.minecraft.resources.Identifier;
|
|
import net.minecraft.server.ServerAdvancementManager;
|
|
import net.minecraft.server.advancements.AdvancementVisibilityEvaluator;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.server.players.PlayerList;
|
|
import net.minecraft.util.FileUtil;
|
|
import net.minecraft.util.StrictJsonParser;
|
|
import net.minecraft.util.datafix.DataFixTypes;
|
|
import net.minecraft.world.level.gamerules.GameRules;
|
|
import org.jspecify.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<AdvancementHolder, AdvancementProgress>();
|
|
private final Set<AdvancementHolder> visible = new HashSet<AdvancementHolder>();
|
|
private final Set<AdvancementHolder> progressChanged = new HashSet<AdvancementHolder>();
|
|
private final Set<AdvancementNode> rootsToUpdate = new HashSet<AdvancementNode>();
|
|
private ServerPlayer player;
|
|
private @Nullable AdvancementHolder lastSelectedTab;
|
|
private boolean isFirstPacket = true;
|
|
private final Codec<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 defaultVersion = 1343;
|
|
this.codec = DataFixTypes.ADVANCEMENTS.wrapCodec(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 advancement : manager.getAllAdvancements()) {
|
|
this.registerListeners(advancement);
|
|
}
|
|
}
|
|
|
|
private void checkForAutomaticTriggers(ServerAdvancementManager manager) {
|
|
for (AdvancementHolder holder : manager.getAllAdvancements()) {
|
|
Advancement advancement = holder.value();
|
|
if (!advancement.criteria().isEmpty()) continue;
|
|
this.award(holder, "");
|
|
advancement.rewards().grant(this.player);
|
|
}
|
|
}
|
|
|
|
private void load(ServerAdvancementManager manager) {
|
|
if (Files.isRegularFile(this.playerSavePath, new LinkOption[0])) {
|
|
try (BufferedReader reader = Files.newBufferedReader(this.playerSavePath, StandardCharsets.UTF_8);){
|
|
JsonElement json = StrictJsonParser.parse(reader);
|
|
Data data = (Data)this.codec.parse((DynamicOps)JsonOps.INSTANCE, (Object)json).getOrThrow(JsonParseException::new);
|
|
this.applyFrom(manager, data);
|
|
}
|
|
catch (JsonIOException | IOException e) {
|
|
LOGGER.error("Couldn't access player advancements in {}", (Object)this.playerSavePath, (Object)e);
|
|
}
|
|
catch (JsonParseException e) {
|
|
LOGGER.error("Couldn't parse player advancements in {}", (Object)this.playerSavePath, (Object)e);
|
|
}
|
|
}
|
|
this.checkForAutomaticTriggers(manager);
|
|
this.registerListeners(manager);
|
|
}
|
|
|
|
public void save() {
|
|
JsonElement json = (JsonElement)this.codec.encodeStart((DynamicOps)JsonOps.INSTANCE, (Object)this.asData()).getOrThrow();
|
|
try {
|
|
FileUtil.createDirectoriesSafe(this.playerSavePath.getParent());
|
|
try (BufferedWriter outputWriter = Files.newBufferedWriter(this.playerSavePath, StandardCharsets.UTF_8, new OpenOption[0]);){
|
|
GSON.toJson(json, GSON.newJsonWriter((Writer)outputWriter));
|
|
}
|
|
}
|
|
catch (JsonIOException | IOException e) {
|
|
LOGGER.error("Couldn't save player advancements to {}", (Object)this.playerSavePath, (Object)e);
|
|
}
|
|
}
|
|
|
|
private void applyFrom(ServerAdvancementManager manager, Data data) {
|
|
data.forEach((id, progress) -> {
|
|
AdvancementHolder advancement = manager.get((Identifier)id);
|
|
if (advancement == null) {
|
|
LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", id, (Object)this.playerSavePath);
|
|
return;
|
|
}
|
|
this.startProgress(advancement, (AdvancementProgress)progress);
|
|
this.progressChanged.add(advancement);
|
|
this.markForVisibilityUpdate(advancement);
|
|
});
|
|
}
|
|
|
|
private Data asData() {
|
|
LinkedHashMap<Identifier, AdvancementProgress> map = new LinkedHashMap<Identifier, AdvancementProgress>();
|
|
this.progress.forEach((advancement, progress) -> {
|
|
if (progress.hasProgress()) {
|
|
map.put(advancement.id(), (AdvancementProgress)progress);
|
|
}
|
|
});
|
|
return new Data(map);
|
|
}
|
|
|
|
public boolean award(AdvancementHolder holder, String criterion) {
|
|
boolean result = false;
|
|
AdvancementProgress progress = this.getOrStartProgress(holder);
|
|
boolean wasDone = progress.isDone();
|
|
if (progress.grantProgress(criterion)) {
|
|
this.unregisterListeners(holder);
|
|
this.progressChanged.add(holder);
|
|
result = true;
|
|
if (!wasDone && progress.isDone()) {
|
|
holder.value().rewards().grant(this.player);
|
|
holder.value().display().ifPresent(display -> {
|
|
if (display.shouldAnnounceChat() && this.player.level().getGameRules().get(GameRules.SHOW_ADVANCEMENT_MESSAGES).booleanValue()) {
|
|
this.playerList.broadcastSystemMessage(display.getType().createAnnouncement(holder, this.player), false);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (!wasDone && progress.isDone()) {
|
|
this.markForVisibilityUpdate(holder);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public boolean revoke(AdvancementHolder advancement, String criterion) {
|
|
boolean result = false;
|
|
AdvancementProgress progress = this.getOrStartProgress(advancement);
|
|
boolean wasDone = progress.isDone();
|
|
if (progress.revokeProgress(criterion)) {
|
|
this.registerListeners(advancement);
|
|
this.progressChanged.add(advancement);
|
|
result = true;
|
|
}
|
|
if (wasDone && !progress.isDone()) {
|
|
this.markForVisibilityUpdate(advancement);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void markForVisibilityUpdate(AdvancementHolder advancement) {
|
|
AdvancementNode node = this.tree.get(advancement);
|
|
if (node != null) {
|
|
this.rootsToUpdate.add(node.root());
|
|
}
|
|
}
|
|
|
|
private void registerListeners(AdvancementHolder holder) {
|
|
AdvancementProgress advancementProgress = this.getOrStartProgress(holder);
|
|
if (advancementProgress.isDone()) {
|
|
return;
|
|
}
|
|
for (Map.Entry<String, Criterion<?>> entry : holder.value().criteria().entrySet()) {
|
|
CriterionProgress criterionProgress = advancementProgress.getCriterion(entry.getKey());
|
|
if (criterionProgress == null || criterionProgress.isDone()) continue;
|
|
this.registerListener(holder, entry.getKey(), entry.getValue());
|
|
}
|
|
}
|
|
|
|
private <T extends CriterionTriggerInstance> void registerListener(AdvancementHolder holder, String key, Criterion<T> criterion) {
|
|
criterion.trigger().addPlayerListener(this, new CriterionTrigger.Listener<T>(criterion.triggerInstance(), holder, key));
|
|
}
|
|
|
|
private void unregisterListeners(AdvancementHolder holder) {
|
|
AdvancementProgress advancementProgress = this.getOrStartProgress(holder);
|
|
for (Map.Entry<String, Criterion<?>> entry : holder.value().criteria().entrySet()) {
|
|
CriterionProgress criterionProgress = advancementProgress.getCriterion(entry.getKey());
|
|
if (criterionProgress == null || !criterionProgress.isDone() && !advancementProgress.isDone()) continue;
|
|
this.removeListener(holder, entry.getKey(), entry.getValue());
|
|
}
|
|
}
|
|
|
|
private <T extends CriterionTriggerInstance> void removeListener(AdvancementHolder holder, String key, Criterion<T> criterion) {
|
|
criterion.trigger().removePlayerListener(this, new CriterionTrigger.Listener<T>(criterion.triggerInstance(), holder, key));
|
|
}
|
|
|
|
public void flushDirty(ServerPlayer player, boolean showAdvancements) {
|
|
if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) {
|
|
HashMap<Identifier, AdvancementProgress> progress = new HashMap<Identifier, AdvancementProgress>();
|
|
HashSet<AdvancementHolder> added = new HashSet<AdvancementHolder>();
|
|
HashSet<Identifier> removed = new HashSet<Identifier>();
|
|
for (AdvancementNode root : this.rootsToUpdate) {
|
|
this.updateTreeVisibility(root, added, removed);
|
|
}
|
|
this.rootsToUpdate.clear();
|
|
for (AdvancementHolder holder : this.progressChanged) {
|
|
if (!this.visible.contains(holder)) continue;
|
|
progress.put(holder.id(), this.progress.get(holder));
|
|
}
|
|
this.progressChanged.clear();
|
|
if (!(progress.isEmpty() && added.isEmpty() && removed.isEmpty())) {
|
|
player.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, added, removed, progress, showAdvancements));
|
|
}
|
|
}
|
|
this.isFirstPacket = false;
|
|
}
|
|
|
|
public void setSelectedTab(@Nullable AdvancementHolder holder) {
|
|
AdvancementHolder old = this.lastSelectedTab;
|
|
this.lastSelectedTab = holder != null && holder.value().isRoot() && holder.value().display().isPresent() ? holder : null;
|
|
if (old != this.lastSelectedTab) {
|
|
this.player.connection.send(new ClientboundSelectAdvancementsTabPacket(this.lastSelectedTab == null ? null : this.lastSelectedTab.id()));
|
|
}
|
|
}
|
|
|
|
public AdvancementProgress getOrStartProgress(AdvancementHolder advancement) {
|
|
AdvancementProgress progress = this.progress.get(advancement);
|
|
if (progress == null) {
|
|
progress = new AdvancementProgress();
|
|
this.startProgress(advancement, progress);
|
|
}
|
|
return progress;
|
|
}
|
|
|
|
private void startProgress(AdvancementHolder holder, AdvancementProgress progress) {
|
|
progress.update(holder.value().requirements());
|
|
this.progress.put(holder, progress);
|
|
}
|
|
|
|
private void updateTreeVisibility(AdvancementNode root, Set<AdvancementHolder> added, Set<Identifier> removed) {
|
|
AdvancementVisibilityEvaluator.evaluateVisibility(root, node -> this.getOrStartProgress(node.holder()).isDone(), (node, shouldBeVisible) -> {
|
|
AdvancementHolder advancement = node.holder();
|
|
if (shouldBeVisible) {
|
|
if (this.visible.add(advancement)) {
|
|
added.add(advancement);
|
|
if (this.progress.containsKey(advancement)) {
|
|
this.progressChanged.add(advancement);
|
|
}
|
|
}
|
|
} else if (this.visible.remove(advancement)) {
|
|
removed.add(advancement.id());
|
|
}
|
|
});
|
|
}
|
|
|
|
private record Data(Map<Identifier, AdvancementProgress> map) {
|
|
public static final Codec<Data> CODEC = Codec.unboundedMap(Identifier.CODEC, AdvancementProgress.CODEC).xmap(Data::new, Data::map);
|
|
|
|
public void forEach(BiConsumer<Identifier, AdvancementProgress> consumer) {
|
|
this.map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach((? super T entry) -> consumer.accept((Identifier)entry.getKey(), (AdvancementProgress)entry.getValue()));
|
|
}
|
|
}
|
|
}
|
|
|