Modding: allow custom ResourcePortraits (#8330)

* Modding: allow custom ResourcePortraits

* Modding: big generification of the icons/portraits mechanism

* Resolve conflict

* Fix background fallback, remove dimming of improvement icons

Co-authored-by: tunerzinc@gmail.com <vfylfhby>
This commit is contained in:
vegeta1k95
2023-01-09 20:03:00 +01:00
committed by GitHub
parent 506d9e65ff
commit 0006aaef55
45 changed files with 1401 additions and 1242 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,257 +1,271 @@
NationIcons.png
size: 2048, 256
size: 2048, 512
format: RGBA8888
filter: MipMapLinearLinear, MipMapLinearLinear
repeat: none
NationIcons/America
rotate: false
xy: 4, 112
xy: 4, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Arabia
rotate: false
xy: 4, 4
xy: 4, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Austria
rotate: false
xy: 112, 112
xy: 112, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Aztecs
rotate: false
xy: 112, 4
xy: 4, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Babylon
rotate: false
xy: 220, 112
xy: 112, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Barbarians
rotate: false
xy: 220, 4
xy: 220, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Byzantium
rotate: false
xy: 328, 112
xy: 4, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Carthage
rotate: false
xy: 328, 4
xy: 112, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Celts
rotate: false
xy: 436, 112
xy: 220, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/China
rotate: false
xy: 436, 4
xy: 328, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/CityState
rotate: false
xy: 544, 112
xy: 112, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Denmark
rotate: false
xy: 544, 4
xy: 220, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Egypt
rotate: false
xy: 652, 112
xy: 328, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/England
rotate: false
xy: 652, 4
xy: 436, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Ethiopia
rotate: false
xy: 760, 112
xy: 220, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/France
rotate: false
xy: 760, 4
xy: 328, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Germany
rotate: false
xy: 868, 112
xy: 436, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Greece
rotate: false
xy: 868, 4
xy: 544, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Inca
rotate: false
xy: 976, 112
xy: 328, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/India
rotate: false
xy: 976, 4
xy: 436, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Iroquois
rotate: false
xy: 1084, 112
xy: 544, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Japan
rotate: false
xy: 1084, 4
xy: 652, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Korea
rotate: false
xy: 1192, 112
xy: 436, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Mongolia
rotate: false
xy: 1192, 4
xy: 544, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Persia
rotate: false
xy: 1300, 112
xy: 652, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Polynesia
rotate: false
xy: 1300, 4
xy: 760, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Random
rotate: false
xy: 544, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Rome
rotate: false
xy: 1408, 112
xy: 652, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Russia
rotate: false
xy: 1408, 4
xy: 760, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Siam
rotate: false
xy: 1516, 112
xy: 868, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Songhai
rotate: false
xy: 1516, 4
xy: 652, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Spain
rotate: false
xy: 1624, 112
xy: 760, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Spectator
rotate: false
xy: 868, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/Sweden
rotate: false
xy: 1624, 4
xy: 976, 328
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/The Huns
rotate: false
xy: 1732, 112
xy: 760, 4
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/The Maya
rotate: false
xy: 1732, 4
xy: 868, 112
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/The Netherlands
rotate: false
xy: 1840, 112
xy: 976, 220
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
NationIcons/The Ottomans
rotate: false
xy: 1840, 4
xy: 1084, 328
size: 100, 100
orig: 100, 100
offset: 0, 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 190 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 KiB

After

Width:  |  Height:  |  Size: 580 KiB

View File

@ -61,8 +61,8 @@ open class Notification() : IsPartOfGameInfoSerialization {
if (icons.isEmpty()) return
for (icon in icons.reversed()) {
val image: Actor = when {
ruleset.technologies.containsKey(icon) -> ImageGetter.getTechIconGroup(icon, iconSize)
ruleset.nations.containsKey(icon) -> ImageGetter.getNationIndicator(
ruleset.technologies.containsKey(icon) -> ImageGetter.getTechIconPortrait(icon, iconSize)
ruleset.nations.containsKey(icon) -> ImageGetter.getNationPortrait(
ruleset.nations[icon]!!,
iconSize
)

View File

@ -23,22 +23,20 @@ data class UnitAction(
val action: (() -> Unit)? = null
) {
fun getIcon(): Actor {
if (type.imageGetter != null) return type.imageGetter.invoke()
.surroundWithCircle(20f)
.surroundWithThinCircle()
if (type.imageGetter != null)
return type.imageGetter.invoke()
return when (type) {
UnitActionType.Create -> {
ImageGetter.getImprovementIcon(title.getPlaceholderParameters()[0])
ImageGetter.getImprovementPortrait(title.getPlaceholderParameters()[0])
}
UnitActionType.SpreadReligion -> {
val religionName = title.getPlaceholderParameters()[0]
ImageGetter.getReligionImage(
ImageGetter.getReligionPortrait(
if (ImageGetter.religionIconExists(religionName)) religionName
else "Pantheon"
).apply { color = Color.BLACK }
.surroundWithCircle(20f).surroundWithThinCircle()
else "Pantheon", 20f
)
}
else -> ImageGetter.getImage("UnitActionIcons/Star").apply { color = Color.BLACK }
else -> ImageGetter.getUnitActionPortrait("Star")
}
}
}
@ -62,77 +60,77 @@ enum class UnitActionType(
val uncivSound: UncivSound = UncivSound.Click
) {
SwapUnits("Swap units",
{ ImageGetter.getImage("UnitActionIcons/Swap") }, 'y', false),
{ ImageGetter.getUnitActionPortrait("Swap") }, 'y', false),
Automate("Automate",
{ ImageGetter.getImage("UnitActionIcons/Automate") }, 'm'),
{ ImageGetter.getUnitActionPortrait("Automate") }, 'm'),
StopAutomation("Stop automation",
{ ImageGetter.getImage("UnitActionIcons/Stop") }, 'm', false),
{ ImageGetter.getUnitActionPortrait("Stop") }, 'm', false),
StopMovement("Stop movement",
{ ImageGetter.getImage("UnitActionIcons/StopMove") }, '.', false),
{ ImageGetter.getUnitActionPortrait("StopMove") }, '.', false),
Sleep("Sleep",
{ ImageGetter.getImage("UnitActionIcons/Sleep") }, 'f'),
{ ImageGetter.getUnitActionPortrait("Sleep") }, 'f'),
SleepUntilHealed("Sleep until healed",
{ ImageGetter.getImage("UnitActionIcons/Sleep") }, 'h'),
{ ImageGetter.getUnitActionPortrait("Sleep") }, 'h'),
Fortify("Fortify",
{ ImageGetter.getImage("UnitActionIcons/Fortify") }, 'f', UncivSound.Fortify),
{ ImageGetter.getUnitActionPortrait("Fortify") }, 'f', UncivSound.Fortify),
FortifyUntilHealed("Fortify until healed",
{ ImageGetter.getImage("UnitActionIcons/FortifyUntilHealed") }, 'h', UncivSound.Fortify),
{ ImageGetter.getUnitActionPortrait("FortifyUntilHealed") }, 'h', UncivSound.Fortify),
Explore("Explore",
{ ImageGetter.getImage("UnitActionIcons/Explore") }, 'x'),
{ ImageGetter.getUnitActionPortrait("Explore") }, 'x'),
StopExploration("Stop exploration",
{ ImageGetter.getImage("UnitActionIcons/Stop") }, 'x', false),
{ ImageGetter.getUnitActionPortrait("Stop") }, 'x', false),
Promote("Promote",
{ ImageGetter.getImage("UnitActionIcons/Promote") }, 'o', false, UncivSound.Promote),
{ ImageGetter.getUnitActionPortrait("Promote") }, 'o', false, UncivSound.Promote),
Upgrade("Upgrade",
{ ImageGetter.getImage("UnitActionIcons/Upgrade") }, 'u', UncivSound.Upgrade),
{ ImageGetter.getUnitActionPortrait("Upgrade") }, 'u', UncivSound.Upgrade),
Pillage("Pillage",
{ ImageGetter.getImage("UnitActionIcons/Pillage") }, 'p', false),
{ ImageGetter.getUnitActionPortrait("Pillage") }, 'p', false),
Paradrop("Paradrop",
{ ImageGetter.getImage("UnitActionIcons/Paradrop") }, 'p', false),
{ ImageGetter.getUnitActionPortrait("Paradrop") }, 'p', false),
AirSweep("Air Sweep",
{ ImageGetter.getImage("UnitActionIcons/AirSweep") }, 'a', false),
{ ImageGetter.getUnitActionPortrait("AirSweep") }, 'a', false),
SetUp("Set up",
{ ImageGetter.getImage("UnitActionIcons/SetUp") }, 't', false, UncivSound.Setup),
{ ImageGetter.getUnitActionPortrait("SetUp") }, 't', false, UncivSound.Setup),
FoundCity("Found city",
{ ImageGetter.getImage("UnitActionIcons/FoundCity") }, 'c', UncivSound.Silent),
{ ImageGetter.getUnitActionPortrait("FoundCity") }, 'c', UncivSound.Silent),
ConstructImprovement("Construct improvement",
{ ImageGetter.getImage("UnitActionIcons/ConstructImprovement") }, 'i'),
{ ImageGetter.getUnitActionPortrait("ConstructImprovement") }, 'i'),
Repair(Constants.repair,
{ ImageGetter.getImage("UnitActionIcons/Repair") }, 'r', UncivSound.Construction),
{ ImageGetter.getUnitActionPortrait("Repair") }, 'r', UncivSound.Construction),
Create("Create",
null, 'i', UncivSound.Chimes),
HurryResearch("Hurry Research",
{ ImageGetter.getImage("UnitActionIcons/HurryResearch") }, 'g', UncivSound.Chimes),
{ ImageGetter.getUnitActionPortrait("HurryResearch") }, 'g', UncivSound.Chimes),
StartGoldenAge("Start Golden Age",
{ ImageGetter.getImage("UnitActionIcons/StartGoldenAge") }, 'g', UncivSound.Chimes),
{ ImageGetter.getUnitActionPortrait("StartGoldenAge") }, 'g', UncivSound.Chimes),
HurryWonder("Hurry Wonder",
{ ImageGetter.getImage("UnitActionIcons/HurryConstruction") }, 'g', UncivSound.Chimes),
{ ImageGetter.getUnitActionPortrait("HurryConstruction") }, 'g', UncivSound.Chimes),
HurryBuilding("Hurry Construction",
{ ImageGetter.getImage("UnitActionIcons/HurryConstruction") }, 'g', UncivSound.Chimes),
{ ImageGetter.getUnitActionPortrait("HurryConstruction") }, 'g', UncivSound.Chimes),
ConductTradeMission("Conduct Trade Mission",
{ ImageGetter.getImage("UnitActionIcons/ConductTradeMission") }, 'g', UncivSound.Chimes),
{ ImageGetter.getUnitActionPortrait("ConductTradeMission") }, 'g', UncivSound.Chimes),
FoundReligion("Found a Religion",
{ ImageGetter.getImage("UnitActionIcons/FoundReligion") }, 'g', UncivSound.Choir),
{ ImageGetter.getUnitActionPortrait("FoundReligion") }, 'g', UncivSound.Choir),
TriggerUnique("Trigger unique",
{ ImageGetter.getImage("UnitActionIcons/Star") }, 'g', false, UncivSound.Chimes),
{ ImageGetter.getUnitActionPortrait("Star") }, 'g', false, UncivSound.Chimes),
SpreadReligion("Spread Religion",
null, 'g', UncivSound.Choir),
RemoveHeresy("Remove Heresy",
{ ImageGetter.getImage("UnitActionIcons/RemoveHeresy") }, 'h', UncivSound.Fire),
{ ImageGetter.getUnitActionPortrait("RemoveHeresy") }, 'h', UncivSound.Fire),
EnhanceReligion("Enhance a Religion",
{ ImageGetter.getImage("UnitActionIcons/EnhanceReligion") }, 'g', UncivSound.Choir),
{ ImageGetter.getUnitActionPortrait("EnhanceReligion") }, 'g', UncivSound.Choir),
DisbandUnit("Disband unit",
{ ImageGetter.getImage("UnitActionIcons/DisbandUnit") }, KeyCharAndCode.DEL, false),
{ ImageGetter.getUnitActionPortrait("DisbandUnit") }, KeyCharAndCode.DEL, false),
GiftUnit("Gift unit",
{ ImageGetter.getImage("UnitActionIcons/Present") }, UncivSound.Silent),
{ ImageGetter.getUnitActionPortrait("Present") }, UncivSound.Silent),
Wait("Wait",
{ ImageGetter.getImage("UnitActionIcons/Wait") }, 'z', UncivSound.Silent),
{ ImageGetter.getUnitActionPortrait("Wait") }, 'z', UncivSound.Silent),
ShowAdditionalActions("Show more",
{ ImageGetter.getImage("UnitActionIcons/ShowMore") }, KeyCharAndCode(Input.Keys.PAGE_DOWN), false),
{ ImageGetter.getUnitActionPortrait("ShowMore") }, KeyCharAndCode(Input.Keys.PAGE_DOWN), false),
HideAdditionalActions("Back",
{ ImageGetter.getImage("UnitActionIcons/HideMore") }, KeyCharAndCode(Input.Keys.PAGE_UP), false),
{ ImageGetter.getUnitActionPortrait("HideMore") }, KeyCharAndCode(Input.Keys.PAGE_UP), false),
AddInCapital( "Add in capital",
{ ImageGetter.getImage("UnitActionIcons/AddInCapital")}, 'g', UncivSound.Chimes),
{ ImageGetter.getUnitActionPortrait("AddInCapital")}, 'g', UncivSound.Chimes),
;
// Allow shorter initializations

View File

@ -302,7 +302,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
table.defaults().pad(2f).minWidth(40f)
if (isFirstConstructionOfItsKind) table.add(getProgressBar(constructionName)).minWidth(5f)
else table.add().minWidth(5f)
table.add(ImageGetter.getPortraitImage(constructionName, 40f)).padRight(10f)
table.add(ImageGetter.getConstructionPortrait(constructionName, 40f)).padRight(10f)
table.add(text.toLabel()).expandX().fillX().left()
if (constructionQueueIndex > 0) table.add(getRaisePriorityButton(constructionQueueIndex, constructionName, city)).right()
@ -361,7 +361,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
}
pickConstructionButton.add(getProgressBar(construction.name)).padRight(5f)
pickConstructionButton.add(ImageGetter.getPortraitImage(construction.name, 40f)).padRight(10f)
pickConstructionButton.add(ImageGetter.getConstructionPortrait(construction.name, 40f)).padRight(10f)
pickConstructionButton.add(constructionButtonDTO.buttonText.toLabel()).expandX().fillX()
if (!cannotAddConstructionToQueue(construction, cityScreen.city, cityScreen.city.cityConstructions)) {

View File

@ -8,8 +8,8 @@ import com.unciv.logic.city.CityReligionManager
import com.unciv.models.Religion
import com.unciv.ui.civilopedia.CivilopediaCategories
import com.unciv.ui.civilopedia.CivilopediaScreen
import com.unciv.ui.images.IconCircleGroup
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.images.Portrait
import com.unciv.ui.overviewscreen.EmpireOverviewCategories
import com.unciv.ui.overviewscreen.EmpireOverviewScreen
import com.unciv.ui.utils.BaseScreen
@ -79,8 +79,8 @@ class CityReligionInfoTable(
return if (religion == null) "Religion" to "None"
else religion.getIconName() to religion.getReligionDisplayName()
}
private fun linkedReligionIcon(iconName: String, religion: String?): IconCircleGroup {
val icon = ImageGetter.getCircledReligionIcon(iconName, 30f)
private fun linkedReligionIcon(iconName: String, religion: String?): Portrait {
val icon = ImageGetter.getReligionPortrait(iconName, 30f)
if (religion == null) return icon
icon.onClick {
val newScreen = if (religion == iconName) {
@ -98,7 +98,7 @@ class CityReligionInfoTable(
return ExpanderTab(
title = "Majority Religion: [$label]",
fontSize = Constants.defaultFontSize,
icon = ImageGetter.getCircledReligionIcon(icon, 30f),
icon = ImageGetter.getReligionPortrait(icon, 30f),
defaultPad = 0f,
persistenceID = "CityStatsTable.Religion",
startsOutOpened = false,

View File

@ -183,7 +183,7 @@ class CityStatsTable(val cityScreen: CityScreen): Table() {
ImageGetter.getStatIcon("Food") to
"We Love The King Day for another [${cityInfo.getFlag(CityFlags.WeLoveTheKing)}] turns".toLabel(Color.LIME)
cityInfo.demandedResource.isNotEmpty() ->
ImageGetter.getResourceImage(cityInfo.demandedResource, 20f) to
ImageGetter.getResourcePortrait(cityInfo.demandedResource, 20f) to
"Demanding [${cityInfo.demandedResource}]".toLabel(Color.CORAL)
else -> null to null
}
@ -270,7 +270,7 @@ class CityStatsTable(val cityScreen: CityScreen): Table() {
val info = Table()
val statsAndSpecialists = Table()
val icon = ImageGetter.getPortraitImage(building.name, 50f)
val icon = ImageGetter.getConstructionPortrait(building.name, 50f)
val isFree = building.name in cityScreen.city.civInfo.civConstructions.getFreeBuildings(cityScreen.city.id)
val displayName = if (isFree) "{${building.name}} ({Free})" else building.name
@ -374,7 +374,7 @@ class CityStatsTable(val cityScreen: CityScreen): Table() {
info.add(progressBar).colspan(2).left().expandX().row()
greatPeopleTable.add(info).growX().top().padBottom(10f)
greatPeopleTable.add(ImageGetter.getPortraitImage(greatPersonName, 50f)).row()
greatPeopleTable.add(ImageGetter.getConstructionPortrait(greatPersonName, 50f)).row()
}
lowerTable.addCategory("Great People", greatPeopleTable)

View File

@ -61,7 +61,7 @@ class ConstructionInfoTable(val cityScreen: CityScreen): Table() {
selectedConstructionTable.run {
pad(10f)
add(ImageGetter.getPortraitImage(construction.name, 50f).apply {
add(ImageGetter.getConstructionPortrait(construction.name, 50f).apply {
val link = (construction as? IRulesetObject)?.makeLink() ?: return@apply
if (link.isEmpty()) return@apply
touchable = Touchable.enabled

View File

@ -52,15 +52,15 @@ object CivilopediaImageGetters {
}
val construction = { name: String, size: Float ->
ImageGetter.getPortraitImage(name, size)
ImageGetter.getConstructionPortrait(name, size)
}
val improvement = { name: String, size: Float ->
ImageGetter.getImprovementIcon(name, size)
ImageGetter.getImprovementPortrait(name, size)
}
val nation = { name: String, size: Float ->
val nation = ImageGetter.ruleset.nations[name]
if (nation == null) null
else ImageGetter.getNationIndicator(nation, size)
else ImageGetter.getNationPortrait(nation, size)
}
val policy = { name: String, size: Float ->
// policy branch start and complete have no icons but are linked -> nonexistence must be passed down
@ -75,13 +75,13 @@ object CivilopediaImageGetters {
else null
}
val resource = { name: String, size: Float ->
ImageGetter.getResourceImage(name, size)
ImageGetter.getResourcePortrait(name, size)
}
val technology = { name: String, size: Float ->
ImageGetter.getTechIconGroup(name, size)
ImageGetter.getTechIconPortrait(name, size)
}
val promotion = { name: String, size: Float ->
ImageGetter.getPromotionIcon(name, size)
ImageGetter.getPromotionPortrait(name, size)
}
val terrain = { name: String, size: Float ->
val terrain = ImageGetter.ruleset.terrains[name]
@ -89,20 +89,7 @@ object CivilopediaImageGetters {
else terrainImage(terrain, ImageGetter.ruleset, size)
}
val belief = { name: String, size: Float ->
// Kludge until we decide how exactly to show Religions
fun getInvertedCircledReligionIcon(iconName: String, size: Float) =
ImageGetter.getCircledReligionIcon(iconName, size).apply {
circle.color = Color.WHITE
actor.color = Color.BLACK
}
if (ImageGetter.religionIconExists(name)) {
getInvertedCircledReligionIcon(name, size)
} else {
val typeName = ImageGetter.ruleset.beliefs[name]?.type?.name
if (typeName != null && ImageGetter.religionIconExists(typeName))
getInvertedCircledReligionIcon(typeName, size)
else null
}
ImageGetter.getReligionPortrait(name, size)
}
}

View File

@ -201,126 +201,71 @@ object ImageGetter {
.apply { setSize(20f, 20f) }
}
fun wonderImageExists(wonderName: String) = imageExists("WonderImages/$wonderName")
fun getWonderImage(wonderName: String) = getImage("WonderImages/$wonderName")
fun getNationIcon(nation: String) = getImage("NationIcons/$nation")
fun getNationPortrait(nation: Nation, size: Float): Portrait {
return PortraitNation(nation.name, size)
}
fun getRandomNationPortrait(size: Float): Portrait {
return PortraitNation("Random", size)
}
fun getUnitIcon(unitName: String, color: Color = Color.BLACK): Image {
return getImage("UnitIcons/$unitName").apply { this.color = color }
}
fun getNationIndicator(nation: Nation, size: Float): IconCircleGroup {
val civIconName = if (nation.isCityState()) "CityState" else nation.name
return if (nationIconExists(civIconName)) {
val cityStateIcon = getNationIcon(civIconName)
cityStateIcon.color = nation.getInnerColor()
cityStateIcon.surroundWithCircle(size * 0.9f).apply { circle.color = nation.getOuterColor() }
.surroundWithCircle(size, false).apply { circle.color = nation.getInnerColor() }
} else getCircle().apply { color = nation.getOuterColor() }
.surroundWithCircle(size).apply { circle.color = nation.getInnerColor() }
}
fun getRandomNationIndicator(size: Float): IconCircleGroup {
return "?"
.toLabel(Color.WHITE, (size * 5f/8f).toInt())
.apply { this.setAlignment(Align.center) }
.surroundWithCircle(size * 0.9f).apply { circle.color = Color.BLACK }
.surroundWithCircle(size, false).apply { circle.color = Color.WHITE }
}
private fun nationIconExists(nation: String) = imageExists("NationIcons/$nation")
fun getNationIcon(nation: String) = getImage("NationIcons/$nation")
fun wonderImageExists(wonderName: String) = imageExists("WonderImages/$wonderName")
fun getWonderImage(wonderName: String) = getImage("WonderImages/$wonderName")
private fun getColorFromStats(stats: Stats): Color? {
if (stats.asSequence().none { it.value > 0 }) return Color.WHITE
val highestStat = stats.asSequence().maxByOrNull { it.value }!!
return highestStat.key.color
}
fun getImprovementIcon(improvementName: String, size: Float = 20f, withCircle: Boolean = true): Group {
if (improvementName == Constants.cancelImprovementOrder)
return getImage("OtherIcons/Stop").surroundWithCircle(size)
val icon = getImage("ImprovementIcons/$improvementName")
if (!withCircle) return icon.toGroup(size)
val group = icon.surroundWithCircle(size)
val improvement = ruleset.tileImprovements[improvementName]
if (improvement != null)
group.circle.color = getColorFromStats(improvement)
return group.surroundWithThinCircle()
}
fun getPortraitImage(construction: String, size: Float): Group {
fun getConstructionPortrait(construction: String, size: Float): Group {
if (ruleset.buildings.containsKey(construction)) {
val buildingPortraitLocation = "BuildingPortraits/$construction"
return if (imageExists(buildingPortraitLocation)) {
getImage(buildingPortraitLocation).toGroup(size)
} else {
val image = if (imageExists("BuildingIcons/$construction")) getImage("BuildingIcons/$construction")
else getImage("BuildingIcons/Fallback")
image.surroundWithCircle(size).surroundWithThinCircle()
}
return PortraitBuilding(construction, size)
}
if (ruleset.units.containsKey(construction)) {
val unitPortraitLocation = "UnitPortraits/$construction"
return if (imageExists(unitPortraitLocation)) {
getImage(unitPortraitLocation).toGroup(size)
} else
getUnitIcon(construction).surroundWithCircle(size).surroundWithThinCircle()
return PortraitUnit(construction, size)
}
if (PerpetualConstruction.perpetualConstructionsMap.containsKey(construction))
return getImage("OtherIcons/Convert$construction").toGroup(size)
return getStatIcon(construction).surroundWithCircle(size).surroundWithThinCircle()
}
fun getPromotionIcon(promotionName: String, size: Float = 30f): Actor {
val nameWithoutBrackets = promotionName.replace("[", "").replace("]", "")
fun getUniquePortrait(uniqueName: String, size: Float): Group {
return PortraitUnique(uniqueName, size)
}
var level = when {
nameWithoutBrackets.endsWith(" I") -> 1
nameWithoutBrackets.endsWith(" II") -> 2
nameWithoutBrackets.endsWith(" III") -> 3
else -> 0
fun getPromotionPortrait(promotionName: String, size: Float = 30f): Group {
return PortraitPromotion(promotionName, size)
}
fun getResourcePortrait(resourceName: String, size: Float, amount: Int = 0): Group {
return PortraitResource(resourceName, size, amount)
}
fun getTechIconPortrait(techName: String, circleSize: Float): Group {
return PortraitTech(techName, circleSize)
}
fun getImprovementPortrait(improvementName: String, size: Float = 20f, dim: Boolean = false): Portrait {
return PortraitImprovement(improvementName, size, dim)
}
fun getUnitActionPortrait(actionName: String, size: Float = 20f): Portrait {
return PortraitUnitAction(actionName, size)
}
fun getReligionIcon(iconName: String): Image { return getImage("ReligionIcons/$iconName") }
fun getReligionPortrait(iconName: String, size: Float): Portrait {
if (religionIconExists(iconName)) {
return PortraitReligion(iconName, size)
} else {
val typeName = ruleset.beliefs[iconName]?.type?.name
if (typeName != null && religionIconExists(typeName))
return PortraitReligion(typeName, size)
}
val basePromotionName = nameWithoutBrackets.dropLast(if (level == 0) 0 else level + 1)
val imageAttempter = ImageAttempter(Unit)
.tryImage { "UnitPromotionIcons/$nameWithoutBrackets" }
.tryImage { "UnitPromotionIcons/$basePromotionName" }
.tryImage { "UnitIcons/${basePromotionName.removeSuffix(" ability")}" }
if (imageAttempter.getPathOrNull() != null && imageAttempter.getPath()!!.endsWith(nameWithoutBrackets))
level = 0
val promotionColor = colorFromRGB(255, 226, 0)
val circle = imageAttempter.getImage()
.apply { color = promotionColor }
.surroundWithCircle(size)
.apply { circle.color = colorFromRGB(0, 12, 49) }
if (level != 0) {
val padding = if (level == 3) 0.5f else 2f
val starTable = Table().apply { defaults().pad(padding) }
for (i in 1..level) starTable.add(getImage("OtherIcons/Star")).size(size / 4f)
starTable.centerX(circle)
starTable.y = size / 6f
circle.addActor(starTable)
}
return circle.surroundWithThinCircle(promotionColor)
return PortraitReligion(iconName, size)
}
fun religionIconExists(iconName: String) = imageExists("ReligionIcons/$iconName")
fun getReligionImage(iconName: String): Image {
return getImage("ReligionIcons/$iconName")
}
fun getCircledReligionIcon(iconName: String, size: Float): IconCircleGroup {
return getReligionImage(iconName).surroundWithCircle(size, color = Color.BLACK )
}
@Deprecated("Use skin defined base color instead", ReplaceWith("BaseScreen.skinStrings.skinConfig.baseColor", "com.unciv.ui.utils.BaseScreen"))
fun getBlue() = Color(0x004085bf)
@ -354,84 +299,6 @@ object ImageGetter {
return image
}
fun getResourceImage(resourceName: String, size: Float, amount: Int = 0): IconCircleGroup {
val iconGroup = getImage("ResourceIcons/$resourceName").surroundWithCircle(size)
val resource = ruleset.tileResources[resourceName]
?: return iconGroup // This is the result of a bad modding setup, just give em an empty circle. Their problem.
val color = resource.resourceType.getColor()
iconGroup.circle.color = color
// Show amount indicator for strategic resources (bottom-right)
if (amount > 0) {
val label = amount.toString().toLabel(
fontSize = 8,
fontColor = Color.WHITE,
alignment = Align.center)
val group = label.surroundWithCircle(size/2, true, Color.BLACK)
label.y -= 0.5f
group.x = iconGroup.width - group.width * 2 / 3
group.y = -group.height / 3
iconGroup.addActor(group)
}
return iconGroup.surroundWithThinCircle()
}
fun getTechIconGroup(techName: String, circleSize: Float, isResearched: Boolean = false): Group {
val portrait: Image
val eraColor = ruleset.eras[ruleset.technologies[techName]?.era()]?.getColor()?.darken(0.6f) ?: Color.BLACK
// Inner part
if (imageExists("TechPortraits/$techName"))
portrait = getImage("TechPortraits/$techName")
else {
portrait = if (imageExists("TechIcons/$techName"))
getImage("TechIcons/$techName")
else
getImage("TechIcons/Fallback")
portrait.color = eraColor
}
// Border / background
if (imageExists("TechPortraits/Background")) {
val background = getImage("TechPortraits/Background")
val ratioW = portrait.width / background.width
val ratioH = portrait.height / background.height
if (isResearched)
background.color = Color.GOLD.cpy().brighten(0.5f)
background.setSize(circleSize, circleSize)
portrait.setSize(circleSize*ratioW, circleSize*ratioH)
val group = Group()
group.setSize(circleSize, circleSize)
group.addActor(background)
group.addActor(portrait)
background.center(group)
portrait.center(group)
return group
} else {
return portrait.surroundWithCircle(circleSize).surroundWithThinCircle(eraColor)
}
}
fun getProgressBarHorizontal(
width: Float, height: Float,
percentComplete: Float,
progressColor: Color,
backgroundColor: Color): Group {
return ProgressBar(width, height, false)
.setBackground(backgroundColor)
.setProgress(progressColor, percentComplete)
}
fun getProgressBarVertical(
width: Float,
height: Float,

View File

@ -0,0 +1,303 @@
package com.unciv.ui.images
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.models.ruleset.Nation
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.stats.Stats
import com.unciv.ui.utils.extensions.center
import com.unciv.ui.utils.extensions.centerX
import com.unciv.ui.utils.extensions.colorFromRGB
import com.unciv.ui.utils.extensions.darken
import com.unciv.ui.utils.extensions.surroundWithCircle
import com.unciv.ui.utils.extensions.toGroup
import com.unciv.ui.utils.extensions.toLabel
import com.unciv.utils.Log
open class Portrait(val type: Type, val imageName: String, val size: Float, val borderSize: Float = 2f) : Group() {
enum class Type(val directory: String) {
Unit("Unit"),
Building("Building"),
Tech("Tech"),
Resource("Resource"),
Improvement("Improvement"),
Promotion("UnitPromotion"),
Unique("Unique"),
Nation("Nation"),
Religion("Religion"),
UnitAction("UnitAction")
}
val image: Image
val background: Group
val ruleset: Ruleset = ImageGetter.ruleset
var isPortrait = false
val pathPortrait = "${type.directory}Portraits/$imageName"
val pathPortraitFallback = "${type.directory}Portraits/Fallback"
val pathIcon = "${type.directory}Icons/$imageName"
val pathIconFallback = "${type.directory}Icons/Fallback"
open fun getDefaultInnerBackgroundTint(): Color { return Color.WHITE.cpy() }
open fun getDefaultOuterBackgroundTint(): Color { return Color.BLACK.cpy() }
open fun getDefaultImageTint(): Color { return Color.WHITE.cpy() }
open fun getDefaultImage(): Image {
return when {
ImageGetter.imageExists(pathIcon) -> ImageGetter.getImage(pathIcon)
ImageGetter.imageExists(pathIconFallback) -> ImageGetter.getImage(pathIconFallback)
else -> ImageGetter.getCircle()
}
}
init {
isTransform = false
image = getMainImage()
background = getMainBackground()
this.setSize(size + borderSize, size + borderSize)
background.center(this)
image.center(this)
this.addActor(background)
this.addActor(image)
}
/** Inner image */
private fun getMainImage() : Image {
return when {
ImageGetter.imageExists(pathPortrait) -> {
isPortrait = true
ImageGetter.getImage(pathPortrait)
}
ImageGetter.imageExists(pathPortraitFallback) -> {
isPortrait = true
ImageGetter.getImage(pathPortraitFallback)
}
else -> getDefaultImage().apply { color = getDefaultImageTint() }
}
}
/** Border / background */
private fun getMainBackground() : Group {
if (isPortrait && ImageGetter.imageExists("${type.directory}Portraits/Background")) {
val backgroundImage = ImageGetter.getImage("${type.directory}Portraits/Background")
val ratioW = image.width / backgroundImage.width
val ratioH = image.height / backgroundImage.height
image.setSize((size + borderSize)*ratioW, (size + borderSize)*ratioH)
return backgroundImage.toGroup(size + borderSize)
} else {
image.setSize(size*0.75f, size*0.75f)
val bg = Group().apply { isTransform = false }
val circleInner = ImageGetter.getCircle()
val circleOuter = ImageGetter.getCircle()
circleInner.setSize(size, size)
circleOuter.setSize(size + borderSize, size + borderSize)
bg.setSize(size + borderSize, size + borderSize)
circleInner.align = Align.center
circleOuter.align = Align.center
circleInner.color = getDefaultInnerBackgroundTint()
circleOuter.color = getDefaultOuterBackgroundTint()
circleOuter.center(bg)
circleInner.center(bg)
circleOuter.setOrigin(Align.center)
circleInner.setOrigin(Align.center)
bg.addActor(circleOuter)
bg.addActor(circleInner)
return bg
}
}
}
class PortraitResource(name: String, size: Float, amount: Int = 0) : Portrait(Type.Resource, name, size) {
init {
if (amount > 0) {
val label = amount.toString().toLabel(
fontSize = 8,
fontColor = Color.WHITE,
alignment = Align.center)
val amountGroup = label.surroundWithCircle(size/2, true, Color.BLACK)
label.y -= 0.5f
amountGroup.x = width - amountGroup.width * 2 / 3
amountGroup.y = -amountGroup.height / 3
addActor(amountGroup)
}
}
override fun getDefaultInnerBackgroundTint(): Color {
return ruleset.tileResources[imageName]?.resourceType?.getColor() ?: Color.WHITE
}
}
class PortraitTech(name: String, size: Float) : Portrait(Type.Tech, name, size) {
override fun getDefaultOuterBackgroundTint(): Color {
return getDefaultImageTint()
}
override fun getDefaultImageTint(): Color {
return ruleset.eras[ruleset.technologies[imageName]?.era()]?.getColor()?.darken(0.6f) ?: Color.BLACK
}
}
class PortraitUnit(name: String, size: Float) : Portrait(Type.Unit, name, size) {
override fun getDefaultImageTint(): Color {
return Color.BLACK
}
}
class PortraitBuilding(name: String, size: Float) : Portrait(Type.Building, name, size) {
override fun getDefaultImageTint(): Color {
return Color.BLACK
}
}
class PortraitUnique(name: String, size: Float) : Portrait(Type.Unique, name, size) {
override fun getDefaultImageTint(): Color {
return Color.BLACK
}
}
class PortraitReligion(name: String, size: Float) : Portrait(Type.Religion, name, size) {
override fun getDefaultImageTint(): Color {
return Color.BLACK
}
}
class PortraitUnitAction(name: String, size: Float) : Portrait(Type.UnitAction, name, size) {
override fun getDefaultImageTint(): Color {
return Color.BLACK
}
}
class PortraitImprovement(name: String, size: Float, dim: Boolean = false) : Portrait(Type.Improvement, name, size) {
init {
if (dim) {
image.color.a = 0.7f
background.color.a = 0.7f
}
}
private fun getColorFromStats(stats: Stats): Color {
if (stats.asSequence().none { it.value > 0 })
return Color.WHITE
return stats.asSequence().maxByOrNull { it.value }!!.key.color
}
override fun getDefaultInnerBackgroundTint(): Color {
val improvement = ImageGetter.ruleset.tileImprovements[imageName]
if (improvement != null)
return getColorFromStats(improvement)
return Color.WHITE
}
}
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"
return when {
ImageGetter.imageExists(pathIcon) -> ImageGetter.getImage(pathIcon)
isCityState && ImageGetter.imageExists(pathCityState)-> ImageGetter.getImage(pathCityState)
ImageGetter.imageExists(pathIconFallback) -> ImageGetter.getImage(pathIconFallback)
else -> ImageGetter.getCircle()
}
}
override fun getDefaultInnerBackgroundTint(): Color {
return ruleset.nations[imageName]?.getOuterColor() ?: Color.BLACK
}
override fun getDefaultOuterBackgroundTint(): Color {
return getDefaultImageTint()
}
override fun getDefaultImageTint(): Color {
return ruleset.nations[imageName]?.getInnerColor() ?: Color.WHITE
}
}
class PortraitPromotion(name: String, size: Float) : Portrait(Type.Promotion, name, size) {
var level = 0
init {
if (level > 0) {
val padding = if (level == 3) 0.5f else 2f
val starTable = Table().apply { defaults().pad(padding) }
for (i in 1..level)
starTable.add(ImageGetter.getImage("OtherIcons/Star")).size(size / 4f)
starTable.centerX(this)
starTable.y = size / 6f
addActor(starTable)
}
}
override fun getDefaultImage(): Image {
val nameWithoutBrackets = imageName.replace("[", "").replace("]", "")
level = when {
nameWithoutBrackets.endsWith(" I") -> 1
nameWithoutBrackets.endsWith(" II") -> 2
nameWithoutBrackets.endsWith(" III") -> 3
else -> 0
}
val basePromotionName = nameWithoutBrackets.dropLast(if (level == 0) 0 else level + 1)
val pathWithoutBrackets = "UnitPromotionIcons/$nameWithoutBrackets"
val pathBase = "UnitPromotionIcons/$basePromotionName"
val pathUnit = "UnitIcons/${basePromotionName.removeSuffix(" ability")}"
return when {
ImageGetter.imageExists(pathWithoutBrackets) -> {
level = 0
ImageGetter.getImage(pathWithoutBrackets)
}
ImageGetter.imageExists(pathBase) -> ImageGetter.getImage(pathBase)
ImageGetter.imageExists(pathUnit) -> ImageGetter.getImage(pathUnit)
else -> ImageGetter.getImage(pathIconFallback)
}
}
override fun getDefaultImageTint(): Color {
return colorFromRGB(255, 226, 0)
}
override fun getDefaultOuterBackgroundTint(): Color {
return getDefaultImageTint()
}
override fun getDefaultInnerBackgroundTint(): Color {
return colorFromRGB(0, 12, 49)
}
}

View File

@ -33,8 +33,8 @@ class NationTable(val nation: Nation, width: Float, minHeight: Float, ruleset: R
"NewGameScreen/NationTable/Title", tintColor = outerColor
)
val nationIndicator: Actor =
if (nation.name == Constants.random) ImageGetter.getRandomNationIndicator(50f)
else ImageGetter.getNationIndicator(nation, 50f)
if (nation.name == Constants.random) ImageGetter.getRandomNationPortrait(50f)
else ImageGetter.getNationPortrait(nation, 50f)
titleTable.add(nationIndicator).pad(10f).padLeft(0f) // left 0 for centering _with_ label
val titleText = if (ruleset == null || nation.name == Constants.random || nation.name == Constants.spectator)

View File

@ -219,8 +219,8 @@ class PlayerPickerTable(
val nationTable = Table()
val nationImage =
if (player.chosenCiv == Constants.random)
ImageGetter.getRandomNationIndicator(40f)
else ImageGetter.getNationIndicator(previousScreen.ruleset.nations[player.chosenCiv]!!, 40f)
ImageGetter.getRandomNationPortrait(40f)
else ImageGetter.getNationPortrait(previousScreen.ruleset.nations[player.chosenCiv]!!, 40f)
nationTable.add(nationImage).pad(5f)
nationTable.add(player.chosenCiv.toLabel()).pad(5f)
nationTable.touchable = Touchable.enabled

View File

@ -194,7 +194,7 @@ class CityOverviewTab(
val construction = city.cityConstructions.currentConstructionFromQueue
if (construction.isNotEmpty()) {
cityInfoTableDetails.add(ImageGetter.getPortraitImage(construction, iconSize*0.8f)).padRight(paddingHorz)
cityInfoTableDetails.add(ImageGetter.getConstructionPortrait(construction, iconSize*0.8f)).padRight(paddingHorz)
} else {
cityInfoTableDetails.add()
}
@ -216,7 +216,7 @@ class CityOverviewTab(
cityInfoTableDetails.add(image)
}
city.demandedResource.isNotEmpty() -> {
val image = ImageGetter.getResourceImage(city.demandedResource, iconSize*0.7f)
val image = ImageGetter.getResourcePortrait(city.demandedResource, iconSize*0.7f)
image.addTooltip("Demanding [${city.demandedResource}]", 18f, tipAlign = Align.topLeft)
cityInfoTableDetails.add(image).padLeft(iconSize*0.3f)
}

View File

@ -139,7 +139,7 @@ class EspionageOverviewScreen(val civInfo: CivilizationInfo) : PickerScreen(true
}
private fun addCityToSelectionTable(city: CityInfo) {
citySelectionTable.add(ImageGetter.getNationIndicator(city.civInfo.nation, 30f)).pad(5f)
citySelectionTable.add(ImageGetter.getNationPortrait(city.civInfo.nation, 30f)).pad(5f)
citySelectionTable.add(city.name.toLabel()).pad(5f)
if (city.espionage.hasSpyOf(civInfo)) {
citySelectionTable.add(

View File

@ -16,7 +16,6 @@ import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.models.ruleset.Policy.PolicyBranchType
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.trade.DiplomacyScreen
import com.unciv.ui.utils.AutoScrollPane
@ -99,7 +98,7 @@ class GlobalPoliticsOverviewTable (
civilizations.removeAll(civilizations.filter { it.isBarbarian() || it.isCityState() || it.isSpectator() })
for (civ in civilizations) {
// civ image
add(ImageGetter.getNationIndicator(civ.nation, 100f)).pad(20f)
add(ImageGetter.getNationPortrait(civ.nation, 100f)).pad(20f)
addSeparatorVertical(Color.GRAY)
@ -314,7 +313,7 @@ class GlobalPoliticsOverviewTable (
private fun getCivMiniTable(civInfo: CivilizationInfo): Table {
val table = Table()
table.add(ImageGetter.getNationIndicator(civInfo.nation, 25f)).pad(5f)
table.add(ImageGetter.getNationPortrait(civInfo.nation, 25f)).pad(5f)
table.add(civInfo.civName.toLabel()).left().padRight(10f)
table.touchable = Touchable.enabled
table.onClick {
@ -409,7 +408,7 @@ class GlobalPoliticsOverviewTable (
val civCount = undefeatedCivs.count()
for ((i, civ) in undefeatedCivs.withIndex()) {
val civGroup = ImageGetter.getNationIndicator(civ.nation, 30f)
val civGroup = ImageGetter.getNationPortrait(civ.nation, 30f)
val vector = HexMath.getVectorForAngle(2 * Math.PI.toFloat() * i / civCount)
civGroup.center(this)

View File

@ -90,11 +90,11 @@ class ReligionOverviewTab(
for (religion in existingReligions) {
val image = if (religion.isPantheon()) {
if (viewingPlayer.knows(religion.foundingCivName) || viewingPlayer.civName == religion.foundingCivName)
ImageGetter.getNationIndicator(religion.getFounder().nation, 60f)
ImageGetter.getNationPortrait(religion.getFounder().nation, 60f)
else
ImageGetter.getRandomNationIndicator(60f)
ImageGetter.getRandomNationPortrait(60f)
} else {
ImageGetter.getCircledReligionIcon(religion.getIconName(), 60f)
ImageGetter.getReligionPortrait(religion.getIconName(), 60f)
}
val button = Button(image, BaseScreen.skin)

View File

@ -77,7 +77,7 @@ class ResourcesOverviewTab(
private fun ResourceSupplyList.getTotalLabel(resource: TileResource): Label =
filter { it.resource == resource }.sumOf { it.amount }.toLabel()
private fun getResourceImage(name: String) =
ImageGetter.getResourceImage(name, iconSize).apply {
ImageGetter.getResourcePortrait(name, iconSize).apply {
onClick {
if (viewingPlayer.gameInfo.notifyExploredResources(viewingPlayer, name, 0, true))
overviewScreen.game.popScreen()

View File

@ -203,7 +203,7 @@ class UnitOverviewTab(
val promotionsTable = Table()
// getPromotions goes by json order on demand, so this is same sorting as on picker
for (promotion in unit.promotions.getPromotions(true))
promotionsTable.add(ImageGetter.getPromotionIcon(promotion.name))
promotionsTable.add(ImageGetter.getPromotionPortrait(promotion.name))
if (unit.promotions.canBePromoted())
promotionsTable.add(
ImageGetter.getImage("OtherIcons/Star").apply {

View File

@ -19,7 +19,7 @@ class DiplomaticVotePickerScreen(private val votingCiv: CivilizationInfo) : Pick
val choosableCivs = votingCiv.gameInfo.civilizations.filter { it.isMajorCiv() && it != votingCiv && !it.isDefeated() }
for (civ in choosableCivs)
{
val button = PickerPane.getPickerOptionButton(ImageGetter.getNationIndicator(civ.nation, PickerPane.pickerOptionIconSize), civ.civName)
val button = PickerPane.getPickerOptionButton(ImageGetter.getNationPortrait(civ.nation, PickerPane.pickerOptionIconSize), civ.civName)
button.pack()
button.onClick {
chosenCiv = civ.civName

View File

@ -35,7 +35,7 @@ class DiplomaticVoteResultScreen(val votesCast: HashMap<String, String>, val vie
val civ = gameInfo.civilizations.firstOrNull { it.civName == civName }
if (civ == null || civ.isDefeated()) return
topTable.add(ImageGetter.getNationIndicator(civ.nation, 30f)).pad(10f)
topTable.add(ImageGetter.getNationPortrait(civ.nation, 30f)).pad(10f)
topTable.add(civName.toLabel()).pad(20f)
if (civName !in votesCast.keys) {
topTable.add("Abstained".toLabel()).row()
@ -49,7 +49,7 @@ class DiplomaticVoteResultScreen(val votesCast: HashMap<String, String>, val vie
}
topTable.add("Voted for".toLabel()).pad(20f)
topTable.add(ImageGetter.getNationIndicator(votedCiv.nation, 30f)).pad(10f)
topTable.add(ImageGetter.getNationPortrait(votedCiv.nation, 30f)).pad(10f)
topTable.add(votedCiv.civName.toLabel()).row()
}
}

View File

@ -97,7 +97,7 @@ class ImprovementPickerScreen(
else suggestRemoval = true
}
val image = ImageGetter.getImprovementIcon(improvement.name, 30f)
val image = ImageGetter.getImprovementPortrait(improvement.name, 30f)
// allow multiple key mappings to technologically supersede each other
var shortcutKey = improvement.shortcutKey
@ -205,11 +205,11 @@ class ImprovementPickerScreen(
// icon for adding the resource by improvement
if (provideResource)
statIcons.add(ImageGetter.getResourceImage(tileInfo.resource.toString(), 30f)).pad(3f)
statIcons.add(ImageGetter.getResourcePortrait(tileInfo.resource.toString(), 30f)).pad(3f)
// icon for removing the resource by replacing improvement
if (removeImprovement && tileInfo.hasViewableResource(currentPlayerCiv) && tileInfo.improvement != null && tileInfo.tileResource.isImprovedBy(tileInfo.improvement!!)) {
val resourceIcon = ImageGetter.getResourceImage(tileInfo.resource!!, 30f)
val resourceIcon = ImageGetter.getResourcePortrait(tileInfo.resource!!, 30f)
statIcons.add(ImageGetter.getCrossedImage(resourceIcon, 30f))
}
return statIcons

View File

@ -101,7 +101,7 @@ class PromotionButton(
pad(5f)
align(Align.left)
add(ImageGetter.getPromotionIcon(node.promotion.name)).padRight(10f)
add(ImageGetter.getPromotionPortrait(node.promotion.name)).padRight(10f)
add(label).left().maxWidth(130f)
updateColor()

View File

@ -117,7 +117,7 @@ class ReligiousBeliefsPickerScreen (
AskTextPopup(
this,
label = "Choose a name for your religion",
icon = ImageGetter.getCircledReligionIcon(religionName!!, 80f),
icon = ImageGetter.getReligionPortrait(religionName!!, 80f),
defaultText = religionName!!,
validate = { religionName ->
religionName != Constants.noReligionName
@ -150,7 +150,7 @@ class ReligiousBeliefsPickerScreen (
for (religionName in ruleset.religions) {
if (religionName == this.religionName)
scrollTo = iconsTable.packIfNeeded().prefWidth
val button = Button(ImageGetter.getCircledReligionIcon(religionName, 60f), skin)
val button = Button(ImageGetter.getReligionPortrait(religionName, 60f), skin)
buttonSetup(button, religionName)
if (religionName == this.religionName) button.disable(Color(greenDisableColor))
else if (gameInfo.religions.keys.any { it == religionName }) button.disable(redDisableColor)

View File

@ -6,7 +6,6 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.logic.civilization.TechManager
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.tile.TileImprovement
@ -19,13 +18,9 @@ import com.unciv.ui.utils.extensions.addBorder
import com.unciv.ui.utils.extensions.brighten
import com.unciv.ui.utils.extensions.center
import com.unciv.ui.utils.extensions.centerY
import com.unciv.ui.utils.extensions.colorFromRGB
import com.unciv.ui.utils.extensions.darken
import com.unciv.ui.utils.extensions.setFontSize
import com.unciv.ui.utils.extensions.setSize
import com.unciv.ui.utils.extensions.surroundWithCircle
import com.unciv.ui.utils.extensions.surroundWithThinCircle
import com.unciv.ui.utils.extensions.toGroup
import com.unciv.ui.utils.extensions.toLabel
class TechButton(techName:String, private val techManager: TechManager, isWorldScreen: Boolean = true) : Table(BaseScreen.skin) {
@ -52,8 +47,7 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
pad(0f).padBottom(5f).padTop(5f).padLeft(5f).padRight(0f)
val isResearched = (techManager.isResearched(techName) && techName != Constants.futureTech)
add(ImageGetter.getTechIconGroup(techName, 46f, isResearched))
add(ImageGetter.getTechIconPortrait(techName, 46f))
.padRight(5f).padLeft(2f).left()
if (isWorldScreen) {
@ -112,18 +106,18 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
val icons = ArrayList<Group>()
for (unit in tech.getEnabledUnits(ruleset, techManager.civInfo)) {
icons.add(ImageGetter.getPortraitImage(unit.name, techIconSize))
icons.add(ImageGetter.getConstructionPortrait(unit.name, techIconSize))
}
for (building in tech.getEnabledBuildings(ruleset, techManager.civInfo)) {
icons.add(ImageGetter.getPortraitImage(building.name, techIconSize))
icons.add(ImageGetter.getConstructionPortrait(building.name, techIconSize))
}
for (obj in tech.getObsoletedObjects(ruleset, techManager.civInfo)) {
val obsoletedIcon = when (obj) {
is Building -> ImageGetter.getPortraitImage(obj.name, techIconSize)
is TileResource -> ImageGetter.getResourceImage(obj.name, techIconSize)
is TileImprovement -> ImageGetter.getImprovementIcon(obj.name, techIconSize)
is Building -> ImageGetter.getConstructionPortrait(obj.name, techIconSize)
is TileResource -> ImageGetter.getResourcePortrait(obj.name, techIconSize)
is TileImprovement -> ImageGetter.getImprovementPortrait(obj.name, techIconSize)
else -> continue
}.also {
val closeImage = ImageGetter.getRedCross(techIconSize / 2, 1f)
@ -134,36 +128,31 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
}
for (resource in ruleset.tileResources.values.filter { it.revealedBy == techName }) {
icons.add(ImageGetter.getResourceImage(resource.name, techIconSize))
icons.add(ImageGetter.getResourcePortrait(resource.name, techIconSize))
}
for (improvement in ruleset.tileImprovements.values.asSequence()
.filter { it.techRequired == techName }
.filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
icons.add(ImageGetter.getImprovementIcon(improvement.name, techIconSize, true))
icons.add(ImageGetter.getImprovementPortrait(improvement.name, techIconSize))
}
for (improvement in ruleset.tileImprovements.values.asSequence()
.filter { it.uniqueObjects.any { u -> u.allParams.contains(techName) } }
.filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
icons.add(
ImageGetter.getImage("OtherIcons/Unique")
.surroundWithCircle(techIconSize)
.surroundWithThinCircle())
icons.add(ImageGetter.getUniquePortrait(improvement.name, techIconSize))
}
for (unique in tech.uniques) {
icons.add(
when (unique) {
UniqueType.EnablesCivWideStatProduction.text.replace("civWideStat", "Gold" )
-> ImageGetter.getImage("OtherIcons/ConvertGold").toGroup(techIconSize)
-> ImageGetter.getConstructionPortrait("Gold", techIconSize)
UniqueType.EnablesCivWideStatProduction.text.replace("civWideStat", "Science" )
-> ImageGetter.getImage("OtherIcons/ConvertScience").toGroup(techIconSize)
else -> ImageGetter.getImage("OtherIcons/Unique")
.surroundWithCircle(techIconSize)
.surroundWithThinCircle()
-> ImageGetter.getConstructionPortrait("Science", techIconSize)
else -> ImageGetter.getUniquePortrait(unique, techIconSize)
}
)
}

View File

@ -1,6 +1,7 @@
package com.unciv.ui.popup
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.unciv.ui.images.IconCircleGroup
@ -24,7 +25,7 @@ import com.unciv.ui.utils.extensions.toLabel
class AskTextPopup(
screen: BaseScreen,
label: String = "Please enter some text",
icon: IconCircleGroup = ImageGetter.getImage("OtherIcons/Pencil").apply { this.color = Color.BLACK }.surroundWithCircle(80f),
icon: Group = ImageGetter.getImage("OtherIcons/Pencil").apply { this.color = Color.BLACK }.surroundWithCircle(80f),
defaultText: String = "",
errorText: String = "Invalid input! Please enter a different string.",
maxLength: Int = 32,

View File

@ -7,10 +7,8 @@ import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
@ -26,14 +24,10 @@ import com.unciv.ui.trade.DiplomacyScreen
import com.unciv.ui.utils.BaseScreen
import com.unciv.ui.utils.BorderedTable
import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.extensions.brighten
import com.unciv.ui.utils.extensions.center
import com.unciv.ui.utils.extensions.centerX
import com.unciv.ui.utils.extensions.colorFromRGB
import com.unciv.ui.utils.extensions.onClick
import com.unciv.ui.utils.extensions.setSize
import com.unciv.ui.utils.extensions.surroundWithCircle
import com.unciv.ui.utils.extensions.surroundWithThinCircle
import com.unciv.ui.utils.extensions.toGroup
import com.unciv.ui.utils.extensions.toLabel
import kotlin.math.max
@ -321,7 +315,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab
if (!forPopup) {
val cityReligion = city.religion.getMajorityReligion()
if (cityReligion != null) {
val religionImage = ImageGetter.getReligionImage(cityReligion.getIconName()).apply {
val religionImage = ImageGetter.getReligionIcon(cityReligion.getIconName()).apply {
color = secondaryColor }.toGroup(20f)
labelTable.add(religionImage).size(20f).padLeft(5f)
}
@ -451,7 +445,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab
table.add(productionBar).padTop(1f).padBottom(1f)
}
val constructionImage = ImageGetter.getPortraitImage(cityCurrentConstruction.name, 24f)
val constructionImage = ImageGetter.getConstructionPortrait(cityCurrentConstruction.name, 24f)
table.add(constructionImage).minHeight(32f).minWidth(26f)
.expand().center().right().pad(0f).padRight(4f).padLeft(3f)
table.pack()

View File

@ -127,14 +127,12 @@ class TileGroupIcons(val tileGroup: TileGroup) {
val shownImprovement = tileGroup.tileInfo.getShownImprovement(viewingCiv)
if (shownImprovement == null || !showResourcesAndImprovements) return
val newImprovementImage = ImageGetter.getImprovementIcon(shownImprovement)
val newImprovementImage = ImageGetter.getImprovementPortrait(shownImprovement, dim = false)
tileGroup.miscLayerGroup.addActor(newImprovementImage)
newImprovementImage.run {
setSize(20f, 20f)
center(tileGroup)
this.x -= 22 // left
this.y -= 10 // bottom
color = Color.WHITE.cpy().apply { a = 0.7f }
this.y -= 12 // bottom
}
improvementIcon = newImprovementImage
}
@ -170,7 +168,7 @@ class TileGroupIcons(val tileGroup: TileGroup) {
tileGroup.resourceImage?.remove()
if (tileGroup.resource == null) tileGroup.resourceImage = null
else {
val newResourceIcon = ImageGetter.getResourceImage(tileGroup.tileInfo.resource!!, 20f, tileGroup.tileInfo.resourceAmount)
val newResourceIcon = ImageGetter.getResourcePortrait(tileGroup.tileInfo.resource!!, 20f, tileGroup.tileInfo.resourceAmount)
newResourceIcon.center(tileGroup)
newResourceIcon.x = newResourceIcon.x - 22 // left
newResourceIcon.y = newResourceIcon.y + 10 // top
@ -215,7 +213,7 @@ class TileGroupIcons(val tileGroup: TileGroup) {
var offsetY = (displayCount - 1) * 2f
for (nation in nations.take(3).asReversed()) {
val newNationIcon =
ImageGetter.getNationIndicator(nation.second, 20f)
ImageGetter.getNationPortrait(nation.second, 20f)
tileGroup.miscLayerGroup.addActor(newNationIcon)
newNationIcon.run {
setSize(20f, 20f)

View File

@ -118,7 +118,7 @@ class DiplomacyScreen(
selectCivY = leftSideTable.prefHeight
}
val civIndicator = ImageGetter.getNationIndicator(civ.nation, nationIconSize)
val civIndicator = ImageGetter.getNationPortrait(civ.nation, nationIconSize)
val relationLevel = civ.getDiplomacyManager(viewingCiv).relationshipLevel()
val relationshipIcon = if (civ.isCityState() && relationLevel == RelationshipLevel.Ally)
@ -202,7 +202,7 @@ class DiplomacyScreen(
continue
val name = supplyList.resource.name
val wrapper = Table()
val image = ImageGetter.getResourceImage(name, 30f)
val image = ImageGetter.getResourcePortrait(name, 30f)
wrapper.add(image).padRight(5f)
wrapper.add(supplyList.amount.toLabel())
resourcesTable.add(wrapper).padRight(20f)

View File

@ -30,7 +30,7 @@ class LeaderIntroTable (
val nation = civInfo.nation
val leaderPortraitFile = "LeaderIcons/" + nation.leaderName
val leaderLabel = civInfo.getLeaderDisplayName().toLabel(fontSize = Constants.headingFontSize)
val nationIndicator = ImageGetter.getNationIndicator(nation, 24f)
val nationIndicator = ImageGetter.getNationPortrait(nation, 24f)
if (nation.leaderName.isNotEmpty() && ImageGetter.imageExists(leaderPortraitFile)) {
val nameTable = Table()
nameTable.add(leaderLabel)

View File

@ -90,9 +90,9 @@ class OffersListScroll(
val tradeLabel = offer.getOfferText(untradableOffers.sumBy(offer.name))
val tradeIcon = when (offer.type) {
Luxury_Resource, Strategic_Resource ->
ImageGetter.getResourceImage(offer.name, 30f)
ImageGetter.getResourcePortrait(offer.name, 30f)
WarDeclaration ->
ImageGetter.getNationIndicator(UncivGame.Current.gameInfo!!.ruleSet.nations[offer.name]!!, 30f)
ImageGetter.getNationPortrait(UncivGame.Current.gameInfo!!.ruleSet.nations[offer.name]!!, 30f)
else -> null
}
val tradeButton = IconTextButton(tradeLabel, tradeIcon).apply {

View File

@ -274,11 +274,11 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|| currentPlayer.isDefeated()
|| currentPlayer.victoryManager.hasWon()
) {
civGroup.add(ImageGetter.getNationIndicator(civ.nation, 30f))
civGroup.add(ImageGetter.getNationPortrait(civ.nation, 30f))
backgroundColor = civ.nation.getOuterColor()
labelColor = civ.nation.getInnerColor()
} else {
civGroup.add(ImageGetter.getRandomNationIndicator(30f))
civGroup.add(ImageGetter.getRandomNationPortrait(30f))
backgroundColor = Color.DARK_GRAY
labelText = Constants.unknownNationName
}

View File

@ -209,7 +209,7 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
.row()
}
} else { // Fallback
add(ImageGetter.getPortraitImage(wonder.name, 100f)).pad(20f).row()
add(ImageGetter.getConstructionPortrait(wonder.name, 100f)).pad(20f).row()
}
val centerTable = Table()
@ -227,7 +227,7 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
addSeparator()
val centerTable = Table()
centerTable.add(tech.quote.toLabel().apply { wrap = true }).width(worldScreen.stage.width / 3)
centerTable.add(ImageGetter.getTechIconGroup(tech.name, 100f)).pad(20f)
centerTable.add(ImageGetter.getTechIconPortrait(tech.name, 100f)).pad(20f)
val descriptionScroll = ScrollPane(tech.getDescription(gameBasics).toLabel().apply { wrap = true })
centerTable.add(descriptionScroll).width(worldScreen.stage.width / 3).maxHeight(worldScreen.stage.height / 2)
add(centerTable).row()

View File

@ -151,7 +151,7 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
val strategicResources = worldScreen.gameInfo.ruleSet.tileResources.values
.filter { it.resourceType == ResourceType.Strategic }
for (resource in strategicResources) {
val resourceImage = ImageGetter.getResourceImage(resource.name, 20f)
val resourceImage = ImageGetter.getResourcePortrait(resource.name, 20f)
val resourceLabel = "0".toLabel()
resourceActors += ResourceActors(resource, resourceLabel, resourceImage)
}
@ -241,7 +241,7 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
selectedCivLabel.setText(newCiv.tr())
val nation = worldScreen.gameInfo.ruleSet.nations[worldScreen.selectedCiv.civName]!!
val selectedCivIcon = ImageGetter.getNationIndicator(nation, 35f)
val selectedCivIcon = ImageGetter.getNationPortrait(nation, 35f)
selectedCivIconHolder.actor = selectedCivIcon
invalidate()
pack()

View File

@ -118,7 +118,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
private fun getIcon(combatant:ICombatant) =
if (combatant is MapUnitCombatant) UnitGroup(combatant.unit,25f)
else ImageGetter.getNationIndicator(combatant.getCivInfo().nation, 25f)
else ImageGetter.getNationPortrait(combatant.getCivInfo().nation, 25f)
private val quarterScreen = worldScreen.stage.width / 4

View File

@ -251,7 +251,7 @@ class UnitTable(val worldScreen: WorldScreen) : Table() {
unitIconHolder.add(UnitGroup(selectedUnit!!, 30f)).pad(5f)
for (promotion in selectedUnit!!.promotions.getPromotions(true))
promotionsTable.add(ImageGetter.getPromotionIcon(promotion.name))
promotionsTable.add(ImageGetter.getPromotionPortrait(promotion.name))
// Since Clear also clears the listeners, we need to re-add them every time
promotionsTable.onClick {

View File

@ -601,6 +601,8 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
- [pennant](https://thenounproject.com/term/pennant/194797/) by Sara Jeffries
- [Maya civilization](https://thenounproject.com/term/maya-civilization/1715786/) by Olena Panasovska for The Maya
- Aztec icon by Kāne, on Unciv Discord server
- [Questionmark](https://thenounproject.com/icon/questionmark-4248169/) by YURR.studio for Random nation indicator
- [View](https://thenounproject.com/icon/view-4211245/) by Adrien Coquet for Spectator
### Promotions

View File

@ -41,11 +41,11 @@ These work best if they are square, between 100x100 and 256x256 pixels, and incl
For example, [here](https://github.com/yairm210/Unciv-leader-portrait-mod-example) is mod showing how to add leader portraits, which can complement the base game.
### Adding Unit, Building and Tech Portraits
### Adding Portraits
The base game uses flat icons, colored to fit the civilization's flag colors, to denote units both on the map and in other UI elements. A mod can supply "Portraits" - static images that will remain uncolored - by adding their images to `/Images/UnitPortraits/`, `/Images/BuildingPortraits/` and '/Images/TechPortraits/', which will be used in all UI elements except for the world map. The file name must correspond exactly with the unit/building/tech name as defined in Units.json, Buildings.json or Techs.json or they will be ignored.
The base game uses flat icons, surrounded with colored circles as backgrounds (e.g. for units to fit the civilization's flag colors), to denote entities such as: units, buildings, techs, resources, improvements, religions, promotions, uniques, unit actions and nations in the UI. A mod can supply "Portraits" - static images that will remain uncolored - by adding images to `/Images/<entityType>Portraits/` (e.g. `/Images/BuildingPortraits/`, /Images/ResourcesPortraits/, etc), which will be used in all UI elements (except for unit icons in the world map). The file name must correspond exactly with the unit/building/tech/resource/etc name defined in corresponding JSONs (e.g. Units.json, Buildings.json, TileResources.json, etc) or have the same name as the file they suppose to replace, or they will be ignored.
If mod supplies '/Images/TechPortraits/Background.png' image, it will be used as a background for tech portraits instead of default circle.
If mod supplies '/Images/<entityType>Portraits/Background.png' images, they will be used as a background for corresponding portraits instead of default circle.
Portraits and backgrounds work best if they are full RGB square, between 100x100 and 256x256 pixels, and include some transparent border within that area.