Add unique to allow for generalized great generals (#10828)

* Add unique to allow for generalized great generals

* Don't add compatibility for rulesets with conditional generals

* Add to rulesets

* add in pre Kotlin 9 parenthesis

* whoops missed a parenthesis

* I guess pre Kotlin 9 parenthesis was unnecessary, whoops

* Add back old variables to clone function

* Move the list of all potential generals to Ruleset

* Move list of unit construction rejections to IConstruction

* flip !any{} to none{}

* Fix imports

* Typo
This commit is contained in:
SeventhM 2024-01-03 23:03:14 -08:00 committed by GitHub
parent 6e84377090
commit 665b5aa87c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 83 additions and 25 deletions

View File

@ -1653,6 +1653,7 @@
"Empire enters a [8]-turn Golden Age <by consuming this unit>", "Empire enters a [8]-turn Golden Age <by consuming this unit>",
"[+15]% Strength bonus for [Military] units within [2] tiles", "[+15]% Strength bonus for [Military] units within [2] tiles",
"Can instantly construct a [Citadel] improvement <by consuming this unit>", "Can instantly construct a [Citadel] improvement <by consuming this unit>",
"Can be earned through combat",
"Great Person - [War]", "Unbuildable", "Uncapturable"], "Great Person - [War]", "Unbuildable", "Uncapturable"],
"movement": 2 "movement": 2
}, },
@ -1666,6 +1667,7 @@
"[+15]% Strength bonus for [Military] units within [2] tiles", "[+15]% Strength bonus for [Military] units within [2] tiles",
"All adjacent units heal [+15] HP when healing", "[+15] HP when healing", "All adjacent units heal [+15] HP when healing", "[+15] HP when healing",
"Can instantly construct a [Citadel] improvement <by consuming this unit>", "Can instantly construct a [Citadel] improvement <by consuming this unit>",
"Can be earned through combat",
"Great Person - [War]", "Unbuildable", "Uncapturable"], "Great Person - [War]", "Unbuildable", "Uncapturable"],
"movement": 5 "movement": 5
}, },

View File

@ -1296,6 +1296,7 @@
"Empire enters a [8]-turn Golden Age <by consuming this unit>", "Empire enters a [8]-turn Golden Age <by consuming this unit>",
"[+15]% Strength bonus for [Military] units within [2] tiles", "[+15]% Strength bonus for [Military] units within [2] tiles",
"Can instantly construct a [Citadel] improvement <by consuming this unit>", "Can instantly construct a [Citadel] improvement <by consuming this unit>",
"Can be earned through combat",
"Great Person - [War]", "Unbuildable", "Uncapturable"], "Great Person - [War]", "Unbuildable", "Uncapturable"],
"movement": 2 "movement": 2
}, },
@ -1309,6 +1310,7 @@
"[+15]% Strength bonus for [Military] units within [2] tiles", "[+15]% Strength bonus for [Military] units within [2] tiles",
"All adjacent units heal [+15] HP when healing", "[+15] HP when healing", "All adjacent units heal [+15] HP when healing", "[+15] HP when healing",
"Can instantly construct a [Citadel] improvement <by consuming this unit>", "Can instantly construct a [Citadel] improvement <by consuming this unit>",
"Can be earned through combat",
"Great Person - [War]", "Unbuildable", "Uncapturable"], "Great Person - [War]", "Unbuildable", "Uncapturable"],
"movement": 5 "movement": 5
}, },

View File

@ -37,6 +37,19 @@ object BackwardCompatibility {
removeTechAndPolicies() removeTechAndPolicies()
} }
fun GameInfo.migrateGreatGeneralPools() {
for (civ in civilizations) civ.greatPeople.run {
if (pointsForNextGreatGeneral >= pointsForNextGreatGeneralCounter["Great General"]) {
pointsForNextGreatGeneralCounter["Great General"] = pointsForNextGreatGeneral
} else pointsForNextGreatGeneral = pointsForNextGreatGeneralCounter["Great General"]
if (greatGeneralPoints >= greatGeneralPointsCounter["Great General"]) {
greatGeneralPointsCounter["Great General"] = greatGeneralPoints
} else greatGeneralPoints = greatGeneralPointsCounter["Great General"]
}
}
private fun GameInfo.removeUnitsAndPromotions() { private fun GameInfo.removeUnitsAndPromotions() {
for (tile in tileMap.values) { for (tile in tileMap.values) {
for (unit in tile.getUnits().toList()) { for (unit in tile.getUnits().toList()) {

View File

@ -7,6 +7,7 @@ import com.unciv.UncivGame.Version
import com.unciv.json.json import com.unciv.json.json
import com.unciv.logic.BackwardCompatibility.convertFortify import com.unciv.logic.BackwardCompatibility.convertFortify
import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions
import com.unciv.logic.BackwardCompatibility.migrateGreatGeneralPools
import com.unciv.logic.BackwardCompatibility.migrateToTileHistory import com.unciv.logic.BackwardCompatibility.migrateToTileHistory
import com.unciv.logic.BackwardCompatibility.removeMissingModReferences import com.unciv.logic.BackwardCompatibility.removeMissingModReferences
import com.unciv.logic.GameInfo.Companion.CURRENT_COMPATIBILITY_NUMBER import com.unciv.logic.GameInfo.Companion.CURRENT_COMPATIBILITY_NUMBER
@ -670,6 +671,8 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
guaranteeUnitPromotions() guaranteeUnitPromotions()
migrateToTileHistory() migrateToTileHistory()
migrateGreatGeneralPools()
} }
private fun updateCivilizationState() { private fun updateCivilizationState() {

View File

@ -482,19 +482,29 @@ object Battle {
promotions.XP += xpGained promotions.XP += xpGained
if (!otherIsBarbarian && civ.isMajorCiv()) { // Can't get great generals from Barbarians if (!otherIsBarbarian && civ.isMajorCiv()) { // Can't get great generals from Barbarians
val greatGeneralPointsBonus = thisCombatant var greatGeneralUnits = civ.gameInfo.ruleset.greatGeneralUnits
.getMatchingUniques(UniqueType.GreatPersonEarnedFaster, stateForConditionals, true) .filter { it.hasUnique(UniqueType.GreatPersonFromCombat, stateForConditionals) &&
.filter { unique -> // Check if the unit is allowed for the Civ, ignoring build constrants
val unitName = unique.params[0] it.getRejectionReasons(civ).none { reason ->
// From the unique we know this unit exists !reason.isConstructionRejection() &&
val unit = civ.gameInfo.ruleset.units[unitName]!! // Allow Generals even if not allowed via tech
unit.uniques.contains("Great Person - [War]") !reason.techPolicyEraWonderRequirements() }
} }.asSequence()
.sumOf { it.params[1].toDouble() } // For compatibility with older rulesets
val greatGeneralPointsModifier = 1.0 + greatGeneralPointsBonus / 100 if (civ.gameInfo.ruleset.greatGeneralUnits.isEmpty() &&
civ.gameInfo.ruleset.units["Great General"] != null)
greatGeneralUnits += civ.gameInfo.ruleset.units["Great General"]!!
val greatGeneralPointsGained = (xpGained * greatGeneralPointsModifier).toInt() for (unit in greatGeneralUnits) {
civ.greatPeople.greatGeneralPoints += greatGeneralPointsGained val greatGeneralPointsBonus = thisCombatant
.getMatchingUniques(UniqueType.GreatPersonEarnedFaster, stateForConditionals, true)
.filter { unit.matchesFilter(it.params[0]) }
.sumOf { it.params[1].toDouble() }
val greatGeneralPointsModifier = 1.0 + greatGeneralPointsBonus / 100
val greatGeneralPointsGained = (xpGained * greatGeneralPointsModifier).toInt()
civ.greatPeople.greatGeneralPointsCounter[unit.name] += greatGeneralPointsGained
}
} }
if (!thisCombatant.isDefeated() && !unitCouldAlreadyPromote && promotions.canBePromoted()) { if (!thisCombatant.isDefeated() && !unitCouldAlreadyPromote && promotions.canBePromoted()) {

View File

@ -19,8 +19,10 @@ class GreatPersonManager : IsPartOfGameInfoSerialization {
/** Base points, without speed modifier */ /** Base points, without speed modifier */
var pointsForNextGreatPersonCounter = Counter<String>() // Initial values assigned in getPointsRequiredForGreatPerson as needed var pointsForNextGreatPersonCounter = Counter<String>() // Initial values assigned in getPointsRequiredForGreatPerson as needed
var pointsForNextGreatGeneral = 200 var pointsForNextGreatGeneral = 200
var pointsForNextGreatGeneralCounter = Counter<String>() // Initial values assigned when needed
var greatPersonPointsCounter = Counter<String>() var greatPersonPointsCounter = Counter<String>()
var greatGeneralPointsCounter = Counter<String>()
var greatGeneralPoints = 0 var greatGeneralPoints = 0
var freeGreatPeople = 0 var freeGreatPeople = 0
/** Marks subset of [freeGreatPeople] as subject to maya ability restrictions (each only once untill all used) */ /** Marks subset of [freeGreatPeople] as subject to maya ability restrictions (each only once untill all used) */
@ -33,6 +35,8 @@ class GreatPersonManager : IsPartOfGameInfoSerialization {
toReturn.freeGreatPeople = freeGreatPeople toReturn.freeGreatPeople = freeGreatPeople
toReturn.greatPersonPointsCounter = greatPersonPointsCounter.clone() toReturn.greatPersonPointsCounter = greatPersonPointsCounter.clone()
toReturn.pointsForNextGreatPersonCounter = pointsForNextGreatPersonCounter.clone() toReturn.pointsForNextGreatPersonCounter = pointsForNextGreatPersonCounter.clone()
toReturn.pointsForNextGreatGeneralCounter = pointsForNextGreatGeneralCounter.clone()
toReturn.greatGeneralPointsCounter = greatGeneralPointsCounter.clone()
toReturn.pointsForNextGreatGeneral = pointsForNextGreatGeneral toReturn.pointsForNextGreatGeneral = pointsForNextGreatGeneral
toReturn.greatGeneralPoints = greatGeneralPoints toReturn.greatGeneralPoints = greatGeneralPoints
toReturn.mayaLimitedFreeGP = mayaLimitedFreeGP toReturn.mayaLimitedFreeGP = mayaLimitedFreeGP
@ -54,10 +58,16 @@ class GreatPersonManager : IsPartOfGameInfoSerialization {
} }
fun getNewGreatPerson(): String? { fun getNewGreatPerson(): String? {
if (greatGeneralPoints > pointsForNextGreatGeneral) { for ((unit, value) in greatGeneralPointsCounter){
greatGeneralPoints -= pointsForNextGreatGeneral if (pointsForNextGreatGeneralCounter[unit] == 0) {
pointsForNextGreatGeneral += 50 pointsForNextGreatGeneralCounter[unit] = 200
return "Great General" }
val requiredPoints = pointsForNextGreatGeneralCounter[unit]
if (value > requiredPoints) {
greatGeneralPointsCounter[unit] -= requiredPoints
pointsForNextGreatGeneralCounter[unit] += 50
return unit
}
} }
for ((greatPerson, value) in greatPersonPointsCounter) { for ((greatPerson, value) in greatPersonPointsCounter) {

View File

@ -118,6 +118,8 @@ class RejectionReason(val type: RejectionReasonType,
fun isImportantRejection(): Boolean = type in orderedImportantRejectionTypes fun isImportantRejection(): Boolean = type in orderedImportantRejectionTypes
fun isConstructionRejection(): Boolean = type in constructionRejectionReasonType
/** Returns the index of [orderedImportantRejectionTypes] with the smallest index having the /** Returns the index of [orderedImportantRejectionTypes] with the smallest index having the
* highest precedence */ * highest precedence */
fun getRejectionPrecedence(): Int { fun getRejectionPrecedence(): Int {
@ -152,6 +154,12 @@ class RejectionReason(val type: RejectionReasonType,
RejectionReasonType.MaxNumberBuildable, RejectionReasonType.MaxNumberBuildable,
RejectionReasonType.NoPlaceToPutUnit, RejectionReasonType.NoPlaceToPutUnit,
) )
// Used for units spawned, not built
private val constructionRejectionReasonType = listOf(
RejectionReasonType.Unbuildable,
RejectionReasonType.CannotBeBuiltUnhappiness,
RejectionReasonType.CannotBeBuilt,
)
} }

View File

@ -15,6 +15,7 @@ import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
@ -69,6 +70,10 @@ class Ruleset {
var victories = LinkedHashMap<String, Victory>() var victories = LinkedHashMap<String, Victory>()
var cityStateTypes = LinkedHashMap<String, CityStateType>() var cityStateTypes = LinkedHashMap<String, CityStateType>()
val greatGeneralUnits by lazy {
units.values.filter { it.hasUnique(UniqueType.GreatPersonFromCombat, StateForConditionals.IgnoreConditionals) }
}
val mods = LinkedHashSet<String>() val mods = LinkedHashSet<String>()
var modOptions = ModOptions() var modOptions = ModOptions()

View File

@ -437,6 +437,7 @@ enum class UniqueType(
// XP // XP
FlatXPGain("[amount] XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global), FlatXPGain("[amount] XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global),
PercentageXPGain("[relativeAmount]% XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global), PercentageXPGain("[relativeAmount]% XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global),
GreatPersonFromCombat("Can be earned through combat", UniqueTarget.Unit),
GreatPersonEarnedFaster("[greatPerson] is earned [relativeAmount]% faster", UniqueTarget.Unit, UniqueTarget.Global), GreatPersonEarnedFaster("[greatPerson] is earned [relativeAmount]% faster", UniqueTarget.Unit, UniqueTarget.Global),
// Invisibility // Invisibility

View File

@ -211,10 +211,14 @@ class StatsOverviewTab(
add(greatPersonPointsPerTurn[greatPerson].toLabel()).right().row() add(greatPersonPointsPerTurn[greatPerson].toLabel()).right().row()
} }
val pointsForGreatGeneral = viewingPlayer.greatPeople.greatGeneralPoints val greatGeneralPoints = viewingPlayer.greatPeople.greatGeneralPointsCounter
val pointsForNextGreatGeneral = viewingPlayer.greatPeople.pointsForNextGreatGeneral val pointsForNextGreatGeneral = viewingPlayer.greatPeople.pointsForNextGreatGeneralCounter
add("Great General".toLabel()).left() for ((unit, points) in greatGeneralPoints) {
add("$pointsForGreatGeneral/$pointsForNextGreatGeneral".toLabel()) val pointsToGreatGeneral = pointsForNextGreatGeneral[unit]
add(unit.toLabel()).left()
add("$points/$pointsToGreatGeneral".toLabel())
}
pack() pack()
} }

View File

@ -216,8 +216,8 @@ class BattleTest {
Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit)) Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(defaultDefenderUnit))
// then // then
assertEquals(5, attackerCiv.greatPeople.greatGeneralPoints) assertEquals(5, attackerCiv.greatPeople.greatGeneralPointsCounter["Great General"])
assertEquals(4, defenderCiv.greatPeople.greatGeneralPoints) assertEquals(4, defenderCiv.greatPeople.greatGeneralPointsCounter["Great General"])
} }
@Test @Test
@ -230,8 +230,8 @@ class BattleTest {
Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(barbarianUnit)) Battle.attack(MapUnitCombatant(defaultAttackerUnit), MapUnitCombatant(barbarianUnit))
// then // then
assertEquals(0, attackerCiv.greatPeople.greatGeneralPoints) assertEquals(0, attackerCiv.greatPeople.greatGeneralPointsCounter["Great General"])
assertEquals(0, barbarianCiv.greatPeople.greatGeneralPoints) assertEquals(0, barbarianCiv.greatPeople.greatGeneralPointsCounter["Great General"])
} }
@Test @Test
@ -243,7 +243,7 @@ class BattleTest {
Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit)) Battle.attack(MapUnitCombatant(attackerUnit), MapUnitCombatant(defaultDefenderUnit))
// then // then
assertEquals(10, attackerCiv.greatPeople.greatGeneralPoints) assertEquals(10, attackerCiv.greatPeople.greatGeneralPointsCounter["Great General"])
} }
@Test @Test