diff --git a/gradle.properties b/gradle.properties
index da10c9912..6cb218115 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@ version = 1.20.4-R0.1-SNAPSHOT
mcVersion = 1.20.4
-paperCommit = 7ac24a18940da12beb39a030113f6e459f348e2f
+paperCommit = ba31f4128208e954d6648001724887352f71ece9
org.gradle.caching = true
org.gradle.parallel = true
diff --git a/patches/0004-Add-more-Player-API.patch b/patches/0004-Add-more-Player-API.patch
index 3c576ded2..cc571bc61 100644
--- a/patches/0004-Add-more-Player-API.patch
+++ b/patches/0004-Add-more-Player-API.patch
@@ -77,8 +77,90 @@ index 7411f58f9f36beaadcc47c2264a4af313956ee03..201fe24d393beb8d0be4c4212aff5643
@NotNull
@Override
public HandlerList getHandlers() {
+diff --git a/src/main/java/moe/caramel/daydream/event/player/AsyncPlayerDataPreLoadEvent.java b/src/main/java/moe/caramel/daydream/event/player/AsyncPlayerDataPreLoadEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..96b35864b50695c27f22005aa5581e5c2bc38735
+--- /dev/null
++++ b/src/main/java/moe/caramel/daydream/event/player/AsyncPlayerDataPreLoadEvent.java
+@@ -0,0 +1,76 @@
++package moe.caramel.daydream.event.player;
++
++import com.destroystokyo.paper.profile.PlayerProfile;
++import com.mojang.authlib.GameProfile;
++import org.bukkit.event.Event;
++import org.bukkit.event.HandlerList;
++import org.jetbrains.annotations.ApiStatus;
++import org.jetbrains.annotations.NotNull;
++import java.net.InetAddress;
++import java.util.UUID;
++
++/**
++ * 비동기 플레이어 데이터 사전 로드 이벤트
++ *
++ * {@link org.bukkit.event.player.AsyncPlayerPreLoginEvent} 호출 이후에
++ * 플레이어의 {@link GameProfile}이 완성되면 호출됩니다.
++ *
++ */
++public abstract class AsyncPlayerDataPreLoadEvent extends Event {
++
++ private static final HandlerList HANDLER_LIST = new HandlerList();
++ public @NotNull HandlerList getHandlers() { return HANDLER_LIST; }
++ public static @NotNull HandlerList getHandlerList() { return HANDLER_LIST; }
++
++ @ApiStatus.Internal
++ public AsyncPlayerDataPreLoadEvent() {
++ super(true);
++ }
++
++ /**
++ * 플레이어의 프로필을 가져옵니다.
++ *
++ * @return 플레이어의 프로필
++ */
++ @NotNull
++ public abstract GameProfile getGameProfile();
++
++ /**
++ * 플레이어의 프로필을 가져옵니다.
++ *
++ * @deprecated 메서드를 호출할 때마다 새로운 프로필 미러가 생성됩니다.
++ * {@link #getGameProfile()}를 사용하세요.
++ * @return 플레이어의 프로필
++ */
++ @NotNull
++ @Deprecated
++ public abstract PlayerProfile getProfile();
++
++ /**
++ * 플레이어의 이름을 가져옵니다.
++ *
++ * @return 플레이어의 이름
++ */
++ @NotNull
++ public final String getName() {
++ return getGameProfile().getName();
++ }
++
++ /**
++ * 플레이어의 UUID를 가져옵니다.
++ *
++ * @return 플레이어의 UUID
++ */
++ @NotNull
++ public final UUID getUniqueId() {
++ return getGameProfile().getId();
++ }
++
++ /**
++ * 플레이어의 IP 주소를 가져옵니다.
++ *
++ * @return 플레이어의 IP 주소
++ */
++ @NotNull
++ public abstract InetAddress getAddress();
++}
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index c6cb4f17469a8f2e60dd3e28d41402851ce5fb21..e95cb88e22b607e193bade4eadf635b4704d14e0 100644
+index d048ae07cc33fd77d128cc1ebf88b0804969fa3c..ecdef5d7376668f15cd5ef778cc86ba860748f6a 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -1939,6 +1939,22 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
diff --git a/patches/0012-Add-more-Advancement-API.patch b/patches/0012-Add-more-Advancement-API.patch
index 64b7ee95b..ee33743ae 100644
--- a/patches/0012-Add-more-Advancement-API.patch
+++ b/patches/0012-Add-more-Advancement-API.patch
@@ -373,6 +373,79 @@ index 0000000000000000000000000000000000000000..98cb796221c7bef250861a2f89e9ccf9
+ @NotNull
+ AdvancementBuilder enableCount(final int count, final @NotNull JsonElement criterion);
+}
+diff --git a/src/main/java/moe/caramel/daydream/advancement/AdvancementFormat.java b/src/main/java/moe/caramel/daydream/advancement/AdvancementFormat.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d89f3ad6fcc5365a3234b26b6765352993e437f3
+--- /dev/null
++++ b/src/main/java/moe/caramel/daydream/advancement/AdvancementFormat.java
+@@ -0,0 +1,67 @@
++package moe.caramel.daydream.advancement;
++
++import com.google.gson.JsonElement;
++import org.jetbrains.annotations.NotNull;
++import java.util.Objects;
++
++/**
++ * 발전 과제 데이터 저장 포맷
++ *
++ * @param 타입
++ */
++public final class AdvancementFormat {
++
++ /**
++ * Json (기본)
++ */
++ public static final AdvancementFormat JSON = new AdvancementFormat<>("json", false);
++ /**
++ * 압축된 Json
++ */
++ public static final AdvancementFormat COMPRESSED_JSON = new AdvancementFormat<>("compressed_json", true);
++ /**
++ * NBT
++ */
++ public static final AdvancementFormat NBT = new AdvancementFormat<>("nbt", false);
++ /**
++ * 압축된 NBT
++ */
++ public static final AdvancementFormat COMPRESSED_NBT = new AdvancementFormat<>("compressed_nbt", true);
++ /**
++ * 진행도 맵
++ */
++ public static final AdvancementFormat PROGRESS_MAP = new AdvancementFormat<>("progress_map", false);
++
++ private final String name;
++ private final boolean compressed;
++
++ private AdvancementFormat(final @NotNull String name, final boolean compressed) {
++ this.name = name;
++ this.compressed = compressed;
++ }
++
++ /**
++ * 요구하는 데이터 형식이 압축된 데이터 형식인지 확인합니다.
++ *
++ * @return 만약 {@code true}인 경우 압축된 데이터를 요구합니다
++ */
++ public boolean isCompressed() {
++ return compressed;
++ }
++
++ @Override
++ public boolean equals(final Object o) {
++ if (this == o) {
++ return true;
++ } else if (o instanceof AdvancementFormat> that) {
++ return (compressed == that.compressed) && Objects.equals(name, that.name);
++ } else {
++ return false;
++ }
++ }
++
++ @Override
++ public int hashCode() {
++ return Objects.hash(name, compressed);
++ }
++}
diff --git a/src/main/java/moe/caramel/daydream/advancement/AdvancementReward.java b/src/main/java/moe/caramel/daydream/advancement/AdvancementReward.java
new file mode 100644
index 0000000000000000000000000000000000000000..74347bc8c9a45be1cb9af9fb41641e903f86b95f
@@ -440,168 +513,366 @@ index 0000000000000000000000000000000000000000..74347bc8c9a45be1cb9af9fb41641e90
+ @Nullable
+ NamespacedKey function();
+}
-diff --git a/src/main/java/moe/caramel/daydream/advancement/AdvancementSaveFormat.java b/src/main/java/moe/caramel/daydream/advancement/AdvancementSaveFormat.java
+diff --git a/src/main/java/moe/caramel/daydream/advancement/PlayerAdvancementData.java b/src/main/java/moe/caramel/daydream/advancement/PlayerAdvancementData.java
new file mode 100644
-index 0000000000000000000000000000000000000000..f7924313d2e08cef47561f4470194193292a1be0
+index 0000000000000000000000000000000000000000..a1f319cbebde6a5476e9ac2b282ffc1ef9f4862d
--- /dev/null
-+++ b/src/main/java/moe/caramel/daydream/advancement/AdvancementSaveFormat.java
-@@ -0,0 +1,60 @@
++++ b/src/main/java/moe/caramel/daydream/advancement/PlayerAdvancementData.java
+@@ -0,0 +1,56 @@
+package moe.caramel.daydream.advancement;
+
-+import com.google.gson.JsonElement;
++import moe.caramel.daydream.advancement.progress.CriterionProgress;
++import org.bukkit.NamespacedKey;
+import org.jetbrains.annotations.NotNull;
-+import java.util.Objects;
++import org.jetbrains.annotations.Nullable;
++import java.time.Instant;
++import java.util.Map;
+
+/**
-+ * 발전 과제 저장 포맷
-+ *
-+ * @param 타입
++ * 플레이어 발전 과제 데이터
+ */
-+public final class AdvancementSaveFormat {
++public interface PlayerAdvancementData {
+
+ /**
-+ * Json (기본)
++ * 새로운 진행도 맵을 생성합니다.
++ *
++ * @see #loadFromData(AdvancementFormat, Object)
++ * @return 진행도 맵
+ */
-+ public static final AdvancementSaveFormat JSON = new AdvancementSaveFormat<>("json", false);
++ @NotNull
++ ProgressMap createProgressMap();
++
+ /**
-+ * 압축된 Json
++ * 새로운 기준 진행도를 생성합니다.
++ *
++ * @see ProgressMap#newProgress(NamespacedKey, Map)
++ * @param obtainedTime 기준 달성 시간 ({@code null}인 경우 달성하지 않음)
++ * @return 기준 진행도
+ */
-+ public static final AdvancementSaveFormat COMPRESSED_JSON = new AdvancementSaveFormat<>("compressed_json", true);
++ @NotNull
++ CriterionProgress createCriterionProgress(final @Nullable Instant obtainedTime);
++
+ /**
-+ * NBT
++ * 데이터에서 발전 과제 진행도를 로드합니다.
++ *
++ * @param format 발전 과제 데이터 저장 포맷
++ * @param data 진행도 데이터
++ * @param 데이터의 타입
++ * @throws Exception 일부 데이터는 읽는 도중 예외가 던져질 수 있습니다.
+ */
-+ public static final AdvancementSaveFormat NBT = new AdvancementSaveFormat<>("nbt", false);
++ void loadFromData(final @NotNull AdvancementFormat format, final @NotNull T data) throws Exception;
++
+ /**
-+ * 압축된 NBT
++ * 발전 과제 진행도를 직렬화된 데이터로 변환합니다.
++ *
++ * @see moe.caramel.daydream.event.player.PlayerAdvancementSaveEvent#getData()
++ * @param format 발전 과제 데이터 저장 포맷
++ * @param progress 진행도 데이터
++ * @return 진행도 데이터
++ * @param 데이터의 타입
++ * @throws Exception 일부 데이터는 저장 도중 예외가 던져질 수 있습니다.
+ */
-+ public static final AdvancementSaveFormat COMPRESSED_NBT = new AdvancementSaveFormat<>("compressed_nbt", true);
++ @NotNull
++ T saveToData(final @NotNull AdvancementFormat format, final @NotNull ProgressMap progress) throws Exception;
++}
+diff --git a/src/main/java/moe/caramel/daydream/advancement/ProgressMap.java b/src/main/java/moe/caramel/daydream/advancement/ProgressMap.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..ba50b933cfd0fc98cddb326317cdbc505083f233
+--- /dev/null
++++ b/src/main/java/moe/caramel/daydream/advancement/ProgressMap.java
+@@ -0,0 +1,53 @@
++package moe.caramel.daydream.advancement;
+
-+ private final String name;
-+ private final boolean compressed;
++import moe.caramel.daydream.advancement.progress.CountableProgress;
++import moe.caramel.daydream.advancement.progress.CriterionProgress;
++import moe.caramel.daydream.advancement.progress.CriteriaProgress;
++import org.bukkit.NamespacedKey;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++import java.time.Instant;
++import java.util.Map;
++import java.util.function.BiConsumer;
+
-+ private AdvancementSaveFormat(final @NotNull String name, final boolean compressed) {
-+ this.name = name;
-+ this.compressed = compressed;
-+ }
++/**
++ * 플레이어 발전 과제 진행도 맵
++ */
++public interface ProgressMap {
+
+ /**
-+ * 요구하는 데이터 형식이 압축된 데이터 형식인지 확인합니다.
++ * 진행도 맵의 데이터를 가져옵니다.
+ *
-+ * @return 만약 {@code true}인 경우 압축된 데이터를 요구합니다
++ * @return 수정할 수 없는 데이터 맵
+ */
-+ public boolean isCompressed() {
-+ return compressed;
-+ }
++ @NotNull
++ Map getData();
+
-+ @Override
-+ public boolean equals(final Object o) {
-+ if (this == o) return true;
-+ if (o == null || getClass() != o.getClass()) return false;
-+ final AdvancementSaveFormat> that = (AdvancementSaveFormat>) o;
-+ return (compressed == that.compressed) && Objects.equals(name, that.name);
-+ }
++ /**
++ * 진행도 맵에 대하여 반복을 실행합니다.
++ *
++ * @param action 수행할 작업
++ */
++ void iterator(final @NotNull BiConsumer action);
+
-+ @Override
-+ public int hashCode() {
-+ return Objects.hash(name, compressed);
-+ }
++ /**
++ * 주어진 발전 과제에 대하여 새로운 진행도를 생성하고 추가합니다.
++ *
++ * @param key 대상 발전 과제의 키
++ * @param criteria 기준 달성 목록
++ * @return 생성된 진행도
++ */
++ @NotNull
++ CriteriaProgress newProgress(final @NotNull NamespacedKey key, final @NotNull Map criteria);
++
++ /**
++ * 주어진 발전 과제에 대하여 새로운 카운트 형식의 진행도를 생성하고 추가합니다.
++ *
++ * @param key 대상 발전 과제의 키
++ * @param currentCount 현재 카운트
++ * @param obtainedTime 기준 달성 시간 ({@code null}인 경우 달성하지 않음)
++ * @return 생성된 카운트 형식의 진행도
++ */
++ @NotNull
++ CountableProgress newCountProgress(final @NotNull NamespacedKey key, final int currentCount, final @Nullable Instant obtainedTime);
++}
+diff --git a/src/main/java/moe/caramel/daydream/advancement/progress/CountableProgress.java b/src/main/java/moe/caramel/daydream/advancement/progress/CountableProgress.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2a26693dc8161dec6e94f8c26e9baec2b8ee0e18
+--- /dev/null
++++ b/src/main/java/moe/caramel/daydream/advancement/progress/CountableProgress.java
+@@ -0,0 +1,14 @@
++package moe.caramel.daydream.advancement.progress;
++
++/**
++ * 카운트 형식의 발전 과제 진행도
++ */
++public interface CountableProgress extends CriteriaProgress {
++
++ /**
++ * 현재 카운트를 가져옵니다.
++ *
++ * @return 현재 카운트
++ */
++ int getCurrentCount();
++}
+diff --git a/src/main/java/moe/caramel/daydream/advancement/progress/CriteriaProgress.java b/src/main/java/moe/caramel/daydream/advancement/progress/CriteriaProgress.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..da5f54828cd0ad73e74c78550415811016bc6b90
+--- /dev/null
++++ b/src/main/java/moe/caramel/daydream/advancement/progress/CriteriaProgress.java
+@@ -0,0 +1,18 @@
++package moe.caramel.daydream.advancement.progress;
++
++/**
++ * 발전 과제 진행도 ({@link CriterionProgress}의 집합)
++ *
++ * {@link org.bukkit.advancement.AdvancementProgress}와 동일하지만
++ * NMS를 직접 래핑하므로 제한된 작업만 가능합니다.
++ *
++ */
++public interface CriteriaProgress {
++
++ /**
++ * 발전 과제를 달성했는지 여부를 가져옵니다.
++ *
++ * @return 발전 과제 달성 여부
++ */
++ boolean isProgressDone();
+}
+diff --git a/src/main/java/moe/caramel/daydream/advancement/progress/CriterionProgress.java b/src/main/java/moe/caramel/daydream/advancement/progress/CriterionProgress.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..56100df41aa947444cdb16b015ab5a91024b257d
+--- /dev/null
++++ b/src/main/java/moe/caramel/daydream/advancement/progress/CriterionProgress.java
+@@ -0,0 +1,37 @@
++package moe.caramel.daydream.advancement.progress;
++
++import org.jetbrains.annotations.Nullable;
++import java.time.Instant;
++
++/**
++ * 기준 진행도
++ *
++ * @see CriteriaProgress
++ */
++public interface CriterionProgress {
++
++ /**
++ * 기준을 달성했는지 여부를 가져옵니다.
++ *
++ * @return 기준 달성 여부
++ */
++ boolean isProgressDone();
++
++ /**
++ * 기준을 달성한 것으로 변경합니다.
++ */
++ void setGrant();
++
++ /**
++ * 기준을 달성하지 않은 것으로 변경합니다.
++ */
++ void setRevoke();
++
++ /**
++ * 기준의 달성 시간을 가져옵니다.
++ *
++ * @return 기준 달성 시간
++ */
++ @Nullable
++ Instant getObtainedTime();
++}
+diff --git a/src/main/java/moe/caramel/daydream/event/player/AsyncPlayerDataPreLoadEvent.java b/src/main/java/moe/caramel/daydream/event/player/AsyncPlayerDataPreLoadEvent.java
+index 96b35864b50695c27f22005aa5581e5c2bc38735..c3a9f5dff5ddf53d5070b429eb9dfad45fd0c193 100644
+--- a/src/main/java/moe/caramel/daydream/event/player/AsyncPlayerDataPreLoadEvent.java
++++ b/src/main/java/moe/caramel/daydream/event/player/AsyncPlayerDataPreLoadEvent.java
+@@ -73,4 +73,18 @@ public abstract class AsyncPlayerDataPreLoadEvent extends Event {
+ */
+ @NotNull
+ public abstract InetAddress getAddress();
++
++ // Daydream start - Add more Advancement API
++ /**
++ * 플레이어 발전 과제 데이터를 생성하고 가져옵니다.
++ *
++ * 플레이어의 로그인이 완료되기 전에 생성되기에 중복 로그인으로
++ * 인한 데이터 증발 등 데이터 관리에 주의해주세요.
++ *
++ *
++ * @return 플레이어 발전 과제 데이터 (이미 생성된 경우 기존의 인스턴스를 가져옴)
++ */
++ @NotNull
++ public abstract moe.caramel.daydream.advancement.PlayerAdvancementData createAndGetAdvancements();
++ // Daydream end - Add more Advancement API
+ }
diff --git a/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementPreLoadEvent.java b/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementPreLoadEvent.java
new file mode 100644
-index 0000000000000000000000000000000000000000..21bbda2d1fae053aa5734652c5670a74b6e1d06c
+index 0000000000000000000000000000000000000000..c4be6c058e86cde01fbd2d84d6cc3d8c548abd65
--- /dev/null
+++ b/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementPreLoadEvent.java
-@@ -0,0 +1,81 @@
+@@ -0,0 +1,114 @@
+package moe.caramel.daydream.event.player;
+
-+import com.google.gson.JsonElement;
++import moe.caramel.daydream.advancement.AdvancementFormat;
++import moe.caramel.daydream.advancement.PlayerAdvancementData;
++import moe.caramel.daydream.advancement.ProgressMap;
++import moe.caramel.daydream.advancement.progress.CriterionProgress;
+import org.bukkit.entity.Player;
++import org.bukkit.event.Cancellable;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
-+import java.util.ArrayList;
-+import java.util.List;
++import org.jetbrains.annotations.Nullable;
++import java.time.Instant;
+
+/**
+ * 플레이어의 발전 과제 데이터를 로드하기 직전에 호출됩니다.
-+ * 일반적으로 {@link org.bukkit.event.player.PlayerJoinEvent} 이전에 호출됩니다.
-+ *
-+ * @param 타입
++ *
++ * 일반적으로 {@link org.bukkit.event.player.PlayerJoinEvent} 이전에 호출됩니다.
++ *
+ */
-+public sealed class PlayerAdvancementPreLoadEvent extends PlayerEvent {
++public final class PlayerAdvancementPreLoadEvent extends PlayerEvent implements Cancellable, PlayerAdvancementData {
+
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+ public @NotNull HandlerList getHandlers() { return HANDLER_LIST; }
+ public static @NotNull HandlerList getHandlerList() { return HANDLER_LIST; }
+
-+ private final boolean compressed;
-+ private List data;
++ private final PlayerAdvancementData provider;
++ private final Reason reason;
++ private boolean cancelled;
+
+ @ApiStatus.Internal
-+ public PlayerAdvancementPreLoadEvent(final @NotNull Player who, final boolean compressed) {
++ public PlayerAdvancementPreLoadEvent(final @NotNull Player who, final @NotNull PlayerAdvancementData provider, final @NotNull Reason reason) {
+ super(who);
-+ this.compressed = compressed;
-+ this.data = new ArrayList<>();
++ this.provider = provider;
++ this.reason = reason;
+ }
+
+ /**
-+ * 요구하는 데이터 형식이 압축된 데이터 형식인지 확인합니다.
++ * 플레이어 발전 과제 데이터를 가져옵니다.
+ *
-+ * @return 만약 {@code true}인 경우 압축된 데이터를 요구합니다
++ * @return 플레이어 발전 과제 데이터
+ */
-+ public boolean isCompressed() {
-+ return compressed;
++ @NotNull
++ public PlayerAdvancementData getData() {
++ return provider;
+ }
+
+ /**
-+ * 로드할 발전 과제 데이터를 가져옵니다.
++ * 이벤트 호출 사유를 가져옵니다.
+ *
-+ * @return 발전 과제 데이터
++ * @return 이벤트 호출 사유
+ */
+ @NotNull
-+ public List getReplaceData() {
-+ return data;
++ public Reason getReason() {
++ return reason;
+ }
+
+ /**
-+ * 로드할 발전 과제 데이터를 설정합니다.
++ * 발전 과제 데이터를 파일에서 읽어오는 바닐라 동작을 취소할지 여부를 가져옵니다.
+ *
-+ * @param data 발전 과제 데이터
++ * @return 바닐라 동작 취소 여부
+ */
-+ public void addReplaceData(final @NotNull T data) {
-+ this.data.add(data);
++ @Override
++ public boolean isCancelled() {
++ return cancelled;
+ }
+
+ /**
-+ * 서버가 Json 형식으로 데이터를 저장하는 경우
++ * 발전 과제 데이터를 파일에서 읽어오는 바닐라 동작을 취소할지 여부를 결정합니다.
++ *
++ * @param cancel 바닐라 동작 취소 여부
+ */
-+ public static final class Json extends PlayerAdvancementPreLoadEvent {
-+
-+ public Json(final @NotNull Player who, final boolean compressed) {
-+ super(who, compressed);
-+ }
++ @Override
++ public void setCancelled(final boolean cancel) {
++ this.cancelled = cancel;
+ }
+
+ /**
-+ * 서버가 Json 형식으로 데이터를 저장하는 경우
++ * 이벤트 호출 사유
+ */
-+ public static final class Nbt extends PlayerAdvancementPreLoadEvent {
++ public enum Reason {
++ /**
++ * 플레이어 접속
++ */
++ PLAYER_JOIN,
++ /**
++ * 서버 리소스 리로드
++ */
++ SERVER_RESOURCE_RELOAD
++ }
+
-+ public Nbt(final @NotNull Player who, final boolean compressed) {
-+ super(who, compressed);
-+ }
++ // =========================== (Provider wrapper)
++
++ @Override
++ public @NotNull ProgressMap createProgressMap() {
++ return provider.createProgressMap();
++ }
++
++ @Override
++ public @NotNull CriterionProgress createCriterionProgress(final @Nullable Instant obtainedTime) {
++ return provider.createCriterionProgress(obtainedTime);
++ }
++
++ @Override
++ public void loadFromData(final @NotNull AdvancementFormat format, final @NotNull T data) throws Exception {
++ this.provider.loadFromData(format, data);
++ }
++
++ @Override
++ public @NotNull T saveToData(final @NotNull AdvancementFormat format, final @NotNull ProgressMap progress) throws Exception {
++ return provider.saveToData(format, progress);
+ }
+}
diff --git a/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementSaveEvent.java b/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementSaveEvent.java
new file mode 100644
-index 0000000000000000000000000000000000000000..81cf8963c7a1ec67fb0368c2f457a3a77561b589
+index 0000000000000000000000000000000000000000..f8eb1cb00a070733a9ee2d7b3dc2ea73ebd63880
--- /dev/null
+++ b/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementSaveEvent.java
-@@ -0,0 +1,95 @@
+@@ -0,0 +1,72 @@
+package moe.caramel.daydream.event.player;
+
-+import com.google.gson.JsonElement;
++import moe.caramel.daydream.advancement.ProgressMap;
+import org.bukkit.NamespacedKey;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
@@ -612,26 +883,22 @@ index 0000000000000000000000000000000000000000..81cf8963c7a1ec67fb0368c2f457a3a7
+
+/**
+ * 플레이어 발전 과제 데이터가 저장될 때 호출됩니다.
-+ *
-+ * @param 타입
+ */
-+public sealed class PlayerAdvancementSaveEvent extends PlayerEvent implements Cancellable {
++public final class PlayerAdvancementSaveEvent extends PlayerEvent implements Cancellable {
+
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+ public @NotNull HandlerList getHandlers() { return HANDLER_LIST; }
+ public static @NotNull HandlerList getHandlerList() { return HANDLER_LIST; }
+
+ private final NamespacedKey section;
-+ private final T data;
-+ private final boolean compressed;
++ private final ProgressMap progress;
+ private boolean cancelled;
+
+ @ApiStatus.Internal
-+ public PlayerAdvancementSaveEvent(final @NotNull Player who, final @NotNull NamespacedKey section, final @NotNull T data, final boolean compressed) {
++ public PlayerAdvancementSaveEvent(final @NotNull Player who, final @NotNull NamespacedKey section, final @NotNull ProgressMap progress) {
+ super(who);
+ this.section = section;
-+ this.data = data;
-+ this.compressed = compressed;
++ this.progress = progress;
+ }
+
+ /**
@@ -646,52 +913,33 @@ index 0000000000000000000000000000000000000000..81cf8963c7a1ec67fb0368c2f457a3a7
+ }
+
+ /**
-+ * 요구하는 데이터 형식이 압축된 데이터 형식인지 확인합니다.
-+ *
-+ * @return 만약 {@code true}인 경우 압축된 데이터를 요구합니다
-+ */
-+ public boolean isCompressed() {
-+ return compressed;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return this.cancelled;
-+ }
-+
-+ @Override
-+ public void setCancelled(final boolean cancel) {
-+ this.cancelled = cancel;
-+ }
-+
-+ /**
+ * 저장될 발전 과제 데이터를 가져옵니다.
+ *
+ * @return 발전 과제 데이터
+ */
+ @NotNull
-+ public T getData() {
-+ return data;
++ public ProgressMap getData() {
++ return progress;
+ }
+
+ /**
-+ * 서버가 Json 형식으로 데이터를 저장하는 경우
++ * 발전 과제 데이터를 파일에 저장하는 바닐라 동작을 취소할지 여부를 가져옵니다.
++ *
++ * @return 바닐라 동작 취소 여부
+ */
-+ public static final class Json extends PlayerAdvancementSaveEvent {
-+
-+ public Json(final @NotNull Player who, final @NotNull NamespacedKey section, final @NotNull JsonElement json, final boolean compressed) {
-+ super(who, section, json, compressed);
-+ }
++ @Override
++ public boolean isCancelled() {
++ return this.cancelled;
+ }
+
+ /**
-+ * 서버가 Json 형식으로 데이터를 저장하는 경우
++ * 발전 과제 데이터를 파일에 저장하는 바닐라 동작을 취소할지 여부를 결정합니다.
++ *
++ * @param cancel 바닐라 동작 취소 여부
+ */
-+ public static final class Nbt extends PlayerAdvancementSaveEvent {
-+
-+ public Nbt(final @NotNull Player who, final @NotNull NamespacedKey section, final @NotNull byte[] nbt, final boolean compressed) {
-+ super(who, section, nbt, compressed);
-+ }
++ @Override
++ public void setCancelled(final boolean cancel) {
++ this.cancelled = cancel;
+ }
+}
diff --git a/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementScreenEvent.java b/src/main/java/moe/caramel/daydream/event/player/PlayerAdvancementScreenEvent.java
@@ -1101,7 +1349,7 @@ index f9bc179da071e7bd57cefc50d6763317fb643b74..3b1b55977d94de338f536896babedc7e
+ // Daydream end - Add more Advancement API
}
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index e8d57cd3373c84853d5adcacdf97eec8aa245a71..573a858d16ba64467f7734752a6ba3461e07bee2 100644
+index acee4f659f0657ccb09514c64b08e86c7abe23fb..e4d8a2bb70efe374d9fe59aaae816e5d687a0e84 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -3195,6 +3195,38 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
@@ -1117,7 +1365,7 @@ index e8d57cd3373c84853d5adcacdf97eec8aa245a71..573a858d16ba64467f7734752a6ba346
+ * @param 데이터 타입
+ */
+ @NotNull
-+ public T serializeAdvancementProgress(@NotNull moe.caramel.daydream.advancement.AdvancementSaveFormat format);
++ public T serializeAdvancementProgress(@NotNull moe.caramel.daydream.advancement.AdvancementFormat format);
+
+ /**
+ * 플레이어의 직렬화된 발전 과제 데이터를 가져옵니다.
@@ -1128,7 +1376,7 @@ index e8d57cd3373c84853d5adcacdf97eec8aa245a71..573a858d16ba64467f7734752a6ba346
+ * @param 데이터 타입
+ */
+ @NotNull
-+ public T serializeAdvancementProgress(@NotNull moe.caramel.daydream.advancement.AdvancementSaveFormat format, @NotNull org.bukkit.NamespacedKey saveSection);
++ public T serializeAdvancementProgress(@NotNull moe.caramel.daydream.advancement.AdvancementFormat format, @NotNull org.bukkit.NamespacedKey saveSection);
+
+ /**
+ * 플레이어에게 발전 과제 알림을 전송합니다.