diff --git a/android/assets/jsons/Civ V - Vanilla/Beliefs.json b/android/assets/jsons/Civ V - Vanilla/Beliefs.json index d6ceb288db..d2a72955c9 100644 --- a/android/assets/jsons/Civ V - Vanilla/Beliefs.json +++ b/android/assets/jsons/Civ V - Vanilla/Beliefs.json @@ -244,7 +244,7 @@ }, ////////////////////////////////////// Enhancer beliefs /////////////////////////////////////// - + { "name": "Defender of the Faith", "type": "Enhancer", @@ -254,7 +254,7 @@ { "name": "Holy Order", "type": "Enhancer", - "uniques": ["[Faith] cost of purchasing [Missionary] units [-30]% ", "[Faith] cost of purchasing [Inquisitor] units [-30]% "] + "uniques": ["[Faith] cost of purchasing [Missionary] units [-30]%", "[Faith] cost of purchasing [Inquisitor] units [-30]%"] }, { "name": "Itinerant Preachers", @@ -293,4 +293,3 @@ "uniques": ["[+50 Faith] whenever a Great Person is expended"] } ] - diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 0813777dd0..3584aaa393 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -331,6 +331,10 @@ No human players selected! = Mods: = Base ruleset mods: = Extension mods: = +The mod you selected is incorrectly defined! = +The mod combination you selected is incorrectly defined! = +The mod combination you selected has problems. = +You can play it, but don't expect everything to work! = Base Ruleset = [amount] Techs = [amount] Nations = diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 7e3a945e78..b8cd0c5dca 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -17,6 +17,7 @@ import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.stats.INamed import com.unciv.models.stats.NamedStats import com.unciv.models.stats.Stat +import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.utils.colorFromRGB import kotlin.collections.set @@ -353,7 +354,9 @@ class Ruleset { val improvementName = unique.params[0] if (improvementName !in tileImprovements) lines += "${unit.name} can place improvement $improvementName which does not exist!" - else if (tileImprovements[improvementName]!!.firstOrNull() == null && !unit.hasUnique("Bonus for units in 2 tile radius 15%")) { + else if ((tileImprovements[improvementName] as Stats).none() && + unit.isCivilian() && + !unit.hasUnique("Bonus for units in 2 tile radius 15%")) { lines += "${unit.name} can place improvement $improvementName which has no stats, preventing unit automation!" warningCount++ } @@ -498,6 +501,10 @@ object RulesetCache : HashMap() { fun getBaseRuleset() = this[BaseRuleset.Civ_V_Vanilla.fullName]!!.clone() // safeguard, so no-one edits the base ruleset by mistake + /** + * Creates a combined [Ruleset] from a list of mods. If no baseRuleset is listed in [mods], + * then the vanilla Ruleset is included automatically. + */ fun getComplexRuleset(mods: LinkedHashSet): Ruleset { val newRuleset = Ruleset() val loadedMods = mods.filter { containsKey(it) }.map { this[it]!! } @@ -519,15 +526,30 @@ object RulesetCache : HashMap() { if (newRuleset.unitTypes.isEmpty()) { newRuleset.unitTypes.putAll(getBaseRuleset().unitTypes) } - + // This one should be permanent if (newRuleset.ruinRewards.isEmpty()) { newRuleset.ruinRewards.putAll(getBaseRuleset().ruinRewards) } - + return newRuleset } + /** + * Runs [Ruleset.checkModLinks] on a temporary [combined Ruleset][getComplexRuleset] for a list of [mods] + */ + fun checkCombinedModLinks(mods: LinkedHashSet): Ruleset.CheckModLinksResult { + return try { + val newRuleset = getComplexRuleset(mods) + newRuleset.modOptions.isBaseRuleset = true // This is so the checkModLinks finds all connections + newRuleset.checkModLinks() + } 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 + Ruleset.CheckModLinksResult(Ruleset.CheckModLinksStatus.Error, ex.localizedMessage) + } + } + } class Specialist: NamedStats() { diff --git a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt index 8756435af5..1e266c08b5 100644 --- a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt @@ -481,7 +481,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { /** This tests whether the buy button should be _shown_ */ private fun isConstructionPurchaseShown(construction: INonPerpetualConstruction, stat: Stat): Boolean { val city = cityScreen.city - return construction.canBePurchasedWithStat(city, stat) || city.civInfo.gameInfo.gameParameters.godMode + return construction.canBePurchasedWithStat(city, stat) } /** This tests whether the buy button should be _enabled_ */ diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt index 1b1540eb2f..98b48c0f50 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt @@ -249,7 +249,7 @@ class FormattedLine ( } val fontSize = when { - header in headerSizes.indices -> headerSizes[header] + header in 1 until headerSizes.size -> headerSizes[header] size == Int.MIN_VALUE -> defaultSize else -> size } diff --git a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt index d0b89cef4b..136aa45410 100644 --- a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt @@ -6,6 +6,7 @@ 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 import com.unciv.ui.utils.* class ModCheckboxTable( @@ -15,9 +16,10 @@ class ModCheckboxTable( onUpdate: (String) -> Unit ): Table(){ private val modRulesets = RulesetCache.values.filter { it.name != "" } + private val baseRulesetCheckboxes = ArrayList() + private var lastToast: ToastPopup? = null init { - val baseRulesetCheckboxes = ArrayList() val extensionRulesetModButtons = ArrayList() for (mod in modRulesets.sortedBy { it.name }) { @@ -51,49 +53,56 @@ class ModCheckboxTable( }).padTop(padTop).growX().row() } } - + private fun checkBoxChanged(checkBox: CheckBox, mod: Ruleset): Boolean { if (checkBox.isChecked) { + // First the quick standalone check val modLinkErrors = mod.checkModLinks() - if (modLinkErrors.isNotOK()) { - ToastPopup("The mod you selected is incorrectly defined!\n\n$modLinkErrors", screen) + if (modLinkErrors.isError()) { + lastToast?.close() + val toastMessage = + "The mod you selected is incorrectly defined!".tr() + "\n\n$modLinkErrors" + lastToast = ToastPopup(toastMessage, screen, 5000L) if (modLinkErrors.isError()) { checkBox.isChecked = false return false } } + // Save selection for a rollback val previousMods = mods.toList() - if (mod.modOptions.isBaseRuleset) + // Ensure only one base can be selected + if (mod.modOptions.isBaseRuleset) { for (oldBaseRuleset in previousMods) // so we don't get concurrent modification exceptions - if (modRulesets.firstOrNull { it.name == oldBaseRuleset }?.modOptions?.isBaseRuleset == true) + if (RulesetCache[oldBaseRuleset]?.modOptions?.isBaseRuleset == true) mods.remove(oldBaseRuleset) + baseRulesetCheckboxes.filter { it != checkBox }.forEach { it.isChecked = false } + } mods.add(mod.name) - var complexModLinkCheck: CheckModLinksResult - try { - val newRuleset = RulesetCache.getComplexRuleset(mods) - newRuleset.modOptions.isBaseRuleset = true // This is so the checkModLinks finds all connections - complexModLinkCheck = newRuleset.checkModLinks() - } 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 - complexModLinkCheck = CheckModLinksResult(Ruleset.CheckModLinksStatus.Error, ex.localizedMessage) - } + // Check over complete combination of selected mods + val complexModLinkCheck = RulesetCache.checkCombinedModLinks(mods) + if (complexModLinkCheck.isNotOK()) { + lastToast?.close() + val toastMessage = ( + if (complexModLinkCheck.isError()) "The mod combination you selected is incorrectly defined!" + else "{The mod combination you selected has problems.}\n{You can play it, but don't expect everything to work!}" + ).tr() + "\n\n$complexModLinkCheck" + lastToast = ToastPopup(toastMessage, screen, 5000L) - 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) - return false + if (complexModLinkCheck.isError()) { + checkBox.isChecked = false + mods.clear() + mods.addAll(previousMods) + return false + } } } else { mods.remove(mod.name) } - return true + return true } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt index e7b6f3f139..eedbadb221 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt @@ -190,16 +190,16 @@ fun String.toLabel() = Label(this.tr(), CameraStageBaseScreen.skin) fun Int.toLabel() = this.toString().toLabel() /** Translate a [String] and make a [Label] widget from it with a specified font color and size */ -fun String.toLabel(fontColor: Color = Color.WHITE, fontSize:Int=18): Label { +fun String.toLabel(fontColor: Color = Color.WHITE, fontSize: Int = 18): Label { // We don't want to use setFontSize and setFontColor because they set the font, // which means we need to rebuild the font cache which means more memory allocation. var labelStyle = CameraStageBaseScreen.skin.get(Label.LabelStyle::class.java) - if(fontColor!= Color.WHITE || fontSize!=18) { // if we want the default we don't need to create another style + if(fontColor != Color.WHITE || fontSize != 18) { // if we want the default we don't need to create another style labelStyle = Label.LabelStyle(labelStyle) // clone this to another labelStyle.fontColor = fontColor if (fontSize != 18) labelStyle.font = Fonts.font } - return Label(this.tr(), labelStyle).apply { setFontScale(fontSize/Fonts.ORIGINAL_FONT_SIZE) } + return Label(this.tr(), labelStyle).apply { setFontScale(fontSize / Fonts.ORIGINAL_FONT_SIZE) } } /** diff --git a/core/src/com/unciv/ui/utils/ToastPopup.kt b/core/src/com/unciv/ui/utils/ToastPopup.kt index f3b3be93cd..2ab4202188 100644 --- a/core/src/com/unciv/ui/utils/ToastPopup.kt +++ b/core/src/com/unciv/ui/utils/ToastPopup.kt @@ -1,6 +1,7 @@ package com.unciv.ui.utils import com.badlogic.gdx.Gdx +import com.badlogic.gdx.scenes.scene2d.Touchable import kotlin.concurrent.thread /** @@ -11,6 +12,7 @@ class ToastPopup (message: String, screen: CameraStageBaseScreen, val time: Long init { //Make this popup unobtrusive setFillParent(false) + onClick { close() } // or `touchable = Touchable.disabled` so you can operate what's behind addGoodSizedLabel(message) open() diff --git a/core/src/com/unciv/ui/utils/UncivTooltip.kt b/core/src/com/unciv/ui/utils/UncivTooltip.kt index 7f4858c6cb..aedd2e5071 100644 --- a/core/src/com/unciv/ui/utils/UncivTooltip.kt +++ b/core/src/com/unciv/ui/utils/UncivTooltip.kt @@ -12,7 +12,7 @@ import com.unciv.models.translations.tr /** * A **Replacement** for Gdx [Tooltip], placement does not follow the mouse. * - * Usage: [group][Group].addStaticTip([text][String], size) builds a [Label] as tip actor and attaches it to your [Group]. + * Usage: [group][Group].addTooltip([text][String], size) builds a [Label] as tip actor and attaches it to your [Group]. * * @param target The widget the tooltip will be added to - take care this is the same for which addListener is called * @param content The actor to display as Tooltip @@ -145,7 +145,7 @@ class UncivTooltip ( return super.touchDown(event, x, y, pointer, button) } //endregion - + companion object { /** * Add a [Label]-based Tooltip with a rounded-corner background to a [Table] or other [Group].