[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.api.v3.levelgen.features.placement.PlacementModifiers;
import org.betterx.bclib.commands.CommandRegistry; import org.betterx.bclib.commands.CommandRegistry;
import org.betterx.bclib.config.Configs; import org.betterx.bclib.config.Configs;
import org.betterx.bclib.networking.VersionChecker;
import org.betterx.bclib.recipes.AnvilRecipe; import org.betterx.bclib.recipes.AnvilRecipe;
import org.betterx.bclib.recipes.CraftingRecipes; import org.betterx.bclib.recipes.CraftingRecipes;
import org.betterx.bclib.registry.BaseBlockEntities; import org.betterx.bclib.registry.BaseBlockEntities;
@ -79,7 +80,8 @@ public class BCLib implements ModInitializer {
Configs.save(); Configs.save();
WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET = Configs.SERVER_CONFIG.forceBetterXPreset(); WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET = Configs.SERVER_CONFIG.forceBetterXPreset();
VersionChecker.registerMod(MOD_ID);
if (false && isDevEnvironment()) { if (false && isDevEnvironment()) {
BCLBiome theYellow = BCLBiomeBuilder BCLBiome theYellow = BCLBiomeBuilder
.start(makeID("the_yellow")) .start(makeID("the_yellow"))
@ -150,7 +152,6 @@ public class BCLib implements ModInitializer {
.surface(Blocks.PURPLE_CONCRETE) .surface(Blocks.PURPLE_CONCRETE)
.build(); .build();
BiomeAPI.registerNetherBiome(thePurple); 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.RenderLayerProvider;
import org.betterx.bclib.interfaces.TagProvider; import org.betterx.bclib.interfaces.TagProvider;
import org.betterx.bclib.interfaces.tools.*; import org.betterx.bclib.interfaces.tools.*;
import org.betterx.bclib.networking.VersionChecker;
import org.betterx.bclib.registry.BaseBlockEntities; import org.betterx.bclib.registry.BaseBlockEntities;
import org.betterx.worlds.together.tag.v3.MineableTags; import org.betterx.worlds.together.tag.v3.MineableTags;
import org.betterx.worlds.together.tag.v3.TagManager; import org.betterx.worlds.together.tag.v3.TagManager;
@ -73,6 +74,8 @@ public class PostInitAPI {
itemTags = null; itemTags = null;
InternalBiomeAPI.loadFabricAPIBiomes(); InternalBiomeAPI.loadFabricAPIBiomes();
Configs.BIOMES_CONFIG.saveChanges(); Configs.BIOMES_CONFIG.saveChanges();
VersionChecker.startCheck(isClient);
} }
@Environment(EnvType.CLIENT) @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") .then(Commands.literal("dimensions")
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS))
.executes(ctx -> PrintInfo.printDimensions(ctx)) .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") .then(Commands.literal("debug_ore")
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS))

View file

@ -1,14 +1,24 @@
package org.betterx.bclib.commands; 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.Command;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style; import net.minecraft.network.chat.Style;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
public class PrintInfo { public class PrintInfo {
static int printDimensions(CommandContext<CommandSourceStack> ctx) { static int printDimensions(CommandContext<CommandSourceStack> ctx) {
@ -37,4 +47,70 @@ public class PrintInfo {
ctx.getSource().sendSuccess(result, false); ctx.getSource().sendSuccess(result, false);
return Command.SINGLE_SUCCESS; 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}; private static final BiomeAPI.BiomeType[] excludeTypes = {BiomeAPI.BiomeType.NETHER, BiomeAPI.BiomeType.END};
public BiomesConfig() { public BiomesConfig() {
super(BCLib.MOD_ID, "biomes", false); super(BCLib.MOD_ID, "biomes", true);
for (var type : includeTypes) { for (var type : includeTypes) {
keeper.registerEntry( keeper.registerEntry(
new ConfigKey(type.getName(), "force_include"), 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) @ConfigUI(leftPadding = 8)
public static final DependendConfigToken<Boolean> ACCEPT_MODS = DependendConfigToken.Boolean( public static final DependendConfigToken<Boolean> ACCEPT_MODS = DependendConfigToken.Boolean(
false, true,
"acceptMods", "acceptMods",
AutoSync.SYNC_CATEGORY, AutoSync.SYNC_CATEGORY,
(config) -> config.get( (config) -> config.get(ENABLED)
ENABLED)
); );
@ConfigUI(leftPadding = 8) @ConfigUI(leftPadding = 8)
public static final DependendConfigToken<Boolean> DISPLAY_MOD_INFO = DependendConfigToken.Boolean( public static final DependendConfigToken<Boolean> DISPLAY_MOD_INFO = DependendConfigToken.Boolean(
@ -72,6 +71,19 @@ public class ClientConfig extends NamedPathConfig {
"rendering" "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() { public ClientConfig() {
super(BCLib.MOD_ID, "client", false); super(BCLib.MOD_ID, "client", false);
} }
@ -112,6 +124,14 @@ public class ClientConfig extends NamedPathConfig {
return get(CUSTOM_FOG_RENDERING); return get(CUSTOM_FOG_RENDERING);
} }
public boolean showUpdateInfo() {
return get(SHOW_UPDATE_INFO);
}
public boolean isDonor() {
return !get(NO_DONOR);
}
public float fogDensity() { public float fogDensity() {
return get(FOG_DENSITY); return get(FOG_DENSITY);
} }

View file

@ -14,6 +14,7 @@ public class Configs {
public static final GeneratorConfig GENERATOR_CONFIG = new GeneratorConfig(); public static final GeneratorConfig GENERATOR_CONFIG = new GeneratorConfig();
public static final MainConfig MAIN_CONFIG = new MainConfig(); 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 PathConfig RECIPE_CONFIG = new PathConfig(BCLib.MOD_ID, "recipes");
public static final BiomesConfig BIOMES_CONFIG = new BiomesConfig(); 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 class GeneratorConfig extends NamedPathConfig {
public GeneratorConfig() { 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) 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() { public MainConfig() {
super(BCLib.MOD_ID, "main", true, true); super(BCLib.MOD_ID, "main", true, true);
} }
@ -29,4 +45,21 @@ public class MainConfig extends NamedPathConfig {
public boolean repairBiomes() { public boolean repairBiomes() {
return get(REPAIR_BIOMES); 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, true,
"offerConfigs", "offerConfigs",
AutoSync.SYNC_CATEGORY, AutoSync.SYNC_CATEGORY,
(config) -> config.get( (config) -> config.get(ENABLED)
ENABLED)
); );
public static final DependendConfigToken<Boolean> OFFER_FILES = DependendConfigToken.Boolean( public static final DependendConfigToken<Boolean> OFFER_FILES = DependendConfigToken.Boolean(
true, true,

View file

@ -1,10 +1,12 @@
package org.betterx.bclib.mixin.client; package org.betterx.bclib.mixin.client;
import org.betterx.bclib.interfaces.CustomColorProvider; import org.betterx.bclib.interfaces.CustomColorProvider;
import org.betterx.bclib.networking.VersionCheckerClient;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.color.block.BlockColors; import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.color.item.ItemColors; import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.main.GameConfig; import net.minecraft.client.main.GameConfig;
import net.minecraft.core.Registry; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.jetbrains.annotations.Nullable;
@Mixin(Minecraft.class) @Mixin(Minecraft.class)
public abstract class MinecraftMixin { public abstract class MinecraftMixin {
@Final @Final
@ -25,6 +29,11 @@ public abstract class MinecraftMixin {
@Shadow @Shadow
private ItemColors itemColors; private ItemColors itemColors;
@Shadow
@Nullable
public Screen screen;
@Inject(method = "<init>*", at = @At("TAIL")) @Inject(method = "<init>*", at = @At("TAIL"))
private void bclib_onMCInit(GameConfig args, CallbackInfo info) { private void bclib_onMCInit(GameConfig args, CallbackInfo info) {
Registry.BLOCK.forEach(block -> { Registry.BLOCK.forEach(block -> {
@ -33,5 +42,7 @@ public abstract class MinecraftMixin {
itemColors.register(provider.getItemProvider(), block.asItem()); 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 com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.components.MultiLineLabel; import net.minecraft.client.gui.components.MultiLineLabel;
import net.minecraft.network.chat.Component; 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> { public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRenderer, MultiLineText> {
net.minecraft.network.chat.Component text; net.minecraft.network.chat.Component text;
int color = ColorUtil.DEFAULT_TEXT; int color = ColorUtil.DEFAULT_TEXT;
protected MultiLineLabel multiLineLabel; protected MultiLineLabel multiLineLabel;
int bufferedContentWidth = 0; int bufferedContentWidth = 0;
List<LineWithWidth> lines = List.of();
public MultiLineText( public MultiLineText(
Value width, Value width,
@ -33,10 +45,25 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
return this; 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) { public MultiLineText setText(Component text) {
this.text = text; this.text = text;
this.updatedContentWidth(); this.updatedContentWidth();
if (multiLineLabel != null) { if (multiLineLabel != null) {
multiLineLabel = createVanillaComponent(); multiLineLabel = createVanillaComponent();
} }
@ -45,11 +72,14 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
} }
protected MultiLineLabel createVanillaComponent() { protected MultiLineLabel createVanillaComponent() {
return MultiLineLabel.create( final int wd = relativeBounds == null ? width.calculatedSize() : relativeBounds.width;
renderer.getFont(), lines = renderer.getFont()
text, .split(text, wd)
relativeBounds == null ? width.calculatedSize() : relativeBounds.width .stream()
); .map((component) -> new LineWithWidth(component, renderer.getFont().width(component)))
.collect(ImmutableList.toImmutableList());
return MultiLineLabel.create(renderer.getFont(), text, wd);
} }
protected void updatedContentWidth() { protected void updatedContentWidth() {
@ -108,7 +138,6 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
Rectangle clipRect Rectangle clipRect
) { ) {
if (linkedComponent != null && linkedComponent.multiLineLabel != null) { if (linkedComponent != null && linkedComponent.multiLineLabel != null) {
int top = bounds.height - getHeight(linkedComponent.text); int top = bounds.height - getHeight(linkedComponent.text);
if (linkedComponent.vAlign == Alignment.MIN) top = 0; if (linkedComponent.vAlign == Alignment.MIN) top = 0;
if (linkedComponent.vAlign == Alignment.CENTER) top /= 2; if (linkedComponent.vAlign == Alignment.CENTER) top /= 2;
@ -119,6 +148,20 @@ public class MultiLineText extends LayoutComponent<MultiLineText.MultiLineTextRe
getLineHeight(linkedComponent.text), getLineHeight(linkedComponent.text),
linkedComponent.color 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 { } else {
linkedComponent.multiLineLabel.renderLeftAligned( linkedComponent.multiLineLabel.renderLeftAligned(
stack, 0, top, stack, 0, top,

View file

@ -75,7 +75,7 @@ public class Text extends LayoutComponent<Text.TextRenderer, Text> {
int top = bounds.height - getLineHeight(linkedComponent.text); int top = bounds.height - getLineHeight(linkedComponent.text);
if (linkedComponent.vAlign == Alignment.MIN) top = 0; 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); 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.ConfirmLinkScreen;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
@ -138,4 +139,12 @@ public abstract class LayoutScreen extends Screen {
public static Value relative(double percentage) { public static Value relative(double percentage) {
return Value.relative(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);
}
} }

View file

@ -48,7 +48,6 @@
"title.bclib.datafixer.error": "Fehler beim Reparieren der Welt", "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.", "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.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", "tooltip.bclib.place_on": "Lebt auf: %s",
"generator.bclib.normal": "BetterX", "generator.bclib.normal": "BetterX",
"title.screen.bclib.worldgen.main": "Welt-Generator Eigenschaften", "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.void_biome_size": "Kleine Inseln",
"title.screen.bclib.worldgen.center_biome_size": "Zentralbiome", "title.screen.bclib.worldgen.center_biome_size": "Zentralbiome",
"title.screen.bclib.worldgen.barrens_biome_size": "Ödniss", "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"
} }

View file

@ -33,7 +33,7 @@
"title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Disable Experimental Warning Screen on Load", "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Disable Experimental Warning Screen on Load",
"title.bclib.syncfiles.modInfo": "Mod Info", "title.bclib.syncfiles.modInfo": "Mod Info",
"title.bclib.syncfiles.modlist": "Mod Information", "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", "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.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!", "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.center_biome_size": "Central Biomes",
"title.screen.bclib.worldgen.barrens_biome_size": "Barrens", "title.screen.bclib.worldgen.barrens_biome_size": "Barrens",
"title.screen.bclib.worldgen.central_radius": "Central Void Radius (in Chunks)", "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"
} }