@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user