feat: old replaceable by mushrooms (#10)

Update upstream (Luminol)
This commit is contained in:
Helvetica Volubi
2025-06-18 16:50:23 +08:00
parent 16f2258eed
commit 71455cca64
49 changed files with 1442 additions and 1439 deletions
@@ -0,0 +1,251 @@
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 fun.bm.lophine.config.modules.optimizations.LanguageConfig;
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.apache.commons.io.FileUtils;
import org.jetbrains.annotations.NotNull;
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.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>
* modified by: Lumine1909 <133463833+Lumine1909@users.noreply.github.com>
* Some of diff form Leaves
*/
public class ServerI18nUtil {
private static final Logger logger = Logger.getLogger("LangLoader");
private static final String VERSION = "1.21.5";
private static final String BASE_PATH = "cache/lophine/" + VERSION + "/";
private static final String manifestUrl = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String resourceBaseUrl = "https://resources.download.minecraft.net/";
private static String langPath;
private static String assetsPath;
private static String versionPath;
private static String manifestPath;
private static String langJsonPath;
public static void init() {
if (Objects.equals(LanguageConfig.lang, "en_us")) {
return;
}
langPath = BASE_PATH + "lang/" + LanguageConfig.lang + ".json";
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));
}
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) {
logger.warning("Failed to load language file for " + lang + "\n" + 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();
}
}
}
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);
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 String createHttpResponse(String path) throws IOException, InterruptedException {
try {
HttpResponse<String> response;
try (HttpClient httpClient = HttpClient.newHttpClient()) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(path))
.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();
}
} catch (Exception e) {
logger.warning("Error in getting info: " + e.getMessage());
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);
Files.createDirectories(outputPath.getParent());
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;
} catch (Exception e) {
logger.warning("Failed to load local JSON: " + e);
return null;
}
}
private static Language createLangInstance() throws IOException {
DeprecatedTranslationsInfo deprecatedTranslationsInfo = DeprecatedTranslationsInfo.loadFromDefaultResource();
Map<String, String> map = new HashMap<>();
parseTranslations(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 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 (Exception e) {
logger.warning("Failed to load language from filesystem " + filePath + "\n" + e);
throw e;
}
}
}
@@ -0,0 +1,181 @@
package fun.bm.lophine.utils;
import fun.bm.lophine.config.modules.misc.ContainerExpansionConfig;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.ItemContainerContents;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
public class ShulkerBoxesUtil {
// Better ShulkerBoxes used
public static HashMap<Player, ShulkerBoxBlockEntity> shulkerMap = new HashMap<>();
public static HashMap<ShulkerBoxBlockEntity, Player> playerMap = new HashMap<>();
// Stackable ShulkerBoxes part
public static boolean shouldCheck() {
return ContainerExpansionConfig.shulkerCount > 1 && ContainerExpansionConfig.shulkerCount <= 64;
}
public static boolean checkShulkerBox(ItemStack itemStack) {
return shouldCheck() && checkIsShulkerBox(itemStack);
}
public static boolean checkIsShulkerBox(ItemStack itemStack) {
return itemStack.getItem() instanceof BlockItem b && b.getBlock() instanceof ShulkerBoxBlock;
}
public static int getItemMaxCount(ItemStack itemStack) {
if (checkShulkerBox(itemStack)) {
return Math.clamp(ContainerExpansionConfig.shulkerCount, 1, 64);
}
return itemStack.getMaxStackSize();
}
public static int getShulkerBoxesMaxCountUnsafe() {
return Math.clamp(ContainerExpansionConfig.shulkerCount, 1, 64);
}
public static boolean emptyShulkerBoxCheck(@NotNull ItemStack stack) {
return stack.getComponents().getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY).stream().findAny().isEmpty();
}
public static boolean isStackable(ItemStack itemStack) {
return getItemMaxCount(itemStack) > 1 && (!itemStack.isDamageableItem() || !itemStack.isDamaged());
}
public static int getItemStackMaxCountReal(ItemStack stack) {
CompoundTag nbt = Optional.ofNullable(stack.get(DataComponents.CUSTOM_DATA)).orElse(CustomData.EMPTY).copyTag();
return nbt.getInt("Lophine.RealStackSize").orElse(stack.getMaxStackSize());
}
public static ItemStack encodeMaxStackSize(ItemStack itemStack) {
int realMaxStackSize = getItemStackMaxCountReal(itemStack);
int modifiedMaxStackSize = getItemMaxCount(itemStack);
if (itemStack.getMaxStackSize() != modifiedMaxStackSize) {
itemStack.set(DataComponents.MAX_STACK_SIZE, modifiedMaxStackSize);
CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
nbt.putInt("Lophine.RealStackSize", realMaxStackSize);
itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
}
return itemStack;
}
public static ItemStack decodeMaxStackSize(ItemStack itemStack) {
int realMaxStackSize = getItemStackMaxCountReal(itemStack);
if (itemStack.getMaxStackSize() != realMaxStackSize) {
itemStack.set(DataComponents.MAX_STACK_SIZE, realMaxStackSize);
CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
nbt.remove("Lophine.RealStackSize");
if (nbt.isEmpty()) {
itemStack.remove(DataComponents.CUSTOM_DATA);
} else {
itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
}
}
return itemStack;
}
// Better ShulkerBox part
public static boolean checkIfCanOpen(ItemStack itemStack) {
return checkIsShulkerBox(itemStack) && itemStack.getCount() == 1;
}
public static boolean checkIfValid(Player player) {
return shulkerMap.get(player) != null;
}
public static void openShulkerBox(Player player, ItemStack item, InteractionHand hand) {
ItemStack itemInHand = item.copy();
ShulkerBoxBlockEntity shulkerBoxEntity = new ShulkerBoxBlockEntity(
player.blockPosition(),
((BlockItem) itemInHand.getItem()).getBlock().defaultBlockState()
);
ItemContainerContents container = itemInHand.getOrDefault(
DataComponents.CONTAINER,
ItemContainerContents.EMPTY
);
NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);
for (int i = 0; i < container.items.size(); i++) {
items.set(i, container.items.get(i));
}
shulkerBoxEntity.setItems(items);
shulkerBoxEntity.haveRealBlock = false;
shulkerBoxEntity.shulkerHand = hand;
shulkerBoxEntity.finalItem = itemInHand;
shulkerBoxEntity.setLevel(player.level());
shulkerMap.put(player, shulkerBoxEntity);
playerMap.put(shulkerBoxEntity, player);
if (player.openMenu(shulkerBoxEntity).isPresent()) {
player.awardStat(Stats.OPEN_SHULKER_BOX);
}
}
public static void shulkerBoxEntityCallBack(ShulkerBoxBlockEntity shulkerBoxEntity) {
Player player = playerMap.get(shulkerBoxEntity);
if (player != null) {
InteractionHand hand = shulkerBoxEntity.shulkerHand;
ItemStack currentItem = player.getItemInHand(hand);
ItemStack copy = currentItem.copy();
copy.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(shulkerBoxEntity.getItems()));
player.setItemInHand(hand, copy);
shulkerBoxEntity.finalItem = copy;
}
}
public static void inventoryCallBack(boolean isMainHand, Player player) {
ShulkerBoxBlockEntity entity = shulkerMap.get(player);
if (entity != null && isMainHand == Objects.equals(entity.shulkerHand, InteractionHand.MAIN_HAND)) {
closeScreen(entity, player);
}
}
public static void closeScreen(ShulkerBoxBlockEntity entity, Player player) {
if (player instanceof ServerPlayer serverPlayer) {
ItemStack stack1 = entity.finalItem;
ItemStack stack2 = player.containerMenu.getCarried();
ItemStack stack = stack2.isEmpty() ? stack1 : stack2;
if (!stack.isEmpty()) {
if (!player.isAlive() || serverPlayer.hasDisconnected()) {
player.drop(stack, false);
} else {
player.getInventory().placeItemBackInInventory(stack);
}
}
}
player.closeContainer();
}
public static void clearMap(Player p) {
ShulkerBoxBlockEntity e = shulkerMap.get(p);
if (e != null) playerMap.remove(e);
shulkerMap.remove(p);
}
public static void clearMap(ShulkerBoxBlockEntity e) {
Player p = playerMap.get(e);
if (p != null) shulkerMap.remove(p);
playerMap.remove(e);
}
}