Newgame Mod incompatibility toast (#5076)

This commit is contained in:
SomeTroglodyte
2021-09-03 11:26:38 +02:00
committed by GitHub
parent 5e8ecff397
commit 2069f227c4
9 changed files with 73 additions and 37 deletions

View File

@ -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"]
}
]

View File

@ -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 =

View File

@ -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<String,Ruleset>() {
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<String>): Ruleset {
val newRuleset = Ruleset()
val loadedMods = mods.filter { containsKey(it) }.map { this[it]!! }
@ -519,15 +526,30 @@ object RulesetCache : HashMap<String,Ruleset>() {
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<String>): 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() {

View File

@ -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_ */

View File

@ -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
}

View File

@ -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<CheckBox>()
private var lastToast: ToastPopup? = null
init {
val baseRulesetCheckboxes = ArrayList<CheckBox>()
val extensionRulesetModButtons = ArrayList<CheckBox>()
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
}
}
}

View File

@ -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) }
}
/**

View File

@ -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()

View File

@ -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 <T: Actor>(
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].