From afce9517248e8c2a4873235230f8a8a66056263e Mon Sep 17 00:00:00 2001 From: OptimizedForDensity <105244635+OptimizedForDensity@users.noreply.github.com> Date: Wed, 1 Jun 2022 13:13:04 -0400 Subject: [PATCH] Pillaging certain improvements may loot gold or other stat resources (#6993) * Pillaging loots gold on certain improvements * Add warning if improvement pillageGold > 0 and has UniqueType.Unpillagable * Pillage yields as a UniqueType * Forgot to undo demonstration * Remove unused line * Eliminate some redundancy * Reword unique text * Slight cleanup and add notification for pillage victim * Reviews * Made pillaging notifications more consistent with other hostile action notifications * Missed a line --- .../TileImprovements.json | 58 ++++++++++--------- .../Civ V - Vanilla/TileImprovements.json | 58 ++++++++++--------- android/assets/jsons/Tutorials.json | 18 +++--- .../jsons/translations/template.properties | 6 +- core/src/com/unciv/models/ruleset/Ruleset.kt | 19 ++++++ .../models/ruleset/tile/TileImprovement.kt | 12 ++-- .../unciv/models/ruleset/unique/UniqueType.kt | 2 + core/src/com/unciv/models/stats/Stats.kt | 20 +++++-- .../unciv/ui/worldscreen/unit/UnitActions.kt | 49 ++++++++++++++++ .../com/unciv/uniques/GlobalUniquesTests.kt | 24 ++++++++ tests/src/com/unciv/uniques/TestGame.kt | 3 + 11 files changed, 191 insertions(+), 78 deletions(-) diff --git a/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json b/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json index ab941aa384..88d046f7a9 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json +++ b/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json @@ -6,9 +6,10 @@ "food": 1, "turnsToBuild": 7, "techRequired": "Agriculture", - "uniques": ["Can also be built on tiles adjacent to fresh water", + "uniques": ["Can also be built on tiles adjacent to fresh water", "[+1 Food] from [Fresh water] tiles ", - "[+1 Food] from [non-fresh water] tiles "], + "[+1 Food] from [non-fresh water] tiles ", + "Pillaging this improvement yields approximately [+18 Gold]"], "shortcutKey": "F" }, { @@ -17,7 +18,7 @@ "production": 1, "turnsToBuild": 7, "techRequired": "Construction", - "uniques": ["[+1 Production] "], + "uniques": ["[+1 Production] ", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "L" }, { @@ -26,7 +27,7 @@ "production": 1, "turnsToBuild": 7, "techRequired": "Mining", - "uniques": ["[+1 Production] "], + "uniques": ["[+1 Production] ", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "M" }, { @@ -35,36 +36,36 @@ "gold": 1, "turnsToBuild": 7, "techRequired": "Guilds", - "uniques": ["[+1 Gold] "], + "uniques": ["[+1 Gold] ", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "T" }, - + // Resource-specific { "name": "Camp", "turnsToBuild": 7, "techRequired": "Trapping", - "uniques": ["Does not need removal of [Forest]","Does not need removal of [Jungle]","[+1 Gold] ", "Can only be built to improve a resource"], + "uniques": ["Does not need removal of [Forest]","Does not need removal of [Jungle]","[+1 Gold] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "C" }, { "name": "Oil well", "turnsToBuild": 9, "techRequired": "Biology", - "uniques": ["Can only be built to improve a resource", "Cannot be built on [Water] tiles"], + "uniques": ["Can only be built to improve a resource", "Cannot be built on [Water] tiles", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "W" }, { "name": "Offshore Platform", "techRequired": "Refrigeration", - "uniques": ["Can only be built to improve a resource", "Can only be built on [Water] tiles"], + "uniques": ["Can only be built to improve a resource", "Can only be built on [Water] tiles", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "P" }, { "name": "Pasture", "turnsToBuild": 8, "techRequired": "Animal Husbandry", - "uniques": ["[+1 Food] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Food] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "P" }, { @@ -72,14 +73,14 @@ "turnsToBuild": 6, "gold": 1, "techRequired": "Calendar", - "uniques": ["[+1 Food] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Food] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "P" }, { "name": "Quarry", "turnsToBuild": 8, "techRequired": "Masonry", - "uniques": ["[+1 Production] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Production] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "Q" }, { @@ -87,7 +88,7 @@ "terrainsCanBeBuiltOn": ["Coast"], "food": 1, "techRequired": "Sailing", - "uniques": ["[+1 Gold] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Gold] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "F" }, @@ -125,9 +126,9 @@ "shortcutKey": "R", "civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}] }, - + // Removals - // Any improvement that starts with 'Remove ' is automatically changed into + // Any improvement that starts with 'Remove ' is automatically changed into // the improvement that removes the terrainfeature after it. { "name": "Remove Forest", @@ -175,37 +176,37 @@ "uniques": ["Can be built outside your borders"], "shortcutKey": "." }, - + // Great Person improvements { "name": "Academy", "terrainsCanBeBuiltOn": ["Land"], "science": 8, - "uniques": ["Great Improvement", "[+2 Science] ", "[+2 Science] ", "Removes removable features when built"] + "uniques": ["Great Improvement", "[+2 Science] ", "[+2 Science] ", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Landmark", "terrainsCanBeBuiltOn": ["Land"], "culture": 6, - "uniques": ["Great Improvement", "Removes removable features when built"] + "uniques": ["Great Improvement", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Manufactory", "terrainsCanBeBuiltOn": ["Land"], "production": 4, - "uniques": ["Great Improvement", "[+1 Production] ", "Removes removable features when built"] + "uniques": ["Great Improvement", "[+1 Production] ", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Customs house", "terrainsCanBeBuiltOn": ["Land"], "gold": 4, - "uniques": ["Great Improvement", "[+1 Gold] ", "Removes removable features when built"] + "uniques": ["Great Improvement", "[+1 Gold] ", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Holy site", "terrainsCanBeBuiltOn": ["Land"], "faith": 6, - "uniques": ["Great Improvement", "Removes removable features when built"] + "uniques": ["Great Improvement", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Citadel", @@ -227,7 +228,7 @@ "terrainsCanBeBuiltOn": ["Land"], "culture": 1, "turnsToBuild": 4, - "uniques": ["Can only be built on [Coastal] tiles", "[+1 Culture] for each adjacent [Moai]", "[+1 Gold] "], + "uniques": ["Can only be built on [Coastal] tiles", "[+1 Culture] for each adjacent [Moai]", "[+1 Gold] ", "Pillaging this improvement yields approximately [+10 Gold]"], "techRequired": "Construction", "shortcutKey": "M" }, @@ -240,7 +241,8 @@ "uniques": ["Cannot be built on [Bonus resource] tiles", "[+1 Food] for each adjacent [Mountain]", "[+1 Food] from [Fresh water] tiles ", - "[+1 Food] from [non-fresh water] tiles "] + "[+1 Food] from [non-fresh water] tiles ", + "Pillaging this improvement yields approximately [+18 Gold]"], "techRequired": "Construction", "shortcutKey": "F" }, @@ -250,24 +252,24 @@ "food": 3, "terrainsCanBeBuiltOn": ["Marsh", "Flood plains"], "turnsToBuild": 7, - "uniques": ["[+1 Production, +2 Gold] "], + "uniques": ["[+1 Production, +2 Gold] ", "Pillaging this improvement yields approximately [+18 Gold]"], "techRequired": "Guilds", "shortcutKey": "F" }, // Unbuildable improvements - { + { "name": "Ancient ruins", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Provides a random bonus when entered", "Unbuildable"] }, - { + { "name": "City ruins", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Unbuildable"], "civilopediaText": [{"text":"A bleak reminder of the destruction wreaked by War"}] }, - { + { "name": "City center", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Irremovable", "Unbuildable"], @@ -276,7 +278,7 @@ {"text":"Appearance changes with the technological era of the owning civilization"} ] }, - { + { "name": "Barbarian encampment", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Unbuildable"], diff --git a/android/assets/jsons/Civ V - Vanilla/TileImprovements.json b/android/assets/jsons/Civ V - Vanilla/TileImprovements.json index bfd003fc50..481cb58834 100644 --- a/android/assets/jsons/Civ V - Vanilla/TileImprovements.json +++ b/android/assets/jsons/Civ V - Vanilla/TileImprovements.json @@ -6,9 +6,10 @@ "food": 1, "turnsToBuild": 7, "techRequired": "Agriculture", - "uniques": ["Can also be built on tiles adjacent to fresh water", + "uniques": ["Can also be built on tiles adjacent to fresh water", "[+1 Food] from [Fresh water] tiles ", - "[+1 Food] from [non-fresh water] tiles "], + "[+1 Food] from [non-fresh water] tiles ", + "Pillaging this improvement yields approximately [+18 Gold]"], "shortcutKey": "F" }, { @@ -17,7 +18,7 @@ "production": 1, "turnsToBuild": 7, "techRequired": "Construction", - "uniques": ["[+1 Production] "], + "uniques": ["[+1 Production] ", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "L" }, { @@ -26,7 +27,7 @@ "production": 1, "turnsToBuild": 7, "techRequired": "Mining", - "uniques": ["[+1 Production] "], + "uniques": ["[+1 Production] ", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "M" }, { @@ -35,36 +36,36 @@ "gold": 1, "turnsToBuild": 7, "techRequired": "Trapping", - "uniques": ["[+1 Gold] "], + "uniques": ["[+1 Gold] ", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "T" }, - + // Resource-specific { "name": "Camp", "turnsToBuild": 7, "techRequired": "Trapping", - "uniques": ["Does not need removal of [Forest]","Does not need removal of [Jungle]","[+1 Gold] ", "Can only be built to improve a resource"], + "uniques": ["Does not need removal of [Forest]","Does not need removal of [Jungle]","[+1 Gold] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "C" }, { "name": "Oil well", "turnsToBuild": 9, "techRequired": "Biology", - "uniques": ["Can only be built to improve a resource", "Cannot be built on [Water] tiles"], + "uniques": ["Can only be built to improve a resource", "Cannot be built on [Water] tiles", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "W" }, { "name": "Offshore Platform", "techRequired": "Refrigeration", - "uniques": ["Can only be built to improve a resource", "Can only be built on [Water] tiles"], + "uniques": ["Can only be built to improve a resource", "Can only be built on [Water] tiles", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "P" }, { "name": "Pasture", "turnsToBuild": 8, "techRequired": "Animal Husbandry", - "uniques": ["[+1 Food] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Food] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "P" }, { @@ -72,14 +73,14 @@ "turnsToBuild": 6, "gold": 1, "techRequired": "Calendar", - "uniques": ["[+1 Food] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Food] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "P" }, { "name": "Quarry", "turnsToBuild": 8, "techRequired": "Masonry", - "uniques": ["[+1 Production] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Production] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+20 Gold]"], "shortcutKey": "Q" }, { @@ -87,7 +88,7 @@ "terrainsCanBeBuiltOn": ["Coast"], "food": 1, "techRequired": "Sailing", - "uniques": ["[+1 Gold] ", "Can only be built to improve a resource"], + "uniques": ["[+1 Gold] ", "Can only be built to improve a resource", "Pillaging this improvement yields approximately [+10 Gold]"], "shortcutKey": "F" }, @@ -125,9 +126,9 @@ "shortcutKey": "R", "civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}] }, - + // Removals - // Any improvement that starts with 'Remove ' is automatically changed into + // Any improvement that starts with 'Remove ' is automatically changed into // the improvement that removes the terrainfeature after it. { "name": "Remove Forest", @@ -175,37 +176,37 @@ "uniques": ["Can be built outside your borders"], "shortcutKey": "." }, - + // Great Person improvements { "name": "Academy", "terrainsCanBeBuiltOn": ["Land"], "science": 8, - "uniques": ["Great Improvement", "[+2 Science] ", "Removes removable features when built"] + "uniques": ["Great Improvement", "[+2 Science] ", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Landmark", "terrainsCanBeBuiltOn": ["Land"], "culture": 6, - "uniques": ["Great Improvement", "Removes removable features when built"] + "uniques": ["Great Improvement", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Manufactory", "terrainsCanBeBuiltOn": ["Land"], "production": 4, - "uniques": ["Great Improvement", "[+1 Production] ", "Removes removable features when built"] + "uniques": ["Great Improvement", "[+1 Production] ", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Customs house", "terrainsCanBeBuiltOn": ["Land"], "gold": 4, - "uniques": ["Great Improvement", "[+1 Gold] ", "Removes removable features when built"] + "uniques": ["Great Improvement", "[+1 Gold] ", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Holy site", "terrainsCanBeBuiltOn": ["Land"], "faith": 6, - "uniques": ["Great Improvement", "Removes removable features when built"] + "uniques": ["Great Improvement", "Removes removable features when built", "Pillaging this improvement yields approximately [+20 Gold]"] }, { "name": "Citadel", @@ -215,7 +216,7 @@ "Gives a defensive bonus of [100]%", "Adjacent enemy units ending their turn take [30] damage", "Can be built just outside your borders", - "Constructing it will take over the tiles around it and assign them to your closest city", + "Constructing it will take over the tiles around it and assign them to your closest city", "Removes removable features when built", ] }, @@ -227,7 +228,7 @@ "terrainsCanBeBuiltOn": ["Land"], "culture": 1, "turnsToBuild": 4, - "uniques": ["Can only be built on [Coastal] tiles", "[+1 Culture] for each adjacent [Moai]", "[+1 Gold] "], + "uniques": ["Can only be built on [Coastal] tiles", "[+1 Culture] for each adjacent [Moai]", "[+1 Gold] ", "Pillaging this improvement yields approximately [+10 Gold]"], "techRequired": "Construction", "shortcutKey": "M" }, @@ -240,24 +241,25 @@ "uniques": ["Cannot be built on [Bonus resource] tiles", "[+1 Food] for each adjacent [Mountain]", "[+1 Food] from [Fresh water] tiles ", - "[+1 Food] from [non-fresh water] tiles "] + "[+1 Food] from [non-fresh water] tiles ", + "Pillaging this improvement yields approximately [+18 Gold]"], "techRequired": "Construction", "shortcutKey": "F" }, // Unbuildable improvements - { + { "name": "Ancient ruins", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Provides a random bonus when entered", "Unbuildable"] }, - { + { "name": "City ruins", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Unbuildable"], "civilopediaText": [{"text":"A bleak reminder of the destruction wreaked by War"}] }, - { + { "name": "City center", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Irremovable", "Unbuildable"], @@ -266,7 +268,7 @@ {"text":"Appearance changes with the technological era of the owning civilization"} ] }, - { + { "name": "Barbarian encampment", "terrainsCanBeBuiltOn": ["Land"], "uniques": ["Unpillagable", "Unbuildable"], diff --git a/android/assets/jsons/Tutorials.json b/android/assets/jsons/Tutorials.json index 45fa464778..fc861c1c38 100644 --- a/android/assets/jsons/Tutorials.json +++ b/android/assets/jsons/Tutorials.json @@ -1,12 +1,12 @@ { // Each entry s a tutorial, but the tutorial may be spread over separate paragraphs. // Entries starting with a _ will NOT be shown in Civilopedia. - // + // Introduction: [ "Welcome to Unciv!\nBecause this is a complex game, there are basic tasks to help familiarize you with the game.\nThese are completely optional, and you're welcome to explore the game on your own!" ], - + // Civilopedia only, because players said this was too wall-of-text New_Game: [ "Your first mission is to found your capital city.\nThis is actually an important task because your capital city will probably be your most prosperous.\nMany game bonuses apply only to your capital city and it will probably be the center of your empire.", @@ -20,27 +20,27 @@ Culture_and_Policies: [ "Each turn, the culture you gain from all your cities is added to your Civilization's culture.\nWhen you have enough culture, you may pick a Social Policy, each one giving you a certain bonus." , "The policies are organized into branches, with each branch providing a bonus ability when all policies in the branch have been adopted." , - "With each policy adopted, and with each city built,\n the cost of adopting another policy rises - so choose wisely!" + "With each policy adopted, and with each city built,\n the cost of adopting another policy rises - so choose wisely!" ], City_Expansion: [ "Once a city has gathered enough Culture, it will expand into a neighboring tile.\nYou have no control over the tile it will expand into, but tiles with resources and higher yields are prioritized.", "Each additional tile will require more culture, but generally your first cities will eventually expand to a wide tile range.", "Although your city will keep expanding forever, your citizens can only work 3 tiles away from city center.\nThis should be taken into account when placing new cities." ], - Happiness: [ + Happiness: [ "As cities grow in size and influence, you have to deal with a happiness mechanic that is no longer tied to each individual city.\nInstead, your entire empire shares the same level of satisfaction.\nAs your cities grow in population you’ll find that it is more and more difficult to keep your empire happy." , "In addition, you can’t even build any city improvements that increase happiness until you’ve done the appropriate research.\nIf your empire’s happiness ever goes below zero the growth rate of your cities will be hurt.\nIf your empire becomes severely unhappy (as indicated by the smiley-face icon at the top of the interface)\n your armies will have a big penalty slapped on to their overall combat effectiveness." , - "This means that it is very difficult to expand quickly in Unciv.\nIt isn’t impossible, but as a new player you probably shouldn't do it.\nSo what should you do? Chill out, scout, and improve the land that you do have by building Workers.\nOnly build new cities once you have found a spot that you believe is appropriate." + "This means that it is very difficult to expand quickly in Unciv.\nIt isn’t impossible, but as a new player you probably shouldn't do it.\nSo what should you do? Chill out, scout, and improve the land that you do have by building Workers.\nOnly build new cities once you have found a spot that you believe is appropriate." ], - Unhappiness: [ + Unhappiness: [ "It seems that your citizens are unhappy!\nWhile unhappy, your civilization will suffer many detrimental effects, increasing in severity as unhappiness gets higher.", "Unhappiness has two main causes: Population and cities.\n Each city causes 3 unhappiness, and each population, 1", - "There are 2 main ways to combat unhappiness:\n by building happiness buildings for your population\n or by having improved luxury resources within your borders." + "There are 2 main ways to combat unhappiness:\n by building happiness buildings for your population\n or by having improved luxury resources within your borders." ], Golden_Age: [ "You have entered a Golden Age!\nGolden age points are accumulated each turn by the total happiness \n of your civilization\nWhen in a golden age, culture and production generation increases +20%,\n and every tile already providing at least one gold will provide an extra gold." ], - + Roads_and_Railroads: [ "Connecting your cities to the capital by roads\n will generate gold via the trade route.\nNote that each road costs 1 gold Maintenance per turn, and each Railroad costs 2 gold,\n so it may be more economical to wait until the cities grow!" ], @@ -97,7 +97,7 @@ "Every rating and review that I get puts a smile on my face =)\n So contact me! Send me an email, review, Github issue\n or mail pigeon, and let's figure out how to make the game \n even more awesome!\n(Contact info is in the Play Store)" ], Pillaging: [ - "Military units can pillage improvements, which heals them 25 health and ruins the improvement.\nThe tile can still be worked, but advantages from the improvement - stat bonuses and resources - will be lost.\nWorkers can repair these improvements, which takes less time than building the improvement from scratch." + "Military units can pillage improvements, which heals them 25 health and ruins the improvement.\nThe tile can still be worked, but advantages from the improvement - stat bonuses and resources - will be lost.\nWorkers can repair these improvements, which takes less time than building the improvement from scratch.\nPillaging certain improvements will result in your units looting gold from the improvement." ], Experience: [ "Units that enter combat gain experience, which can then be used on promotions for that unit.\nUnits gain more experience when in Melee combat than Ranged, and more when attacking than when defending.", diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 4a1d5d2f4c..547461336d 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -864,6 +864,9 @@ Stop exploration = Pillage = Wait = Are you sure you want to pillage this [improvement]? = +We have looted [amount] from a [improvement] = +We have looted [amount] from a [improvement] which has been sent to [cityName] = +An enemy [unitName] has pillaged our [improvement] = Create [improvement] = Start Golden Age = Trigger unique = @@ -1286,7 +1289,8 @@ Eras = Embarked strength: [amount]† = Base unit buy cost: [amount]¤ = Research agreement cost: [amount]¤ = - +Pillaging this improvement yields [stats] = +Pillaging this improvement yields approximately [stats] = # Policies diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 9619498688..279a985895 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -21,6 +21,7 @@ import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileResource 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.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType @@ -694,6 +695,24 @@ class Ruleset { RulesetErrorSeverity.Warning ) } + for (unique in improvement.uniqueObjects) { + if (unique.type == UniqueType.PillageYieldRandom || unique.type == UniqueType.PillageYieldFixed) { + if (!Stats.isStats(unique.params[0])) continue + val params = Stats.parse(unique.params[0]) + if (params.values.any { it < 0 }) lines.add( + "${improvement.name} cannot have a negative value for a pillage yield!", + RulesetErrorSeverity.Error + ) + } + } + if ((improvement.hasUnique(UniqueType.PillageYieldRandom, StateForConditionals.IgnoreConditionals) + || improvement.hasUnique(UniqueType.PillageYieldFixed, StateForConditionals.IgnoreConditionals)) + && improvement.hasUnique(UniqueType.Unpillagable, StateForConditionals.IgnoreConditionals)) { + lines.add( + "${improvement.name} has both an `Unpillagable` unique type and a `PillageYieldRandom` or `PillageYieldFixed` unique type!", + RulesetErrorSeverity.Warning + ) + } checkUniques(improvement, lines, rulesetSpecific, forOptionsPopup) } diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index f43374c001..80459e7d67 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -26,7 +26,7 @@ class TileImprovement : RulesetStatsObject() { override fun getUniqueTarget() = UniqueTarget.Improvement val shortcutKey: Char? = null // This is the base cost. A cost of 0 means created instead of buildable. - val turnsToBuild: Int = 0 + val turnsToBuild: Int = 0 fun getTurnsToBuild(civInfo: CivilizationInfo, unit: MapUnit): Int { @@ -71,7 +71,7 @@ class TileImprovement : RulesetStatsObject() { fun canBeBuiltOn(terrain: String): Boolean { return terrain in terrainsCanBeBuiltOn } - + fun handleImprovementCompletion(builder: MapUnit) { val tile = builder.getTile() if (hasUnique(UniqueType.TakesOverAdjacentTiles)) @@ -88,18 +88,18 @@ class TileImprovement : RulesetStatsObject() { // and that aren't explicitly allowed under the improvement val removableTerrainFeatures = tile.terrainFeatures.filter { feature -> val removingAction = "${Constants.remove}$feature" - + removingAction in tile.ruleset.tileImprovements && !isAllowedOnFeature(feature) && tile.ruleset.tileImprovements[removingAction]!!.let { it.techRequired == null || builder.civInfo.tech.isResearched(it.techRequired!!) } } - + tile.setTerrainFeatures(tile.terrainFeatures.filterNot { it in removableTerrainFeatures }) } } - + /** * Check: Is this improvement allowed on a [given][name] terrain feature? * @@ -198,7 +198,7 @@ class TileImprovement : RulesetStatsObject() { } val unit = ruleset.units.asSequence().firstOrNull { - entry -> entry.value.uniques.any { + entry -> entry.value.uniques.any { it.startsWith("Can construct [$name]") } }?.key diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 6c26524371..667f9db28a 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -635,6 +635,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: TakesOverAdjacentTiles("Constructing it will take over the tiles around it and assign them to your closest city", UniqueTarget.Improvement), Unpillagable("Unpillagable", UniqueTarget.Improvement), + PillageYieldRandom("Pillaging this improvement yields approximately [stats]", UniqueTarget.Improvement), + PillageYieldFixed("Pillaging this improvement yields [stats]", UniqueTarget.Improvement), Irremovable("Irremovable", UniqueTarget.Improvement), //endregion diff --git a/core/src/com/unciv/models/stats/Stats.kt b/core/src/com/unciv/models/stats/Stats.kt index 243b0b628e..d5fe6f976f 100644 --- a/core/src/com/unciv/models/stats/Stats.kt +++ b/core/src/com/unciv/models/stats/Stats.kt @@ -6,9 +6,9 @@ import kotlin.reflect.KMutableProperty0 /** * A container for the seven basic ["currencies"][Stat] in Unciv, * **Mutable**, allowing for easy merging of sources and applying bonuses. - * + * * Supports e.g. `for ((key,value) in )` - the [iterator] will skip zero values automatically. - * + * * Also possible: ``.[values].sum() and similar aggregates over a Sequence. */ open class Stats( @@ -20,7 +20,7 @@ open class Stats( var happiness: Float = 0f, var faith: Float = 0f ): Iterable { - + // This is what facilitates indexed access by [Stat] or add(Stat,Float) // without additional memory allocation or expensive conditionals private fun statToProperty(stat: Stat):KMutableProperty0{ @@ -124,9 +124,9 @@ open class Stats( happiness *= number faith *= number } - + operator fun div(number: Float) = times(1/number) - + /** Apply weighting for Production Ranking */ fun applyRankingWeights(){ food *= 14 @@ -139,7 +139,7 @@ open class Stats( } /** ***Not*** only a debug helper. It returns a string representing the content, already _translated_. - * + * * Example output: `+1 Production, -1 Food`. */ override fun toString(): String { @@ -155,6 +155,14 @@ open class Stats( } } + // function that removes the icon from the Stats object since the circular icons all appear the same + // delete this and replace above instances with toString() once the text-coloring-affecting-font-icons bug is fixed (e.g., in notification text) + fun toStringWithoutIcons(): String { + return this.joinToString { + it.value.toInt().toString() + " " + it.key.name.tr().substring(startIndex = 1) + } + } + /** Represents one [key][Stat]/[value][Float] pair returned by the [iterator] */ data class StatValuePair (val key: Stat, val value: Float) diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 5d6fac5c44..6907a7fd98 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -20,6 +20,7 @@ import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.unique.UniqueTriggerActivation import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat +import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.pickerscreens.ImprovementPickerScreen import com.unciv.ui.pickerscreens.PromotionPickerScreen @@ -28,6 +29,7 @@ import com.unciv.ui.popup.hasOpenPopups import com.unciv.ui.utils.toPercent import com.unciv.ui.worldscreen.WorldScreen import kotlin.math.min +import kotlin.random.Random object UnitActions { @@ -284,6 +286,8 @@ object UnitActions { return UnitAction(UnitActionType.Pillage, action = { + tile.getOwner()?.addNotification("An enemy [${unit.baseUnit.name}] has pillaged our [${tile.improvement}]", tile.position, "ImprovementIcons/${tile.improvement!!}", NotificationIcon.War, unit.baseUnit.name) + pillageLooting(tile, unit) tile.setPillaged() unit.civInfo.lastSeenImprovement.remove(tile.position) if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource @@ -296,6 +300,51 @@ object UnitActions { }.takeIf { unit.currentMovement > 0 && canPillage(unit, tile) }) } + private fun pillageLooting(tile: TileInfo, unit: MapUnit) { + // Stats objects for reporting pillage results in a notification + val pillageYield = Stats() + val globalPillageYield = Stats() + val toCityPillageYield = Stats() + val closestCity = unit.civInfo.cities.minByOrNull { it.getCenterTile().aerialDistanceTo(tile) } + val improvement = tile.ruleset.tileImprovements[tile.improvement]!! + + for (unique in improvement.getMatchingUniques(UniqueType.PillageYieldRandom)) { + for (stat in unique.stats) { + val looted = Random.nextInt((stat.value + 1).toInt()) + Random.nextInt((stat.value + 1).toInt()) + pillageYield.add(stat.key, looted.toFloat()) + } + } + for (unique in improvement.getMatchingUniques(UniqueType.PillageYieldFixed)) { + for (stat in unique.stats) { + pillageYield.add(stat.key, stat.value) + } + } + + for (stat in pillageYield) { + when (stat.key) { + in Stat.statsWithCivWideField -> { + unit.civInfo.addStat(stat.key, stat.value.toInt()) + globalPillageYield[stat.key] += stat.value + } + else -> { + if (closestCity != null) { + closestCity.addStat(stat.key, stat.value.toInt()) + toCityPillageYield[stat.key] += stat.value + } + } + } + } + + if (!toCityPillageYield.isEmpty() && closestCity != null) { + val pillagerLootLocal = "We have looted [${toCityPillageYield.toStringWithoutIcons()}] from a [${improvement.name}] which has been sent to [${closestCity.name}]" + unit.civInfo.addNotification(pillagerLootLocal, tile.position, "ImprovementIcons/${improvement.name}", NotificationIcon.War) + } + if (!globalPillageYield.isEmpty()) { + val pillagerLootGlobal = "We have looted [${globalPillageYield.toStringWithoutIcons()}] from a [${improvement.name}]" + unit.civInfo.addNotification(pillagerLootGlobal, tile.position, "ImprovementIcons/${improvement.name}", NotificationIcon.War) + } + } + private fun addExplorationActions(unit: MapUnit, actionList: ArrayList) { if (unit.baseUnit.movesLikeAirUnits()) return if (unit.isExploring()) return diff --git a/tests/src/com/unciv/uniques/GlobalUniquesTests.kt b/tests/src/com/unciv/uniques/GlobalUniquesTests.kt index 6c8050ac59..506bcc9381 100644 --- a/tests/src/com/unciv/uniques/GlobalUniquesTests.kt +++ b/tests/src/com/unciv/uniques/GlobalUniquesTests.kt @@ -5,8 +5,10 @@ import com.badlogic.gdx.math.Vector2 import com.unciv.Constants import com.unciv.logic.map.RoadStatus import com.unciv.models.ruleset.BeliefType +import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.stats.Stats import com.unciv.testing.GdxTestRunner +import com.unciv.ui.worldscreen.unit.UnitActions import org.junit.Assert import org.junit.Before import org.junit.Test @@ -339,4 +341,26 @@ class GlobalUniquesTests { Assert.assertTrue(civInfo.gold == 250) } + @Test + fun pillageYieldTest() { + game.makeHexagonalMap(2) + val civInfo = game.addCiv() + + val tile = game.setTileFeatures(Vector2(0f, 0f), Constants.grassland) + val cityTile = game.setTileFeatures(Vector2(2f,0f), Constants.grassland) + val cityInfo = game.addCity(civInfo, cityTile, true) + cityInfo.population.foodStored = 0 // just to be sure + civInfo.addGold(-civInfo.gold) // reset gold just to be sure + + val testImprovement = game.createTileImprovement("Pillaging this improvement yields [+20 Gold, +11 Food]") + tile.improvement = testImprovement.name + val unit = game.addUnit("Warrior", civInfo, tile) + unit.currentMovement = 2f + + val pillageAction = UnitActions.getPillageAction(unit) + pillageAction?.action?.invoke() + Assert.assertTrue("Pillaging should transfer gold to the civ", civInfo.gold == 20) + Assert.assertTrue("Pillaging should transfer food to the nearest city", cityInfo.population.foodStored == 11) + } + } diff --git a/tests/src/com/unciv/uniques/TestGame.kt b/tests/src/com/unciv/uniques/TestGame.kt index 089f292095..d7dd1e4ce8 100644 --- a/tests/src/com/unciv/uniques/TestGame.kt +++ b/tests/src/com/unciv/uniques/TestGame.kt @@ -16,6 +16,7 @@ import com.unciv.models.Religion import com.unciv.models.metadata.BaseRuleset import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.* +import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.unique.UniqueType class TestGame { @@ -172,4 +173,6 @@ class TestGame { createRulesetObject(ruleset.buildings, *uniques) { Building() } fun createPolicy(vararg uniques: String) = createRulesetObject(ruleset.policies, *uniques) { Policy() } + fun createTileImprovement(vararg uniques: String) = + createRulesetObject(ruleset.tileImprovements, *uniques) { TileImprovement() } }