diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties index 0a3aae3c54..2e9451a37b 100644 --- a/android/assets/jsons/translations/German.properties +++ b/android/assets/jsons/translations/German.properties @@ -982,6 +982,7 @@ Download [modName] = [modName] herunterladen Update [modName] = [modName] aktualisieren Could not download mod list = Modliste konnte nicht heruntergeladen werden Download mod from URL = Modifikation von einer URL herunterladen +Please enter the mod repository -or- archive zip url: = Geben Sie die URL des Mod Repository oder des zip-Archivs ein: Download = Herunterladen Done! = Abgeschlossen! Delete [modName] = Lösche [modName] @@ -1001,7 +1002,8 @@ No description provided = Keine Beschreibung mitgeliefert [stargazers]✯ = [stargazers]✯ Author: [author] = Autor: [author] Size: [size] kB = Größe: [size] kB - +The mod you selected is incompatible with the defined ruleset! = Die gewählte Modifikation ist inkompatibel! + # Uniques that are relevant to more than one type of game object [stats] from every [param] = [stats] von jedem Gebäude "[param]" diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index dd5187c212..ae535cb796 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -1000,6 +1000,7 @@ Download [modName] = Update [modName] = Could not download mod list = Download mod from URL = +Please enter the mod repository -or- archive zip url: = Download = Done! = Delete [modName] = @@ -1019,7 +1020,8 @@ No description provided = [stargazers]✯ = Author: [author] = Size: [size] kB = - +The mod you selected is incompatible with the defined ruleset! = + # Uniques that are relevant to more than one type of game object [stats] from every [param] = diff --git a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt index 33ac07c9ba..ba503190ca 100644 --- a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt @@ -2,6 +2,7 @@ package com.unciv.ui.newgamescreen import com.badlogic.gdx.scenes.scene2d.ui.CheckBox import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset.CheckModLinksResult import com.unciv.models.ruleset.RulesetCache import com.unciv.models.translations.tr @@ -39,21 +40,19 @@ class ModCheckboxTable(val mods:LinkedHashSet, val screen: CameraStageBa mods.remove(oldBaseRuleset) mods.add(mod.name) - var isCompatibleWithCurrentRuleset = true var complexModLinkCheck = CheckModLinksResult() try { val newRuleset = RulesetCache.getComplexRuleset(mods) newRuleset.modOptions.isBaseRuleset = true // This is so the checkModLinks finds all connections complexModLinkCheck = newRuleset.checkModLinks() - isCompatibleWithCurrentRuleset = !complexModLinkCheck.isError() - } catch (x: Exception) { + } catch (ex: Exception) { // This happens if a building is dependent on a tech not in the base ruleset // because newRuleset.updateBuildingCosts() in getComplexRuleset() throws an error - isCompatibleWithCurrentRuleset = false + complexModLinkCheck = CheckModLinksResult(Ruleset.CheckModLinksStatus.Error, ex.localizedMessage) } - if (!isCompatibleWithCurrentRuleset) { - ToastPopup("The mod you selected is incompatible with the defined ruleset!\n\n$complexModLinkCheck", screen) + if (complexModLinkCheck.isError()) { + ToastPopup("{The mod you selected is incompatible with the defined ruleset!}\n\n{$complexModLinkCheck}", screen) checkBox.isChecked = false mods.clear() mods.addAll(previousMods) diff --git a/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt b/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt index 910d6c90f4..90e0509a04 100644 --- a/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt @@ -317,13 +317,14 @@ class ModManagementScreen: PickerScreen(disableScroll = true) { val downloadButton = "Download mod from URL".toTextButton() downloadButton.onClick { val popup = Popup(this) + popup.addGoodSizedLabel("Please enter the mod repository -or- archive zip url:").row() val textArea = TextArea("https://github.com/...", skin) popup.add(textArea).width(stage.width / 2).row() val actualDownloadButton = "Download".toTextButton() actualDownloadButton.onClick { actualDownloadButton.setText("Downloading...".tr()) actualDownloadButton.disable() - downloadMod(Github.Repo().apply { html_url = textArea.text; default_branch = "master" }) { popup.close() } + downloadMod(Github.Repo().parseUrl(textArea.text)) { popup.close() } } popup.add(actualDownloadButton).row() popup.addCloseButton() @@ -354,13 +355,13 @@ class ModManagementScreen: PickerScreen(disableScroll = true) { addModInfoToActionTable(repo) } - /** Download and install a mod in the background, called from the right-bottom button */ + /** Download and install a mod in the background, called both from the right-bottom button and the URL entry popup */ private fun downloadMod(repo: Github.Repo, postAction: () -> Unit = {}) { thread(name="DownloadMod") { // to avoid ANRs - we've learnt our lesson from previous download-related actions try { val modFolder = Github.downloadAndExtract(repo.html_url, repo.default_branch, Gdx.files.local("mods")) - ?: return@thread + ?: throw Exception() // downloadAndExtract returns null for 404 errors and the like -> display something! rewriteModOptions(repo, modFolder) Gdx.app.postRunnable { ToastPopup("Downloaded!", this) diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/GitHub.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/GitHub.kt index eaaaf460fa..ba24f5b7b8 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/GitHub.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/GitHub.kt @@ -57,7 +57,7 @@ object Github { defaultBranch: String, folderFileHandle: FileHandle ): FileHandle? { - // Initiate download - the helper returns null when it fails + // Initiate download - the helper returns null when it fails val zipUrl = "$gitRepoUrl/archive/$defaultBranch.zip" val inputStream = download(zipUrl) ?: return null @@ -247,6 +247,38 @@ object Github { //var stargazers_url = "" //var homepage: String? = null // might use instead of go to repo? //var has_wiki = false // a wiki could mean proper documentation for the mod? + + /** + * Initialize `this` with an url, extracting all possible fields from it. + * + * Allows basic repo url or complete 'zip' url from github's code->download zip menu + * + * @return `this` to allow chaining + */ + fun parseUrl(url: String): Repo { + // Allow url formats + // https://github.com/author/repoName + // or + // https://github.com/author/repoName/archive/refs/heads/branchName.zip + // and extract author, repoName, branchName + + html_url = url + default_branch = "master" + val matchZip = Regex("""^(.*/(.*)/(.*))/archive/(?:.*/)?([^.]+).zip$""").matchEntire(url) + if (matchZip != null && matchZip.groups.size > 3) { + html_url = matchZip.groups[1]!!.value + owner.login = matchZip.groups[2]!!.value + name = matchZip.groups[3]!!.value + default_branch = matchZip.groups[4]!!.value + } else { + val matchRepo = Regex("""^.*/(.*)/(.*)/?$""").matchEntire(url) + if (matchRepo != null && matchRepo.groups.size > 2) { + owner.login = matchRepo.groups[1]!!.value + name = matchRepo.groups[2]!!.value + } + } + return this + } } /** Part of [Repo] in Github API response */