diff --git a/src/main/java/org/betterx/bclib/BCLib.java b/src/main/java/org/betterx/bclib/BCLib.java index e212bf9f..9a5b183c 100644 --- a/src/main/java/org/betterx/bclib/BCLib.java +++ b/src/main/java/org/betterx/bclib/BCLib.java @@ -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); - } } diff --git a/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java b/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java index 36b49f43..d0a32893 100644 --- a/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java +++ b/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java @@ -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) diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/UpdatesScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/UpdatesScreen.java new file mode 100644 index 00000000..76363263 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/UpdatesScreen.java @@ -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); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/WelcomeScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/WelcomeScreen.java new file mode 100644 index 00000000..57a4eaf2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/WelcomeScreen.java @@ -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); + } +} diff --git a/src/main/java/org/betterx/bclib/commands/CommandRegistry.java b/src/main/java/org/betterx/bclib/commands/CommandRegistry.java index b5a87765..13177749 100644 --- a/src/main/java/org/betterx/bclib/commands/CommandRegistry.java +++ b/src/main/java/org/betterx/bclib/commands/CommandRegistry.java @@ -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)) diff --git a/src/main/java/org/betterx/bclib/commands/PrintInfo.java b/src/main/java/org/betterx/bclib/commands/PrintInfo.java index 4fdba12f..7cc0b155 100644 --- a/src/main/java/org/betterx/bclib/commands/PrintInfo.java +++ b/src/main/java/org/betterx/bclib/commands/PrintInfo.java @@ -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 ctx) { @@ -37,4 +47,70 @@ public class PrintInfo { ctx.getSource().sendSuccess(result, false); return Command.SINGLE_SUCCESS; } + + static int printUpdates(CommandContext 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; + } } diff --git a/src/main/java/org/betterx/bclib/config/BiomesConfig.java b/src/main/java/org/betterx/bclib/config/BiomesConfig.java index 30c185da..ea73ba70 100644 --- a/src/main/java/org/betterx/bclib/config/BiomesConfig.java +++ b/src/main/java/org/betterx/bclib/config/BiomesConfig.java @@ -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"), diff --git a/src/main/java/org/betterx/bclib/config/CachedConfig.java b/src/main/java/org/betterx/bclib/config/CachedConfig.java new file mode 100644 index 00000000..07604a0e --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/CachedConfig.java @@ -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 LAST_CHECK_DATE = ConfigToken.String( + "never", + "last", + "version" + ); + + @ConfigUI(hide = true) + public static final ConfigToken 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(); + } + } +} diff --git a/src/main/java/org/betterx/bclib/config/ClientConfig.java b/src/main/java/org/betterx/bclib/config/ClientConfig.java index c93dcf73..2f021e76 100644 --- a/src/main/java/org/betterx/bclib/config/ClientConfig.java +++ b/src/main/java/org/betterx/bclib/config/ClientConfig.java @@ -31,11 +31,10 @@ public class ClientConfig extends NamedPathConfig { ); @ConfigUI(leftPadding = 8) public static final DependendConfigToken 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 DISPLAY_MOD_INFO = DependendConfigToken.Boolean( @@ -72,6 +71,19 @@ public class ClientConfig extends NamedPathConfig { "rendering" ); + public static final ConfigToken SHOW_UPDATE_INFO = ConfigToken.Boolean( + true, + "showUpdateInfo", + "ui" + ); + + @ConfigUI(leftPadding = 8) + public static final ConfigToken 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); } diff --git a/src/main/java/org/betterx/bclib/config/Configs.java b/src/main/java/org/betterx/bclib/config/Configs.java index 5670759c..b83926b6 100644 --- a/src/main/java/org/betterx/bclib/config/Configs.java +++ b/src/main/java/org/betterx/bclib/config/Configs.java @@ -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(); diff --git a/src/main/java/org/betterx/bclib/config/GeneratorConfig.java b/src/main/java/org/betterx/bclib/config/GeneratorConfig.java index 48091087..250542b9 100644 --- a/src/main/java/org/betterx/bclib/config/GeneratorConfig.java +++ b/src/main/java/org/betterx/bclib/config/GeneratorConfig.java @@ -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); } } diff --git a/src/main/java/org/betterx/bclib/config/MainConfig.java b/src/main/java/org/betterx/bclib/config/MainConfig.java index 685b2994..70c74eb5 100644 --- a/src/main/java/org/betterx/bclib/config/MainConfig.java +++ b/src/main/java/org/betterx/bclib/config/MainConfig.java @@ -18,6 +18,22 @@ public class MainConfig extends NamedPathConfig { APPLY_PATCHES) ); + + @ConfigUI(hide = true) + public static final ConfigToken DID_SHOW_WELCOME = ConfigToken.Boolean( + false, + "did_show_welcome", + "version" + ); + + public static final ConfigToken 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); + } } diff --git a/src/main/java/org/betterx/bclib/config/ServerConfig.java b/src/main/java/org/betterx/bclib/config/ServerConfig.java index 92ae389d..86ebdae8 100644 --- a/src/main/java/org/betterx/bclib/config/ServerConfig.java +++ b/src/main/java/org/betterx/bclib/config/ServerConfig.java @@ -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 OFFER_FILES = DependendConfigToken.Boolean( true, diff --git a/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java b/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java index 90679b0e..d306bfa9 100644 --- a/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java +++ b/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java @@ -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 = "*", 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); } } diff --git a/src/main/java/org/betterx/bclib/networking/VersionChecker.java b/src/main/java/org/betterx/bclib/networking/VersionChecker.java new file mode 100644 index 00000000..43652a65 --- /dev/null +++ b/src/main/java/org/betterx/bclib/networking/VersionChecker.java @@ -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 KNOWN_MODS = new LinkedList<>(); + private static final List 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 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); + } + } +} diff --git a/src/main/java/org/betterx/bclib/networking/VersionCheckerClient.java b/src/main/java/org/betterx/bclib/networking/VersionCheckerClient.java new file mode 100644 index 00000000..553a300d --- /dev/null +++ b/src/main/java/org/betterx/bclib/networking/VersionCheckerClient.java @@ -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)); + } + } +} diff --git a/src/main/java/org/betterx/ui/layout/components/MultiLineText.java b/src/main/java/org/betterx/ui/layout/components/MultiLineText.java index 744eda2d..e76b91f7 100644 --- a/src/main/java/org/betterx/ui/layout/components/MultiLineText.java +++ b/src/main/java/org/betterx/ui/layout/components/MultiLineText.java @@ -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 { net.minecraft.network.chat.Component text; int color = ColorUtil.DEFAULT_TEXT; protected MultiLineLabel multiLineLabel; int bufferedContentWidth = 0; + List lines = List.of(); public MultiLineText( Value width, @@ -33,10 +45,25 @@ public class MultiLineText extends LayoutComponent 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 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 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, diff --git a/src/main/java/org/betterx/ui/layout/components/Text.java b/src/main/java/org/betterx/ui/layout/components/Text.java index 5cede707..06047167 100644 --- a/src/main/java/org/betterx/ui/layout/components/Text.java +++ b/src/main/java/org/betterx/ui/layout/components/Text.java @@ -75,7 +75,7 @@ public class Text extends LayoutComponent { 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); } diff --git a/src/main/java/org/betterx/ui/vanilla/LayoutScreen.java b/src/main/java/org/betterx/ui/vanilla/LayoutScreen.java index 6718995f..bba39306 100644 --- a/src/main/java/org/betterx/ui/vanilla/LayoutScreen.java +++ b/src/main/java/org/betterx/ui/vanilla/LayoutScreen.java @@ -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); + } } diff --git a/src/main/resources/assets/bclib/lang/de_de.json b/src/main/resources/assets/bclib/lang/de_de.json index 40a770e0..edf2b490 100644 --- a/src/main/resources/assets/bclib/lang/de_de.json +++ b/src/main/resources/assets/bclib/lang/de_de.json @@ -48,7 +48,6 @@ "title.bclib.datafixer.error": "Fehler beim Reparieren der Welt", "message.bclib.datafixer.error": "Es gab Fehler beim Reparieren der Welt. Das bedeutet, dass dieser Level wahrscheinlich in einem inkonsistenten Zustand ist und Sie ihn nicht spielen sollten. Bitte stellen Sie Ihr Backup wieder her und beheben Sie die unten aufgeführten Fehler, bevor Sie es erneut versuchen.", "title.bclib.datafixer.error.continue": "Continue and Mark as Fixed", - "title.config.bclib.main.ui.suppressExperimentalDialogOnLoad": "Disable Experimental Warning Screen on Load", "tooltip.bclib.place_on": "Lebt auf: %s", "generator.bclib.normal": "BetterX", "title.screen.bclib.worldgen.main": "Welt-Generator Eigenschaften", @@ -64,5 +63,18 @@ "title.screen.bclib.worldgen.void_biome_size": "Kleine Inseln", "title.screen.bclib.worldgen.center_biome_size": "Zentralbiome", "title.screen.bclib.worldgen.barrens_biome_size": "Ödniss", - "title.screen.bclib.worldgen.central_radius": "Innerer Radius (in Chunks)" + "title.screen.bclib.worldgen.central_radius": "Innerer Radius (in Chunks)", + "title.config.bclib.client.rendering.customFogRendering": "Angepasster Nebel", + "title.config.bclib.client.rendering.netherThickFog": "Dicker Nether-Nebel", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.title": "Mod Aktualisierungen", + "bclib.updates.disable_check": "Nicht Prüfen", + "bclib.updates.donate_pre": "Gefallen Dir unsere Inhalte?\nDann erwäge eine kleine Spende :)", + "bclib.updates.donate": "Spenden", + "bclib.updates.description": "Einige der installierten Mods sind veraltet. Wir verbessern und erweitern ständig unseren Inhalt und stellen wichtige Fehlerbehebungen zur Verfügung.\nBitte aktualisiere Deine Mods über die unten angegebenen Links.", + "bclib.welcome.title": "Wilkommen bei BetterX", + "bclib.welcome.description": "... und ein riesiges herzliches **Dankeschön** für das Herunterladen und Spielen unserer Mods. Wir hoffen, dass sie euch genauso viel Spaß machen wie uns.\n\nBevor wir anfangen, gibt es ein paar Dinge, die wir einrichten müssen, also lest bitte den folgenden langweiligen Teil weiter.", + "bclib.welcome.donation": "Wenn Dir unsere Mods so gut gefallen, wie wir hoffen, dann denken Sie bitte über eine kleine Spende nach :)", + "bclib.welcome.updater.description": "BCLib enthält eine einfache Versionsüberprüfung, die Dich benachrichtigen kann, wenn neue Updates unserer Mods verfügbar sind. Dazu müssen wir eine Ressource von einem unserer Webserver abrufen. Um die Anfrage zu bearbeiten versenden wir zusammen mit Deiner IP-Address (wir müssen ja wissen wohin die Antwort gehen soll) auch Deine Minecraft-Version (damit wir die richtige Mod-Version auswählen können). Die übertragenen Daten werden von uns niemals für andere Zwecke verwendet, verarbeitet oder weitergegeben, müssen aber aus rechtlichen Gründen für 4 Wochen in unseren Log-Dateien gespeichert werden.", + "bclib.welcome.updater.title": "Versionsprüfung erlauben" } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/lang/en_us.json b/src/main/resources/assets/bclib/lang/en_us.json index e6871151..af209875 100644 --- a/src/main/resources/assets/bclib/lang/en_us.json +++ b/src/main/resources/assets/bclib/lang/en_us.json @@ -33,7 +33,7 @@ "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Disable Experimental Warning Screen on Load", "title.bclib.syncfiles.modInfo": "Mod Info", "title.bclib.syncfiles.modlist": "Mod Information", - "message.bclib.syncfiles.modlist": "The following shows the state of your installed installed Mods.\n\nAll Mods that do not exist locally, or have a different version on the Server will be synchronized.", + "message.bclib.syncfiles.modlist": "The following shows the state of your installed Mods.\n\nAll Mods that do not exist locally, or have a different version on the Server will be synchronized.", "title.bclib.modmissmatch": "Mod Version Conflict", "message.bclib.modmissmatch": "Some Mods on this client do not match the version of Mods on the Server.\n\nMismatching Mods can result in odd game behavior or crashes. Please make sue that you use the same mods as the server.", "message.bclib.datafixer.progress.waitbackup": "Waiting for Backup to finish. This may take a while!", @@ -65,5 +65,16 @@ "title.screen.bclib.worldgen.center_biome_size": "Central Biomes", "title.screen.bclib.worldgen.barrens_biome_size": "Barrens", "title.screen.bclib.worldgen.central_radius": "Central Void Radius (in Chunks)", - "title.screen.bclib.worldgen.end_void": "Generate Small Islands" + "title.screen.bclib.worldgen.end_void": "Generate Small Islands", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.title": "Mod Updates", + "bclib.updates.disable_check": "Disable Check", + "bclib.updates.donate_pre": "Like our Content?\nPlease consider a small Donation :)", + "bclib.updates.donate": "Donate", + "bclib.updates.description": "Some of the installed mods are outdated. We continually improve and extend our content as well as provide important bug-fixes.\nPlease consider updating your mods using the provided links below.", + "bclib.welcome.title": "Welcome to BetterX", + "bclib.welcome.description": "... and a huge hearty **thank you** for downloading and playing our mods. We hope you enjoy them as much as we do.\n\nBefore we start, there are a few things we need to set up, so please continue reading the following boring part.", + "bclib.welcome.donation": "If, you enjoy our Mods as much as we hope you do, please consider a small Donation :)", + "bclib.welcome.updater.description": "BCLib includes a simple version checker that can notify you when new updates of our mods are available. To do this, we need to read a resource from one of our web servers. To process the request, together with your IP address (we need to know where to send the response) we also send your Minecraft version (so we can choose the right mod version). The transmitted data will never be used or processed by us for other purposes, but must be stored in our log files for 4 weeks for legal reasons.", + "bclib.welcome.updater.title": "Enable Version Check" } \ No newline at end of file