DataFixerAPI, WorldDataAPI, ServerLevelMixin
This commit is contained in:
parent
47bb37515e
commit
5796825516
4 changed files with 287 additions and 0 deletions
146
src/main/java/ru/bclib/api/DataFixerAPI.java
Normal file
146
src/main/java/ru/bclib/api/DataFixerAPI.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
package ru.bclib.api;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
|
||||
public class DataFixerAPI {
|
||||
private static final Map<String, String> REPLACEMENT = Maps.newHashMap();
|
||||
private static final Map<String, Integer> FIX_VERSIONS = Maps.newHashMap();
|
||||
|
||||
public static void fixData(File dir) {
|
||||
if (REPLACEMENT.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shoudFix = false;
|
||||
Collection<ModContainer> mods = FabricLoader.getInstance().getAllMods();
|
||||
for (ModContainer mod: mods) {
|
||||
String name = mod.getMetadata().getId();
|
||||
int version = getModVersion(mod.getMetadata().getVersion().toString());
|
||||
if (version > 0) {
|
||||
shoudFix |= FIX_VERSIONS.getOrDefault(name, version) < version;
|
||||
}
|
||||
};
|
||||
if (!shoudFix) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<File> regions = getAllRegions(dir, null);
|
||||
regions.parallelStream().forEach((file) -> {
|
||||
try {
|
||||
System.out.println("Fixing " + file);
|
||||
boolean[] changed = new boolean[1];
|
||||
RegionFile region = new RegionFile(file, file.getParentFile(), true);
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
ChunkPos pos = new ChunkPos(x, z);
|
||||
changed[0] = false;
|
||||
if (region.hasChunk(pos)) {
|
||||
DataInputStream input = region.getChunkDataInputStream(pos);
|
||||
CompoundTag root = NbtIo.read(input);
|
||||
input.close();
|
||||
ListTag sections = root.getCompound("Level").getList("Sections", 10);
|
||||
sections.forEach((tag) -> {
|
||||
ListTag palette = ((CompoundTag) tag).getList("Palette", 10);
|
||||
palette.forEach((blockTag) -> {
|
||||
CompoundTag blockTagCompound = ((CompoundTag) blockTag);
|
||||
String name = blockTagCompound.getString("Name");
|
||||
String replace = REPLACEMENT.get(name);
|
||||
if (replace != null) {
|
||||
blockTagCompound.putString("Name", replace);
|
||||
changed[0] = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (changed[0]) {
|
||||
System.out.println("Write!");
|
||||
DataOutputStream output = region.getChunkDataOutputStream(pos);
|
||||
NbtIo.write(root, output);
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
region.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block data fix. Fix will be applied on world load if current mod version will be newer than specified one.
|
||||
* @param modID - {@link String} mod id;
|
||||
* @param modVersion - {@link String} mod version, should be in format: %d.%d.%d
|
||||
* @param result - {@link String} new block name;
|
||||
* @param names - array of {@link String}, old block names to convert.
|
||||
*/
|
||||
protected static void addFix(String modID, String modVersion, String result, String... names) {
|
||||
FIX_VERSIONS.put(modID, getModVersion(modVersion));
|
||||
for (String name: names) {
|
||||
REPLACEMENT.put(name, result);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<File> getAllRegions(File dir, List<File> list) {
|
||||
if (list == null) {
|
||||
list = Lists.newArrayList();
|
||||
}
|
||||
for (File file: dir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
getAllRegions(file, list);
|
||||
}
|
||||
else if (file.isFile() && file.getName().endsWith(".mca")) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mod version from string. String should be in format: %d.%d.%d
|
||||
* @param version - {@link String} mod version.
|
||||
* @return
|
||||
*/
|
||||
public static int getModVersion(String version) {
|
||||
if (version.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
String[] values = version.split("\\.");
|
||||
return Integer.parseInt(values[0]) << 12 | Integer.parseInt(values[1]) << 6 | Integer.parseInt(values[2]);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mod version from integer. String will be in format %d.%d.%d
|
||||
* @param version
|
||||
* @return
|
||||
*/
|
||||
public static String getModVersion(int version) {
|
||||
int a = (version >> 12) & 63;
|
||||
int b = (version >> 6) & 63;
|
||||
int c = version & 63;
|
||||
return String.format(Locale.ROOT, "%d.%d.%d", a, b, c);
|
||||
}
|
||||
}
|
88
src/main/java/ru/bclib/api/WorldDataAPI.java
Normal file
88
src/main/java/ru/bclib/api/WorldDataAPI.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
package ru.bclib.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import ru.bclib.BCLib;
|
||||
|
||||
public class WorldDataAPI {
|
||||
private static final Map<String, CompoundTag> TAGS = Maps.newHashMap();
|
||||
private static final List<String> MODS = Lists.newArrayList();
|
||||
private static File dataDir;
|
||||
|
||||
public static void load(File dataDir) {
|
||||
WorldDataAPI.dataDir = dataDir;
|
||||
MODS.stream().parallel().forEach(modID -> {
|
||||
File file = new File(dataDir, modID);
|
||||
CompoundTag root = new CompoundTag();
|
||||
if (file.exists()) {
|
||||
try {
|
||||
root = NbtIo.readCompressed(file);
|
||||
}
|
||||
catch (IOException e) {
|
||||
BCLib.LOGGER.error("World data loading failed", e);
|
||||
}
|
||||
}
|
||||
TAGS.put(modID, root);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register mod cache, world cache is located in world data folder.
|
||||
* @param modID - {@link String} modID.
|
||||
*/
|
||||
public static void registerModCache(String modID) {
|
||||
MODS.add(modID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get root {@link CompoundTag} for mod cache in world data folder.
|
||||
* @param modID - {@link String} modID.
|
||||
* @return {@link CompoundTag}
|
||||
*/
|
||||
public static CompoundTag getRootTag(String modID) {
|
||||
CompoundTag root = TAGS.get(modID);
|
||||
if (root == null) {
|
||||
root = new CompoundTag();
|
||||
TAGS.put(modID, root);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link CompoundTag} with specified path from mod cache in world data folder.
|
||||
* @param modID - {@link String} path to tag, dot-separated.
|
||||
* @return {@link CompoundTag}
|
||||
*/
|
||||
public static CompoundTag getCompoundTag(String modID, String path) {
|
||||
String[] parts = path.split("\\.");
|
||||
CompoundTag tag = getRootTag(modID);
|
||||
for (String part: parts) {
|
||||
if (tag.contains(part)) {
|
||||
tag = tag.getCompound(part);
|
||||
}
|
||||
else {
|
||||
CompoundTag t = new CompoundTag();
|
||||
tag.put(part, t);
|
||||
tag = t;
|
||||
}
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static void saveFile(String modID) {
|
||||
try {
|
||||
NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt"));
|
||||
}
|
||||
catch (IOException e) {
|
||||
BCLib.LOGGER.error("World data saving failed", e);
|
||||
}
|
||||
}
|
||||
}
|
52
src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java
Normal file
52
src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
package ru.bclib.mixin.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.CustomSpawner;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.minecraft.world.level.storage.WritableLevelData;
|
||||
import ru.bclib.api.DataFixerAPI;
|
||||
import ru.bclib.api.WorldDataAPI;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public abstract class ServerLevelMixin extends Level {
|
||||
private static String be_lastWorld = null;
|
||||
|
||||
protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, DimensionType dimensionType, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l) {
|
||||
super(writableLevelData, resourceKey, dimensionType, supplier, bl, bl2, l);
|
||||
}
|
||||
|
||||
@Inject(method = "<init>*", at = @At("TAIL"))
|
||||
private void be_onServerWorldInit(MinecraftServer server, Executor workerExecutor, LevelStorageSource.LevelStorageAccess session, ServerLevelData properties, ResourceKey<Level> registryKey, DimensionType dimensionType, ChunkProgressListener worldGenerationProgressListener, ChunkGenerator chunkGenerator, boolean debugWorld, long l, List<CustomSpawner> list, boolean bl, CallbackInfo info) {
|
||||
if (be_lastWorld != null && be_lastWorld.equals(session.getLevelId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
be_lastWorld = session.getLevelId();
|
||||
|
||||
ServerLevel world = ServerLevel.class.cast(this);
|
||||
File dir = session.getDimensionPath(world.dimension());
|
||||
if (!new File(dir, "level.dat").exists()) {
|
||||
dir = dir.getParentFile();
|
||||
}
|
||||
|
||||
DataFixerAPI.fixData(dir);
|
||||
WorldDataAPI.load(dir);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
"EnchantmentMenuMixin",
|
||||
"RecipeManagerMixin",
|
||||
"BoneMealItemMixin",
|
||||
"ServerLevelMixin",
|
||||
"TagLoaderMixin"
|
||||
],
|
||||
"injectors": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue