Add "Unavailable" unique for all except beliefs, which are a mess right now

I think a lot of the 'hidden after' can be replaced by the Unavailable, but that's for later ;)
This commit is contained in:
Yair Morgenstern 2024-01-25 22:35:28 +02:00
parent ab7f23835e
commit c8bc15c800
10 changed files with 49 additions and 24 deletions

View File

@ -166,8 +166,9 @@ class PolicyManager : IsPartOfGameInfoSerialization {
if (policy.policyBranchType == PolicyBranchType.BranchComplete) return false
if (!getAdoptedPolicies().containsAll(policy.requires!!)) return false
if (checkEra && civInfo.gameInfo.ruleset.eras[policy.branch.era]!!.eraNumber > civInfo.getEraNumber()) return false
if (policy.uniqueObjects.filter { it.type == UniqueType.OnlyAvailable }
if (policy.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals)
.any { !it.conditionalsApply(civInfo) }) return false
if (policy.hasUnique(UniqueType.Unavailable)) return false
return true
}

View File

@ -31,18 +31,8 @@ class RuinsManager(
}
private fun getShuffledPossibleRewards(triggeringUnit: MapUnit): Iterable<RuinReward> {
val stateForOnlyAvailableWhen = StateForConditionals(civInfo, unit = triggeringUnit, tile = triggeringUnit.getTile())
val candidates =
validRewards.asSequence()
// Filter out what shouldn't be considered right now, before the random choice
.filterNot { possibleReward ->
possibleReward.name in lastChosenRewards
|| civInfo.gameInfo.difficulty in possibleReward.excludedDifficulties
|| possibleReward.hasUnique(UniqueType.HiddenWithoutReligion) && !civInfo.gameInfo.isReligionEnabled()
|| possibleReward.hasUnique(UniqueType.HiddenAfterGreatProphet) && civInfo.religionManager.greatProphetsEarned() > 0
|| possibleReward.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals)
.any { !it.conditionalsApply(stateForOnlyAvailableWhen) }
}
validRewards.asSequence().filter { isPossibleReward(it, triggeringUnit) }
// This might be a dirty way to do this, but it works (we do have randomWeighted in CollectionExtensions, but below we
// need to choose another when the first choice's TriggerActivations report failure, and that's simpler this way)
// For each possible reward, this feeds (reward.weight) copies of this reward to the overall Sequence to implement 'weight'.
@ -55,6 +45,18 @@ class RuinsManager(
return candidates
}
private fun isPossibleReward(ruinReward: RuinReward, unit: MapUnit): Boolean {
if (ruinReward.name in lastChosenRewards) return false
if (civInfo.gameInfo.difficulty in ruinReward.excludedDifficulties) return false
val stateForConditionals = StateForConditionals(civInfo, unit = unit, tile = unit.getTile())
if (ruinReward.hasUnique(UniqueType.HiddenWithoutReligion, stateForConditionals) && !civInfo.gameInfo.isReligionEnabled()) return false
if (ruinReward.hasUnique(UniqueType.HiddenAfterGreatProphet, stateForConditionals) && civInfo.religionManager.greatProphetsEarned() > 0) return false
if (ruinReward.hasUnique(UniqueType.Unavailable, stateForConditionals)) return false
if (ruinReward.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals)
.any { !it.conditionalsApply(stateForConditionals) }) return false
return true
}
fun selectNextRuinsReward(triggeringUnit: MapUnit) {
for (possibleReward in getShuffledPossibleRewards(triggeringUnit)) {
var atLeastOneUniqueHadEffect = false

View File

@ -161,8 +161,9 @@ class TechManager : IsPartOfGameInfoSerialization {
fun canBeResearched(techName: String): Boolean {
val tech = getRuleset().technologies[techName]!!
if (tech.uniqueObjects.any { it.type == UniqueType.OnlyAvailable && !it.conditionalsApply(civInfo) })
if (tech.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).any { !it.conditionalsApply(civInfo) })
return false
if (tech.hasUnique(UniqueType.Unavailable, StateForConditionals(civInfo))) return false
if (isResearched(tech.name) && !tech.isContinuallyResearchable())
return false

View File

@ -113,14 +113,18 @@ class UnitPromotions : IsPartOfGameInfoSerialization {
* Checks unit type, already acquired promotions, prerequisites and incompatibility uniques.
*/
fun getAvailablePromotions(): Sequence<Promotion> {
return unit.civ.gameInfo.ruleset.unitPromotions.values
.asSequence()
.filter { unit.type.name in it.unitTypes && it.name !in promotions }
.filter { it.prerequisites.isEmpty() || it.prerequisites.any { p->p in promotions } }
.filter { promotion -> promotion.uniqueObjects
.none { it.type == UniqueType.OnlyAvailable
&& !it.conditionalsApply(StateForConditionals(unit.civ, unit = unit)) }
}
return unit.civ.gameInfo.ruleset.unitPromotions.values.asSequence().filter { isAvailable(it) }
}
private fun isAvailable(promotion: Promotion):Boolean {
if (promotion.name in promotions) return false
if (unit.type.name !in promotion.unitTypes) return false
if (promotion.prerequisites.isNotEmpty() && promotion.prerequisites.none { it in promotions }) return false
val stateForConditionals = StateForConditionals(unit.civ, unit = unit)
if (promotion.hasUnique(UniqueType.Unavailable, stateForConditionals)) return false
if (promotion.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals)
.any { !it.conditionalsApply(stateForConditionals) }) return false
return true
}
fun clone(): UnitPromotions {

View File

@ -53,6 +53,9 @@ class TileInfoImprovementFunctions(val tile: Tile) {
else if (improvement.hasUnique(UniqueType.Unbuildable, stateForConditionals))
yield(ImprovementBuildingProblem.ConditionallyUnbuildable)
if (improvement.hasUnique(UniqueType.Unavailable, stateForConditionals))
yield(ImprovementBuildingProblem.ConditionallyUnbuildable)
if (tile.getOwner() != civInfo && !improvement.hasUnique(UniqueType.CanBuildOutsideBorders, stateForConditionals)) {
if (!improvement.hasUnique(UniqueType.CanBuildJustOutsideBorders, stateForConditionals))
yield(ImprovementBuildingProblem.OutsideBorders)

View File

@ -255,6 +255,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
if (!unique.conditionalsApply(civ, cityConstructions.city))
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
UniqueType.Unavailable ->
if (!unique.conditionalsApply(civ, cityConstructions.city))
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
UniqueType.RequiresPopulation ->
if (unique.params[0].toInt() > cityConstructions.city.population.population)
yield(RejectionReasonType.PopulationRequirement.toInstance(unique.text))

View File

@ -93,7 +93,9 @@ open class Policy : RulesetObject() {
fun isEnabledByPolicy(rulesetObject: IRulesetObject) =
rulesetObject.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).any { it.conditionals.any {
it.type == UniqueType.ConditionalAfterPolicyOrBelief && it.params[0] == name
} }
} } || rulesetObject.getMatchingUniques(UniqueType.Unavailable).any { it.conditionals.any {
it.type == UniqueType.ConditionalBeforePolicyOrBelief && it.params[0] == name
}}
val enabledBuildings = ruleset.buildings.values.filter { isEnabledByPolicy(it) }
val enabledUnits = ruleset.units.values.filter { isEnabledByPolicy(it) }
@ -110,6 +112,8 @@ open class Policy : RulesetObject() {
fun isDisabledByPolicy(rulesetObject: IRulesetObject) =
rulesetObject.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).any { it.conditionals.any {
it.type == UniqueType.ConditionalBeforePolicyOrBelief && it.params[0] == name
} } || rulesetObject.getMatchingUniques(UniqueType.Unavailable).any { it.conditionals.any {
it.type == UniqueType.ConditionalAfterPolicyOrBelief && it.params[0] == name
} }

View File

@ -54,8 +54,8 @@ interface IHasUniques : INamed {
// Currently an OnlyAvailableWhen can have multiple conditionals, implicitly a conjunction.
// Therefore, if any of its several conditionals is a ConditionalTech, then that tech is required.
.flatMap{ it.conditionals }
.filter{ it.isOfType(UniqueType.ConditionalTech) }
.map{ it.params[0] }
.filter{ it.type == UniqueType.ConditionalTech }
.map { it.params[0] }
}
fun legacyRequiredTechs(): Sequence<String> = sequenceOf()

View File

@ -263,8 +263,11 @@ enum class UniqueType(
MaxNumberBuildable("Limited to [amount] per Civilization", UniqueTarget.Building, UniqueTarget.Unit),
HiddenBeforeAmountPolicies("Hidden until [amount] social policy branches have been completed", UniqueTarget.Building, UniqueTarget.Unit),
// Meant to be used together with conditionals, like "Only available <after adopting [policy]> <while the empire is happy>"
/** A special unique, as it only activates when it has conditionals that *do not* apply */
OnlyAvailable("Only available", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins, UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief),
Unavailable("Unavailable", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins),
ConvertFoodToProductionWhenConstructed("Excess Food converted to Production when under construction", UniqueTarget.Building, UniqueTarget.Unit),
RequiresPopulation("Requires at least [amount] population", UniqueTarget.Building, UniqueTarget.Unit),

View File

@ -163,6 +163,9 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
if (!unique.conditionalsApply(civInfo, cityConstructions.city))
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
for (unique in getMatchingUniques(UniqueType.Unavailable, StateForConditionals(civInfo, cityConstructions.city)))
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
for (unique in getMatchingUniques(UniqueType.RequiresPopulation))
if (unique.params[0].toInt() > cityConstructions.city.population.population)
yield(RejectionReasonType.PopulationRequirement.toInstance(unique.text))