mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 15:27:50 +07:00
Newgame Mod incompatibility toast (#5076)
This commit is contained in:
@ -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"]
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -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 =
|
||||
|
@ -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() {
|
||||
|
@ -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_ */
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
|
@ -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].
|
||||
|
Reference in New Issue
Block a user