From 4d39c80c936f3037611017e867c968f645cc3343 Mon Sep 17 00:00:00 2001 From: yairm210 Date: Mon, 19 Aug 2024 16:24:45 +0300 Subject: [PATCH] Allowed specifying custom colors for unit promotions --- .../Civ V - Gods & Kings/UnitPromotions.json | 2 ++ .../jsons/Civ V - Vanilla/UnitPromotions.json | 4 ++- .../com/unciv/models/ruleset/nation/Nation.kt | 4 +-- .../unciv/models/ruleset/unit/Promotion.kt | 6 ++++ .../ruleset/validation/RulesetValidator.kt | 35 +++++++++++++------ core/src/com/unciv/ui/images/Portrait.kt | 14 +++++--- .../4-Unit-related-JSON-files.md | 2 ++ 7 files changed, 47 insertions(+), 20 deletions(-) diff --git a/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json b/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json index 79ad07c733..185fa28ea7 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json +++ b/android/assets/jsons/Civ V - Gods & Kings/UnitPromotions.json @@ -4,6 +4,8 @@ "name": "Heal Instantly", "uniques": ["Heal this unit by [50] HP", "Doing so will consume this opportunity to choose a Promotion"], "unitTypes": ["Sword","Gunpowder","Mounted","Scout","Siege","Archery","Ranged Gunpowder","Armored","Melee Water","Ranged Water","Submarine"], + "innerColor": [195,53,43], + "outerColor": [253,236,234], "row": 0, "column": 0 }, diff --git a/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json b/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json index dbc866045d..0209701cf7 100644 --- a/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json +++ b/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json @@ -3,7 +3,9 @@ { "name": "Heal Instantly", "uniques": ["Heal this unit by [50] HP", "Doing so will consume this opportunity to choose a Promotion"], - "unitTypes": ["Sword","Gunpowder","Mounted","Scout","Siege","Archery","Ranged Gunpowder","Armored","Melee Water","Ranged Water","Submarine"] + "unitTypes": ["Sword","Gunpowder","Mounted","Scout","Siege","Archery","Ranged Gunpowder","Armored","Melee Water","Ranged Water","Submarine"], + "innerColor": [195,53,43], + "outerColor": [253,236,234] }, // Ranged+Siege diff --git a/core/src/com/unciv/models/ruleset/nation/Nation.kt b/core/src/com/unciv/models/ruleset/nation/Nation.kt index 074616c6eb..e1901f1483 100644 --- a/core/src/com/unciv/models/ruleset/nation/Nation.kt +++ b/core/src/com/unciv/models/ruleset/nation/Nation.kt @@ -257,9 +257,7 @@ class Nation : RulesetObject() { } } } - - fun getContrastRatio() = getContrastRatio(getInnerColor(), getOuterColor()) - + fun matchesFilter(filter: String): Boolean { return MultiFilter.multiFilter(filter, ::matchesSingleFilter) } diff --git a/core/src/com/unciv/models/ruleset/unit/Promotion.kt b/core/src/com/unciv/models/ruleset/unit/Promotion.kt index 9908d7c104..cfdc450d12 100644 --- a/core/src/com/unciv/models/ruleset/unit/Promotion.kt +++ b/core/src/com/unciv/models/ruleset/unit/Promotion.kt @@ -5,6 +5,7 @@ import com.unciv.models.ruleset.RulesetObject import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr +import com.unciv.ui.components.extensions.colorFromRGB import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.objectdescriptions.uniquesToDescription import com.unciv.ui.screens.civilopediascreen.FormattedLine @@ -15,6 +16,11 @@ class Promotion : RulesetObject() { var unitTypes = listOf() // The json parser wouldn't agree to deserialize this as a list of UnitTypes. =( + var innerColor: List? = null + val innerColorObject by lazy { if (innerColor == null) null else colorFromRGB(innerColor!!)} + var outerColor: List? = null + val outerColorObject by lazy { if (outerColor == null) null else colorFromRGB(outerColor!!)} + /** Used as **column** hint in the current [PromotionPickerScreen] * This is no longer a direct position, it is used to sort before an automatic distribution. * -1 determines that the modder has not set a position */ diff --git a/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt b/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt index ff6500ff3c..9b1461b379 100644 --- a/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt +++ b/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt @@ -23,6 +23,7 @@ import com.unciv.models.tilesets.TileSetCache import com.unciv.models.tilesets.TileSetConfig import com.unciv.ui.images.AtlasPreview import com.unciv.ui.images.Portrait +import com.unciv.ui.images.PortraitPromotion class RulesetValidator(val ruleset: Ruleset) { @@ -644,7 +645,8 @@ class RulesetValidator(val ruleset: Ruleset) { ) { for (promotion in ruleset.unitPromotions.values) { uniqueValidator.checkUniques(promotion, lines, false, tryFixUnknownUniques) - + checkContrasts(promotion.innerColorObject ?: PortraitPromotion.defaultInnerColor, + promotion.outerColorObject ?: PortraitPromotion.defaultOuterColor, promotion, lines) addPromotionErrorRulesetInvariant(promotion, lines) } } @@ -673,10 +675,20 @@ class RulesetValidator(val ruleset: Ruleset) { lines.add("${nation.name} can settle cities, but has no city names!", sourceObject = nation) } - // https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast - val constrastRatio = nation.getContrastRatio() - if (constrastRatio < 3) { - val (newInnerColor, newOuterColor) = getSuggestedColors(nation) + checkContrasts(nation.getInnerColor(), nation.getOuterColor(), nation, lines) + } + + + + private fun checkContrasts( + innerColor: Color, + outerColor: Color, + nation: RulesetObject, + lines: RulesetErrorList + ) { + val constrastRatio = getContrastRatio(innerColor, outerColor) + if (constrastRatio < 3) { // https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast + val (newInnerColor, newOuterColor) = getSuggestedColors(innerColor, outerColor) var text = "${nation.name}'s colors do not contrast enough - it is unreadable!" text += "\nSuggested colors: " @@ -687,11 +699,12 @@ class RulesetValidator(val ruleset: Ruleset) { } } + data class SuggestedColors(val innerColor: Color, val outerColor: Color) - private fun getSuggestedColors(nation: Nation): SuggestedColors { - val innerColorLuminance = getRelativeLuminance(nation.getInnerColor()) - val outerColorLuminance = getRelativeLuminance(nation.getOuterColor()) + private fun getSuggestedColors(innerColor: Color, outerColor: Color): SuggestedColors { + val innerColorLuminance = getRelativeLuminance(innerColor) + val outerColorLuminance = getRelativeLuminance(outerColor) val innerLerpColor: Color val outerLerpColor: Color @@ -706,12 +719,12 @@ class RulesetValidator(val ruleset: Ruleset) { for (i in 1..10) { - val newInnerColor = nation.getInnerColor().cpy().lerp(innerLerpColor, 0.05f * i) - val newOuterColor = nation.getOuterColor().cpy().lerp(outerLerpColor, 0.05f * i) + val newInnerColor = innerColor.cpy().lerp(innerLerpColor, 0.05f * i) + val newOuterColor = outerColor.cpy().lerp(outerLerpColor, 0.05f * i) if (getContrastRatio(newInnerColor, newOuterColor) > 3) return SuggestedColors(newInnerColor, newOuterColor) } - throw Exception("Error getting suggested colors for nation "+nation.name) + throw Exception("Error getting suggested colors") } private fun addBuildingErrorsRulesetInvariant( diff --git a/core/src/com/unciv/ui/images/Portrait.kt b/core/src/com/unciv/ui/images/Portrait.kt index f57fddd28a..72aea71469 100644 --- a/core/src/com/unciv/ui/images/Portrait.kt +++ b/core/src/com/unciv/ui/images/Portrait.kt @@ -223,7 +223,6 @@ class PortraitImprovement(name: String, size: Float, dim: Boolean = false, isPil class PortraitNation(name: String, size: Float) : Portrait(Type.Nation, name, size, size*0.1f) { override fun getDefaultImage(): Image { - val nation = ruleset.nations[imageName] val isCityState = nation != null && nation.isCityState val pathCityState = "NationIcons/CityState" @@ -240,7 +239,6 @@ class PortraitNation(name: String, size: Float) : Portrait(Type.Nation, name, si ruleset.nations[imageName]?.getOuterColor() ?: Color.BLACK override fun getDefaultOuterBackgroundTint(): Color = getDefaultImageTint() - override fun getDefaultImageTint(): Color = ruleset.nations[imageName]?.getInnerColor() ?: Color.WHITE } @@ -280,9 +278,15 @@ class PortraitPromotion(name: String, size: Float) : Portrait(Type.Promotion, na else -> ImageGetter.getImage(pathIconFallback) } } - - override fun getDefaultImageTint(): Color = colorFromRGB(255, 226, 0) + + override fun getDefaultImageTint(): Color = ruleset.unitPromotions[imageName]?.innerColorObject + ?: defaultInnerColor override fun getDefaultOuterBackgroundTint(): Color = getDefaultImageTint() - override fun getDefaultInnerBackgroundTint(): Color = colorFromRGB(0, 12, 49) + override fun getDefaultInnerBackgroundTint(): Color = ruleset.unitPromotions[imageName]?.outerColorObject + ?: defaultOuterColor + companion object { + val defaultInnerColor = colorFromRGB(255, 226, 0) + val defaultOuterColor = colorFromRGB(0, 12, 49) + } } diff --git a/docs/Modders/Mod-file-structure/4-Unit-related-JSON-files.md b/docs/Modders/Mod-file-structure/4-Unit-related-JSON-files.md index 3dc211412b..d66f392ef2 100644 --- a/docs/Modders/Mod-file-structure/4-Unit-related-JSON-files.md +++ b/docs/Modders/Mod-file-structure/4-Unit-related-JSON-files.md @@ -53,6 +53,8 @@ Each promotion has the following structure: | unitTypes | List of Strings | empty | The unit types for which this promotion applies as specified in [UnitTypes.json](#unittypesjson) | | uniques | List of Strings | empty | List of [unique abilities](../../uniques.md) this promotion grants to the units | | civilopediaText | List | empty | See [civilopediaText chapter](5-Miscellaneous-JSON-files.md#civilopedia-text) | +| innerColor | List | empty | Color of the *icon* | +| outerColor | List | empty | Color of the *background* | ## UnitTypes.json