Update some patches from Leaves

This commit is contained in:
Helvetica Volubi
2025-08-15 03:06:55 +08:00
parent 4ed8600511
commit 75baf3dfaf
23 changed files with 601 additions and 512 deletions
@@ -3,6 +3,7 @@ package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class OldFeatureConfig implements IConfigModule {
@ConfigInfo(baseName = "spawn_invulnerable_time")
@@ -14,6 +15,10 @@ public class OldFeatureConfig implements IConfigModule {
@ConfigInfo(baseName = "old_explosion_damage_calculator")
public static boolean oldExplosionDamageCalculator = false;
@TransformedConfig(name = "give_bad_omen_when_kill_raid_captain", category = {"misc", "revert_raid_changes"}, transformComments = false)
@ConfigInfo(baseName = "old_raid_behavior")
public static boolean oldRaidBehavior = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
@@ -1,54 +0,0 @@
package fun.bm.lophine.config.modules.misc;
import me.earthme.luminol.config.EnumConfigCategory;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigInfo;
public class RaidChangesConfig implements IConfigModule {
@ConfigInfo(baseName = "allow_skip_cooldown", comments =
"""
Allow players with bad omen to skip a
30-second cooldown and trigger attacks directly""")
public static boolean trigger = false;
@ConfigInfo(baseName = "give_bad_omen_when_kill_raid_captain", comments =
"""
Enable players to obtain a bad omen
effect when killing the raid captain""")
public static boolean effect = false;
@ConfigInfo(baseName = "bad_omen_infinite", comments =
"""
Enable bad omen effect infinite time
--- this config is not old version's function""")
public static boolean infinite = false;
@ConfigInfo(baseName = "skip_height_check", comments =
"""
Disable y <= 96 check.
If you enabled use_old_position_find, this config
will useless and always behavior of enabled""")
public static boolean heightCheck = false;
@ConfigInfo(baseName = "skip_self_raid_check", comments =
"""
Disable raid self check
--- this config is not old version's function""")
public static boolean selfCheck = false;
@ConfigInfo(baseName = "use_old_position_find", comments =
"""
Revert Old raid's find spawn position logic
--- This revert MC-274911""")
public static boolean posRevert = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
}
@Override
public String getBaseName() {
return "revert_raid_changes";
}
}
@@ -12,9 +12,6 @@ public class RedStoneConfig implements IConfigModule {
Allows you to use the Shears to right-click to rotate the block.""")
public static boolean shears = false;
@ConfigInfo(baseName = "vanilla_hopper")
public static boolean vanillaHopper = false;
@Override
public EnumConfigCategory getCategory() {
return EnumConfigCategory.MISC;
@@ -6,6 +6,12 @@ import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
public class RemovedConfig implements IConfigModule {
@TransformedConfig(name = "allow_skip_cooldown", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "bad_omen_infinite", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "skip_height_check", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "skip_self_raid_check", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "use_old_position_find", category = {"misc", "revert_raid_changes"}, transform = false)
@TransformedConfig(name = "vanilla_hopper", category = {"misc", "redstone"}, transform = false)
@TransformedConfig(name = "old_replaceable_by_mushrooms", category = {"misc", "old-feature"}, transform = false)
@TransformedConfig(name = "old_nether_portal_collision", category = {"misc", "old-feature"}, transform = false)
@TransformedConfig(name = "better_shulker_box", category = {"misc", "container_expansion"}, transform = false)
@@ -4,7 +4,10 @@ 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.optimizations.LanguageConfig;
import me.earthme.luminol.config.ConfigManager;
import me.earthme.luminol.config.ConfigsInstance;
import net.minecraft.DetectedVersion;
import net.minecraft.locale.DeprecatedTranslationsInfo;
import net.minecraft.locale.Language;
@@ -12,8 +15,8 @@ import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.Style;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.StringDecomposer;
import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
@@ -23,13 +26,13 @@ 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;
import java.util.logging.Logger;
/*
* authored by: Helvetica Volubi <suisuroru@blue-millennium.fun>
@@ -37,30 +40,62 @@ import java.util.logging.Logger;
* Some of diff form Leaves
*/
public class ServerI18nUtil {
private static final Logger logger = Logger.getLogger("LangLoader");
private static final Logger logger = LogUtils.getClassLogger();
private static final String VERSION = DetectedVersion.BUILT_IN.name();
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);
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";
langJsonPath = "minecraft/lang/" + LanguageConfig.lang + ".json";
logger.info("Starting load language: " + LanguageConfig.lang);
CompletableFuture.runAsync(() -> loadI18n(LanguageConfig.lang, 2));
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.configfiles.get("lophine");
configsInstance.setConfig(new String[]{"optimizations", "lang"}, "en_us");
configsInstance.reloadAsync();
} 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) {
@@ -69,15 +104,16 @@ public class ServerI18nUtil {
downloadLang(true);
}
Language.inject(createLangInstance());
logger.info("Successfully loaded language: " + lang);
logger.info("Successfully loaded language: {}", lang);
} catch (Exception e) {
logger.warning("Failed to load language file for " + lang + "\n" + e);
if (e instanceof MalformedJsonException malformedJson) {
malformedJson.clean();
}
logger.warn("Failed to load language file for {}", lang, e);
if (retryTime > 0) {
cleanCache();
loadI18n(lang, retryTime - 1);
} else {
logger.severe("Failed to load for many times, use default lang \"en_us\" instead");
cleanCache();
logger.error("Failed to load for many times, use default lang \"en_us\" instead");
}
}
}
@@ -94,6 +130,10 @@ public class ServerI18nUtil {
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");
@@ -145,37 +185,31 @@ public class ServerI18nUtil {
fetchAndSave(versionUrl, versionPath);
}
private static String createHttpResponse(String path) throws IOException, InterruptedException {
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(path))
.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());
logger.info("Unexpected response code: {}", responseCode);
logger.info("Response body: {}", response.body());
throw new UnsupportedEncodingException("Unexpected response code");
} else {
return response.body();
return response.body().getBytes();
}
} catch (Exception e) {
logger.warning("Error in getting info: " + e.getMessage());
logger.warn("Error in getting info!");
throw e;
}
}
private static byte[] fetch(String urlString) throws IOException, InterruptedException {
String ret = createHttpResponse(urlString);
return ret.getBytes();
}
private static void fetchAndSave(String url, String savePath) throws IOException, InterruptedException {
byte[] data = fetch(url);
Path outputPath = Path.of(savePath);
@@ -183,23 +217,15 @@ public class ServerI18nUtil {
Files.write(outputPath, data, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
}
private static void cleanCache() {
try {
FileUtils.deleteDirectory(Path.of(BASE_PATH).toFile());
} catch (IOException e) {
logger.severe("Cache cleanup failed: " + e);
}
}
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.warning("Corrupt json file: " + e);
throw e;
logger.warn("Corrupt json file!");
throw new MalformedJsonException(e, path);
} catch (Exception e) {
logger.warning("Failed to load local JSON: " + e);
logger.warn("Failed to load local JSON!");
return null;
}
}
@@ -208,6 +234,7 @@ public class ServerI18nUtil {
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() {
@@ -237,16 +264,52 @@ public class ServerI18nUtil {
};
}
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 noSuchFileException) {
logger.warning("Couldn't find language file: " + langPath);
throw noSuchFileException;
} 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.warning("Failed to load language from filesystem " + filePath + "\n" + 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);
}
}
}
}
@@ -0,0 +1,141 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.util;
import fun.bm.lophine.config.modules.misc.RedStoneConfig;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.piston.PistonBaseBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.RailShape;
import java.util.List;
public class ShearsWrenchUtil {
public static InteractionResult tryApplyRotate(UseOnContext context, BlockState blockState) {
Level level = context.getLevel();
BlockPos clickedPos = context.getClickedPos();
Block block = blockState.getBlock();
if (!RedStoneConfig.shears || !(block instanceof ObserverBlock || block instanceof DispenserBlock || block instanceof PistonBaseBlock || block instanceof HopperBlock || block instanceof RepeaterBlock || block instanceof ComparatorBlock || block instanceof CrafterBlock || block instanceof LeverBlock || block instanceof CocoaBlock || block instanceof TrapDoorBlock || block instanceof FenceGateBlock || block instanceof LightningRodBlock || block instanceof CalibratedSculkSensorBlock || block instanceof BaseRailBlock)) {
return null;
}
StateDefinition<Block, BlockState> blockstatelist = block.getStateDefinition();
Property<?> iblockstate;
if (block instanceof CrafterBlock) {
iblockstate = blockstatelist.getProperty("orientation");
} else if (block instanceof BaseRailBlock) {
iblockstate = blockstatelist.getProperty("shape");
} else {
iblockstate = blockstatelist.getProperty("facing");
}
Player player = context.getPlayer();
if (iblockstate == null || player == null) {
return InteractionResult.FAIL;
}
if (block instanceof BaseRailBlock) {
if (block instanceof RailBlock) {
if (blockState.getValue(RailBlock.SHAPE).isSlope()) {
return InteractionResult.FAIL;
}
} else {
if (getNameHelper(blockState, PoweredRailBlock.POWERED).equals("true")) {
return InteractionResult.FAIL;
}
if (blockState.getValue(PoweredRailBlock.SHAPE).isSlope()) {
return InteractionResult.FAIL;
}
}
}
if (block instanceof PistonBaseBlock) {
if (getNameHelper(blockState, PistonBaseBlock.EXTENDED).equals("true")) {
return InteractionResult.FAIL;
}
}
if (block instanceof RepeaterBlock || block instanceof ComparatorBlock) {
if (getNameHelper(blockState, ComparatorBlock.POWERED).equals("true")) {
return InteractionResult.FAIL;
}
if (block instanceof RepeaterBlock) {
if (getNameHelper(blockState, RepeaterBlock.LOCKED).equals("true")) {
return InteractionResult.FAIL;
}
}
}
if (block instanceof CrafterBlock) {
if (getNameHelper(blockState, CrafterBlock.CRAFTING).equals("true")) {
return InteractionResult.FAIL;
}
}
BlockState iblockdata1 = cycleState(blockState, iblockstate, player.isSecondaryUseActive());
level.setBlock(clickedPos, iblockdata1, 18);
message(player, Component.translatable("item.minecraft.debug_stick.update", iblockstate.getName(), getNameHelper(iblockdata1, iblockstate)));
return InteractionResult.CONSUME;
}
public static boolean shouldSkipPlace(BlockPlaceContext context) {
return RedStoneConfig.shears &&
context.getPlayer() != null &&
context.getPlayer().getItemInHand(InteractionHand.MAIN_HAND).is(Items.SHEARS);
}
private static <T extends Comparable<T>> BlockState cycleState(BlockState state, Property<T> property, boolean inverse) {
List<T> possibleValues = property.getPossibleValues();
if (possibleValues.getFirst() instanceof RailShape) {
boolean isRailBlock = state.getBlock() instanceof RailBlock;
possibleValues = possibleValues.stream().filter(possibleValue -> {
RailShape shape = (RailShape) possibleValue;
if (isRailBlock) {
return !shape.isSlope();
}
return !shape.isSlope() && shape != RailShape.NORTH_EAST && shape != RailShape.NORTH_WEST && shape != RailShape.SOUTH_EAST && shape != RailShape.SOUTH_WEST;
}).toList();
}
return state.setValue(property, getRelative(possibleValues, state.getValue(property), inverse)); // CraftBukkit - decompile error
}
private static <T> T getRelative(Iterable<T> elements, T current, boolean inverse) {
return inverse ? Util.findPreviousInIterable(elements, current) : Util.findNextInIterable(elements, current);
}
private static void message(Player player, Component message) {
((ServerPlayer) player).sendSystemMessage(message, true);
}
private static <T extends Comparable<T>> String getNameHelper(BlockState state, Property<T> property) {
return property.getName(state.getValue(property));
}
}