From 00d9c7e1a499250fca7e89e6ff0938f4636c035b Mon Sep 17 00:00:00 2001 From: buthed010203 Date: Mon, 28 Oct 2024 12:58:49 -0400 Subject: [PATCH] - Automatically install and manage floodcompat. - Localize claj ping --- core/assets/bundles/bundle.properties | 2 + core/src/mindustry/client/Commands.kt | 2 +- .../client/claj/ClajManagerDialog.kt | 3 +- .../src/mindustry/client/utils/ClientUtils.kt | 2 +- .../src/mindustry/client/utils/ServerUtils.kt | 78 +++++++++++++------ core/src/mindustry/mod/Mods.java | 13 ++-- core/src/mindustry/ui/dialogs/ModsDialog.java | 13 ++-- .../mindustry/ui/fragments/ChatFragment.java | 2 +- 8 files changed, 72 insertions(+), 43 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 84d9373703..4ef2ab73ee 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -102,6 +102,8 @@ client.claj.portnotfound = CLaJ link missing port client.claj.colonnotfound = CLaJ link missing colon client.claj.wrongkeylength = CLaJ link is wrong length client.claj.missingprefix = CLaJ link missing prefix +client.claj.ping.error = Error contacting server. +client.claj.ping.pending = Not yet contacted. # Editor client.editor.autosaved = Autosaved! diff --git a/core/src/mindustry/client/Commands.kt b/core/src/mindustry/client/Commands.kt index 61b510aa1d..6c1031b4c2 100644 --- a/core/src/mindustry/client/Commands.kt +++ b/core/src/mindustry/client/Commands.kt @@ -113,7 +113,7 @@ fun setupCommands() { } } - player.sendMessage("client.command.count.success"[type.localizedName, total, cap, free, freeFlagged, flagged, unflagged, players, command, logic, logicFlagged]) + player.sendMessage("client.command.count.success".bundle(type.localizedName, total, cap, free, freeFlagged, flagged, unflagged, players, command, logic, logicFlagged)) } // FINISHME: Add unit control/select command(s) diff --git a/core/src/mindustry/client/claj/ClajManagerDialog.kt b/core/src/mindustry/client/claj/ClajManagerDialog.kt index eec102826d..1b455a9379 100644 --- a/core/src/mindustry/client/claj/ClajManagerDialog.kt +++ b/core/src/mindustry/client/claj/ClajManagerDialog.kt @@ -7,6 +7,7 @@ import arc.scene.ui.layout.* import arc.util.* import mindustry.* import mindustry.client.claj.ClajSupport.createRoom +import mindustry.client.utils.* import mindustry.gen.* import mindustry.ui.* import mindustry.ui.dialogs.* @@ -40,7 +41,7 @@ class ClajManagerDialog : BaseDialog("@client.claj.manage") { Vars.ui.showErrorMessage(ignored.message) } }.disabled { list.children.size >= 4 }.padTop(8f).row() - rooms.label { if (Time.timeSinceMillis(lastPingAt) > 1000) { lastPingAt = Time.millis(); Vars.net.pingHost(serverIP, serverPort, { lastPing = it.ping }) { lastPing = -2 } }; "Ping: " + if (lastPing == -2) "[scarlet]Error contacting server." else if (lastPing == -1) "[orange]Not yet contacted." else "${lastPing}ms" } + rooms.label { if (Time.timeSinceMillis(lastPingAt) > 1000) { lastPingAt = Time.millis(); Vars.net.pingHost(serverIP, serverPort, { lastPing = it.ping }) { lastPing = -2 } }; if (lastPing == -2) "[scarlet]" + "client.claj.ping.error".bundle() else if (lastPing == -1) "[orange]" + "client.claj.ping.pending".bundle() else "ping".bundle(lastPing) } }.height(550f).row() val l = cont.labelWrap("@client.claj.info").labelAlign(2, 8).padTop(16f).width(400f).get() l.style.fontColor = Color.lightGray diff --git a/core/src/mindustry/client/utils/ClientUtils.kt b/core/src/mindustry/client/utils/ClientUtils.kt index ec918f467a..0e61dd9df7 100644 --- a/core/src/mindustry/client/utils/ClientUtils.kt +++ b/core/src/mindustry/client/utils/ClientUtils.kt @@ -220,7 +220,7 @@ fun Iterable.unescape(escapement: T, vararg escape: T): List { } fun String.bundle(): String = Core.bundle[removePrefix("@")] -operator fun String.get(vararg args: Any?): String = Core.bundle.formatKt(removePrefix("@"), args) +fun String.bundle(vararg args: Any?): String = Core.bundle.formatKt(removePrefix("@"), args) val X509Certificate.readableName: String get() = subjectX500Principal.name.removePrefix("CN=") diff --git a/core/src/mindustry/client/utils/ServerUtils.kt b/core/src/mindustry/client/utils/ServerUtils.kt index f684543d0d..e232a61692 100644 --- a/core/src/mindustry/client/utils/ServerUtils.kt +++ b/core/src/mindustry/client/utils/ServerUtils.kt @@ -3,6 +3,7 @@ package mindustry.client.utils import arc.* +import arc.files.* import arc.util.* import mindustry.Vars.* import mindustry.client.* @@ -180,36 +181,63 @@ enum class CustomMode( none, flood { val ioFloodCompatRepo = "mindustry-antigrief/FloodCompat" + var hasLoaded = false override fun enable() { super.enable() - flood.name - if (io() && net.client()) { // FINISHME: Load the mod at runtime - val floodcompatMod: Mods.LoadedMod? = mods.list().find { it.repo?.lowercase() == ioFloodCompatRepo.lowercase() } - Timer.schedule({ - if (floodcompatMod === null) { - ui.chatfrag.addMsg( - "[accent]Floodcompat mod [scarlet]not[] found! Installing the [white]${ioFloodCompatRepo}[] mod is recommended for a better game experience." + - "\n[green]INSTALL") - .addButton("INSTALL") { - Toast(3f).add("Downloading mod") - ui.mods.githubImportMod(ioFloodCompatRepo, false, null, "") { - Toast(3f).add("Downloaded!") - val mod: Mods.LoadedMod = mods.list().find { it.repo?.lowercase() == ioFloodCompatRepo.lowercase() } ?: return@githubImportMod - mods.setEnabled(mod, true) - ui.chatfrag.addMsg("[accent]Mod downloaded! Restart game to apply mod.\n[red]RESTART").addButton("RESTART") { restartGame() }; - } - } - } else if (!floodcompatMod.enabled()){ - ui.chatfrag.addMsg( - "[accent]Floodcompat mod [scarlet]disabled[]! Enabling it is recommended for a better game experience." + - "\n[green]ENABLE (requires restart)") - .addButton("ENABLE (requires restart)") { - mods.setEnabled(floodcompatMod, true) - restartGame() + if (io() && net.client()) { + var floodMod: Mods.LoadedMod? = mods.getMod("floodcompat") + + fun enable() { // Just enables the mod + if (hasLoaded) return // Only attempt to enable the mod once + hasLoaded = true + + Log.warn("FloodCompat installed but disabled. Foo's will load it at runtime.") + + mods.mods.remove(floodMod) + floodMod!!.dispose() + Core.settings.put("mod-floodcompat-enabled", true) // Has to be enabled for the mod to load + val mod = Reflect.invoke(mods, "loadMod", arrayOf(floodMod!!.file), Fi::class.java) // Load the mod and call the init() function + mod.main.init() + // Next 5 lines sort the new mod as if it were enabled without actually keeping it enabled after a restart + mod.state = Mods.ModState.enabled + mods.mods.add(mod) + Reflect.invoke(mods, "sortMods") + Reflect.set(mods, "lastOrderedMods", null) // Reset orderedMods cache + Core.settings.put("mod-floodcompat-enabled", false) // May as well disable it as it was before + } + + fun download(update: Boolean = false) { // Downloads and enables the mod + Toast(3f).add(if (update) "Updating" else "Installing" + " FloodCompat") + Log.debug(if (update) "Updating" else "Installing" + " FloodCompat") + ui.mods.githubImportMod(ioFloodCompatRepo, true, null, floodMod?.meta?.version) { + val new = mods.mods.last { it.name == "floodcompat"} // newly downloaded flood compat if any + if (update && new != floodMod) { // Delete old flood mod for update. If new == old, there was no update. + floodMod!!.file.deleteDirectory() + floodMod!!.dispose() + mods.mods.remove(floodMod) } + val reload = Reflect.get(mods, "requiresReload") + Reflect.set(mods, "requiresReload", reload) + Toast(3f).add("FloodCompat " + if (update) "updated" else "installed" + " successfully!") + Core.settings.put("mod-floodcompat-enabled", false) // Set as disabled as there's no reason to load it outside of flood gamemode + floodMod = mods.getMod("floodcompat") // floodMod is still null from before, set it to the mod we just downloaded + enable() } - }, 2f) + } + + if (floodMod === null) { + ui.showConfirm("[scarlet]FloodCompat mod not found!", "Installing the [accent]${ioFloodCompatRepo}[] mod is recommended for a better game experience. Would you like to install it?\nThis will not require a restart.") { + Toast(3f).add("Downloading mod") + download() + } + } else if (!floodMod.enabled()) { + if (!hasLoaded && Time.timeSinceMillis(Core.settings.getLong("lastfloodcompatupdate", Time.millis())) > 1000 * 60 * 30L) { // Update floodCompat every 30m + Core.settings.put("lastfloodcompatupdate", Time.millis()) + (floodMod.root as? ZipFi)?.delete() // Close the current flood zip just in case its open somehow (it should not be) + download(true) + } else enable() // Enable the mod as normal otherwise + } } } }, diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index 008f1f96a6..b197a2aef3 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -222,9 +222,8 @@ public void loadIcons(long start, int limit) { // FINISHME: This could be furthe } private void loadIcon(LoadedMod mod){ - mod.attemptedIconLoad = true; //try to load icon for each mod that can have one - if(mod.root.child("icon.png").exists() && !headless){ + if(mod.root.child("icon.png").exists() && !headless && !mod.attemptedIconLoad){ try{ mod.iconTexture = new Texture(mod.root.child("icon.png")); mod.iconTexture.setFilter(TextureFilter.linear); @@ -232,6 +231,7 @@ private void loadIcon(LoadedMod mod){ Log.err("Failed to load icon for mod '" + mod.name + "'.", t); } } + mod.attemptedIconLoad = true; } private int packSprites(Seq sprites, LoadedMod mod, boolean prefix, LinkedBlockingQueue>[] queues){ @@ -1133,8 +1133,8 @@ public static class LoadedMod implements Publishable, Disposable{ /** Foo's addition to track whether we have tried to load this mod's icon */ public boolean attemptedIconLoad; - private static boolean iconLoadingOptimization = Core.settings.getBool("modiconloadingoptimization"); - private static ObjectSet iconDeferralUnsupported = ObjectSet.with("mi2-utilities-java", "olupis"); + private static final boolean iconLoadingOptimization = Core.settings.getBool("modiconloadingoptimization"); + private static final ObjectSet iconDeferralUnsupported = ObjectSet.with("mi2-utilities-java", "olupis"); public LoadedMod(Fi file, Fi root, Mod main, ClassLoader loader, ModMeta meta){ this.root = root; @@ -1143,9 +1143,9 @@ public LoadedMod(Fi file, Fi root, Mod main, ClassLoader loader, ModMeta meta){ this.main = main; this.meta = meta; this.name = meta.name.toLowerCase(Locale.ROOT).replace(" ", "-"); - if(shouldBeEnabled() && (!iconLoadingOptimization || iconDeferralUnsupported.contains(this.name))){ // This is terrible. + if(shouldBeEnabled() && (!iconLoadingOptimization || iconDeferralUnsupported.contains(this.name))){ // This is terrible. Loads icons immediately if icon loading optimization is disabled Core.app.post(() -> Vars.mods.loadIcon(this)); - } + }else Vars.mods.hasLoadedIcons = false; // Makes sure that another round of icon loading will happen if icon loading optimization is enabled and we're adding a new mod } /** @return whether this is a java class mod. */ @@ -1210,6 +1210,7 @@ public int getMinMajor(){ @Override public void dispose(){ + attemptedIconLoad = true; // Prevents attempts at loading it again if(iconTexture != null){ iconTexture.dispose(); iconTexture = null; diff --git a/core/src/mindustry/ui/dialogs/ModsDialog.java b/core/src/mindustry/ui/dialogs/ModsDialog.java index e457a3a612..d4f3506baf 100644 --- a/core/src/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/mindustry/ui/dialogs/ModsDialog.java @@ -52,19 +52,16 @@ public class ModsDialog extends BaseDialog{ private boolean autoUpdating; // Whether mods are currently being auto updated private float scroll = 0f; - private Runnable autoUpdaterHandler = () -> { // RUN THIS ON THE MAIN THREAD + private final Runnable autoUpdaterHandler = () -> { // RUN THIS ON THE MAIN THREAD if (++prompted == expected) { // FINISHME: Awful autoUpdating = false; if (mods.requiresReload()){ - if (Core.settings.getInt("modautoupdate") == 2) { - ClientUtils.restartGame(); - reload(); - } + if (Core.settings.getInt("modautoupdate") == 2) reload(); new Toast(5f).add("[accent]Mod updates found, they will be installed after restart."); } else new Toast(5f).add("[accent]No mod updates found."); } }; - private ObjectMap onSuccess = new ObjectMap(); + private final ObjectMap onSuccess = new ObjectMap<>(); public ModsDialog(){ super("@mods"); @@ -126,7 +123,7 @@ public ModsDialog(){ autoUpdating = true; Log.debug("Checking for mod updates @", Time.timeSinceMillis(settings.getLong("lastmodupdate", hour + 1)) / (60*1000f)); Core.settings.put("lastmodupdate", Time.millis()); - for (Mods.LoadedMod mod : mods.mods.copy().shuffle()) { // Use shuffled mod list, if the user has more than 30 active mods, this will ensure that each is checked at least somewhat frequently + for (Mods.LoadedMod mod : mods.mods.copy().shuffle()) { // Use shuffled mod list, if the user has more than 30 active mods, this will ensure that each is checked at least somewhat frequently FINISHME: This should take dependencies and requirements into account which we don't do currently if (!mod.enabled() || mod.getRepo() == null || !settings.getBool(mod.autoUpdateString(), true)) continue; if (expected++ >= 30) continue; // Only make up to 30 api requests onSuccess.put(mod.getRepo(), autoUpdaterHandler); @@ -711,7 +708,7 @@ private void handleMod(String repo, HttpResponse result, @Nullable String prevVe private void importSuccess(String repo){ var func = onSuccess.remove(repo); if(func == null) return; - Core.app.post(() -> func.run()); + Core.app.post(func); } private void importFail(Throwable t){ diff --git a/core/src/mindustry/ui/fragments/ChatFragment.java b/core/src/mindustry/ui/fragments/ChatFragment.java index b292d08bdf..71ed44780c 100644 --- a/core/src/mindustry/ui/fragments/ChatFragment.java +++ b/core/src/mindustry/ui/fragments/ChatFragment.java @@ -680,7 +680,7 @@ private void format(boolean moveButtons) { if(sender == null){ //no sender, this is a server message? formattedMessage = prefix + (message == null ? "" : message); } else { - if (Server.darkdustry.b()) formattedMessage = prefix + message; // Hack to allow darkdustry translation as they don't change sender + if (Server.darkdustry.b()) formattedMessage = prefix + message; // Hack to allow darkdustry translation as they don't change sender FINISHME: We need to rework this system badly as some servers change the messages significantly else formattedMessage = prefix + "[coral][[[white]" + sender + "[coral]]:[white] " + unformatted; } int shift = formattedMessage.length() - initial;