[Feature] Update Checker

This commit is contained in:
Frank 2022-07-22 00:17:25 +02:00
parent 2ef9e51ef1
commit 69d472d107
21 changed files with 633 additions and 22 deletions

View file

@ -17,6 +17,7 @@ import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicate
import org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers;
import org.betterx.bclib.commands.CommandRegistry;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.networking.VersionChecker;
import org.betterx.bclib.recipes.AnvilRecipe;
import org.betterx.bclib.recipes.CraftingRecipes;
import org.betterx.bclib.registry.BaseBlockEntities;
@ -79,7 +80,8 @@ public class BCLib implements ModInitializer {
Configs.save();
WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET = Configs.SERVER_CONFIG.forceBetterXPreset();
VersionChecker.registerMod(MOD_ID);
if (false && isDevEnvironment()) {
BCLBiome theYellow = BCLBiomeBuilder
.start(makeID("the_yellow"))
@ -150,7 +152,6 @@ public class BCLib implements ModInitializer {
.surface(Blocks.PURPLE_CONCRETE)
.build();
BiomeAPI.registerNetherBiome(thePurple);
}
}

View file

@ -14,6 +14,7 @@ import org.betterx.bclib.interfaces.PostInitable;
import org.betterx.bclib.interfaces.RenderLayerProvider;
import org.betterx.bclib.interfaces.TagProvider;
import org.betterx.bclib.interfaces.tools.*;
import org.betterx.bclib.networking.VersionChecker;
import org.betterx.bclib.registry.BaseBlockEntities;
import org.betterx.worlds.together.tag.v3.MineableTags;
import org.betterx.worlds.together.tag.v3.TagManager;
@ -73,6 +74,8 @@ public class PostInitAPI {
itemTags = null;
InternalBiomeAPI.loadFabricAPIBiomes();
Configs.BIOMES_CONFIG.saveChanges();
VersionChecker.startCheck(isClient);
}
@Environment(EnvType.CLIENT)

View file

@ -0,0 +1,103 @@
package org.betterx.bclib.client.gui.screens;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.networking.VersionChecker;
import org.betterx.ui.ColorUtil;
import org.betterx.ui.layout.components.HorizontalStack;
import org.betterx.ui.layout.components.LayoutComponent;
import org.betterx.ui.layout.components.VerticalStack;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
@Environment(EnvType.CLIENT)
public class UpdatesScreen extends BCLibLayoutScreen {
public static final String DONATION_URL = "https://www.paypal.com/donate/?hosted_button_id=7VTXYRXBHZQZJ&item_name=BetterX%20Mods&cmd=_s-xclick";
public UpdatesScreen(Screen parent) {
super(parent, Component.translatable("bclib.updates.title"), 10, 10, 10);
}
@Override
protected LayoutComponent<?, ?> initContent() {
VerticalStack rows = new VerticalStack(relative(1), fit()).centerHorizontal();
rows.addMultilineText(fill(), fit(), Component.translatable("bclib.updates.description"))
.centerHorizontal();
rows.addSpacer(16);
VersionChecker.forEachUpdate((mod, cur, updated) -> {
ModContainer nfo = FabricLoader.getInstance().getModContainer(mod).orElse(null);
HorizontalStack row = rows.addRow(relative(0.8), fit()).centerHorizontal();
if (nfo != null) {
row.addText(fit(), fit(), Component.literal(nfo.getMetadata().getName()))
.setColor(ColorUtil.WHITE);
} else {
row.addText(fit(), fit(), Component.literal(mod)).setColor(ColorUtil.WHITE);
}
row.addSpacer(4);
row.addText(fit(), fit(), Component.literal(cur));
row.addText(fit(), fit(), Component.literal(" -> "));
row.addText(fit(), fit(), Component.literal(updated)).setColor(ColorUtil.GREEN);
row.addFiller();
if (nfo != null && nfo.getMetadata().getContact().get("homepage").isPresent()) {
row.addButton(fit(), fit(), Component.translatable("bclib.updates.curseforge_link"))
.onPress((bt) -> {
this.openLink(nfo.getMetadata().getContact().get("homepage").get());
});
}
});
VerticalStack layout = new VerticalStack(relative(1), fill()).centerHorizontal();
layout.addSpacer(8);
layout.addScrollable(rows);
layout.addSpacer(8);
HorizontalStack footer = layout.addRow(fill(), fit());
if (Configs.CLIENT_CONFIG.isDonor()) {
footer.addButton(
fit(),
fit(),
Component.translatable("bclib.updates.donate").setStyle(Style.EMPTY.withColor(ColorUtil.YELLOW))
)
.onPress((bt) -> openLink(DONATION_URL));
footer.addSpacer(2);
footer.addMultilineText(fit(), fit(), Component.translatable("bclib.updates.donate_pre"))
.alignBottom();
}
footer.addFiller();
footer.addCheckbox(
fit(),
fit(),
Component.translatable("Disable Check"),
!Configs.MAIN_CONFIG.checkVersions()
)
.onChange((cb, state) -> {
Configs.MAIN_CONFIG.setCheckVersions(!state);
Configs.MAIN_CONFIG.saveChanges();
});
footer.addSpacer(4);
footer.addButton(fit(), fit(), CommonComponents.GUI_DONE).onPress((bt -> {
onClose();
}));
return layout;
}
@Override
protected void renderBackground(PoseStack poseStack, int i, int j, float f) {
GuiComponent.fill(poseStack, 0, 0, width, height, 0xBD343444);
}
}

View file

@ -0,0 +1,55 @@
package org.betterx.bclib.client.gui.screens;
import org.betterx.bclib.config.Configs;
import org.betterx.ui.ColorUtil;
import org.betterx.ui.layout.components.*;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
public class WelcomeScreen extends BCLibLayoutScreen {
public WelcomeScreen(Screen parent) {
super(parent, translatable("bclib.welcome.title"));
}
@Override
protected LayoutComponent<?, ?> initContent() {
VerticalStack content = new VerticalStack(fill(), fit()).setDebugName("content");
content.addMultilineText(fill(), fit(), MultiLineText.parse(translatable("bclib.welcome.description")))
.centerHorizontal();
if (Configs.CLIENT_CONFIG.isDonor()) {
content.addHorizontalSeparator(48);
HorizontalStack donationRow = content.addRow(relative(0.9), fit())
.setDebugName("donationRow")
.centerHorizontal();
donationRow.addMultilineText(fill(), fit(), translatable("bclib.welcome.donation"))
.alignLeft()
.alignRight();
donationRow.addSpacer(4);
donationRow.addButton(
fit(), fit(),
Component.translatable("bclib.updates.donate").setStyle(Style.EMPTY.withColor(ColorUtil.YELLOW))
)
.onPress((bt) -> openLink(UpdatesScreen.DONATION_URL)).centerVertical();
}
content.addHorizontalSeparator(48);
content.addCheckbox(fit(), fit(), translatable("bclib.welcome.updater.title"), true)
.onChange((cb, state) -> Configs.MAIN_CONFIG.setCheckVersions(state));
content.addSpacer(2);
content.indent(24)
.addMultilineText(fill(), fit(), translatable("bclib.welcome.updater.description"))
.setColor(ColorUtil.GRAY);
content.addSpacer(16);
content.addButton(fit(), fit(), CommonComponents.GUI_PROCEED).onPress((bt) -> {
Configs.MAIN_CONFIG.setDidShowWelcomeScreen();
onClose();
}).alignRight();
return VerticalScroll.create(fill(), fill(), content);
}
}

View file

@ -53,7 +53,10 @@ public class CommandRegistry {
.then(Commands.literal("dimensions")
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS))
.executes(ctx -> PrintInfo.printDimensions(ctx))
)
).then(Commands.literal("updates")
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS))
.executes(ctx -> PrintInfo.printUpdates(ctx, true))
)
)
.then(Commands.literal("debug_ore")
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS))

View file

@ -1,14 +1,24 @@
package org.betterx.bclib.commands;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.client.gui.screens.UpdatesScreen;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.networking.VersionChecker;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.world.level.Level;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
public class PrintInfo {
static int printDimensions(CommandContext<CommandSourceStack> ctx) {
@ -37,4 +47,70 @@ public class PrintInfo {
ctx.getSource().sendSuccess(result, false);
return Command.SINGLE_SUCCESS;
}
static int printUpdates(CommandContext<CommandSourceStack> ctx, boolean withUI) {
boolean hasOne = false;
MutableComponent header = Component.literal("Mod Updates:")
.setStyle(Style.EMPTY.withBold(false)
.withColor(ChatFormatting.WHITE));
ctx.getSource().sendSuccess(header, false);
VersionChecker.forEachUpdate((mod, cur, updated) -> {
ModContainer nfo = FabricLoader.getInstance().getModContainer(mod).orElse(null);
MutableComponent result = Component.literal(" - ")
.setStyle(Style.EMPTY.withBold(false)
.withUnderlined(false)
.withColor(ChatFormatting.WHITE));
if (nfo != null)
result.append(Component.literal(nfo.getMetadata().getName())
.setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE)));
else
result.append(Component.literal(mod)
.setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE)));
result.append(Component.literal(": ")
.setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE)));
result.append(Component.literal(cur).setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE)));
result.append(Component.literal(" -> ")
.setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE)));
if (nfo != null && nfo.getMetadata().getContact().get("homepage").isPresent()) {
var ce = new ClickEvent(
ClickEvent.Action.OPEN_URL,
nfo.getMetadata().getContact().get("homepage").get()
);
result.append(Component.literal(updated)
.setStyle(Style.EMPTY.withClickEvent(ce)
.withBold(false)
.withItalic(true)
.withColor(ChatFormatting.GREEN)));
result.append(Component.literal(" ")
.setStyle(Style.EMPTY.withClickEvent(ce).withBold(true).withItalic(false)));
result = result.append(Component.literal("[CurseForge]")
.setStyle(Style.EMPTY.withClickEvent(ce)
.withBold(true)
.withColor(ChatFormatting.GREEN)
.withUnderlined(true)));
} else {
result.append(Component.literal(updated)
.setStyle(Style.EMPTY.withBold(false)
.withItalic(true)
.withColor(ChatFormatting.WHITE)));
result.append(Component.literal(" ").setStyle(Style.EMPTY.withBold(true).withItalic(false)));
}
ctx.getSource().sendSuccess(result, false);
});
MutableComponent footer = Component.literal("\n")
.setStyle(Style.EMPTY.withBold(false)
.withUnderlined(true)
.withColor(ChatFormatting.WHITE));
ctx.getSource().sendSuccess(footer, false);
if (withUI && BCLib.isClient() && Configs.CLIENT_CONFIG.showUpdateInfo() && !VersionChecker.isEmpty()) {
Minecraft.getInstance().setScreen(new UpdatesScreen(Minecraft.getInstance().screen));
}
return Command.SINGLE_SUCCESS;
}
}

View file

@ -26,7 +26,7 @@ public class BiomesConfig extends PathConfig {
private static final BiomeAPI.BiomeType[] excludeTypes = {BiomeAPI.BiomeType.NETHER, BiomeAPI.BiomeType.END};
public BiomesConfig() {
super(BCLib.MOD_ID, "biomes", false);
super(BCLib.MOD_ID, "biomes", true);
for (var type : includeTypes) {
keeper.registerEntry(
new ConfigKey(type.getName(), "force_include"),

View file

@ -0,0 +1,58 @@
package org.betterx.bclib.config;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.networking.VersionChecker;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
public class CachedConfig extends NamedPathConfig {
@ConfigUI(hide = true)
public static final ConfigToken<String> LAST_CHECK_DATE = ConfigToken.String(
"never",
"last",
"version"
);
@ConfigUI(hide = true)
public static final ConfigToken<String> LAST_JSON = ConfigToken.String(
"",
"cached",
"version"
);
public CachedConfig() {
super(BCLib.MOD_ID, "cache", false, false);
}
public String lastVersionJson() {
byte[] decodedBytes = Base64.getUrlDecoder().decode(get(LAST_JSON));
return new String(decodedBytes, StandardCharsets.UTF_8);
}
public void setLastVersionJson(String json) {
set(LAST_JSON, Base64.getUrlEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)));
}
public Instant lastCheckDate() {
String d = get(LAST_CHECK_DATE);
if (d.trim().toLowerCase().equals("never")) {
return Instant.now().minus(VersionChecker.WAIT_FOR_DAYS + 1, ChronoUnit.DAYS);
}
return Instant.parse(d);
}
public void setLastCheckDate() {
set(LAST_CHECK_DATE, Instant.now().toString());
}
@Override
public void saveChanges() {
synchronized (this) {
super.saveChanges();
}
}
}

View file

@ -31,11 +31,10 @@ public class ClientConfig extends NamedPathConfig {
);
@ConfigUI(leftPadding = 8)
public static final DependendConfigToken<Boolean> ACCEPT_MODS = DependendConfigToken.Boolean(
false,
true,
"acceptMods",
AutoSync.SYNC_CATEGORY,
(config) -> config.get(
ENABLED)
(config) -> config.get(ENABLED)
);
@ConfigUI(leftPadding = 8)
public static final DependendConfigToken<Boolean> DISPLAY_MOD_INFO = DependendConfigToken.Boolean(
@ -72,6 +71,19 @@ public class ClientConfig extends NamedPathConfig {
"rendering"
);
public static final ConfigToken<Boolean> SHOW_UPDATE_INFO = ConfigToken.Boolean(
true,
"showUpdateInfo",
"ui"
);
@ConfigUI(leftPadding = 8)
public static final ConfigToken<Boolean> NO_DONOR = ConfigToken.Boolean(
false,
"no_donor",
"version"
);
public ClientConfig() {
super(BCLib.MOD_ID, "client", false);
}
@ -112,6 +124,14 @@ public class ClientConfig extends NamedPathConfig {
return get(CUSTOM_FOG_RENDERING);
}
public boolean showUpdateInfo() {
return get(SHOW_UPDATE_INFO);
}
public boolean isDonor() {
return !get(NO_DONOR);
}
public float fogDensity() {
return get(FOG_DENSITY);
}

View file

@ -14,6 +14,7 @@ public class Configs {
public static final GeneratorConfig GENERATOR_CONFIG = new GeneratorConfig();
public static final MainConfig MAIN_CONFIG = new MainConfig();
public static final CachedConfig CACHED_CONFIG = new CachedConfig();
public static final PathConfig RECIPE_CONFIG = new PathConfig(BCLib.MOD_ID, "recipes");
public static final BiomesConfig BIOMES_CONFIG = new BiomesConfig();

View file

@ -4,7 +4,7 @@ import org.betterx.bclib.BCLib;
public class GeneratorConfig extends NamedPathConfig {
public GeneratorConfig() {
super(BCLib.MOD_ID, "generator", false);
super(BCLib.MOD_ID, "generator", true);
}
}

View file

@ -18,6 +18,22 @@ public class MainConfig extends NamedPathConfig {
APPLY_PATCHES)
);
@ConfigUI(hide = true)
public static final ConfigToken<Boolean> DID_SHOW_WELCOME = ConfigToken.Boolean(
false,
"did_show_welcome",
"version"
);
public static final ConfigToken<Boolean> CHECK_VERSIONS = DependendConfigToken.Boolean(
true,
"check",
"version",
(config) -> !config.get(DID_SHOW_WELCOME)
);
public MainConfig() {
super(BCLib.MOD_ID, "main", true, true);
}
@ -29,4 +45,21 @@ public class MainConfig extends NamedPathConfig {
public boolean repairBiomes() {
return get(REPAIR_BIOMES);
}
public boolean checkVersions() {
return get(CHECK_VERSIONS);
}
public boolean didShowWelcomeScreen() {
return get(DID_SHOW_WELCOME);
}
public void setDidShowWelcomeScreen() {
set(DID_SHOW_WELCOME, true);
}
public void setCheckVersions(boolean newValue) {
set(CHECK_VERSIONS, newValue);
}
}

View file

@ -12,8 +12,7 @@ public class ServerConfig extends NamedPathConfig {
true,
"offerConfigs",
AutoSync.SYNC_CATEGORY,
(config) -> config.get(
ENABLED)
(config) -> config.get(ENABLED)
);
public static final DependendConfigToken<Boolean> OFFER_FILES = DependendConfigToken.Boolean(
true,

View file

@ -1,10 +1,12 @@
package org.betterx.bclib.mixin.client;
import org.betterx.bclib.interfaces.CustomColorProvider;
import org.betterx.bclib.networking.VersionCheckerClient;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.main.GameConfig;
import net.minecraft.core.Registry;
@ -15,6 +17,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.jetbrains.annotations.Nullable;
@Mixin(Minecraft.class)
public abstract class MinecraftMixin {
@Final
@ -25,6 +29,11 @@ public abstract class MinecraftMixin {
@Shadow
private ItemColors itemColors;
@Shadow
@Nullable
public Screen screen;
@Inject(method = "<init>*", at = @At("TAIL"))
private void bclib_onMCInit(GameConfig args, CallbackInfo info) {
Registry.BLOCK.forEach(block -> {
@ -33,5 +42,7 @@ public abstract class MinecraftMixin {
itemColors.register(provider.getItemProvider(), block.asItem());
}
});
VersionCheckerClient.presentUpdateScreen(screen);
}
}

View file

@ -0,0 +1,149 @@
package org.betterx.bclib.networking;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.config.Configs;
import org.betterx.worlds.together.util.ModUtil;
import com.google.gson.Gson;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.LinkedList;
import java.util.List;
public class VersionChecker implements Runnable {
@FunctionalInterface
public interface UpdateInfoProvider {
void send(String modID, String currentVersion, String newVersion);
}
private static final List<String> KNOWN_MODS = new LinkedList<>();
private static final List<ModVersion> NEW_VERSIONS = new LinkedList<>();
public static class ModVersion {
String n;
String v;
@Override
public String toString() {
return n + ":" + v;
}
}
public static class Versions {
String mc;
String loader;
List<ModVersion> mods;
@Override
public String toString() {
return "Versions{" +
"mc='" + mc + '\'' +
", loader='" + loader + '\'' +
", mods=" + mods +
'}';
}
}
public static final int WAIT_FOR_DAYS = 5;
private static final String BASE_URL = "https://wunderreich.ambertation.de/api/v1/versions/";
private static Thread versionChecker;
public static void startCheck(boolean isClient) {
if (versionChecker == null) {
if (Configs.MAIN_CONFIG.checkVersions()) {
versionChecker = new Thread(isClient ? new VersionCheckerClient() : new VersionChecker());
versionChecker.start();
}
}
}
public static void registerMod(String modID) {
KNOWN_MODS.add(modID);
}
boolean needRecheck() {
Instant lastCheck = Configs.CACHED_CONFIG.lastCheckDate().plus(WAIT_FOR_DAYS, ChronoUnit.DAYS);
Instant now = Instant.now();
return now.isAfter(lastCheck);
}
@Override
public void run() {
Gson gson = new Gson();
if (needRecheck()) {
String minecraftVersion = ModUtil.getModVersion("minecraft").replace(".", "_");
BCLib.LOGGER.info("Check Versions for minecraft=" + minecraftVersion);
try {
String fileName = "mc_fabric_" + URLEncoder.encode(
minecraftVersion,
StandardCharsets.ISO_8859_1.toString()
) + ".json";
URL url = new URL(BASE_URL + fileName);
try (InputStreamReader reader = new InputStreamReader(url.openStream())) {
Versions json = gson.fromJson(reader, Versions.class);
String str = gson.getAdapter(Versions.class).toJson(json);
Configs.CACHED_CONFIG.setLastVersionJson(str);
Configs.CACHED_CONFIG.setLastCheckDate();
Configs.CACHED_CONFIG.saveChanges();
processVersions(json);
}
} catch (UnsupportedEncodingException e) {
BCLib.LOGGER.error("Failed to encode URL during VersionCheck", e);
return;
} catch (MalformedURLException e) {
BCLib.LOGGER.error("Invalid URL during VersionCheck", e);
return;
} catch (IOException e) {
BCLib.LOGGER.error("I/O Error during VersionCheck", e);
return;
}
} else {
String str = Configs.CACHED_CONFIG.lastVersionJson();
if (str != null && str.trim().length() > 0) {
Versions json = gson.fromJson(str, Versions.class);
processVersions(json);
}
}
}
private void processVersions(Versions json) {
if (json != null) {
BCLib.LOGGER.info("Received Version Info for minecraft=" + json.mc + ", loader=" + json.loader);
if (json.mods != null) {
for (ModVersion mod : json.mods) {
if (mod.n != null && mod.v != null && KNOWN_MODS.contains(mod.n)) {
boolean isNew = ModUtil.isLargerVersion(mod.v, ModUtil.getModVersion(mod.n));
BCLib.LOGGER.info(" - " + mod.n + ":" + mod.v + (isNew ? " (update available)" : ""));
if (isNew)
NEW_VERSIONS.add(mod);
}
}
}
} else {
BCLib.LOGGER.warning("No valid Version Info");
}
}
public static boolean isEmpty() {
return NEW_VERSIONS.isEmpty();
}
public static void forEachUpdate(UpdateInfoProvider consumer) {
for (ModVersion v : NEW_VERSIONS) {
String currrent = ModUtil.getModVersion(v.n);
consumer.send(v.n, currrent, v.v);
}
}
}

View file

@ -0,0 +1,24 @@
package org.betterx.bclib.networking;
import org.betterx.bclib.client.gui.screens.UpdatesScreen;
import org.betterx.bclib.client.gui.screens.WelcomeScreen;
import org.betterx.bclib.config.Configs;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment(EnvType.CLIENT)
public class VersionCheckerClient extends VersionChecker {
public static void presentUpdateScreen(Screen parent) {
if (!Configs.MAIN_CONFIG.didShowWelcomeScreen()) {
Minecraft.getInstance().setScreen(new WelcomeScreen(parent));
} else if (Configs.CLIENT_CONFIG.showUpdateInfo() && !VersionChecker.isEmpty()) {
Minecraft.getInstance().setScreen(new UpdatesScreen(parent));
}
}
}

View file

@ -10,12 +10,24 @@ import org.betterx.ui.layout.values.Value;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.components.MultiLineLabel;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.util.FormattedCharSequence;
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.List;
record LineWithWidth(FormattedCharSequence text, int width) {
}
public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRenderer, MultiLineText> {
net.minecraft.network.chat.Component text;
int color = ColorUtil.DEFAULT_TEXT;
protected MultiLineLabel multiLineLabel;
int bufferedContentWidth = 0;
List<LineWithWidth> lines = List.of();
public MultiLineText(
Value width,
@ -33,10 +45,25 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
return this;
}
public static Component parse(Component text) {
String[] parts = text.getString().split("\\*\\*");
if (parts.length > 0) {
boolean bold = false;
MutableComponent c = Component.literal(parts[0]);
for (int i = 1; i < parts.length; i++) {
bold = !bold;
c.append(Component.literal(parts[i]).setStyle(Style.EMPTY.withBold(bold)));
}
return c;
}
return text;
}
public MultiLineText setText(Component text) {
this.text = text;
this.updatedContentWidth();
if (multiLineLabel != null) {
multiLineLabel = createVanillaComponent();
}
@ -45,11 +72,14 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
}
protected MultiLineLabel createVanillaComponent() {
return MultiLineLabel.create(
renderer.getFont(),
text,
relativeBounds == null ? width.calculatedSize() : relativeBounds.width
);
final int wd = relativeBounds == null ? width.calculatedSize() : relativeBounds.width;
lines = renderer.getFont()
.split(text, wd)
.stream()
.map((component) -> new LineWithWidth(component, renderer.getFont().width(component)))
.collect(ImmutableList.toImmutableList());
return MultiLineLabel.create(renderer.getFont(), text, wd);
}
protected void updatedContentWidth() {
@ -108,7 +138,6 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
Rectangle clipRect
) {
if (linkedComponent != null && linkedComponent.multiLineLabel != null) {
int top = bounds.height - getHeight(linkedComponent.text);
if (linkedComponent.vAlign == Alignment.MIN) top = 0;
if (linkedComponent.vAlign == Alignment.CENTER) top /= 2;
@ -119,6 +148,20 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
getLineHeight(linkedComponent.text),
linkedComponent.color
);
} else if (linkedComponent.hAlign == Alignment.MAX) {
int lineY = 0;
int lineHeight = getLineHeight(linkedComponent.text);
for (Iterator<LineWithWidth> iter = linkedComponent.lines.iterator(); iter.hasNext(); lineY += lineHeight) {
LineWithWidth textWithWidth = iter.next();
getFont().drawShadow(
stack,
textWithWidth.text(),
linkedComponent.width.calculatedSize() - textWithWidth.width(),
lineY,
linkedComponent.color
);
}
} else {
linkedComponent.multiLineLabel.renderLeftAligned(
stack, 0, top,

View file

@ -75,7 +75,7 @@ public class Text extends LayoutComponent<Text.TextRenderer, Text> {
int top = bounds.height - getLineHeight(linkedComponent.text);
if (linkedComponent.vAlign == Alignment.MIN) top = 0;
if (linkedComponent.vAlign == Alignment.CENTER) top /= 2;
if (linkedComponent.vAlign == Alignment.CENTER) top = top / 2 + 1;
GuiComponent.drawString(stack, getFont(), linkedComponent.text, left, top, linkedComponent.color);
}

View file

@ -10,6 +10,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.ConfirmLinkScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@ -138,4 +139,12 @@ public abstract class LayoutScreen extends Screen {
public static Value relative(double percentage) {
return Value.relative(percentage);
}
public static MutableComponent translatable(String key) {
return Component.translatable(key);
}
public static MutableComponent literal(String content) {
return Component.literal(content);
}
}