mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-07 01:28:23 +07:00
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
This commit is contained in:
parent
b0a1eed872
commit
46a84c23b0
50
android/assets/jsons/ModCategories.json
Normal file
50
android/assets/jsons/ModCategories.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
@ -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] =
|
||||
|
@ -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"
|
||||
|
@ -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())
|
||||
|
85
core/src/com/unciv/models/metadata/ModCategories.kt
Normal file
85
core/src/com/unciv/models/metadata/ModCategories.kt
Normal file
@ -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<ModCategories.Category>() {
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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") {
|
||||
|
@ -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") {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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<Topic>()
|
||||
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)
|
||||
|
@ -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<String>): TextButton {
|
||||
val categories = ArrayList<ModManagementOptions.Category>()
|
||||
for (category in ModManagementOptions.Category.values()) {
|
||||
if (category== ModManagementOptions.Category.All) continue
|
||||
private fun getTextButton(nameString: String, topics: List<String>): TextButton {
|
||||
val categories = ArrayList<ModCategories.Category>()
|
||||
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<String>): 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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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") {}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user