From 4ffb21f5259480f17b5e92e7dfcb3a1bd142d25b Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Sun, 13 Mar 2022 21:49:54 +0100 Subject: [PATCH] Support more freely modded Worker-like units (#6339) * TileImprovementTime UniqueType supports UniqueTarget.Unit * Reduce UniqueType.ConstructImprovementConsumingUnit hardcoded Great People behaviour * Some linting Co-authored-by: Yair Morgenstern --- .../logic/automation/WorkerAutomation.kt | 6 +- core/src/com/unciv/logic/map/TileInfo.kt | 13 +- .../models/ruleset/tile/TileImprovement.kt | 18 +-- .../unciv/models/ruleset/unique/UniqueType.kt | 122 +++++++++--------- .../pickerscreens/ImprovementPickerScreen.kt | 38 +++--- .../unciv/ui/worldscreen/unit/UnitActions.kt | 19 ++- 6 files changed, 113 insertions(+), 103 deletions(-) diff --git a/core/src/com/unciv/logic/automation/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/WorkerAutomation.kt index 80a91cf933..bf19892abd 100644 --- a/core/src/com/unciv/logic/automation/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/WorkerAutomation.kt @@ -146,7 +146,7 @@ class WorkerAutomation( && tileCanBeImproved(unit, currentTile)) { if (WorkerAutomationConst.consoleOutput) println("WorkerAutomation: ${unit.label()} -> start improving $currentTile") - return currentTile.startWorkingOnImprovement(chooseImprovement(unit, currentTile)!!, civInfo) + return currentTile.startWorkingOnImprovement(chooseImprovement(unit, currentTile)!!, civInfo, unit) } if (currentTile.improvementInProgress != null) return // we're working! @@ -206,7 +206,7 @@ class WorkerAutomation( BFS(toConnectTile, isCandidateTilePredicate).apply { maxSize = HexMath.getNumberOfTilesInHexagon( WorkerAutomationConst.maxBfsReachPadding + - tilesOfConnectedCities.map { it.aerialDistanceTo(toConnectTile) }.minOrNull()!! + tilesOfConnectedCities.minOf { it.aerialDistanceTo(toConnectTile) } ) bfsCache[toConnectTile.position] = this@apply } @@ -234,7 +234,7 @@ class WorkerAutomation( if (unit.currentMovement > 0 && currentTile == tileToConstructRoadOn && currentTile.improvementInProgress != bestRoadAvailable.name) { val improvement = bestRoadAvailable.improvement(ruleSet)!! - tileToConstructRoadOn.startWorkingOnImprovement(improvement, civInfo) + tileToConstructRoadOn.startWorkingOnImprovement(improvement, civInfo, unit) } if (WorkerAutomationConst.consoleOutput) println("WorkerAutomation: ${unit.label()} -> connect city ${bfs.startingPoint.getCity()?.name} to ${cityTile.getCity()!!.name} on $tileToConstructRoadOn") diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 01534f853e..292d03ffa1 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -193,12 +193,12 @@ open class TileInfo { // We have to .toList() so that the values are stored together once for caching, // and the toSequence so that aggregations (like neighbors.flatMap{it.units} don't take up their own space - /** Returns the left shared neighbor of [this] and [neighbor] (relative to the view direction [this]->[neighbor]), or null if there is no such tile. */ + /** Returns the left shared neighbor of `this` and [neighbor] (relative to the view direction `this`->[neighbor]), or null if there is no such tile. */ fun getLeftSharedNeighbor(neighbor: TileInfo): TileInfo? { return tileMap.getClockPositionNeighborTile(this,(tileMap.getNeighborTileClockPosition(this, neighbor) - 2) % 12) } - /** Returns the right shared neighbor [this] and [neighbor] (relative to the view direction [this]->[neighbor]), or null if there is no such tile. */ + /** Returns the right shared neighbor of `this` and [neighbor] (relative to the view direction `this`->[neighbor]), or null if there is no such tile. */ fun getRightSharedNeighbor(neighbor: TileInfo): TileInfo? { return tileMap.getClockPositionNeighborTile(this,(tileMap.getNeighborTileClockPosition(this, neighbor) + 2) % 12) } @@ -262,8 +262,8 @@ open class TileInfo { fun getTileStats(city: CityInfo?, observingCiv: CivilizationInfo?): Stats { var stats = getBaseTerrain().cloneStats() - - val stateForConditionals = StateForConditionals(civInfo = observingCiv, cityInfo = city, tile = this); + + val stateForConditionals = StateForConditionals(civInfo = observingCiv, cityInfo = city, tile = this) for (terrainFeatureBase in terrainFeatureObjects) { when { @@ -897,9 +897,10 @@ open class TileInfo { } } - fun startWorkingOnImprovement(improvement: TileImprovement, civInfo: CivilizationInfo) { + fun startWorkingOnImprovement(improvement: TileImprovement, civInfo: CivilizationInfo, unit: MapUnit) { improvementInProgress = improvement.name - turnsToImprovement = if (civInfo.gameInfo.gameParameters.godMode) 1 else improvement.getTurnsToBuild(civInfo) + turnsToImprovement = if (civInfo.gameInfo.gameParameters.godMode) 1 + else improvement.getTurnsToBuild(civInfo, unit) } fun stopWorkingOnImprovement() { diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index 7905c50737..db5ab75d30 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -2,10 +2,12 @@ package com.unciv.models.ruleset.tile import com.unciv.UncivGame import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.map.MapUnit import com.unciv.logic.map.RoadStatus import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetStatsObject +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr @@ -24,14 +26,14 @@ class TileImprovement : RulesetStatsObject() { val turnsToBuild: Int = 0 // This is the base cost. - fun getTurnsToBuild(civInfo: CivilizationInfo): Int { - var realTurnsToBuild = turnsToBuild.toFloat() * civInfo.gameInfo.gameParameters.gameSpeed.modifier - for (unique in civInfo.getMatchingUniques(UniqueType.TileImprovementTime)) { - realTurnsToBuild *= unique.params[0].toPercent() - } + fun getTurnsToBuild(civInfo: CivilizationInfo, unit: MapUnit?): Int { + val state = StateForConditionals(civInfo, unit = unit) + val uniques = civInfo.getMatchingUniques(UniqueType.TileImprovementTime, state) + + (unit?.getMatchingUniques(UniqueType.TileImprovementTime, state) ?: sequenceOf()) + return uniques.fold(turnsToBuild.toFloat() * civInfo.gameInfo.gameParameters.gameSpeed.modifier) { + it, unique -> it * unique.params[0].toPercent() + }.roundToInt().coerceAtLeast(1) // In some weird cases it was possible for something to take 0 turns, leading to it instead never finishing - if (realTurnsToBuild < 1) realTurnsToBuild = 1f - return realTurnsToBuild.roundToInt() } fun getDescription(ruleset: Ruleset): String { @@ -74,7 +76,7 @@ class TileImprovement : RulesetStatsObject() { * a terrain feature, thus the unique name. */ fun isAllowedOnFeature(name: String) = getMatchingUniques(UniqueType.NoFeatureRemovalNeeded).any { it.params[0] == name } - + fun matchesFilter(filter: String): Boolean { return when (filter) { name -> true diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 1e1cbdc8e0..65508c4d61 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -79,7 +79,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: StatsPerPopulation("[stats] per [amount] population [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), // ToDo: Reword to `[stats] ` for consistency with other conditionals StatsFromXPopulation("[stats] in cities with [amount] or more population", UniqueTarget.Global, UniqueTarget.FollowerBelief), - + StatsFromCitiesOnSpecificTiles("[stats] in cities on [terrainFilter] tiles", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatsFromBuildings("[stats] from all [buildingFilter] buildings", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatsSpendingGreatPeople("[stats] whenever a Great Person is expended", UniqueTarget.Global), @@ -98,7 +98,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: StatPercentFromReligionFollowers("[amount]% [stat] from every follower, up to [amount]%", UniqueTarget.FollowerBelief), BonusStatsFromCityStates("[amount]% [stat] from City-States", UniqueTarget.Global), GoldBonusFromTradeRouts("Gold from all trade routes +25%", UniqueTarget.Global), - + NullifiesStat("Nullifies [stat] [cityFilter]", UniqueTarget.Global), NullifiesGrowth("Nullifies Growth [cityFilter]", UniqueTarget.Global), @@ -123,7 +123,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: CityStateUniqueLuxury("Provides a unique luxury", UniqueTarget.CityState), // No conditional support as of yet CityStateGiftedUnitsStartWithXp("Military Units gifted from City-States start with [amount] XP", UniqueTarget.Global), CityStateMoreGiftedUnits("Militaristic City-States grant units [amount] times as fast when you are at war with a common nation", UniqueTarget.Global), - + CityStateGoldGiftsProvideMoreInfluence("Gifts of Gold to City-States generate [amount]% more Influence", UniqueTarget.Global), CityStateCanBeBoughtForGold("Can spend Gold to annex or puppet a City-State that has been your ally for [amount] turns.", UniqueTarget.Global), CityStateTerritoryAlwaysFriendly("City-State territory always counts as friendly territory", UniqueTarget.Global), @@ -132,14 +132,14 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: CityStateDeprecated("Will not be chosen for new games", UniqueTarget.Nation), // implemented for CS only for now CityStateInfluenceDegradation("[amount]% City-State Influence degradation", UniqueTarget.Global), CityStateRestingPoint("Resting point for Influence with City-States is increased by [amount]", UniqueTarget.Global), - + CityStateStatPercent("Allied City-States provide [stat] equal to [amount]% of what they produce for themselves", UniqueTarget.Global), CityStateResources("[amount]% resources gifted by City-States", UniqueTarget.Global), CityStateLuxuryHappiness("[amount]% Happiness from luxury resources gifted by City-States", UniqueTarget.Global), CityStateInfluenceRecoversTwiceNormalRate("City-State Influence recovers at twice the normal rate", UniqueTarget.Global), // endregion - + /////// region Other global uniques FreeUnits("[amount] units cost no maintenance", UniqueTarget.Global), @@ -173,15 +173,15 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: BuyBuildingsForAmountStat("May buy [buildingFilter] buildings for [amount] [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), BuyUnitsWithStat("May buy [baseUnitFilter] units with [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), BuyBuildingsWithStat("May buy [buildingFilter] buildings with [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), - + BuyUnitsByProductionCost("May buy [baseUnitFilter] units with [stat] for [amount] times their normal Production cost", UniqueTarget.FollowerBelief, UniqueTarget.Global), BuyBuildingsByProductionCost("May buy [buildingFilter] buildings with [stat] for [amount] times their normal Production cost", UniqueTarget.FollowerBelief, UniqueTarget.Global), - + // ToDo: Unify EnablesGoldProduction("Enables conversion of city production to gold", UniqueTarget.Global), EnablesScienceProduction("Enables conversion of city production to science", UniqueTarget.Global), - + BuyItemsDiscount("[stat] cost of purchasing items in cities [amount]%", UniqueTarget.Global, UniqueTarget.FollowerBelief), BuyBuildingsDiscount("[stat] cost of purchasing [buildingFilter] buildings [amount]%", UniqueTarget.Global, UniqueTarget.FollowerBelief), BuyUnitsDiscount("[stat] cost of purchasing [baseUnitFilter] units [amount]%", UniqueTarget.Global, UniqueTarget.FollowerBelief), @@ -189,10 +189,10 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: // Should be replaced with moddable improvements when roads become moddable RoadMovementSpeed("Improves movement speed on roads",UniqueTarget.Global), RoadsConnectAcrossRivers("Roads connect tiles across rivers", UniqueTarget.Global), - RoadMaintenance("[amount]% maintenance on road & railroads", UniqueTarget.Global), + RoadMaintenance("[amount]% maintenance on road & railroads", UniqueTarget.Global), BuildingMaintenance("[amount]% maintenance cost for buildings [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), - + // This should probably support conditionals, e.g. MayanGainGreatPerson("Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once.", UniqueTarget.Global), MayanCalendarDisplay("Once The Long Count activates, the year on the world screen displays as the traditional Mayan Long Count.", UniqueTarget.Global), @@ -202,17 +202,17 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: LessPolicyCostFromCities("Each city founded increases culture cost of policies [amount]% less than normal", UniqueTarget.Global), LessPolicyCost("[amount]% Culture cost of adopting new Policies", UniqueTarget.Global), - + StrategicResourcesIncrease("Quantity of strategic resources produced by the empire +[amount]%", UniqueTarget.Global), // used in Policy DoubleResourceProduced("Double quantity of [resource] produced", UniqueTarget.Global), // Todo: should probably be changed to "[stats] from every known Natural Wonder", and that'll give us the global unique as well DoubleHappinessFromNaturalWonders("Double Happiness from Natural Wonders", UniqueTarget.Global), - + EnablesConstructionOfSpaceshipParts("Enables construction of Spaceship parts", UniqueTarget.Global), EnemyLandUnitsSpendExtraMovement("Enemy land units must spend 1 extra movement point when inside your territory (obsolete upon Dynamite)", UniqueTarget.Global), - + ProductionToScienceConversionBonus("Production to science conversion in cities increased by 33%", UniqueTarget.Global), - + // Misc national uniques NotifiedOfBarbarianEncampments("Notified of new Barbarian encampments", UniqueTarget.Global), BorrowsCityNames("\"Borrows\" city names from other civilizations in the game", UniqueTarget.Global), @@ -236,24 +236,25 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: // Todo can be replaced with a conditional StrengthWithinTilesOfTile("+[amount]% Strength if within [amount] tiles of a [tileFilter]", UniqueTarget.Global), StatBonusPercentFromCityStates("[amount]% [stat] from City-States", UniqueTarget.Global), - + ProvidesGoldWheneverGreatPersonExpended("Provides a sum of gold each time you spend a Great Person", UniqueTarget.Global), ProvidesStatsWheneverGreatPersonExpended("[stats] whenever a Great Person is expended", UniqueTarget.Global), - + // Acts as a trigger - this should be generalized somehow but the current setup does not allow this // It would currently mean cycling through EVERY unique type to find ones with a specific conditional... + @Suppress("SpellCheckingInspection") // Not worth fixing RecieveFreeUnitWhenDiscoveringTech("Receive free [baseUnitFilter] when you discover [tech]", UniqueTarget.Global), - + EnablesOpenBorders("Enables Open Borders agreements", UniqueTarget.Global), // Should the 'R' in 'Research agreements' be capitalized? EnablesResearchAgreements("Enables Research agreements", UniqueTarget.Global), ScienceFromResearchAgreements("Science gained from research agreements [amount]%", UniqueTarget.Global), TriggersVictory("Triggers victory", UniqueTarget.Global), TriggersCulturalVictory("Triggers a Cultural Victory upon completion", UniqueTarget.Global), - + BetterDefensiveBuildings("[amount]% City Strength from defensive buildings", UniqueTarget.Global), - - TileImprovementTime("[amount]% tile improvement construction time", UniqueTarget.Global), + + TileImprovementTime("[amount]% tile improvement construction time", UniqueTarget.Global, UniqueTarget.Unit), PercentGoldFromTradeMissions("[amount]% Gold from Great Merchant trade missions", UniqueTarget.Global), // Todo: Lowercase the 'U' of 'Units' in this unique CityHealingUnits("[mapUnitFilter] Units adjacent to this city heal [amount] HP per turn when healing", UniqueTarget.Global, UniqueTarget.FollowerBelief), @@ -274,7 +275,6 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: EmbarkAndEnterOcean("Can embark and move over Coasts and Oceans immediately", UniqueTarget.Global), PopulationLossFromNukes("Population loss from nuclear attacks [amount]% [cityFilter]", UniqueTarget.Global), - NaturalReligionSpreadStrength("[amount]% Natural religion spread [cityFilter]", UniqueTarget.FollowerBelief, UniqueTarget.Global), ReligionSpreadDistance("Religion naturally spreads to cities [amount] tiles away", UniqueTarget.Global, UniqueTarget.FollowerBelief), @@ -288,14 +288,14 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ResearchableMultipleTimes("Can be continually researched", UniqueTarget.Global), BaseUnitSupply("[amount] Unit Supply", UniqueTarget.Global), - UnitSupplyPerPop("[amount] Unit Supply per [amount] population [cityFilter]", UniqueTarget.Global), + UnitSupplyPerPop("[amount] Unit Supply per [amount] population [cityFilter]", UniqueTarget.Global), UnitSupplyPerCity("[amount] Unit Supply per city", UniqueTarget.Global), UnitsInCitiesNoMaintenance("Units in cities cost no Maintenance", UniqueTarget.Global), - + SpawnRebels("Rebel units may spawn", UniqueTarget.Global), - + //endregion - + //endregion Global uniques ///////////////////////////////////////// region CONSTRUCTION UNIQUES ///////////////////////////////////////// @@ -328,20 +328,20 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: " OR \"Only available \"" + " OR \"Only available ")) Requires("Requires [buildingName/tech/era/policy]", 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), TriggersAlertOnStart("Triggers a global alert upon build start", UniqueTarget.Building, UniqueTarget.Unit), TriggersAlertOnCompletion("Triggers a global alert upon completion", UniqueTarget.Building, UniqueTarget.Unit), //endregion - - + + ///////////////////////////////////////// region BUILDING UNIQUES ///////////////////////////////////////// CostIncreasesPerCity("Cost increases by [amount] per owned city", UniqueTarget.Building), - + @Deprecated("as of 3.19.9", ReplaceWith("Only available ")) CannotBeBuiltWith("Cannot be built with [buildingName]", UniqueTarget.Building), @Deprecated("as of 3.19.9", ReplaceWith("Only available ")) @@ -371,24 +371,24 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ObsoleteWith("Obsolete with [tech]", UniqueTarget.Building, UniqueTarget.Resource, UniqueTarget.Improvement), IndicatesCapital("Indicates the capital city", UniqueTarget.Building), ProvidesExtraLuxuryFromCityResources("Provides 1 extra copy of each improved luxury resource near this City", UniqueTarget.Building), - + DestroyedWhenCityCaptured("Destroyed when the city is captured", UniqueTarget.Building), NotDestroyedWhenCityCaptured("Never destroyed when the city is captured", UniqueTarget.Building), DoublesGoldFromCapturingCity("Doubles Gold given to enemy if city is captured", UniqueTarget.Building), - + RemoveAnnexUnhappiness("Remove extra unhappiness from annexed cities", UniqueTarget.Building), //endregion - + ///////////////////////////////////////// region UNIT UNIQUES ///////////////////////////////////////// FoundCity("Founds a new city", UniqueTarget.Unit), ConstructImprovementConsumingUnit("Can construct [improvementName]", UniqueTarget.Unit), BuildImprovements("Can build [improvementFilter/terrainFilter] improvements on tiles", UniqueTarget.Unit), CreateWaterImprovements("May create improvements on water resources", UniqueTarget.Unit), - + Strength("[amount]% Strength", UniqueTarget.Unit, UniqueTarget.Global), StrengthNearCapital("[amount]% Strength decreasing with distance from the capital", UniqueTarget.Unit, UniqueTarget.Global), FlankAttackBonus("[amount]% to Flank Attack bonuses", UniqueTarget.Unit, UniqueTarget.Global), @@ -400,10 +400,10 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: Range("[amount] Range", UniqueTarget.Unit, UniqueTarget.Global), Heal("[amount] HP when healing", UniqueTarget.Unit, UniqueTarget.Global), SpreadReligionStrength("[amount]% Spread Religion Strength", UniqueTarget.Unit, UniqueTarget.Global), - + MayFoundReligion("May found a religion", UniqueTarget.Unit), MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit), - + CanOnlyAttackUnits("Can only attack [combatantFilter] units", UniqueTarget.Unit), CanOnlyAttackTiles("Can only attack [tileFilter] tiles", UniqueTarget.Unit), CannotAttack("Cannot attack", UniqueTarget.Unit), @@ -411,17 +411,17 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: SelfDestructs("Self-destructs when attacking", UniqueTarget.Unit), BlastRadius("Blast radius [amount]", UniqueTarget.Unit), IndirectFire("Ranged attacks may be performed over obstacles", UniqueTarget.Unit), - + NoDefensiveTerrainBonus("No defensive terrain bonus", UniqueTarget.Unit, UniqueTarget.Global), NoDefensiveTerrainPenalty("No defensive terrain penalty", UniqueTarget.Unit, UniqueTarget.Global), Uncapturable("Uncapturable", UniqueTarget.Unit), MayWithdraw("May withdraw before melee ([amount]%)", UniqueTarget.Unit), CannotCaptureCities("Unable to capture cities", UniqueTarget.Unit), - + NoMovementToPillage("No movement cost to pillage", UniqueTarget.Unit, UniqueTarget.Global), CanMoveAfterAttacking("Can move after attacking", UniqueTarget.Unit), MoveImmediatelyOnceBought("Can move immediately once bought", UniqueTarget.Unit), - + HealsOutsideFriendlyTerritory("May heal outside of friendly territory", UniqueTarget.Unit, UniqueTarget.Global), HealingEffectsDoubled("All healing effects doubled", UniqueTarget.Unit, UniqueTarget.Global), HealsAfterKilling("Heals [amount] damage if it kills a unit", UniqueTarget.Unit, UniqueTarget.Global), @@ -437,7 +437,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: AttackAcrossCoast("Eliminates combat penalty for attacking across a coast", UniqueTarget.Unit), SixTilesAlwaysVisible("6 tiles in every direction always visible", UniqueTarget.Unit), - + CarryAirUnits("Can carry [amount] [mapUnitFilter] units", UniqueTarget.Unit), CarryExtraAirUnits("Can carry [amount] extra [mapUnitFilter] units", UniqueTarget.Unit), CannotBeCarriedBy("Cannot be carried by [mapUnitFilter] units", UniqueTarget.Unit), @@ -451,16 +451,16 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: KillUnitPlunder("Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [plunderableStat]", UniqueTarget.Unit, UniqueTarget.Global), KillUnitPlunderNearCity("Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [plunderableStat] when killed within 4 tiles of a city following this religion", UniqueTarget.FollowerBelief), KillUnitCapture("May capture killed [mapUnitFilter] units", UniqueTarget.Unit), - + FlatXPGain("[amount] XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global), PercentageXPGain("[amount]% XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global), Invisible("Invisible to others", UniqueTarget.Unit), InvisibleToNonAdjacent("Invisible to non-adjacent units", UniqueTarget.Unit), CanSeeInvisibleUnits("Can see invisible [mapUnitFilter] units", UniqueTarget.Unit), - + RuinsUpgrade("May upgrade to [baseUnitFilter] through ruins-like effects", UniqueTarget.Unit), - + // The following block gets cached in MapUnit for faster getMovementCostBetweenAdjacentTiles DoubleMovementOnTerrain("Double movement in [terrainFilter]", UniqueTarget.Unit), AllTilesCost1Move("All tiles cost 1 movement", UniqueTarget.Unit), @@ -474,12 +474,12 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: CanEnterForeignTilesButLosesReligiousStrength("May enter foreign tiles without open borders, but loses [amount] religious strength each turn it ends there", UniqueTarget.Unit), CannotBeBarbarian("Never appears as a Barbarian unit", UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers), - + ReligiousUnit("Religious Unit", UniqueTarget.Unit), - SpaceshipPart("Spaceship part", UniqueTarget.Unit, UniqueTarget.Building), // Usage for buildings is deprecated + SpaceshipPart("Spaceship part", UniqueTarget.Unit, UniqueTarget.Building), // Usage for buildings is deprecated AddInCapital("Can be added to [comment] in the Capital", UniqueTarget.Unit), - + //endregion ///////////////////////////////////////// region TILE UNIQUES ///////////////////////////////////////// @@ -495,7 +495,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: // The "Except [terrainFilter]" could theoretically be implemented with a conditional NaturalWonderConvertNeighborsExcept("Neighboring tiles except [baseTerrain] will convert to [baseTerrain]", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers), GrantsGoldToFirstToDiscover("Grants 500 Gold to the first civilization to discover it", UniqueTarget.Terrain), - + // General terrain DamagesContainingUnits("Units ending their turn on this terrain take [amount] damage", UniqueTarget.Terrain), TerrainGrantsPromotion("Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game", UniqueTarget.Terrain), @@ -505,10 +505,10 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: TileProvidesYieldWithoutPopulation("Tile provides yield without assigned population", UniqueTarget.Terrain, UniqueTarget.Improvement), NullifyYields("Nullifies all other stats this tile provides", UniqueTarget.Terrain), RestrictedBuildableImprovements("Only [improvementFilter] improvements may be built on this tile", UniqueTarget.Terrain), - + BlocksLineOfSightAtSameElevation("Blocks line-of-sight from tiles at same elevation", UniqueTarget.Terrain), VisibilityElevation("Has an elevation of [amount] for visibility calculations", UniqueTarget.Terrain), - + OverrideFertility("Always Fertility [amount] for Map Generation", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers), AddFertility("[amount] to Fertility for Map Generation", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers), @@ -530,21 +530,21 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ResourceFrequency("Generated on every [amount] tiles", UniqueTarget.Resource, flags = UniqueFlag.setOfHiddenToUsers), StrategicBalanceResource("Guaranteed with Strategic Balance resource option", UniqueTarget.Resource), - + NoNaturalGeneration("Doesn't generate naturally", UniqueTarget.Terrain, UniqueTarget.Resource, flags = UniqueFlag.setOfHiddenToUsers), TileGenerationConditions("Occurs at temperature between [amount] and [amount] and humidity between [amount] and [amount]", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers), OccursInChains("Occurs in chains at high elevations", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers), OccursInGroups("Occurs in groups around high elevations", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers), MajorStrategicFrequency("Every [amount] tiles with this terrain will receive a major deposit of a strategic resource.", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers), - + RareFeature("Rare feature", UniqueTarget.Terrain), - + ResistsNukes("Resistant to nukes", UniqueTarget.Terrain), DestroyableByNukes("Can be destroyed by nukes", UniqueTarget.Terrain), - + FreshWater("Fresh water", UniqueTarget.Terrain), RoughTerrain("Rough terrain", UniqueTarget.Terrain), - + /////// Resource uniques ResourceAmountOnTiles("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource), CityStateOnlyResource("Can only be created by Mercantile City-States", UniqueTarget.Resource), @@ -560,14 +560,14 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: CanOnlyBeBuiltOnTile("Can only be built on [tileFilter] tiles", UniqueTarget.Improvement), CannotBuildOnTile("Cannot be built on [tileFilter] tiles", UniqueTarget.Improvement), NoFeatureRemovalNeeded("Does not need removal of [tileFilter]", UniqueTarget.Improvement), - + DefensiveBonus("Gives a defensive bonus of [amount]%", UniqueTarget.Improvement), ImprovementMaintenance("Costs [amount] gold per turn when in your territory", UniqueTarget.Improvement), // Unused DamagesAdjacentEnemyUnits("Adjacent enemy units ending their turn take [amount] damage", UniqueTarget.Improvement), - + GreatImprovement("Great Improvement", UniqueTarget.Improvement), IsAncientRuinsEquivalent("Provides a random bonus when entered", UniqueTarget.Improvement), - + Unpillagable("Unpillagable", UniqueTarget.Improvement), Indestructible("Indestructible", UniqueTarget.Improvement), Irremovable("Irremovable", UniqueTarget.Improvement), @@ -575,7 +575,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ///////////////////////////////////////// region CONDITIONALS ///////////////////////////////////////// - + /////// civ conditionals ConditionalWar("when at war", UniqueTarget.Conditional), ConditionalNotWar("when not at war", UniqueTarget.Conditional), @@ -585,7 +585,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ConditionalHappy("while the empire is happy", UniqueTarget.Conditional), ConditionalBetweenHappiness("when between [amount] and [amount] Happiness", UniqueTarget.Conditional), ConditionalBelowHappiness("when below [amount] Happiness", UniqueTarget.Conditional), - + ConditionalDuringEra("during the [era]", UniqueTarget.Conditional), ConditionalBeforeEra("before the [era]", UniqueTarget.Conditional), ConditionalStartingFromEra("starting from the [era]", UniqueTarget.Conditional), @@ -639,7 +639,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ///////////////////////////////////////// region TRIGGERED ONE-TIME ///////////////////////////////////////// - + OneTimeFreeUnit("Free [baseUnitFilter] appears", UniqueTarget.Triggerable), // used in Policies, Buildings OneTimeAmountFreeUnits("[amount] free [baseUnitFilter] units appear", UniqueTarget.Triggerable), // used in Buildings OneTimeFreeUnitRuins("Free [baseUnitFilter] found in the ruins", UniqueTarget.Ruins), // Differs from "Free [] appears" in that it spawns near the ruins instead of in a city @@ -686,8 +686,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: HiddenAfterGreatProphet("Hidden after generating a Great Prophet", UniqueTarget.Ruins), HiddenWithoutVictoryType("Hidden when [victoryType] Victory is disabled", UniqueTarget.Building, UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers), HiddenFromCivilopedia("Will not be displayed in Civilopedia", *UniqueTarget.values(), flags = UniqueFlag.setOfHiddenToUsers), - - // endregion + + // endregion // region DEPRECATED AND REMOVED diff --git a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt index 211cd616bc..b0ba13d9aa 100644 --- a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt @@ -16,7 +16,11 @@ import com.unciv.ui.utils.* import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import kotlin.math.roundToInt -class ImprovementPickerScreen(val tileInfo: TileInfo, unit: MapUnit, val onAccept: ()->Unit) : PickerScreen() { +class ImprovementPickerScreen( + private val tileInfo: TileInfo, + private val unit: MapUnit, + private val onAccept: ()->Unit +) : PickerScreen() { private var selectedImprovement: TileImprovement? = null private val gameInfo = tileInfo.tileMap.gameInfo private val ruleSet = gameInfo.ruleSet @@ -32,8 +36,8 @@ class ImprovementPickerScreen(val tileInfo: TileInfo, unit: MapUnit, val onAccep // no onAccept() - Worker can stay selected } else { if (improvement.name != tileInfo.improvementInProgress) - tileInfo.startWorkingOnImprovement(improvement, currentPlayerCiv) - if (tileInfo.civilianUnit != null) tileInfo.civilianUnit!!.action = null // this is to "wake up" the worker if it's sleeping + tileInfo.startWorkingOnImprovement(improvement, currentPlayerCiv, unit) + unit.action = null // this is to "wake up" the worker if it's sleeping onAccept() } game.setWorldScreen() @@ -51,26 +55,23 @@ class ImprovementPickerScreen(val tileInfo: TileInfo, unit: MapUnit, val onAccep val regularImprovements = Table() regularImprovements.defaults().pad(5f) - + // clone tileInfo without "top" feature if it could be removed // Keep this copy around for speed - val tileInfoNoLast:TileInfo = tileInfo.clone() - if (ruleSet.tileImprovements.any { it.key == Constants.remove + tileInfoNoLast.getLastTerrain().name }) { + val tileInfoNoLast: TileInfo = tileInfo.clone() + if (Constants.remove + tileInfoNoLast.getLastTerrain().name in ruleSet.tileImprovements) { tileInfoNoLast.removeTerrainFeature(tileInfoNoLast.getLastTerrain().name) } for (improvement in ruleSet.tileImprovements.values) { - var suggestRemoval:Boolean = false + var suggestRemoval = false // canBuildImprovement() would allow e.g. great improvements thus we need to exclude them - except cancel if (improvement.turnsToBuild == 0 && improvement.name != Constants.cancelImprovementOrder) continue if (improvement.name == tileInfo.improvement) continue // also checked by canImprovementBeBuiltHere, but after more expensive tests if (!tileInfo.canBuildImprovement(improvement, currentPlayerCiv)) { // if there is an improvement that could remove that terrain - if (tileInfoNoLast.canBuildImprovement(improvement, currentPlayerCiv)) { - suggestRemoval = true - } else { - continue - } + if (!tileInfoNoLast.canBuildImprovement(improvement, currentPlayerCiv)) continue + suggestRemoval = true } if (!unit.canBuildImprovement(improvement)) continue @@ -94,7 +95,7 @@ class ImprovementPickerScreen(val tileInfo: TileInfo, unit: MapUnit, val onAccep var labelText = improvement.name.tr() val turnsToBuild = if (tileInfo.improvementInProgress == improvement.name) tileInfo.turnsToImprovement - else improvement.getTurnsToBuild(currentPlayerCiv) + else improvement.getTurnsToBuild(currentPlayerCiv, unit) if (turnsToBuild > 0) labelText += " - $turnsToBuild${Fonts.turn}" val provideResource = tileInfo.hasViewableResource(currentPlayerCiv) && tileInfo.tileResource.improvement == improvement.name if (provideResource) labelText += "\n" + "Provides [${tileInfo.resource}]".tr() @@ -104,12 +105,11 @@ class ImprovementPickerScreen(val tileInfo: TileInfo, unit: MapUnit, val onAccep && improvement.name != Constants.cancelImprovementOrder) if (tileInfo.improvement != null && removeImprovement) labelText += "\n" + "Replaces [${tileInfo.improvement}]".tr() - val pickNow = if (suggestRemoval) - (Constants.remove + "[" + tileInfo.getLastTerrain().name + "] first").toLabel() - else if (tileInfo.improvementInProgress != improvement.name) - "Pick now!".toLabel().onClick { accept(improvement) } - else - "Current construction".toLabel() + val pickNow = when { + suggestRemoval -> "${Constants.remove}[${tileInfo.getLastTerrain().name}] first".toLabel() + tileInfo.improvementInProgress != improvement.name -> "Pick now!".toLabel().onClick { accept(improvement) } + else -> "Current construction".toLabel() + } val statIcons = getStatIconsTable(provideResource, removeImprovement) diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index e4fcb5f7df..92fa3383fb 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -635,8 +635,14 @@ object UnitActions { title = "Create [$improvementName]", action = { val unitTile = unit.getTile() - for (terrainFeature in tile.terrainFeatures.filter { unitTile.ruleset.tileImprovements.containsKey("Remove $it") }) - unitTile.removeTerrainFeature(terrainFeature)// remove forest/jungle/marsh + unitTile.setTerrainFeatures( + // Remove terrainFeatures that a Worker can remove + // and that aren't explicitly allowed under the improvement + unitTile.terrainFeatures.filter { + "Remove $it" !in unitTile.ruleset.tileImprovements || + it in improvement.terrainsCanBeBuiltOn + } + ) unitTile.improvement = improvementName unitTile.improvementInProgress = null unitTile.turnsToImprovement = 0 @@ -647,7 +653,8 @@ object UnitActions { city.cityStats.update() city.civInfo.updateDetailedCivResources() } - addStatsPerGreatPersonUsage(unit) + if (unit.isGreatPerson()) + addStatsPerGreatPersonUsage(unit) unit.destroy() }.takeIf { resourcesAvailable @@ -703,7 +710,7 @@ object UnitActions { otherCiv.addNotification("[${unit.civInfo}] has stolen your territory!", unit.currentTile.position, unit.civInfo.civName, NotificationIcon.War) } - fun addStatsPerGreatPersonUsage(unit: MapUnit) { + private fun addStatsPerGreatPersonUsage(unit: MapUnit) { if (!unit.isGreatPerson()) return val civInfo = unit.civInfo @@ -782,7 +789,7 @@ object UnitActions { if (getGiftAction != null) actionList += getGiftAction } - fun getGiftAction(unit: MapUnit, tile: TileInfo): UnitAction? { + private fun getGiftAction(unit: MapUnit, tile: TileInfo): UnitAction? { val recipient = tile.getOwner() // We need to be in another civs territory. if (recipient == null || recipient.isCurrentPlayer()) return null @@ -824,7 +831,7 @@ object UnitActions { return UnitAction(UnitActionType.GiftUnit, action = giftAction) } - fun addTriggerUniqueActions(unit: MapUnit, actionList: ArrayList){ + private fun addTriggerUniqueActions(unit: MapUnit, actionList: ArrayList){ for (unique in unit.getUniques()) { if (!unique.conditionals.any { it.type == UniqueType.ConditionalConsumeUnit }) continue val unitAction = UnitAction(type = UnitActionType.TriggerUnique, unique.text){