apply more patches

This commit is contained in:
Helvetica Volubi
2026-05-03 16:45:10 +08:00
parent 017e49da8a
commit 0fc9a1426d
11 changed files with 145 additions and 152 deletions
@@ -0,0 +1,38 @@
package fun.bm.lophine.config.modules.experiment;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.EXPERIMENT, name = "command")
public class CommandConfig implements IConfigModule {
// @ConfigInfo(name = "tick_command_enabled", comments =
// """
// Allow to use tick command""")
// public static boolean tick = false;
//
// @ConfigInfo(name = "function_command_enabled", comments =
// """
// Allow to use function command""")
// public static boolean function = false;
//
// @ConfigInfo(name = "scoreboard_command_enabled", comments =
// """
// Allow to use scoreboard command""")
// public static boolean scoreboard = false;
@ConfigInfo(name = "enabled", directory = {"save_all_command"}, comments =
"""
Allow to use save-all command""")
public static boolean saveAll = false;
@ConfigInfo(name = "log_all_process", directory = {"save_all_command"}, comments =
"""
Log all process of save-all command to console""")
public static boolean logAllProcess = false;
@ConfigInfo(name = "save_all_command_timeout", directory = {"save_all_command"}, comments = """
Maximum seconds to save before the chunk report it is timeout.""")
public static long saveAllTimeout = 30;
}
@@ -23,12 +23,12 @@ public class ContainerExpansionConfig implements IConfigModule {
range: 1~6""")
public static int enderchestRows = 3;
@CommandSuggestions(suggest = {"1", "2", "32", "64"})
@ConfigInfo(name = "shulker_stackable_count", directory = {"shulker_box"}, comments =
"""
range: 1~64""")
public static int shulkerCount = 1;
@ConfigInfo(name = "same_nbt_shulker_stackable", directory = {"shulker_box"})
public static boolean nbtShulkerStackable = false;
// @CommandSuggestions(suggest = {"1", "2", "32", "64"})
// @ConfigInfo(name = "shulker_stackable_count", directory = {"shulker_box"}, comments =
// """
// range: 1~64""")
// public static int shulkerCount = 1;
//
// @ConfigInfo(name = "same_nbt_shulker_stackable", directory = {"shulker_box"})
// public static boolean nbtShulkerStackable = false;
}
@@ -0,0 +1,24 @@
package fun.bm.lophine.config.modules.function;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.HotReloadUnsupported;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "language")
public class LanguageConfig implements IConfigModule {
@HotReloadUnsupported
@ConfigInfo(name = "lang", comments = """
Please use the key from https://minecraft.wiki/w/Language
Sample of format: en_us zh_cn zh_hk zh_tw""")
public static String lang = "en_us";
@ConfigInfo(name = "full_blocking_load", comments = """
Whether to allow blocking server loading when loading localized language.
If you want only use your localized language to shown in your terminal,
you need to enable it.
WARNING: This may slow down the startup speed!""")
public static boolean full_blocking_load = false;
}
@@ -0,0 +1,17 @@
package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.MISC, name = "disable-check")
public class DisableCheckConfig implements IConfigModule {
@ConfigInfo(name = "disable-op-move-check", comments = """
Disable the check for the operator's move check""")
public static boolean disableOpMoveCheck = false;
@ConfigInfo(name = "disable-op-fly-check", comments = """
Disable the check for the operator's fly check""")
public static boolean disableOpFlyCheck = false;
}
@@ -0,0 +1,88 @@
package fun.bm.lophine.utils;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Pair;
import fun.bm.lophine.config.modules.experiment.CommandConfig;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.commands.SaveAllCommand;
import net.minecraft.server.level.ServerLevel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class SaveAllUtil {
public static final Object lock = new Object();
public static volatile long lastSaveAllTime = 0;
private static volatile Pair<CommandSourceStack, Boolean> currentSaveAll = null;
private static volatile int regionCount = 0;
private static final AtomicInteger savedRegionCount = new AtomicInteger(0);
private static final AtomicBoolean withError = new AtomicBoolean(false);
public static void preSaveAll(CommandSourceStack source, boolean flush) {
synchronized (lock) {
if (isSaving()) {
source.sendFailure(Component.literal("Server is already in saving! Please wait..."));
} else {
source.sendSuccess(() -> Component.translatable("commands.save.saving"), false);
currentSaveAll = Pair.of(source, flush);
// we need to count how many regions we need to save
AtomicInteger count = new AtomicInteger();
for (ServerLevel world : source.getServer().getAllLevels()) {
world.regioniser.computeForAllRegions(unused -> count.getAndIncrement());
}
regionCount = count.get();
savedRegionCount.set(0);
withError.set(false);
lastSaveAllTime = System.currentTimeMillis();
}
}
}
public static void postRegionSave(io.papermc.paper.threadedregions.TickRegions.TickRegionData region) {
Pair<CommandSourceStack, Boolean> currentSaveAll = SaveAllUtil.currentSaveAll;
synchronized (lock) {
if (lastSaveAllTime < region.lastSavedTime) return; // already saved
}
try {
region.world.moonrise$getChunkTaskScheduler().chunkHolderManager.saveAllChunksRegionised(currentSaveAll.getSecond(), false, CommandConfig.logAllProcess, false, false, true, false);
currentSaveAll.getFirst().getServer().getPlayerList().saveAll();
MinecraftServer.LOGGER.info("Saved chunks in region around chunk {} in world '{}'", region.region.getCenterChunk(), region.region.regioniser.world.getWorld().getName());
} catch (final Throwable thr) {
CommandSyntaxException error = SaveAllCommand.getErrorFailed().create();
currentSaveAll.getFirst().sendFailure(Component.literal(error.getMessage()));
MinecraftServer.LOGGER.error(error.getMessage(), thr);
withError.set(true);
}
region.lastSavedTime = System.currentTimeMillis();
int saved;
synchronized (savedRegionCount) {
saved = savedRegionCount.incrementAndGet();
}
if (saved >= regionCount) {
if (withError.get()) {
currentSaveAll.getFirst().sendFailure(Component.literal("At least one region failed to save!"));
} else if (saved == regionCount) {
currentSaveAll.getFirst().sendSuccess(() -> Component.translatable("commands.save.success"), true);
SaveAllUtil.currentSaveAll = null;
}
}
}
public static void checkTimeout() {
Pair<CommandSourceStack, Boolean> currentSaveAll = SaveAllUtil.currentSaveAll;
if (!isSaving()) return;
if (System.currentTimeMillis() - lastSaveAllTime > CommandConfig.saveAllTimeout) {
currentSaveAll.getFirst().sendFailure(Component.literal("At least one region save data timeout!"));
currentSaveAll.getFirst().sendFailure(Component.literal("Regions need to save expect is " + regionCount + ", but only " + savedRegionCount.get() + " saved!"));
SaveAllUtil.currentSaveAll = null;
}
}
public static boolean isSaving() {
synchronized (lock) {
return currentSaveAll != null;
}
}
}
@@ -0,0 +1,319 @@
package fun.bm.lophine.utils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.LanguageConfig;
import io.papermc.paper.ServerBuildInfo;
import me.earthme.luminol.config.ConfigManager;
import me.earthme.luminol.config.ConfigsInstance;
import net.minecraft.locale.DeprecatedTranslationsInfo;
import net.minecraft.locale.Language;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.Style;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.StringDecomposer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
/*
* authored by: Helvetica Volubi <suisuroru@blue-millennium.fun>
* modified by: Lumine1909 <133463833+Lumine1909@users.noreply.github.com>
* Some of diff form Leaves
*/
public class ServerI18nUtil {
private static final Logger logger = LogUtils.getClassLogger();
private static final String VERSION = ServerBuildInfo.buildInfo().minecraftVersionId();
private static final String BASE_PATH = "cache/lophine/" + VERSION + "/";
private static final String defaultLophineLangPath = "/assets/lophine/lang/en_us.json";
private static final String manifestUrl = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String resourceBaseUrl = "https://resources.download.minecraft.net/";
// pre-load
private static CompletableFuture<Void> preloadTask;
// paths
private static String langPath;
private static String assetsPath;
private static String versionPath;
private static String manifestPath;
private static String langJsonPath;
private static String lophineLangPath;
public static void init() {
if (Objects.equals(LanguageConfig.lang, "en_us")) {
return;
}
langPath = BASE_PATH + "lang/" + LanguageConfig.lang + ".json";
langJsonPath = "minecraft/lang/" + LanguageConfig.lang + ".json";
lophineLangPath = "/assets/lophine/lang/" + LanguageConfig.lang + ".json";
logger.info("Starting load language: {}", LanguageConfig.lang);
if (LanguageConfig.full_blocking_load) {
loadI18n(LanguageConfig.lang, 2);
} else {
preloadTask.thenAcceptAsync(v -> loadI18n(LanguageConfig.lang, 2));
}
}
public static void preInit() {
assetsPath = BASE_PATH + "assets.json";
versionPath = BASE_PATH + VERSION + ".json";
manifestPath = BASE_PATH + "manifest.json";
preloadTask = CompletableFuture.runAsync(() -> preLoadI18n(2));
}
private static void preLoadI18n(int retryTime) {
try {
if (!Files.exists(Path.of(assetsPath))) {
downloadAssets(true);
}
} catch (UnsupportedLanguageException e) {
logger.warn("Unsupported language: {}", LanguageConfig.lang);
// Fallback to English
final ConfigsInstance configsInstance = ConfigManager.getConfigs("lophine");
configsInstance.setConfig(new String[]{"function", "language", "lang"}, "en_us");
configsInstance.reloadAsync(true);
} catch (Exception e) {
if (e instanceof MalformedJsonException malformedJson) {
malformedJson.clean();
}
logger.warn("Failed to download language list file: ", e);
if (retryTime > 0) {
preLoadI18n(retryTime - 1);
} else {
logger.error("Failed to download language list file for many times, skip pre-load language.");
}
}
}
private static void loadI18n(String lang, int retryTime) {
try {
if (!Files.exists(Path.of(langPath))) {
downloadLang(true);
}
Language.inject(createLangInstance());
logger.info("Successfully loaded language: {}", lang);
} catch (Exception e) {
if (e instanceof MalformedJsonException malformedJson) {
malformedJson.clean();
}
logger.warn("Failed to load language file for {}", lang, e);
if (retryTime > 0) {
loadI18n(lang, retryTime - 1);
} else {
logger.error("Failed to load for many times, use default lang \"en_us\" instead");
}
}
}
private static void downloadLang(boolean fetchFromAssets) throws Exception {
JsonObject json;
if (!Files.exists(Path.of(assetsPath)) || (json = loadJson(assetsPath)) == null) {
if (fetchFromAssets) {
downloadAssets(true);
downloadLang(false);
}
return;
}
JsonObject langEntry = json.getAsJsonObject("objects").getAsJsonObject(langJsonPath);
if (langEntry == null) {
throw new UnsupportedLanguageException();
}
String hash = langEntry.get("hash").getAsString();
if (hash == null || hash.length() < 2) {
throw new IllegalArgumentException("Invalid hash value");
}
String langUrl = resourceBaseUrl + hash.substring(0, 2) + "/" + hash;
fetchAndSave(langUrl, langPath);
}
private static void downloadAssets(boolean fetchFromVersion) throws Exception {
JsonObject json;
if (!Files.exists(Path.of(versionPath)) || (json = loadJson(versionPath)) == null) {
if (fetchFromVersion) {
downloadVersion(true);
downloadAssets(false);
}
return;
}
JsonObject assetIndex = json.getAsJsonObject("assetIndex");
String assetUrl = assetIndex.get("url").getAsString();
fetchAndSave(assetUrl, assetsPath);
}
private static void downloadVersion(boolean fetchFromManifest) throws Exception {
JsonObject json;
if (!Files.exists(Path.of(manifestPath)) || (json = loadJson(manifestPath)) == null) {
if (fetchFromManifest) {
fetchAndSave(manifestUrl, manifestPath);
downloadVersion(false);
}
return;
}
String versionUrl = null;
for (JsonElement element : json.getAsJsonArray("versions")) {
String id = element.getAsJsonObject().get("id").getAsString();
String url = element.getAsJsonObject().get("url").getAsString();
if (VERSION.equals(id)) {
versionUrl = url;
break;
}
}
if (versionUrl == null) {
throw new RuntimeException("Could not find version URL");
}
fetchAndSave(versionUrl, versionPath);
}
private static byte[] fetch(String urlString) throws IOException, InterruptedException {
try {
HttpResponse<String> response;
try (HttpClient httpClient = HttpClient.newHttpClient()) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(urlString))
.timeout(Duration.ofSeconds(10))
.build();
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}
int responseCode = response.statusCode();
if (responseCode != 200) {
logger.info("Unexpected response code: {}", responseCode);
logger.info("Response body: {}", response.body());
throw new UnsupportedEncodingException("Unexpected response code");
} else {
return response.body().getBytes();
}
} catch (Exception e) {
logger.warn("Error in getting info!");
throw e;
}
}
private static void fetchAndSave(String url, String savePath) throws IOException, InterruptedException {
byte[] data = fetch(url);
Path outputPath = Path.of(savePath);
Files.createDirectories(outputPath.getParent());
Files.write(outputPath, data, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
}
private static JsonObject loadJson(String path) {
try {
byte[] data = Files.readAllBytes(Paths.get(path));
return JsonParser.parseString(new String(data)).getAsJsonObject();
} catch (JsonSyntaxException e) {
logger.warn("Corrupt json file!");
throw new MalformedJsonException(e, path);
} catch (Exception e) {
logger.warn("Failed to load local JSON!");
return null;
}
}
private static Language createLangInstance() throws IOException {
DeprecatedTranslationsInfo deprecatedTranslationsInfo = DeprecatedTranslationsInfo.loadFromDefaultResource();
Map<String, String> map = new HashMap<>();
parseTranslations(map::put);
loadLophineI18n(map::put);
deprecatedTranslationsInfo.applyToMap(map);
final Map<String, String> map1 = Map.copyOf(map);
return new Language() {
@Override
public @NotNull String getOrDefault(@NotNull String key, @NotNull String defaultValue) {
return map1.getOrDefault(key, defaultValue);
}
@Override
public boolean has(@NotNull String id) {
return map1.containsKey(id);
}
@Override
public boolean isDefaultRightToLeft() {
return false;
}
@Override
public @NotNull FormattedCharSequence getVisualOrder(@NotNull FormattedText text) {
return sink -> text.visit(
(style, content) -> StringDecomposer.iterateFormatted(content, style, sink) ? Optional.empty() : FormattedText.STOP_ITERATION,
Style.EMPTY
)
.isPresent();
}
};
}
private static void loadLophineI18n(BiConsumer<String, String> bi) {
if (Language.class.getResource(lophineLangPath) != null) {
Language.parseTranslations(bi, lophineLangPath);
} else {
loadLophineI18nDefault(bi);
}
}
public static void loadLophineI18nDefault(BiConsumer<String, String> bi) {
if (Language.class.getResource(defaultLophineLangPath) != null) {
Language.parseTranslations(bi, defaultLophineLangPath);
}
}
private static void parseTranslations(BiConsumer<String, String> output) throws IOException {
Path filePath = Path.of(langPath);
try (InputStream fileStream = Files.newInputStream(filePath)) {
Language.loadFromJson(fileStream, output);
} catch (NoSuchFileException e) {
logger.warn("Couldn't find language file: {}", langPath);
throw e;
} catch (JsonSyntaxException e) {
throw new MalformedJsonException(e, langPath);
} catch (Exception e) {
logger.warn("Failed to load language from filesystem {}", filePath);
throw e;
}
}
private static class UnsupportedLanguageException extends Exception {
}
private static class MalformedJsonException extends RuntimeException {
private final Path path;
protected MalformedJsonException(Throwable root, String path) {
super(root);
this.path = Path.of(path);
}
public void clean() {
try {
Files.delete(path);
} catch (IOException e) {
logger.info("Failed to delete malformed JSON file: {}", path);
}
}
}
}