diff --git a/src/main/java/net/gensokyoreimagined/nitori/core/MixinCraftPlayer.java b/src/main/java/net/gensokyoreimagined/nitori/core/MixinCraftPlayer.java
new file mode 100644
index 0000000..78dd35f
--- /dev/null
+++ b/src/main/java/net/gensokyoreimagined/nitori/core/MixinCraftPlayer.java
@@ -0,0 +1,46 @@
+// Nitori Copyright (C) 2024 Gensokyo Reimagined
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+package net.gensokyoreimagined.nitori.core;
+
+import net.minecraft.Util;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.storage.PlayerDataStorage;
+import org.bukkit.craftbukkit.CraftServer;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.util.concurrent.CompletableFuture;
+
+@Mixin(CraftPlayer.class)
+public class MixinCraftPlayer {
+ @Redirect(method = "saveData", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/PlayerDataStorage;save(Lnet/minecraft/world/entity/player/Player;)V"))
+ private void gensouHacks$savePlayerData(PlayerDataStorage instance, Player path) {
+ Runnable writeRunnable = () -> {
+ var craftPlayer = (CraftPlayer) (Object) this;
+ var server = craftPlayer.getServer();
+
+ if (server instanceof CraftServer craftServer) {
+ var handle = craftPlayer.getHandle();
+ var playerIo = craftServer.getHandle().playerIo;
+ playerIo.save(handle);
+ }
+ };
+
+ var ioExecutor = Util.backgroundExecutor();
+ CompletableFuture.runAsync(writeRunnable, ioExecutor);
+ }
+}
diff --git a/src/main/java/net/gensokyoreimagined/nitori/core/MixinLevelStorageAccess.java b/src/main/java/net/gensokyoreimagined/nitori/core/MixinLevelStorageAccess.java
new file mode 100644
index 0000000..c8b75c4
--- /dev/null
+++ b/src/main/java/net/gensokyoreimagined/nitori/core/MixinLevelStorageAccess.java
@@ -0,0 +1,46 @@
+// Nitori Copyright (C) 2024 Gensokyo Reimagined
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+package net.gensokyoreimagined.nitori.core;
+
+import net.minecraft.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.level.storage.LevelStorageSource;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.util.concurrent.CompletableFuture;
+
+@Mixin(LevelStorageSource.LevelStorageAccess.class)
+public abstract class MixinLevelStorageAccess {
+ @Shadow protected abstract void saveLevelData(CompoundTag nbt);
+
+ @Redirect(method = "modifyLevelDataWithoutDatafix", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;saveLevelData(Lnet/minecraft/nbt/CompoundTag;)V"))
+ private void gensouHacks$saveLevelData(LevelStorageSource.LevelStorageAccess levelStorageAccess, CompoundTag compoundTag) {
+ Runnable writeRunnable = () -> saveLevelData(compoundTag);
+
+ var ioExecutor = Util.backgroundExecutor();
+ CompletableFuture.runAsync(writeRunnable, ioExecutor);
+ }
+
+ @Redirect(method = "saveDataTag(Lnet/minecraft/core/RegistryAccess;Lnet/minecraft/world/level/storage/WorldData;Lnet/minecraft/nbt/CompoundTag;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;saveLevelData(Lnet/minecraft/nbt/CompoundTag;)V"))
+ private void gensouHacks$saveLevelData2(LevelStorageSource.LevelStorageAccess levelStorageAccess, CompoundTag compoundTag) {
+ Runnable writeRunnable = () -> saveLevelData(compoundTag);
+
+ var ioExecutor = Util.backgroundExecutor();
+ CompletableFuture.runAsync(writeRunnable, ioExecutor);
+ }
+}
diff --git a/src/main/java/net/gensokyoreimagined/nitori/core/MixinPlayerList.java b/src/main/java/net/gensokyoreimagined/nitori/core/MixinPlayerList.java
new file mode 100644
index 0000000..6f02988
--- /dev/null
+++ b/src/main/java/net/gensokyoreimagined/nitori/core/MixinPlayerList.java
@@ -0,0 +1,42 @@
+// Nitori Copyright (C) 2024 Gensokyo Reimagined
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+package net.gensokyoreimagined.nitori.core;
+
+import net.minecraft.Util;
+import net.minecraft.server.players.PlayerList;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.storage.PlayerDataStorage;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.util.concurrent.CompletableFuture;
+
+@Mixin(PlayerList.class)
+public class MixinPlayerList {
+ @Final
+ @Shadow
+ public PlayerDataStorage playerIo;
+
+ @Redirect(method = "save", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/PlayerDataStorage;save(Lnet/minecraft/world/entity/player/Player;)V"))
+ private void gensouHacks$savePlayerData(PlayerDataStorage instance, Player player) {
+ Runnable writeRunnable = () -> playerIo.save(player);
+
+ var ioExecutor = Util.backgroundExecutor();
+ CompletableFuture.runAsync(writeRunnable, ioExecutor);
+ }
+}
diff --git a/src/main/resources/mixins.core.json b/src/main/resources/mixins.core.json
index 0e7ef4a..df12eec 100755
--- a/src/main/resources/mixins.core.json
+++ b/src/main/resources/mixins.core.json
@@ -26,6 +26,9 @@
"MixinWorldGenRegion",
"ChunkMapMixin",
"ChunkMapMixin$TrackedEntity",
- "MixinReobfServer"
+ "MixinReobfServer",
+ "MixinLevelStorageAccess",
+ "MixinPlayerList",
+ "MixinCraftPlayer"
]
}