From cbd3c2910e9ef863440cc5433b722fe10f108840 Mon Sep 17 00:00:00 2001 From: SimonCeder <63475501+SimonCeder@users.noreply.github.com> Date: Thu, 9 Sep 2021 20:28:30 +0200 Subject: [PATCH 1/3] Icons for city states (#5160) * Add city state icons * Add the icons * Get filename from cityStateType.name * less padding * Icon names in enum, icons in diplomacy screen --- android/Images/OtherIcons/Cultured.png | Bin 0 -> 6778 bytes android/Images/OtherIcons/Maritime.png | Bin 0 -> 7525 bytes android/Images/OtherIcons/Mercantile.png | Bin 0 -> 7169 bytes android/Images/OtherIcons/Militaristic.png | Bin 0 -> 16689 bytes android/Images/OtherIcons/Religious.png | Bin 0 -> 3917 bytes .../unciv/logic/civilization/CityStateType.kt | 11 ++++++----- core/src/com/unciv/ui/tilegroups/CityButton.kt | 9 ++++++++- core/src/com/unciv/ui/trade/DiplomacyScreen.kt | 11 +++++++++++ docs/Credits.md | 4 ++++ 9 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 android/Images/OtherIcons/Cultured.png create mode 100644 android/Images/OtherIcons/Maritime.png create mode 100644 android/Images/OtherIcons/Mercantile.png create mode 100644 android/Images/OtherIcons/Militaristic.png create mode 100644 android/Images/OtherIcons/Religious.png diff --git a/android/Images/OtherIcons/Cultured.png b/android/Images/OtherIcons/Cultured.png new file mode 100644 index 0000000000000000000000000000000000000000..b25fc10e75079bbb4d2a1e5c09c1f4acc2533446 GIT binary patch literal 6778 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 CNx|y? literal 0 HcmV?d00001 diff --git a/android/Images/OtherIcons/Maritime.png b/android/Images/OtherIcons/Maritime.png new file mode 100644 index 0000000000000000000000000000000000000000..94f4053505942f07c3c6f81af073110099ace217 GIT binary patch literal 7525 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 a&aoolYM$K|^!xUkH`>U`@Xk^1)Bgck)K!}R literal 0 HcmV?d00001 diff --git a/android/Images/OtherIcons/Mercantile.png b/android/Images/OtherIcons/Mercantile.png new file mode 100644 index 0000000000000000000000000000000000000000..2847c04dd85403db9a89880d3a1c8e516f3cc07d GIT binary patch literal 7169 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 euK^nS7b3{S_Ay<=ENAV-j7YFDxwGHxl6fqdjS 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 literal 0 HcmV?d00001 diff --git a/android/Images/OtherIcons/Religious.png b/android/Images/OtherIcons/Religious.png new file mode 100644 index 0000000000000000000000000000000000000000..7cde1f5791ae6cfa693ed2e50c4c15d002e12583 GIT binary patch literal 3917 zcmV-T53=xyP)ues`QWw&S>N(j;xNtuZZI8z`ZhvPMG; zi50|)rj=?!h)D1U5G17WS2ghul@LWBgpi6@1Y-m?1e&(B+hAc^Gv1<wpGDURWg}O9@qzL2g<dI=>~oc*i-Y> zb>L6!oqofB^2-d+2J{6vIKZ%h@`#B1HgGU$+G`3??-?aMWn4bk?3kW+`DxMaN z(!4gw80b4Qnan02L1GOv>eCK@w}JmK3VX)rX9b?XyQ(@^qXqZ;1fXc2wU&HDRo}l! zSpiZk77-Cq)kDB8Ut_)wOsVQ~W(R_#1Y7}z0^I#7un+)9hlq@ch;#b%=}katrLC>a zYoMPp7q*tJz6rF8$a4X`{~o5mw1^BF9leLLOoRM@WGrCJkByCO0utcrjsX6ac^CMB zY0dlUT;eV;Yqm#1Y)r*IYh@H@&*$@-fV`(Q@7~3NJU=C!0VY*-%p&$MnKDmmD9l6! zXb3>BY33#%_sG9gqPMrVVD4;3?N_RL8bCyT+=9L^xow)UH6#Yfh5*v-yXPh#Mi+e+ zf7Eu>SrK{D8tnriROHDuvoGCs0I3F0R-4aeZ31F+F$z5F`)a~si(yt~aQ$xW6$=Qv zR;eMm+PGzQc9yxhxd#`J<>h5Ba(mozR<&{gn6{|8ug+zvs(MXT`>e+YwL?C{sAoM5 zQB}ITyQQnEi)JMrQ=fh>l}hAtxx9!x;d5!#ysHxTGrFO;8gN@f++wi|Ks7)^GC-SY z=|igeEO1vuUQ^YDxQ%`4HKe1X1M8vteKuklIA@*eKtOEa4+`&?hGj!2t33petg05w zgZl#!`7;su6IK1Nh6wHCOGtKNV}>& zX(^j_V5f+@0CaYCc0Q1R^aa?6yTA=QKG2ByfJM`gFpeo$oZ<(>5s@vxZr+=e{9#}? z?(}@>0kN_29Rcp@l+jSX1#k6a(I~FB4j>sjXE}h*x&!EJz?_K(2av_ZMZ}fe0m0dA zps1=N5#ofB(b1NA%4p{-1sw0PoLTL_NGEVWVz%n32ITVP%bq8AFu)jJv&I`CJfY}G zxu#(oofia1kBHpyX-^h-KajnC{rZCe$lkqsG0k|?M>SR85}+a3+n~TO6G4;8A(YjF zn=SSa9qt6$1_lN;79giiomx>}H8C+!Yta2sctusm?fHirq)a?OoRFZfw#QcjG{ox^ zLH4B|XphsduX|uPKR?gm!-v65i|@6=7Y7CguA33v0c;Bh^d>E>F%0A1kcISkd6l4Sf`yg z9}=TLaN;o^r{|kEAQvxQB$LTF)oOJ}L^7(n&+J5yJfo`5;FgPCtvNnxUeC5waCo~> zPd&=Yh5(WcaU3#n(|+QB?Ao;pV29&49|f`^vZsza+ASiV0&W3s)Eu7%=2dl|0S(;? z_)R-95dqNvKpcWI!MT1q;MvmPIr@PQipUvrbrra5jr>T2pb*PNFO%3Vm>+`hZg&L10au&rtF93@oa>tDrtHX7!2 zfai?5{wDyC_5gYmAQxvbb|Qcb4-W$ji^z+fn<^p&R%t}ZU-#UPtKYzZ~vpQJQv$|>mu||Lt%*DNeTRJ*tUfOEX z7Fil+dvm~1Rh{)|Zij)g7-B=DWdPZ;XAi)jX-8+xi*(xkXCnZTGupZiyk*AuvABDu zXRk!0&!-I?rjex}%4!)6IdbHPH%I(g+^WN%R7Dg(G9of)RQ2!n-1{QBysMR7R!))5 z;}&xV0n$2=`s=U1j`8*jtQ-?x4ImC~ntjdyX*0@dhU6@dmh(mA`$8NLr)2;!`5^AZ z{GiC#QPsJCq+O8gum-tqy?Cfe&npB^>In-%TLX}4wTg&z0>8k@0Vd3Aaf}9{%sn15 z%}{f9`$P z$i&2i$Gbnl%E|klEEpL1h`p?j`;&DZjyq)$v8{sKW=%aH+qZAW+{B-(;}$LeRa*og z3Lu@TdcdB!N;*U%(rqe?`s27o&|%_5=-m<3J})94v0m!cWTgd^^a5hlhI!MDdIbpO z8XrF@4?hLG&x+P6DFLLMYCsBw0_Fuh)8P7*8XrHZJY-&5>^*a>&LqJ_5vilBM{%pg zIzYA(ZQ#grcT20(kge38lb$49eI1y(dGltf0cnp& zCk5Ohf#ND7Zzm<%aR>OOs#b=EhEfekWLv=Mvb|}O^Q5bRJAH1uQlM= zysB<9K#DB_#uD(4z~q%HS6TxQxVK>CIPPlGVMLp=6w6`+dy^I!$6X`1GCDfiI)L={ z_M)m4+?{K}f~TB_jN;Z>jkgFO-vQ3YnQl)kQZJQC;4Xjhw}e;x9mFk`JJSN5){Ls2 zTwY!-Hp9DWIv}sMdWlgoAp+?Yb`*)-Oa#)sDBCx z{zh>Z0y_u%XNtinsp{VWH{;CgCJsn8n?+SixVxfV4LGF>_@o6}e~vqkFpY9aM7|1C zYGkr34fO{snk5sU6!A zn}rM9QPt1mj+gkQ0K5ns!`&_C81Oaf&nr(trhpTwI+xVuV2RviWMsrkI(`SYFd#^J zjJl3np!h2UZvvU5e9yEsF-dF)*Vb^q27J2?9rfXEdG|-S)hnL`{(@+8+2fX3+zpJU zjvhUl*i94NoY=JFljh>qTllML#{JW{l_@82x1&f>{uTInRlSzvhQ;eq_DH$#?G^(|E` zH)Du46@b_j=?)S3Sys1TOcpoawUl5?vMeI&mU&JDkeVsd&$7BXA>KUjYJx_+1k5Dq zStSZcu~_ta`o~#)k+iqcv_o^&H3^b=++7zoCLm_aM}SYT`bjq4rooL?rH<9*C3qps z)(7OwnKRyo!bhk-MLJ9tMPwRxd8DK^cPz4wWl2p1#5VLDu;_d(4LSo%Te(nMLTz%# zJgtod$o%}gHx_ajcgE9NK@ZF3q}Zf0f4x&UH6{8n2@u6K=>xoEVtkK`tK zP6NwUHM21QnVFgKhHjtY{uUIgj+j;Tto?p4Aup?nyJ}r!L%F>@Jv}&%(}SC>-MYrh z6m2!@ZW`}X7#kp}dJE8ImiiE^X zkJ=Xs1w_Ov@7coIry88a9b{w*xW#h| zro~_6{th=)3mCnfoy)#sVc&Uw*Z!`)a~7;4teeC>VtDf8NuGcHc^t=~QdwJx)wy%$ zIB?(qj^lV^C>d+K+Mh<6vZiCI%H-rE{r&wL1CTi7op;`0cz77caZpvt Date: Thu, 9 Sep 2021 20:29:13 +0200 Subject: [PATCH 2/3] Fix Denunceation typo, reorg compatibility code (#5156) * Fix Denunceation typo, reorg compatibility code * Fix Denunceation typo - lint --- .../com/unciv/logic/BackwardCompatibility.kt | 131 ++++++++++++++ core/src/com/unciv/logic/GameInfo.kt | 167 +++++------------- .../logic/automation/NextTurnAutomation.kt | 37 ++-- .../diplomacy/DiplomacyManager.kt | 23 +-- .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 3 +- 5 files changed, 207 insertions(+), 154 deletions(-) create mode 100644 core/src/com/unciv/logic/BackwardCompatibility.kt diff --git a/core/src/com/unciv/logic/BackwardCompatibility.kt b/core/src/com/unciv/logic/BackwardCompatibility.kt new file mode 100644 index 0000000000..111c97ff8a --- /dev/null +++ b/core/src/com/unciv/logic/BackwardCompatibility.kt @@ -0,0 +1,131 @@ +package com.unciv.logic + +import com.unciv.logic.city.CityConstructions +import com.unciv.logic.city.PerpetualConstruction +import com.unciv.logic.civilization.TechManager +import com.unciv.logic.civilization.diplomacy.DiplomacyFlags +import com.unciv.logic.civilization.diplomacy.DiplomacyManager +import com.unciv.models.ruleset.Ruleset + +/** + * Container for all temporarily used code managing transitions from deprecated elements to their replacements. + * + * Please place ***all*** such code here and call it _only_ from [GameInfo.setTransients]. + * Functions are allowed to remain once no longer used if you think they might serve as template for + * similar usecases in the future. Please comment sufficiently :) + */ +@Suppress("unused") // as mentioned above +object BackwardCompatibility { + + /** + * Mods can change, leading to things on the map that are no longer defined in the mod. + * This function removes them so the game doesn't crash when it tries to access them. + */ + fun GameInfo.removeMissingModReferences() { + for (tile in tileMap.values) { + for (terrainFeature in tile.terrainFeatures.filter{ !ruleSet.terrains.containsKey(it) }) + tile.terrainFeatures.remove(terrainFeature) + if (tile.resource != null && !ruleSet.tileResources.containsKey(tile.resource!!)) + tile.resource = null + if (tile.improvement != null && !ruleSet.tileImprovements.containsKey(tile.improvement!!)) + tile.improvement = null + + for (unit in tile.getUnits()) { + if (!ruleSet.units.containsKey(unit.name)) tile.removeUnit(unit) + + for (promotion in unit.promotions.promotions.toList()) + if (!ruleSet.unitPromotions.containsKey(promotion)) + unit.promotions.promotions.remove(promotion) + } + } + + for (city in civilizations.asSequence().flatMap { it.cities.asSequence() }) { + + for (building in city.cityConstructions.builtBuildings.toHashSet()) + if (!ruleSet.buildings.containsKey(building)) + city.cityConstructions.builtBuildings.remove(building) + + fun isInvalidConstruction(construction: String) = + !ruleSet.buildings.containsKey(construction) + && !ruleSet.units.containsKey(construction) + && !PerpetualConstruction.perpetualConstructionsMap.containsKey(construction) + + // Remove invalid buildings or units from the queue - don't just check buildings and units because it might be a special construction as well + for (construction in city.cityConstructions.constructionQueue.toList()) { + if (isInvalidConstruction(construction)) + city.cityConstructions.constructionQueue.remove(construction) + } + // And from being in progress + for (construction in city.cityConstructions.inProgressConstructions.keys.toList()) + if (isInvalidConstruction(construction)) + city.cityConstructions.inProgressConstructions.remove(construction) + } + + for (civInfo in civilizations) { + for (tech in civInfo.tech.techsResearched.toList()) + if (!ruleSet.technologies.containsKey(tech)) + civInfo.tech.techsResearched.remove(tech) + } + } + + /** + * Replaces all occurrences of [oldBuildingName] in [cityConstructions] with [newBuildingName] + * if the former is not contained in the ruleset. + */ + private fun changeBuildingNameIfNotInRuleset( + ruleSet: Ruleset, + cityConstructions: CityConstructions, + oldBuildingName: String, + newBuildingName: String + ) { + if (ruleSet.buildings.containsKey(oldBuildingName)) + return + // Replace in built buildings + if (cityConstructions.builtBuildings.contains(oldBuildingName)) { + cityConstructions.builtBuildings.remove(oldBuildingName) + cityConstructions.builtBuildings.add(newBuildingName) + } + // Replace in construction queue + if (!cityConstructions.builtBuildings.contains(newBuildingName) && !cityConstructions.constructionQueue.contains(newBuildingName)) + cityConstructions.constructionQueue = cityConstructions.constructionQueue + .map { if (it == oldBuildingName) newBuildingName else it } + .toMutableList() + else + cityConstructions.constructionQueue.remove(oldBuildingName) + // Replace in in-progress constructions + if (cityConstructions.inProgressConstructions.containsKey(oldBuildingName)) { + if (!cityConstructions.builtBuildings.contains(newBuildingName) && !cityConstructions.inProgressConstructions.containsKey(newBuildingName)) + cityConstructions.inProgressConstructions[newBuildingName] = cityConstructions.inProgressConstructions[oldBuildingName]!! + cityConstructions.inProgressConstructions.remove(oldBuildingName) + } + } + + /** Replace a changed tech name */ + private fun TechManager.replaceUpdatedTechName(oldTechName: String, newTechName: String) { + if (oldTechName in techsResearched) { + techsResearched.remove(oldTechName) + techsResearched.add(newTechName) + } + val index = techsToResearch.indexOf(oldTechName) + if (index >= 0) { + techsToResearch[index] = newTechName + } + if (oldTechName in techsInProgress) { + techsInProgress[newTechName] = researchOfTech(oldTechName) + techsInProgress.remove(oldTechName) + } + } + + /** Replace a deprecated DiplomacyFlags instance */ + fun GameInfo.replaceDiplomacyFlag(old: DiplomacyFlags, new: DiplomacyFlags) { + fun DiplomacyManager.replaceFlag() { + if (hasFlag(old)) { + val value = getFlag(old) + removeFlag(old) + setFlag(new, value) + } + } + civilizations.flatMap { civ -> civ.diplomacy.values }.forEach { it.replaceFlag() } + } + +} \ No newline at end of file diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 55adf51036..f0c0657a40 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -2,10 +2,11 @@ package com.unciv.logic import com.unciv.Constants import com.unciv.UncivGame +import com.unciv.logic.BackwardCompatibility.removeMissingModReferences +import com.unciv.logic.BackwardCompatibility.replaceDiplomacyFlag import com.unciv.logic.automation.NextTurnAutomation -import com.unciv.logic.city.CityConstructions -import com.unciv.logic.city.PerpetualConstruction import com.unciv.logic.civilization.* +import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.Religion @@ -18,20 +19,7 @@ import java.util.* class UncivShowableException(missingMods: String) : Exception(missingMods) class GameInfo { - @Transient - lateinit var difficultyObject: Difficulty // Since this is static game-wide, and was taking a large part of nextTurn - - @Transient - lateinit var currentPlayerCiv: CivilizationInfo // this is called thousands of times, no reason to search for it with a find{} every time - - /** This is used in multiplayer games, where I may have a saved game state on my phone - * that is inconsistent with the saved game on the cloud */ - @Transient - var isUpToDate = false - - @Transient - lateinit var ruleSet: Ruleset - + //region Fields - Serialized var civilizations = mutableListOf() var religions: HashMap = hashMapOf() var difficulty = "Chieftain" // difficulty is game-wide, think what would happen if 2 human players could play on different difficulties? @@ -54,15 +42,36 @@ class GameInfo { @Volatile var customSaveLocation: String? = null + //endregion + //region Fields - Transient + + @Transient + lateinit var difficultyObject: Difficulty // Since this is static game-wide, and was taking a large part of nextTurn + + @Transient + lateinit var currentPlayerCiv: CivilizationInfo // this is called thousands of times, no reason to search for it with a find{} every time + + /** This is used in multiplayer games, where I may have a saved game state on my phone + * that is inconsistent with the saved game on the cloud */ + @Transient + var isUpToDate = false + + @Transient + lateinit var ruleSet: Ruleset + /** Simulate until any player wins, * or turns exceeds indicated number * Does not update World View until finished. * Should be set manually on each new game start. */ + @Transient var simulateMaxTurns: Int = 1000 + @Transient var simulateUntilWin = false - //region pure functions + //endregion + //region Pure functions + fun clone(): GameInfo { val toReturn = GameInfo() toReturn.tileMap = tileMap.clone() @@ -103,7 +112,14 @@ class GameInfo { fun getCities() = civilizations.asSequence().flatMap { it.cities } fun getAliveCityStates() = civilizations.filter { it.isAlive() && it.isCityState() } fun getAliveMajorCivs() = civilizations.filter { it.isAlive() && it.isMajorCiv() } + + fun hasReligionEnabled() = + // Temporary function to check whether religion should be used for this game + (gameParameters.religionEnabled || ruleSet.hasReligion()) + && (ruleSet.eras.isEmpty() || !ruleSet.eras[gameParameters.startingEra]!!.hasUnique("Starting in this era disables religion")) + //endregion + //region State changing functions fun nextTurn() { val previousHumanPlayer = getCurrentPlayerCivilization() @@ -236,7 +252,7 @@ class GameInfo { return tile } - fun placeBarbarianUnit(tileToPlace: TileInfo) { + private fun placeBarbarianUnit(tileToPlace: TileInfo) { // if we don't make this into a separate list then the retain() will happen on the Tech keys, // which effectively removes those techs from the game and causes all sorts of problems val allResearchedTechs = ruleSet.technologies.keys.toMutableList() @@ -252,10 +268,9 @@ class GameInfo { val landUnits = unitList.filter { it.isLandUnit() } val waterUnits = unitList.filter { it.isWaterUnit() } - val unit: String - if (waterUnits.isNotEmpty() && tileToPlace.isCoastalTile() && Random().nextBoolean()) - unit = waterUnits.random().name - else unit = landUnits.random().name + val unit: String = if (waterUnits.isNotEmpty() && tileToPlace.isCoastalTile() && Random().nextBoolean()) + waterUnits.random().name + else landUnits.random().name tileMap.placeUnitNearTile(tileToPlace.position, unit, getBarbarianCivilization()) } @@ -264,7 +279,7 @@ class GameInfo { * [CivilizationInfo.addNotification][Add a notification] to every civilization that have * adopted Honor policy and have explored the [tile] where the Barbarian Encampment has spawned. */ - fun notifyCivsOfBarbarianEncampment(tile: TileInfo) { + private fun notifyCivsOfBarbarianEncampment(tile: TileInfo) { civilizations.filter { it.hasUnique("Notified of new Barbarian encampments") && it.exploredTiles.contains(tile.position) @@ -289,6 +304,8 @@ class GameInfo { removeMissingModReferences() + replaceDiplomacyFlag(DiplomacyFlags.Denunceation, DiplomacyFlags.Denunciation) + for (baseUnit in ruleSet.units.values) baseUnit.ruleset = ruleSet @@ -296,14 +313,14 @@ class GameInfo { // the nation of their civilization when setting transients for (civInfo in civilizations) civInfo.gameInfo = this for (civInfo in civilizations) civInfo.setNationTransient() - + tileMap.setTransients(ruleSet) if (currentPlayer == "") currentPlayer = civilizations.first { it.isPlayerCivilization() }.civName currentPlayerCiv = getCivilization(currentPlayer) difficultyObject = ruleSet.difficulties[difficulty]!! - + for (religion in religions.values) religion.setTransients(this) for (civInfo in civilizations) civInfo.setTransients() @@ -338,105 +355,7 @@ class GameInfo { } } - - // Mods can change, leading to things on the map that are no longer defined in the mod. - // So we remove them so the game doesn't crash when it tries to access them. - private fun removeMissingModReferences() { - for (tile in tileMap.values) { - for (terrainFeature in tile.terrainFeatures.filter{ !ruleSet.terrains.containsKey(it) }) - tile.terrainFeatures.remove(terrainFeature) - if (tile.resource != null && !ruleSet.tileResources.containsKey(tile.resource!!)) - tile.resource = null - if (tile.improvement != null && !ruleSet.tileImprovements.containsKey(tile.improvement!!)) - tile.improvement = null - - for (unit in tile.getUnits()) { - if (!ruleSet.units.containsKey(unit.name)) tile.removeUnit(unit) - - for (promotion in unit.promotions.promotions.toList()) - if (!ruleSet.unitPromotions.containsKey(promotion)) - unit.promotions.promotions.remove(promotion) - } - } - - for (city in civilizations.asSequence().flatMap { it.cities.asSequence() }) { - - for (building in city.cityConstructions.builtBuildings.toHashSet()) - if (!ruleSet.buildings.containsKey(building)) - city.cityConstructions.builtBuildings.remove(building) - - fun isInvalidConstruction(construction: String) = - !ruleSet.buildings.containsKey(construction) - && !ruleSet.units.containsKey(construction) - && !PerpetualConstruction.perpetualConstructionsMap.containsKey(construction) - - // Remove invalid buildings or units from the queue - don't just check buildings and units because it might be a special construction as well - for (construction in city.cityConstructions.constructionQueue.toList()) { - if (isInvalidConstruction(construction)) - city.cityConstructions.constructionQueue.remove(construction) - } - // And from being in progress - for (construction in city.cityConstructions.inProgressConstructions.keys.toList()) - if (isInvalidConstruction(construction)) - city.cityConstructions.inProgressConstructions.remove(construction) - } - - for (civinfo in civilizations) { - for (tech in civinfo.tech.techsResearched.toList()) - if (!ruleSet.technologies.containsKey(tech)) - civinfo.tech.techsResearched.remove(tech) - } - } - - /** - * Replaces all occurrences of [oldBuildingName] in [cityConstructions] with [newBuildingName] - * if the former is not contained in the ruleset. - * This function can be used for backwards compatibility with older save files when a building - * name is changed. - */ - @Suppress("unused") // it's OK if there's no deprecation currently needing this - private fun changeBuildingNameIfNotInRuleset(cityConstructions: CityConstructions, oldBuildingName: String, newBuildingName: String) { - if (ruleSet.buildings.containsKey(oldBuildingName)) - return - // Replace in built buildings - if (cityConstructions.builtBuildings.contains(oldBuildingName)) { - cityConstructions.builtBuildings.remove(oldBuildingName) - cityConstructions.builtBuildings.add(newBuildingName) - } - // Replace in construction queue - if (!cityConstructions.builtBuildings.contains(newBuildingName) && !cityConstructions.constructionQueue.contains(newBuildingName)) - cityConstructions.constructionQueue = cityConstructions.constructionQueue.map{ if (it == oldBuildingName) newBuildingName else it }.toMutableList() - else - cityConstructions.constructionQueue.remove(oldBuildingName) - // Replace in in-progress constructions - if (cityConstructions.inProgressConstructions.containsKey(oldBuildingName)) { - if (!cityConstructions.builtBuildings.contains(newBuildingName) && !cityConstructions.inProgressConstructions.containsKey(newBuildingName)) - cityConstructions.inProgressConstructions[newBuildingName] = cityConstructions.inProgressConstructions[oldBuildingName]!! - cityConstructions.inProgressConstructions.remove(oldBuildingName) - } - } - - /** Replace a changed tech name, only temporarily used for breaking ruleset updates */ - private fun TechManager.replaceUpdatedTechName(oldTechName: String, newTechName: String) { - if (oldTechName in techsResearched) { - techsResearched.remove(oldTechName) - techsResearched.add(newTechName) - } - val index = techsToResearch.indexOf(oldTechName) - if (index >= 0) { - techsToResearch[index] = newTechName - } - if (oldTechName in techsInProgress) { - techsInProgress[newTechName] = researchOfTech(oldTechName) - techsInProgress.remove(oldTechName) - } - } - - - fun hasReligionEnabled() = - // Temporary function to check whether religion should be used for this game - (gameParameters.religionEnabled || ruleSet.hasReligion()) - && (ruleSet.eras.isEmpty() || !ruleSet.eras[gameParameters.startingEra]!!.hasUnique("Starting in this era disables religion")) + //endregion } // reduced variant only for load preview diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index 4888e4a54d..b3ce904572 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -63,7 +63,7 @@ object NextTurnAutomation { // Can only be done now, as the prophet first has to decide to found/enhance a religion chooseReligiousBeliefs(civInfo) } - + reassignWorkedTiles(civInfo) // second most expensive trainSettler(civInfo) tryVoteForDiplomaticVictory(civInfo) @@ -103,7 +103,7 @@ object NextTurnAutomation { val requestingCiv = civInfo.gameInfo.getCivilization(popupAlert.value) val diploManager = civInfo.getDiplomacyManager(requestingCiv) if (diploManager.relationshipLevel() > RelationshipLevel.Neutral - && !diploManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.Denunceation)) { + && !diploManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.Denunciation)) { diploManager.signDeclarationOfFriendship() requestingCiv.addNotification("We have signed a Declaration of Friendship with [${civInfo.civName}]!", NotificationIcon.Diplomacy, civInfo.civName) } else requestingCiv.addNotification("[${civInfo.civName}] has denied our Declaration of Friendship!", NotificationIcon.Diplomacy, civInfo.civName) @@ -206,8 +206,9 @@ object NextTurnAutomation { } // Bonus for luxury resources we can get from them - value += cityState.detailedCivResources.count { it.resource.resourceType == ResourceType.Luxury - && it.resource !in civInfo.detailedCivResources.map { it.resource } + value += cityState.detailedCivResources.count { + it.resource.resourceType == ResourceType.Luxury + && it.resource !in civInfo.detailedCivResources.map { supply -> supply.resource } } return value @@ -298,13 +299,13 @@ object NextTurnAutomation { civInfo.policies.adopt(policyToAdopt) } } - + private fun chooseReligiousBeliefs(civInfo: CivilizationInfo) { choosePantheon(civInfo) foundReligion(civInfo) enhanceReligion(civInfo) } - + private fun choosePantheon(civInfo: CivilizationInfo) { if (!civInfo.religionManager.canFoundPantheon()) return // So looking through the source code of the base game available online, @@ -319,7 +320,7 @@ object NextTurnAutomation { val chosenPantheon = availablePantheons.random() // Why calculate stuff? civInfo.religionManager.choosePantheonBelief(chosenPantheon) } - + private fun foundReligion(civInfo: CivilizationInfo) { if (civInfo.religionManager.religionState != ReligionState.FoundingReligion) return val religionIcon = civInfo.gameInfo.ruleSet.religions @@ -329,7 +330,7 @@ object NextTurnAutomation { val chosenBeliefs = chooseBeliefs(civInfo, civInfo.religionManager.getBeliefsToChooseAtFounding()).toList() civInfo.religionManager.chooseBeliefs(religionIcon, religionIcon, chosenBeliefs) } - + private fun enhanceReligion(civInfo: CivilizationInfo) { civInfo.religionManager.chooseBeliefs( null, @@ -337,11 +338,11 @@ object NextTurnAutomation { chooseBeliefs(civInfo, civInfo.religionManager.getBeliefsToChooseAtEnhancing()).toList() ) } - + private fun chooseBeliefs(civInfo: CivilizationInfo, beliefContainer: BeliefContainer): HashSet { val chosenBeliefs = hashSetOf() - // The 'continues' should never be reached, but just in case I'd rather have AI have a - // belief less than make the game crash. The 'continue's should only be reached whenever + // The `continue`s should never be reached, but just in case I'd rather have the AI have a + // belief less than make the game crash. The `continue`s should only be reached whenever // there are not enough beliefs to choose, but there should be, as otherwise we could // not have used a great prophet to found/enhance our religion. for (counter in 0 until beliefContainer.pantheonBeliefCount) @@ -362,7 +363,7 @@ object NextTurnAutomation { ) return chosenBeliefs } - + private fun chooseBeliefOfType(civInfo: CivilizationInfo, beliefType: BeliefType, additionalBeliefsToExclude: HashSet = hashSetOf()): Belief? { return civInfo.gameInfo.ruleSet.beliefs .filter { @@ -436,7 +437,7 @@ object NextTurnAutomation { it.isMajorCiv() && !it.isAtWarWith(civInfo) && it.getDiplomacyManager(civInfo).relationshipLevel() > RelationshipLevel.Neutral && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclarationOfFriendship) - && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.Denunceation) + && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.Denunciation) } .sortedByDescending { it.getDiplomacyManager(civInfo).relationshipLevel() } for (civ in civsThatWeCanDeclareFriendshipWith) { @@ -682,20 +683,20 @@ object NextTurnAutomation { private fun tryVoteForDiplomaticVictory(civInfo: CivilizationInfo) { if (!civInfo.mayVoteForDiplomaticVictory()) return val chosenCiv: String? = if (civInfo.isMajorCiv()) { - + val knownMajorCivs = civInfo.getKnownCivs().filter { it.isMajorCiv() } val highestOpinion = knownMajorCivs .maxOfOrNull { civInfo.getDiplomacyManager(it).opinionOfOtherCiv() } - + if (highestOpinion == null) null else knownMajorCivs.filter { civInfo.getDiplomacyManager(it).opinionOfOtherCiv() == highestOpinion}.random().civName - + } else { civInfo.getAllyCiv() } - + civInfo.diplomaticVoteForCiv(chosenCiv) } @@ -742,4 +743,4 @@ object NextTurnAutomation { return cityDistances.minByOrNull { it.aerialDistance }!! } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 619cca6667..cfb2ae9561 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -32,6 +32,7 @@ enum class DiplomacyFlags { DeclaredWar, DeclarationOfFriendship, ResearchAgreement, + @Deprecated("Deprecated after 3.16.13", ReplaceWith("Denunciation")) Denunceation, BorderConflict, SettledCitiesNearUs, @@ -43,7 +44,8 @@ enum class DiplomacyFlags { NotifiedAfraid, RecentlyPledgedProtection, RecentlyWithdrewProtection, - AngerFreeIntrusion + AngerFreeIntrusion, + Denunciation } enum class DiplomaticModifiers { @@ -107,7 +109,7 @@ class DiplomacyManager() { get() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else field /** Total of each turn Science during Research Agreement */ - var totalOfScienceDuringRA = 0 + private var totalOfScienceDuringRA = 0 fun clone(): DiplomacyManager { val toReturn = DiplomacyManager() @@ -188,6 +190,7 @@ class DiplomacyManager() { return 0 } + @Suppress("unused") //todo Finish original intent or remove fun matchesCityStateRelationshipFilter(filter: String): Boolean { val relationshipLevel = relationshipLevel() return when (filter) { @@ -210,7 +213,7 @@ class DiplomacyManager() { } // To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different. - fun getCityStateInfluenceRestingPoint(): Float { + private fun getCityStateInfluenceRestingPoint(): Float { var restingPoint = 0f for (unique in otherCiv().getMatchingUniques("Resting point for Influence with City-States is increased by []")) @@ -287,7 +290,7 @@ class DiplomacyManager() { return goldPerTurnForUs } - fun scienceFromResearchAgreement() { + private fun scienceFromResearchAgreement() { // https://forums.civfanatics.com/resources/research-agreements-bnw.25568/ val scienceFromResearchAgreement = min(totalOfScienceDuringRA, otherCivDiplomacy().totalOfScienceDuringRA) civInfo.tech.scienceFromResearchAgreements += scienceFromResearchAgreement @@ -696,16 +699,16 @@ class DiplomacyManager() { diplomaticModifiers[modifier.name] = amount } - fun getModifier(modifier: DiplomaticModifiers): Float { + private fun getModifier(modifier: DiplomaticModifiers): Float { if (!hasModifier(modifier)) return 0f return diplomaticModifiers[modifier.name]!! } - fun removeModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.remove(modifier.name) + private fun removeModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.remove(modifier.name) fun hasModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.containsKey(modifier.name) /** @param amount always positive, so you don't need to think about it */ - fun revertToZero(modifier: DiplomaticModifiers, amount: Float) { + private fun revertToZero(modifier: DiplomaticModifiers, amount: Float) { if (!hasModifier(modifier)) return val currentAmount = getModifier(modifier) if (currentAmount > 0) addModifier(modifier, -amount) @@ -728,7 +731,7 @@ class DiplomacyManager() { } } - fun setFriendshipBasedModifier() { + private fun setFriendshipBasedModifier() { removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies) for (thirdCiv in getCommonKnownCivs() @@ -747,8 +750,8 @@ class DiplomacyManager() { fun denounce() { setModifier(DiplomaticModifiers.Denunciation, -35f) otherCivDiplomacy().setModifier(DiplomaticModifiers.Denunciation, -35f) - setFlag(DiplomacyFlags.Denunceation, 30) - otherCivDiplomacy().setFlag(DiplomacyFlags.Denunceation, 30) + setFlag(DiplomacyFlags.Denunciation, 30) + otherCivDiplomacy().setFlag(DiplomacyFlags.Denunciation, 30) otherCiv().addNotification("[${civInfo.civName}] has denounced us!", NotificationIcon.Diplomacy, civInfo.civName) diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index f95df66260..d308d684f0 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -16,7 +16,6 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeType -import com.unciv.models.ruleset.Era import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.Quest import com.unciv.models.ruleset.tile.ResourceType @@ -593,7 +592,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { diplomacyTable.add(researchAgreementButton).row() } - if (!diplomacyManager.hasFlag(DiplomacyFlags.Denunceation) + if (!diplomacyManager.hasFlag(DiplomacyFlags.Denunciation) && !diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) ) { val denounceButton = "Denounce ([30] turns)".toTextButton() From 83050c2b1190a46e1c265aec7ee8785f753fde85 Mon Sep 17 00:00:00 2001 From: SimonCeder <63475501+SimonCeder@users.noreply.github.com> Date: Thu, 9 Sep 2021 20:30:12 +0200 Subject: [PATCH 3/3] Militaristic city states can give unique unit; refactor city state initialization (#5147) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Pick unique unit for militaristic CS * Provide unique unit when able * refactor some code * remove debug * template.properties * fix failing build * Add variance to spawning rate, add ≈ to string, better formatting. --- .../assets/jsons/Civ V - Vanilla/Eras.json | 36 ++++++------- .../jsons/translations/template.properties | 1 + core/src/com/unciv/logic/GameStarter.kt | 24 ++++----- .../logic/civilization/CityStateFunctions.kt | 53 ++++++++++++++++++- .../logic/civilization/CivilizationInfo.kt | 5 ++ .../diplomacy/DiplomacyManager.kt | 10 ++-- .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 12 ++++- 7 files changed, 101 insertions(+), 40 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/Eras.json b/android/assets/jsons/Civ V - Vanilla/Eras.json index c06358bb56..39bd3e9488 100644 --- a/android/assets/jsons/Civ V - Vanilla/Eras.json +++ b/android/assets/jsons/Civ V - Vanilla/Eras.json @@ -16,13 +16,13 @@ "Cultured": ["Provides [3] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [2] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [6] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [2] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [255, 87, 35] }, @@ -42,13 +42,13 @@ "Cultured": ["Provides [3] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [2] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [6] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [2] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [233, 31, 99] }, @@ -70,13 +70,13 @@ "Cultured": ["Provides [6] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [3] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [12] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [157, 39, 176] }, @@ -99,13 +99,13 @@ "Cultured": ["Provides [6] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [3] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [12] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [104, 58, 183] }, @@ -129,13 +129,13 @@ "Cultured": ["Provides [13] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [3] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [26] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [63, 81, 182], "uniques": ["May not generate great prophet equivalents naturally", @@ -164,13 +164,13 @@ "Cultured": ["Provides [13] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [3] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [26] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [33, 150, 243], "uniques": ["May not generate great prophet equivalents naturally", @@ -200,13 +200,13 @@ "Cultured": ["Provides [13] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [3] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [26] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [0, 150, 136], "uniques": ["May not generate great prophet equivalents naturally", @@ -240,13 +240,13 @@ "Cultured": ["Provides [13] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [3] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [26] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [76, 176, 81], "uniques": ["May not generate great prophet equivalents naturally", @@ -279,13 +279,13 @@ "Cultured": ["Provides [13] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]"], "Mercantile": ["Provides [3] Happiness"], - "Militaristic": ["Provides military units every [20] turns"] + "Militaristic": ["Provides military units every ≈[20] turns"] }, "allyBonus": { "Cultured": ["Provides [26] [Culture] per turn"], "Maritime": ["Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], - "Militaristic": ["Provides military units every [17] turns"] + "Militaristic": ["Provides military units every ≈[17] turns"] }, "iconRGB": [76, 176, 81], "uniques": ["May not generate great prophet equivalents naturally", diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 22523d7aff..ac10799c9f 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -163,6 +163,7 @@ Pledge to protect = Declare Protection of [cityStateName]? = Build [improvementName] on [resourceName] (200 Gold) = Gift Improvement = +[civName] is able to provide [unitName] once [techName] is researched. = Diplomatic Marriage ([amount] Gold) = We have married into the ruling family of [civName], bringing them under our control. = diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index c71133ba0a..061983c4c6 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -193,23 +193,17 @@ object GameStarter { // and then all the other City-States in a random order! Because the sortedBy function is stable! availableCityStatesNames.addAll(ruleset.nations.filter { it.value.isCityState() }.keys .shuffled().sortedByDescending { it in civNamesWithStartingLocations }) - - val allMercantileResources = ruleset.tileResources.values.filter { it.unique == "Can only be created by Mercantile City-States" }.map { it.name } - val unusedMercantileResources = Stack() - unusedMercantileResources.addAll(allMercantileResources.shuffled()) - - for (cityStateName in availableCityStatesNames.take(newGameParameters.numberOfCityStates)) { + var addedCityStates = 0 + // Keep trying to add city states until we reach the target number. + while (addedCityStates < newGameParameters.numberOfCityStates) { + if (availableCityStatesNames.isEmpty()) // We ran out of city-states somehow + break + val cityStateName = availableCityStatesNames.pop() val civ = CivilizationInfo(cityStateName) - civ.cityStatePersonality = CityStatePersonality.values().random() - civ.cityStateResource = when { - ruleset.nations[cityStateName]?.cityStateType != CityStateType.Mercantile -> null - allMercantileResources.isEmpty() -> null - unusedMercantileResources.empty() -> allMercantileResources.random() // When unused luxuries exhausted, random - else -> unusedMercantileResources.pop() // First pick an unused luxury if possible + if (civ.initCityState(ruleset, newGameParameters.startingEra, availableCivNames)) { // true if successful init + gameInfo.civilizations.add(civ) + addedCityStates++ } - gameInfo.civilizations.add(civ) - for (tech in startingTechs) - civ.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet } } diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index 21bfd33f42..20c0d87132 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -6,6 +6,7 @@ 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.metadata.GameSpeed +import com.unciv.models.ruleset.Ruleset import com.unciv.models.stats.Stat import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.getPlaceholderText @@ -18,6 +19,51 @@ import kotlin.math.pow /** Class containing city-state-specific functions */ class CityStateFunctions(val civInfo: CivilizationInfo) { + /** Attempts to initialize the city state, returning true if successful. */ + fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection): Boolean { + val cityStateType = ruleset.nations[civInfo.civName]?.cityStateType + if (cityStateType == null) return false + + val startingTechs = ruleset.technologies.values.filter { it.uniques.contains("Starting tech") } + for (tech in startingTechs) + civInfo.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet + + val allMercantileResources = ruleset.tileResources.values.filter { it.unique == "Can only be created by Mercantile City-States" }.map { it.name } + val allPossibleBonuses = HashSet() // We look through these to determine what kind of city state we are + for (era in ruleset.eras.values) { + val allyBonuses = era.allyBonus[cityStateType.name] + val friendBonuses = era.friendBonus[cityStateType.name] + if (allyBonuses != null) + allPossibleBonuses.addAll(allyBonuses) + if (friendBonuses != null) + allPossibleBonuses.addAll(friendBonuses) + } + + // CS Personality + civInfo.cityStatePersonality = CityStatePersonality.values().random() + + // Mercantile bonus resources + if ("Provides a unique luxury" in allPossibleBonuses + || (allPossibleBonuses.isEmpty() && cityStateType == CityStateType.Mercantile)) { // Fallback for badly defined Eras.json + civInfo.cityStateResource = allMercantileResources.random() + } + + // Unique unit for militaristic city-states + if (allPossibleBonuses.any { it.getPlaceholderText() == "Provides military units every ≈[] turns" } + || (allPossibleBonuses.isEmpty() && cityStateType == CityStateType.Militaristic)) { // Fallback for badly defined Eras.json + + val possibleUnits = ruleset.units.values.filter { it.requiredTech != null + && ruleset.eras[ruleset.technologies[it.requiredTech!!]!!.era()]!!.eraNumber > ruleset.eras[startingEra]!!.eraNumber // Not from the start era or before + && it.uniqueTo != null && it.uniqueTo in unusedMajorCivs // Must be from a major civ not in the game + && ruleset.unitTypes[it.unitType]!!.isLandUnit() && ( it.strength > 0 || it.rangedStrength > 0 ) } // Must be a land military unit + if (possibleUnits.isNotEmpty()) + civInfo.cityStateUniqueUnit = possibleUnits.random().name + } + + // TODO: Return false if attempting to put a religious city-state in a game without religion + + return true + } /** Gain a random great person from the city state */ fun giveGreatPersonToPatron(receivingCiv: CivilizationInfo) { @@ -37,7 +83,12 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { fun giveMilitaryUnitToPatron(receivingCiv: CivilizationInfo) { val cities = NextTurnAutomation.getClosestCities(receivingCiv, civInfo) val city = cities.city1 - val militaryUnit = city.cityConstructions.getConstructableUnits() + val uniqueUnit = civInfo.gameInfo.ruleSet.units[civInfo.cityStateUniqueUnit] + // If the receiving civ has discovered the required tech and not the obsolete tech for our unique, always give them the unique + val militaryUnit = if (uniqueUnit != null && receivingCiv.tech.isResearched(uniqueUnit.requiredTech!!) + && (uniqueUnit.obsoleteTech == null || !receivingCiv.tech.isResearched(uniqueUnit.obsoleteTech!!))) uniqueUnit + // Otherwise pick at random + else city.cityConstructions.getConstructableUnits() .filter { !it.isCivilian() && it.isLandUnit() && it.uniqueTo==null } .toList().random() // placing the unit may fail - in that case stay quiet diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 7bc229513f..843077094a 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -174,6 +174,7 @@ class CivilizationInfo { toReturn.naturalWonders.addAll(naturalWonders) toReturn.cityStatePersonality = cityStatePersonality toReturn.cityStateResource = cityStateResource + toReturn.cityStateUniqueUnit = cityStateUniqueUnit toReturn.flagsCountdown.putAll(flagsCountdown) toReturn.temporaryUniques.addAll(temporaryUniques) toReturn.boughtConstructionsWithGloballyIncreasingPrice.putAll(boughtConstructionsWithGloballyIncreasingPrice) @@ -208,6 +209,7 @@ class CivilizationInfo { val cityStateType: CityStateType get() = nation.cityStateType!! var cityStatePersonality: CityStatePersonality = CityStatePersonality.Neutral var cityStateResource: String? = null + var cityStateUniqueUnit: String? = null // Unique unit for militaristic city state. Might still be null if there are no appropriate units fun isMajorCiv() = nation.isMajorCiv() fun isAlive(): Boolean = !isDefeated() fun hasEverBeenFriendWith(otherCiv: CivilizationInfo): Boolean = getDiplomacyManager(otherCiv).everBeenFriends() @@ -894,6 +896,9 @@ class CivilizationInfo { } //////////////////////// City State wrapper functions //////////////////////// + + fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection) + = cityStateFunctions.initCityState(ruleset, startingEra, unusedMajorCivs) /** Gain a random great person from the city state */ private fun giveGreatPersonToPatron(receivingCiv: CivilizationInfo) { cityStateFunctions.giveGreatPersonToPatron(receivingCiv) diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index cfb2ae9561..d354137863 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -541,6 +541,8 @@ class DiplomacyManager() { if (relationshipLevel() < RelationshipLevel.Friend) { if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit)) removeFlag(DiplomacyFlags.ProvideMilitaryUnit) } else { + val variance = listOf(-1, 0, 1).random() + val relevantBonuses = if (relationshipLevel() == RelationshipLevel.Friend) eraInfo.friendBonus[otherCiv().cityStateType.name] @@ -549,19 +551,19 @@ class DiplomacyManager() { if (relevantBonuses == null && otherCiv().cityStateType == CityStateType.Militaristic) { // Deprecated, assume Civ V values for compatibility if (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) && relationshipLevel() == RelationshipLevel.Friend) - setFlag(DiplomacyFlags.ProvideMilitaryUnit, 20) + setFlag(DiplomacyFlags.ProvideMilitaryUnit, 20 + variance) if ((!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) || getFlag(DiplomacyFlags.ProvideMilitaryUnit) > 17) && relationshipLevel() == RelationshipLevel.Ally) - setFlag(DiplomacyFlags.ProvideMilitaryUnit, 17) + setFlag(DiplomacyFlags.ProvideMilitaryUnit, 17 + variance) } if (relevantBonuses == null) return for (bonus in relevantBonuses) { // Reset the countdown if it has ended, or if we have longer to go than the current maximum (can happen when going from friend to ally) - if (bonus.getPlaceholderText() == "Provides military units every [] turns" && + if (bonus.getPlaceholderText() == "Provides military units every ≈[] turns" && (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) || getFlag(DiplomacyFlags.ProvideMilitaryUnit) > bonus.getPlaceholderParameters()[0].toInt())) - setFlag(DiplomacyFlags.ProvideMilitaryUnit, bonus.getPlaceholderParameters()[0].toInt()) + setFlag(DiplomacyFlags.ProvideMilitaryUnit, bonus.getPlaceholderParameters()[0].toInt() + variance) } } } diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index d308d684f0..a1ef52b2c1 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -132,9 +132,9 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { val otherCivDiplomacyManager = otherCiv.getDiplomacyManager(viewingCiv) val diplomacyTable = Table() - diplomacyTable.defaults().pad(10f) + diplomacyTable.defaults().pad(2.5f) - diplomacyTable.add(LeaderIntroTable(otherCiv)).row() + diplomacyTable.add(LeaderIntroTable(otherCiv)).padBottom(15f).row() diplomacyTable.add("{Type}: {${otherCiv.cityStateType}}".toLabel()).row() diplomacyTable.add("{Personality}: {${otherCiv.cityStatePersonality}}".toLabel()).row() @@ -162,6 +162,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { } diplomacyTable.add(resourcesTable).row() } + diplomacyTable.row().padTop(15f) otherCiv.updateAllyCivForCityState() val ally = otherCiv.getAllyCiv() @@ -186,6 +187,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { if (nextLevelString.isNotEmpty()) { diplomacyTable.add(nextLevelString.toLabel()).row() } + diplomacyTable.row().padTop(15f) val eraInfo = viewingCiv.getEra() @@ -215,6 +217,12 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { .apply { setAlignment(Align.center) } diplomacyTable.add(allyBonusLabel).row() + if (otherCiv.cityStateUniqueUnit != null) { + val unitName = otherCiv.cityStateUniqueUnit + val techName = viewingCiv.gameInfo.ruleSet.units[otherCiv.cityStateUniqueUnit]!!.requiredTech + diplomacyTable.add("[${otherCiv.civName}] is able to provide [${unitName}] once [${techName}] is researched.".toLabel(fontSize = 18)).row() + } + return diplomacyTable }