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 (policy.policyBranchType == PolicyBranchType.BranchComplete) return false
if (!getAdoptedPolicies().containsAll(policy.requires!!)) return false if (!getAdoptedPolicies().containsAll(policy.requires!!)) return false
if (checkEra && civInfo.gameInfo.ruleset.eras[policy.branch.era]!!.eraNumber > civInfo.getEraNumber()) 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 .any { !it.conditionalsApply(civInfo) }) return false
if (policy.hasUnique(UniqueType.Unavailable)) return false
return true return true
} }

View File

@ -31,18 +31,8 @@ class RuinsManager(
} }
private fun getShuffledPossibleRewards(triggeringUnit: MapUnit): Iterable<RuinReward> { private fun getShuffledPossibleRewards(triggeringUnit: MapUnit): Iterable<RuinReward> {
val stateForOnlyAvailableWhen = StateForConditionals(civInfo, unit = triggeringUnit, tile = triggeringUnit.getTile())
val candidates = val candidates =
validRewards.asSequence() validRewards.asSequence().filter { isPossibleReward(it, triggeringUnit) }
// 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) }
}
// This might be a dirty way to do this, but it works (we do have randomWeighted in CollectionExtensions, but below we // 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) // 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'. // 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 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) { fun selectNextRuinsReward(triggeringUnit: MapUnit) {
for (possibleReward in getShuffledPossibleRewards(triggeringUnit)) { for (possibleReward in getShuffledPossibleRewards(triggeringUnit)) {
var atLeastOneUniqueHadEffect = false var atLeastOneUniqueHadEffect = false

View File

@ -161,8 +161,9 @@ class TechManager : IsPartOfGameInfoSerialization {
fun canBeResearched(techName: String): Boolean { fun canBeResearched(techName: String): Boolean {
val tech = getRuleset().technologies[techName]!! 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 return false
if (tech.hasUnique(UniqueType.Unavailable, StateForConditionals(civInfo))) return false
if (isResearched(tech.name) && !tech.isContinuallyResearchable()) if (isResearched(tech.name) && !tech.isContinuallyResearchable())
return false return false

View File

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

View File

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

View File

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

View File

@ -93,7 +93,9 @@ open class Policy : RulesetObject() {
fun isEnabledByPolicy(rulesetObject: IRulesetObject) = fun isEnabledByPolicy(rulesetObject: IRulesetObject) =
rulesetObject.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).any { it.conditionals.any { rulesetObject.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).any { it.conditionals.any {
it.type == UniqueType.ConditionalAfterPolicyOrBelief && it.params[0] == name 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 enabledBuildings = ruleset.buildings.values.filter { isEnabledByPolicy(it) }
val enabledUnits = ruleset.units.values.filter { isEnabledByPolicy(it) } val enabledUnits = ruleset.units.values.filter { isEnabledByPolicy(it) }
@ -110,6 +112,8 @@ open class Policy : RulesetObject() {
fun isDisabledByPolicy(rulesetObject: IRulesetObject) = fun isDisabledByPolicy(rulesetObject: IRulesetObject) =
rulesetObject.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).any { it.conditionals.any { rulesetObject.getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).any { it.conditionals.any {
it.type == UniqueType.ConditionalBeforePolicyOrBelief && it.params[0] == name 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. // 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. // Therefore, if any of its several conditionals is a ConditionalTech, then that tech is required.
.flatMap{ it.conditionals } .flatMap{ it.conditionals }
.filter{ it.isOfType(UniqueType.ConditionalTech) } .filter{ it.type == UniqueType.ConditionalTech }
.map{ it.params[0] } .map { it.params[0] }
} }
fun legacyRequiredTechs(): Sequence<String> = sequenceOf() fun legacyRequiredTechs(): Sequence<String> = sequenceOf()

View File

@ -263,8 +263,11 @@ enum class UniqueType(
MaxNumberBuildable("Limited to [amount] per Civilization", UniqueTarget.Building, UniqueTarget.Unit), MaxNumberBuildable("Limited to [amount] per Civilization", UniqueTarget.Building, UniqueTarget.Unit),
HiddenBeforeAmountPolicies("Hidden until [amount] social policy branches have been completed", 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>" // 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, OnlyAvailable("Only available", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins, UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief), 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), ConvertFoodToProductionWhenConstructed("Excess Food converted to Production when under construction", UniqueTarget.Building, UniqueTarget.Unit),
RequiresPopulation("Requires at least [amount] population", 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)) if (!unique.conditionalsApply(civInfo, cityConstructions.city))
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance()) yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
for (unique in getMatchingUniques(UniqueType.Unavailable, StateForConditionals(civInfo, cityConstructions.city)))
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
for (unique in getMatchingUniques(UniqueType.RequiresPopulation)) for (unique in getMatchingUniques(UniqueType.RequiresPopulation))
if (unique.params[0].toInt() > cityConstructions.city.population.population) if (unique.params[0].toInt() > cityConstructions.city.population.population)
yield(RejectionReasonType.PopulationRequirement.toInstance(unique.text)) yield(RejectionReasonType.PopulationRequirement.toInstance(unique.text))