From 46a84c23b0db8b5a2f54e3de404a633ec98c387a Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Wed, 14 Jun 2023 08:12:24 +0200 Subject: [PATCH] Make mod categories curated in Json (#9542) * Dynamic mod categories from online query * Move Working string to Constants * Move Mod categories to json * Move Mod categories to json - UI * Move Mod categories to json - initial json --- android/assets/jsons/ModCategories.json | 50 +++++++++++ .../jsons/translations/template.properties | 2 + core/src/com/unciv/Constants.kt | 1 + core/src/com/unciv/json/UncivJson.kt | 9 +- .../unciv/models/metadata/ModCategories.kt | 85 +++++++++++++++++++ .../unciv/ui/popups/options/AdvancedTab.kt | 24 ++++-- .../screens/mainmenuscreen/MainMenuScreen.kt | 3 +- .../tabs/MapEditorGenerateTab.kt | 5 +- .../mapeditorscreen/tabs/MapEditorSaveTab.kt | 3 +- .../AddMultiplayerGameScreen.kt | 3 +- .../EditMultiplayerGameInfoScreen.kt | 3 +- .../ui/screens/newgamescreen/NewGameScreen.kt | 4 +- .../unciv/ui/screens/pickerscreens/GitHub.kt | 42 +++++++++ .../pickerscreens/ModManagementOptions.kt | 52 ++++-------- .../pickerscreens/ModManagementScreen.kt | 5 +- .../ui/screens/savescreens/LoadGameScreen.kt | 2 +- .../worldscreen/status/NextTurnButton.kt | 2 +- docs/Modders/Mods.md | 22 ++--- 18 files changed, 251 insertions(+), 66 deletions(-) create mode 100644 android/assets/jsons/ModCategories.json create mode 100644 core/src/com/unciv/models/metadata/ModCategories.kt diff --git a/android/assets/jsons/ModCategories.json b/android/assets/jsons/ModCategories.json new file mode 100644 index 0000000000..51e1b745d1 --- /dev/null +++ b/android/assets/jsons/ModCategories.json @@ -0,0 +1,50 @@ +[ +{ + "label": "All mods", + "topic": "unciv-mod", + "createDate": "2020-08-26T11:35:39Z", + "modifyDate": "2023-06-08T05:39:47Z" +}, +{ + "label": "Rulesets", + "topic": "unciv-mod-rulesets", + "createDate": "2022-07-23T06:41:09Z", + "modifyDate": "2022-11-22T01:58:08Z" +}, +{ + "label": "Expansions", + "topic": "unciv-mod-expansions", + "createDate": "2022-07-23T03:55:05Z", + "modifyDate": "2023-02-25T19:44:45Z" +}, +{ + "label": "Graphics", + "topic": "unciv-mod-graphics", + "createDate": "2022-06-26T11:55:31Z", + "modifyDate": "2022-11-06T00:09:20Z" +}, +{ + "label": "Audio", + "topic": "unciv-mod-audio", + "createDate": "2022-06-26T11:55:15Z", + "modifyDate": "2023-02-19T11:55:56Z" +}, +{ + "label": "Maps", + "topic": "unciv-mod-maps", + "createDate": "2022-07-28T20:24:37Z", + "modifyDate": "2022-07-28T21:17:57Z" +}, +{ + "label": "Fun", + "topic": "unciv-mod-fun", + "createDate": "2022-07-24T08:15:47Z", + "modifyDate": "2023-04-07T16:32:11Z" +}, +{ + "label": "Mods of mods", + "topic": "unciv-mod-modsofmods", + "createDate": "2022-08-05T10:00:15Z", + "modifyDate": "2022-12-13T23:01:19Z" +} +] \ No newline at end of file diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 517e247ee5..049d6f71eb 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -794,6 +794,7 @@ Advanced = Generate translation files = Translation files are generated successfully. = Fastlane files are generated successfully. = +Update Mod categories = Screen orientation = @@ -1732,6 +1733,7 @@ Downloaded! = [modName] Downloaded! = Could not download [modName] = Online query result is incomplete = +Sorting and filtering needs to wait until the online query finishes = No description provided = [stargazers]✯ = Author: [author] = diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index ec59cabb09..bbe5e2b2f1 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -70,6 +70,7 @@ object Constants { const val yes = "Yes" const val no = "No" const val loading = "Loading..." + const val working = "Working..." const val barbarians = "Barbarians" const val spectator = "Spectator" diff --git a/core/src/com/unciv/json/UncivJson.kt b/core/src/com/unciv/json/UncivJson.kt index aacce7422e..a1aca15f52 100644 --- a/core/src/com/unciv/json/UncivJson.kt +++ b/core/src/com/unciv/json/UncivJson.kt @@ -16,14 +16,13 @@ import java.time.Duration * [Json] is not thread-safe. Use a new one for each parse. */ fun json() = Json(JsonWriter.OutputType.json).apply { + // Gdx default output type is JsonWriter.OutputType.minimal, which generates invalid Json - e.g. most quotes removed. + // The constructor parameter above changes that to valid Json + // Note an instance set to json can read minimal and vice versa + setIgnoreDeprecated(true) ignoreUnknownFields = true - // Default output type is JsonWriter.OutputType.minimal, which generates invalid Json - e.g. most quotes removed. - // To get better Json, use: - // setOutputType(JsonWriter.OutputType.json) - // Note an instance set to json can read minimal and vice versa - setSerializer(HashMapVector2.getSerializerClass(), HashMapVector2.createSerializer()) setSerializer(Duration::class.java, DurationSerializer()) setSerializer(KeyCharAndCode::class.java, KeyCharAndCode.Serializer()) diff --git a/core/src/com/unciv/models/metadata/ModCategories.kt b/core/src/com/unciv/models/metadata/ModCategories.kt new file mode 100644 index 0000000000..5dc0d68d73 --- /dev/null +++ b/core/src/com/unciv/models/metadata/ModCategories.kt @@ -0,0 +1,85 @@ +package com.unciv.models.metadata + +import com.badlogic.gdx.Gdx +import com.unciv.json.json +import com.unciv.ui.screens.newgamescreen.TranslatedSelectBox +import com.unciv.ui.screens.pickerscreens.Github + + +class ModCategories : ArrayList() { + + class Category( + val label: String, + val topic: String, + val hidden: Boolean, + /** copy of github created_at, no function except help evaluate */ + @Suppress("unused") + val createDate: String, + /** copy of github updated_at, no function except help evaluate */ + var modifyDate: String + ) { + constructor() : + this("", "", false, "", "") + + constructor(topic: Github.TopicSearchResponse.Topic) : + this(labelSuggestion(topic), topic.name, true, topic.created_at, topic.updated_at) + + companion object { + val All = Category("All mods", "unciv-mod", false, "", "") + fun labelSuggestion(topic: Github.TopicSearchResponse.Topic) = + topic.display_name?.takeUnless { it.isBlank() } + ?: topic.name.removePrefix("unciv-mod-").replaceFirstChar(Char::titlecase) + } + + override fun equals(other: Any?) = this === other || other is Category && topic == other.topic + override fun hashCode() = topic.hashCode() + override fun toString() = label + } + + companion object { + private const val fileLocation = "jsons/ModCategories.json" + + private val INSTANCE: ModCategories + + init { + val file = Gdx.files.internal(fileLocation) + INSTANCE = if (file.exists()) + json().fromJson(ModCategories::class.java, Category::class.java, file) + else ModCategories().apply { add(default()) } + } + + fun default() = Category.All + fun mergeOnline() = INSTANCE.mergeOnline() + fun fromSelectBox(selectBox: TranslatedSelectBox) = INSTANCE.fromSelectBox(selectBox) + fun asSequence() = INSTANCE.asSequence().filter { !it.hidden } + operator fun iterator() = asSequence().iterator() + } + + private fun save() { + val json = json() + val compact = json.toJson(this, ModCategories::class.java, Category::class.java) + val verbose = json.prettyPrint(compact) + Gdx.files.local(fileLocation).writeString(verbose, false, "UTF-8") + } + + fun fromSelectBox(selectBox: TranslatedSelectBox): Category { + val selected = selectBox.selected.value + return firstOrNull { it.label == selected } ?: Category.All + } + + fun mergeOnline(): String { + val topics = Github.tryGetGithubTopics() ?: return "Failed" + var newCount = 0 + for (topic in topics.items.sortedBy { it.name }) { + val existing = firstOrNull { it.topic == topic.name } + if (existing != null) { + existing.modifyDate = topic.updated_at + } else { + add(Category(topic)) + newCount++ + } + } + save() + return "$newCount new categories" + } +} diff --git a/core/src/com/unciv/ui/popups/options/AdvancedTab.kt b/core/src/com/unciv/ui/popups/options/AdvancedTab.kt index de5000c9a6..0e7fa5a3e4 100644 --- a/core/src/com/unciv/ui/popups/options/AdvancedTab.kt +++ b/core/src/com/unciv/ui/popups/options/AdvancedTab.kt @@ -13,9 +13,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.SelectBox import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Array +import com.unciv.Constants import com.unciv.GUI import com.unciv.UncivGame import com.unciv.models.metadata.GameSettings +import com.unciv.models.metadata.ModCategories import com.unciv.models.metadata.ScreenSize import com.unciv.models.translations.TranslationFileWriter import com.unciv.models.translations.tr @@ -234,8 +236,8 @@ private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) { val generateTranslationsButton = "Generate translation files".toTextButton() generateTranslationsButton.onActivation { - optionsPopup.tabs.selectPage("Advanced") - generateTranslationsButton.setText("Working...".tr()) + optionsPopup.tabs.selectPage("Advanced") // only because key F12 works from any page + generateTranslationsButton.setText(Constants.working.tr()) Concurrency.run("WriteTranslations") { val result = TranslationFileWriter.writeNewTranslationFiles() launchOnGLThread { @@ -250,12 +252,24 @@ private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) { generateTranslationsButton.addTooltip("F12", 18f) table.add(generateTranslationsButton).colspan(2).row() + val updateModCategoriesButton = "Update Mod categories".toTextButton() + updateModCategoriesButton.onActivation { + updateModCategoriesButton.setText(Constants.working.tr()) + Concurrency.run("GithubTopicQuery") { + val result = ModCategories.mergeOnline() + launchOnGLThread { + updateModCategoriesButton.setText(result) + } + } + } + table.add(updateModCategoriesButton).colspan(2).row() + + if (!UncivGame.Current.files.getSave("ScreenshotGenerationGame").exists()) return val generateScreenshotsButton = "Generate screenshots".toTextButton() generateScreenshotsButton.onActivation { - optionsPopup.tabs.selectPage("Advanced") - generateScreenshotsButton.setText("Working...".tr()) + generateScreenshotsButton.setText(Constants.working.tr()) Concurrency.run("GenerateScreenshot") { val extraImagesLocation = "../../extraImages" // I'm not sure why we need to advance the y by 2 for every screenshot... but that's the only way it remains centered @@ -267,8 +281,8 @@ private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) { )) } } -// table.add(generateScreenshotsButton).colspan(2).row() + table.add(generateScreenshotsButton).colspan(2).row() } data class ScreenshotConfig(val width: Int, val height: Int, val screenSize: ScreenSize, var fileLocation:String, var centerTile:Vector2, var attackCity:Boolean=true) diff --git a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt index b06958b0f3..517c5a7c59 100644 --- a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt +++ b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt @@ -6,6 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.actions.Actions import com.badlogic.gdx.scenes.scene2d.ui.Stack import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align +import com.unciv.Constants import com.unciv.GUI import com.unciv.UncivGame import com.unciv.logic.GameInfo @@ -289,7 +290,7 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { } private fun quickstartNewGame() { - ToastPopup("Working...", this) + ToastPopup(Constants.working, this) val errorText = "Cannot start game with the default new game parameters!" Concurrency.run("QuickStart") { val newGame: GameInfo diff --git a/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorGenerateTab.kt b/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorGenerateTab.kt index 3026ee7e3d..6b3983cd8d 100644 --- a/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorGenerateTab.kt +++ b/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorGenerateTab.kt @@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup import com.badlogic.gdx.scenes.scene2d.ui.CheckBox import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.unciv.Constants import com.unciv.logic.map.MapGeneratedMainType import com.unciv.logic.map.MapParameters import com.unciv.logic.map.MapType @@ -59,9 +60,9 @@ class MapEditorGenerateTab( private fun setButtonsEnabled(enable: Boolean) { newTab.generateButton.isEnabled = enable - newTab.generateButton.setText( (if(enable) "Create" else "Working...").tr()) + newTab.generateButton.setText( (if(enable) "Create" else Constants.working).tr()) partialTab.generateButton.isEnabled = enable - partialTab.generateButton.setText( (if(enable) "Generate" else "Working...").tr()) + partialTab.generateButton.setText( (if(enable) "Generate" else Constants.working).tr()) } private fun generate(step: MapGeneratorSteps) { diff --git a/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorSaveTab.kt b/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorSaveTab.kt index 2727403d09..21de04cc25 100644 --- a/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorSaveTab.kt +++ b/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorSaveTab.kt @@ -4,6 +4,7 @@ import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextField +import com.unciv.Constants import com.unciv.logic.files.MapSaver import com.unciv.logic.map.MapGeneratedMainType import com.unciv.logic.map.TileMap @@ -81,7 +82,7 @@ class MapEditorSaveTab( private fun setSaveButton(enabled: Boolean) { saveButton.isEnabled = enabled - saveButton.setText((if (enabled) "Save map" else "Working...").tr()) + saveButton.setText((if (enabled) "Save map" else Constants.working).tr()) } private fun saveHandler() { diff --git a/core/src/com/unciv/ui/screens/multiplayerscreens/AddMultiplayerGameScreen.kt b/core/src/com/unciv/ui/screens/multiplayerscreens/AddMultiplayerGameScreen.kt index 0867b57e75..576b944cca 100644 --- a/core/src/com/unciv/ui/screens/multiplayerscreens/AddMultiplayerGameScreen.kt +++ b/core/src/com/unciv/ui/screens/multiplayerscreens/AddMultiplayerGameScreen.kt @@ -2,6 +2,7 @@ package com.unciv.ui.screens.multiplayerscreens import com.badlogic.gdx.Gdx import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.unciv.Constants import com.unciv.logic.IdChecker import com.unciv.models.translations.tr import com.unciv.ui.screens.pickerscreens.PickerScreen @@ -53,7 +54,7 @@ class AddMultiplayerGameScreen : PickerScreen() { } val popup = Popup(this) - popup.addGoodSizedLabel("Working...") + popup.addGoodSizedLabel(Constants.working) popup.open() Concurrency.run("AddMultiplayerGame") { diff --git a/core/src/com/unciv/ui/screens/multiplayerscreens/EditMultiplayerGameInfoScreen.kt b/core/src/com/unciv/ui/screens/multiplayerscreens/EditMultiplayerGameInfoScreen.kt index 013350d238..0b05458b60 100644 --- a/core/src/com/unciv/ui/screens/multiplayerscreens/EditMultiplayerGameInfoScreen.kt +++ b/core/src/com/unciv/ui/screens/multiplayerscreens/EditMultiplayerGameInfoScreen.kt @@ -2,6 +2,7 @@ package com.unciv.ui.screens.multiplayerscreens import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle +import com.unciv.Constants import com.unciv.logic.multiplayer.OnlineMultiplayerGame import com.unciv.logic.multiplayer.storage.MultiplayerAuthException import com.unciv.models.translations.tr @@ -103,7 +104,7 @@ class EditMultiplayerGameInfoScreen(val multiplayerGame: OnlineMultiplayerGame) private fun resign(multiplayerGame: OnlineMultiplayerGame) { //Create a popup val popup = Popup(this) - popup.addGoodSizedLabel("Working...").row() + popup.addGoodSizedLabel(Constants.working).row() popup.open() Concurrency.runOnNonDaemonThreadPool("Resign") { diff --git a/core/src/com/unciv/ui/screens/newgamescreen/NewGameScreen.kt b/core/src/com/unciv/ui/screens/newgamescreen/NewGameScreen.kt index e87df08215..f9021dd5cc 100644 --- a/core/src/com/unciv/ui/screens/newgamescreen/NewGameScreen.kt +++ b/core/src/com/unciv/ui/screens/newgamescreen/NewGameScreen.kt @@ -206,7 +206,7 @@ class NewGameScreen( } rightSideButton.disable() - rightSideButton.setText("Working...".tr()) + rightSideButton.setText(Constants.working.tr()) setSkin() // Creating a new game can take a while and we don't want ANRs @@ -280,7 +280,7 @@ class NewGameScreen( private suspend fun startNewGame() = coroutineScope { val popup = Popup(this@NewGameScreen) launchOnGLThread { - popup.addGoodSizedLabel("Working...").row() + popup.addGoodSizedLabel(Constants.working).row() popup.open() } diff --git a/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt b/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt index db95f2274b..53ce956a1a 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt @@ -395,6 +395,48 @@ object Github { var avatar_url: String? = null } + /** + * Query GitHub for topics named "unciv-mod*" + * @return Parsed [TopicSearchResponse] json on success, `null` on failure. + */ + fun tryGetGithubTopics(): TopicSearchResponse? { + // `+repositories:>1` means ignore unused or practically unused topics + val link = "https://api.github.com/search/topics?q=unciv-mod+repositories:%3E1&sort=name&order=asc" + var retries = 2 + while (retries > 0) { + retries-- + // obey rate limit + if (RateLimit.waitForLimit()) return null + // try download + val inputStream = download(link) { + if (it.responseCode == 403 || it.responseCode == 200 && retries == 1) { + // Pass the response headers to the rate limit handler so it can process the rate limit headers + RateLimit.notifyHttpResponse(it) + retries++ // An extra retry so the 403 is ignored in the retry count + } + } ?: continue + return json().fromJson(TopicSearchResponse::class.java, inputStream.bufferedReader().readText()) + } + return null + } + + /** Topic search response */ + @Suppress("PropertyName") + class TopicSearchResponse { + // Commented out: Github returns them, but we're not interested +// var total_count = 0 +// var incomplete_results = false + var items = ArrayList() + class Topic { + var name = "" + var display_name: String? = null // Would need to be curated, which is alottawork +// var featured = false +// var curated = false + var created_at = "" // iso datetime with "Z" timezone + var updated_at = "" // iso datetime with "Z" timezone + } + } + /** Rewrite modOptions file for a mod we just installed to include metadata we got from the GitHub api * * (called on background thread) diff --git a/core/src/com/unciv/ui/screens/pickerscreens/ModManagementOptions.kt b/core/src/com/unciv/ui/screens/pickerscreens/ModManagementOptions.kt index 59e54c25a4..677d145a39 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/ModManagementOptions.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/ModManagementOptions.kt @@ -7,6 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align import com.unciv.Constants +import com.unciv.models.metadata.ModCategories import com.unciv.models.ruleset.Ruleset import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter @@ -72,27 +73,6 @@ class ModManagementOptions(private val modManagementScreen: ModManagementScreen) } } - enum class Category( - val label: String, - val topic: String - ) { - All("All mods", "unciv-mod"), - Rulesets("Rulesets", "unciv-mod-rulesets"), - Expansions("Expansions", "unciv-mod-expansions"), - Graphics("Graphics", "unciv-mod-graphics"), - Audio("Audio", "unciv-mod-audio"), - Maps("Maps", "unciv-mod-maps"), - Fun("Fun", "unciv-mod-fun"), - ModsOfMods("Mods of mods", "unciv-mod-modsofmods"); - - companion object { - fun fromSelectBox(selectBox: TranslatedSelectBox): Category { - val selected = selectBox.selected.value - return values().firstOrNull { it.label == selected } ?: All - } - } - } - class Filter( val text: String, val topic: String @@ -104,7 +84,8 @@ class ModManagementOptions(private val modManagementScreen: ModManagementScreen) private val textField = UncivTextField.create("Enter search text") - var category = Category.All + var category = ModCategories.default() + var sortInstalled = SortType.Name var sortOnline = SortType.Stars @@ -140,12 +121,12 @@ class ModManagementOptions(private val modManagementScreen: ModManagementScreen) } categorySelect = TranslatedSelectBox( - Category.values().map { category -> category.label }, + ModCategories.asSequence().map { it.label }.toList(), category.label, BaseScreen.skin ) categorySelect.onChange { - category = Category.fromSelectBox(categorySelect) + category = ModCategories.fromSelectBox(categorySelect) modManagementScreen.refreshInstalledModTable() modManagementScreen.refreshOnlineModTable() } @@ -209,10 +190,10 @@ class ModManagementOptions(private val modManagementScreen: ModManagementScreen) } } -private fun getTextButton(nameString:String, topics: List): TextButton { - val categories = ArrayList() - for (category in ModManagementOptions.Category.values()) { - if (category== ModManagementOptions.Category.All) continue +private fun getTextButton(nameString: String, topics: List): TextButton { + val categories = ArrayList() + for (category in ModCategories) { + if (category == ModCategories.default()) continue if (topics.contains(category.topic)) categories += category } @@ -225,8 +206,11 @@ private fun getTextButton(nameString:String, topics: List): TextButton { return button } -/** Helper class holds combined mod info for ModManagementScreen, used for both installed and online lists */ -class ModUIData( +/** Helper class holds combined mod info for ModManagementScreen, used for both installed and online lists + * + * Note it is guaranteed either ruleset or repo are non-null, never both. + */ +class ModUIData private constructor( val name: String, val description: String, val ruleset: Ruleset?, @@ -270,12 +254,10 @@ class ModUIData( } private fun matchesCategory(filter: ModManagementOptions.Filter): Boolean { - val modTopic = repo?.topics ?: ruleset?.modOptions?.topics!! - if (filter.topic == ModManagementOptions.Category.All.topic) + if (filter.topic == ModCategories.default().topic) return true - if (modTopic.size < 2) return false - if (modTopic[1] == filter.topic) return true - return false + val modTopics = repo?.topics ?: ruleset?.modOptions?.topics!! + return filter.topic in modTopics } } diff --git a/core/src/com/unciv/ui/screens/pickerscreens/ModManagementScreen.kt b/core/src/com/unciv/ui/screens/pickerscreens/ModManagementScreen.kt index f72306c478..fd198e4970 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/ModManagementScreen.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/ModManagementScreen.kt @@ -718,7 +718,10 @@ class ModManagementScreen( } internal fun refreshOnlineModTable() { - if (runningSearchJob != null) return // cowardice: prevent concurrent modification, avoid a manager layer + if (runningSearchJob != null) { + ToastPopup("Sorting and filtering needs to wait until the online query finishes", this) + return // cowardice: prevent concurrent modification, avoid a manager layer + } val newHeaderText = optionsManager.getOnlineHeader() onlineHeaderLabel?.setText(newHeaderText) diff --git a/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt b/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt index 257da976d1..992fe5e9be 100644 --- a/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt +++ b/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt @@ -141,7 +141,7 @@ class LoadGameScreen : LoadOrSaveScreen() { private fun getLoadFromClipboardButton(): TextButton { val pasteButton = loadFromClipboard.toTextButton() pasteButton.onActivation { - pasteButton.setText("Working...".tr()) + pasteButton.setText(Constants.working.tr()) pasteButton.disable() Concurrency.run(loadFromClipboard) { try { diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt index 33cfbfce75..33b7dcf34b 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt @@ -192,7 +192,7 @@ class NextTurnButton : IconTextButton("", null, 30) { class NextTurnAction(val text: String, val color: Color, val icon: String? = null, val action: () -> Unit) { companion object Prefabs { val Default = NextTurnAction("", Color.BLACK) {} - val Working = NextTurnAction("Working...", Color.GRAY, "NotificationIcons/Working") {} + val Working = NextTurnAction(Constants.working, Color.GRAY, "NotificationIcons/Working") {} val Waiting = NextTurnAction("Waiting for other players...",Color.GRAY, "NotificationIcons/Waiting") {} } } diff --git a/docs/Modders/Mods.md b/docs/Modders/Mods.md index 7e317d61e5..b113c96403 100644 --- a/docs/Modders/Mods.md +++ b/docs/Modders/Mods.md @@ -84,23 +84,25 @@ In order to do this, all you need to do is: - Click the gear icon next to the About (top-right part of the page) - In 'Topics', add "unciv-mod" -Optionally add one of the following topics (make sure this topic is added afer "unciv-mod"): +Optionally add one or more of the following topics to mark your mod as belonging to specific categories: -- unciv-mod-rulesets -- unciv-mod-expansions -- unciv-mod-graphics -- unciv-mod-audio -- unciv-mod-maps -- unciv-mod-fun -- unciv-mod-modsofmods +- unciv-mod-rulesets (for base ruleset mods) +- unciv-mod-expansions (for mods extending vanilla rulesets - please use this, **not** unciv-mod-expansion) +- unciv-mod-graphics (for mods altering graphics - icons, portraits, tilesets) +- unciv-mod-audio (for mods supplying music or modifying sounds) +- unciv-mod-maps (for mods containing maps) +- unciv-mod-fun (for mods mainly tweaking mechanics or other gameplay aspects) +- unciv-mod-modsofmods (for mods extending another mod's ruleset) -When you open your app, it will query Github's [list of repos with that topic](https://github.com/topics/unciv-mod), and now YOUR repo will appear there! +When you open Unciv's Mod Manager, it will query Github's [list of repos with that topic](https://github.com/topics/unciv-mod), and now YOUR repo will appear there! +The categories will appear als annotations on the mod buttons, and the user can filter for them. They are not required for the game to use the content - e.g. you can still load maps from mods lacking the unciv-mod-maps topic. +If you want new categories, github will accept any topic, but you'll have to ask the Unciv team to enable them in the game. ## I have the mod, now what? The primary use of mods is to add them when starting a new game, or configuring a map. This will mean that both the ruleset of the mod, and the images, will be in use for that specific game/map. -For mods which are primarily visual or audio, there is a second use - through the mod manager, you can enable them as **permanent audiovisual mods**. This means that the images, sounds (or upcoming: music) from the mod will replace the original media everywhere in the game. +For mods which are primarily visual or audio, there is a second use - through the mod manager, you can enable them as **permanent audiovisual mods**. This means that the images and/or sounds from the mod will replace the original media everywhere in the game, and contained music will be available - [see here](Images-and-Audio.md#supply-additional-music). ## Mod location for manual loading of mods