diff --git a/android/ImagesToPackSeparately/UnitIcons/Missionary.png b/android/ImagesToPackSeparately/UnitIcons/Missionary.png new file mode 100644 index 0000000000..0754606b13 Binary files /dev/null and b/android/ImagesToPackSeparately/UnitIcons/Missionary.png differ diff --git a/android/assets/UnitIcons.atlas b/android/assets/UnitIcons.atlas index d9b2cc5127..be069ef488 100644 --- a/android/assets/UnitIcons.atlas +++ b/android/assets/UnitIcons.atlas @@ -494,275 +494,282 @@ Missile Cruiser orig: 100, 100 offset: 0, 0 index: -1 -Mobile SAM +Missionary rotate: false xy: 652, 214 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 -Modern Armor +Mobile SAM rotate: false xy: 760, 322 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 -Mohawk Warrior +Modern Armor rotate: false xy: 868, 430 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 -Musketeer +Mohawk Warrior rotate: false xy: 976, 538 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 +Musketeer + rotate: false + xy: 1084, 646 + size: 100, 100 + orig: 100, 100 + offset: 0, 0 + index: -1 Musketman rotate: false - xy: 1084, 647 + xy: 1192, 755 size: 100, 99 orig: 100, 99 offset: 0, 0 index: -1 Naresuan's Elephant rotate: false - xy: 1192, 754 + xy: 1300, 862 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Norwegian Ski Infantry rotate: false - xy: 1300, 862 + xy: 652, 106 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Nuclear Missile rotate: false - xy: 652, 106 + xy: 760, 214 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Nuclear Submarine rotate: false - xy: 760, 214 + xy: 868, 322 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Panzer rotate: false - xy: 868, 322 + xy: 976, 430 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Paratrooper rotate: false - xy: 976, 430 + xy: 1084, 538 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Persian Immortal rotate: false - xy: 1084, 539 + xy: 1192, 647 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Pikeman rotate: false - xy: 1192, 646 + xy: 1300, 754 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Privateer rotate: false - xy: 1300, 754 + xy: 1408, 862 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Rifleman rotate: false - xy: 1408, 862 + xy: 760, 106 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Rocket Artillery rotate: false - xy: 760, 106 + xy: 868, 214 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Samurai rotate: false - xy: 868, 214 + xy: 976, 322 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Scout rotate: false - xy: 976, 322 + xy: 1084, 430 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Sea Beggar rotate: false - xy: 1084, 431 + xy: 1192, 539 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Settler rotate: false - xy: 1192, 538 + xy: 1300, 646 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Ship of the Line rotate: false - xy: 1300, 646 + xy: 1408, 754 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Sipahi rotate: false - xy: 1408, 754 + xy: 1516, 862 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Slinger rotate: false - xy: 1516, 862 + xy: 868, 106 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Spearman rotate: false - xy: 868, 106 + xy: 976, 214 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Stealth Bomber rotate: false - xy: 976, 214 + xy: 1084, 322 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Submarine rotate: false - xy: 1084, 323 + xy: 1192, 431 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Swordsman rotate: false - xy: 1192, 430 + xy: 1300, 538 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Tank rotate: false - xy: 1300, 538 + xy: 1408, 646 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Tercio rotate: false - xy: 1408, 646 + xy: 1516, 754 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Trebuchet rotate: false - xy: 1516, 754 + xy: 1624, 862 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Triplane rotate: false - xy: 1624, 862 + xy: 976, 106 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Trireme rotate: false - xy: 1084, 214 + xy: 1084, 213 size: 100, 101 orig: 100, 101 offset: 0, 0 index: -1 Turtle Ship rotate: false - xy: 976, 106 + xy: 1192, 323 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 War Chariot rotate: false - xy: 1192, 322 + xy: 1300, 430 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 War Elephant rotate: false - xy: 1300, 430 + xy: 1408, 538 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Warrior rotate: false - xy: 1408, 538 + xy: 1516, 646 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Work Boats rotate: false - xy: 1516, 646 + xy: 1624, 754 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Worker rotate: false - xy: 1624, 754 + xy: 1732, 862 size: 100, 100 orig: 100, 100 offset: 0, 0 index: -1 Zero rotate: false - xy: 1732, 862 + xy: 1192, 215 size: 100, 100 orig: 100, 100 offset: 0, 0 diff --git a/android/assets/UnitIcons.png b/android/assets/UnitIcons.png index ecfdbdefff..a59d5f0db0 100644 Binary files a/android/assets/UnitIcons.png and b/android/assets/UnitIcons.png differ diff --git a/android/assets/jsons/Civ V - Vanilla/Buildings.json b/android/assets/jsons/Civ V - Vanilla/Buildings.json index 82997bbccc..e0f93c17b1 100644 --- a/android/assets/jsons/Civ V - Vanilla/Buildings.json +++ b/android/assets/jsons/Civ V - Vanilla/Buildings.json @@ -847,7 +847,7 @@ "gold": 4, "greatPersonPoints": {"Great Merchant": 2}, "isWonder": true, - "uniques": ["Cost of purchasing items in cities reduced by [15]%"], + "uniques": ["[Gold] cost of purchasing items in cities [-15]%"], "requiredTech": "Industrialization", "quote": "'To achieve great things, two things are needed: a plan, and not quite enough time.' - Leonard Bernstein" }, diff --git a/android/assets/jsons/Civ V - Vanilla/Eras.json b/android/assets/jsons/Civ V - Vanilla/Eras.json index c9d7b475c0..9a60f07957 100644 --- a/android/assets/jsons/Civ V - Vanilla/Eras.json +++ b/android/assets/jsons/Civ V - Vanilla/Eras.json @@ -10,6 +10,7 @@ "startingMilitaryUnitCount": 1, "startingMilitaryUnit": "Warrior", "settlerPopulation": 1, + "baseUnitBuyCost": 200, "iconRGB": [255, 87, 35] }, { @@ -22,6 +23,7 @@ "startingGold": 10, "startingCulture": 100, "settlerPopulation": 1, + "baseUnitBuyCost": 200, "iconRGB": [233, 31, 99] }, { @@ -36,6 +38,7 @@ "settlerPopulation": 1, "settlerBuildings": ["Shrine","Monument"], "startingObsoleteWonders": ["Temple of Artemis", "Stonehenge", "The Great Library", "Mausoleum of Halicarnassus", "The Pyramids", "Statue of Zeus"], + "baseUnitBuyCost": 200, "iconRGB": [157, 39, 176] }, { @@ -51,6 +54,7 @@ "settlerBuildings": ["Shrine","Monument","Granary","Lighthouse"], "startingObsoleteWonders": ["Temple of Artemis", "Stonehenge", "The Great Library", "Mausoleum of Halicarnassus", "The Pyramids", "Statue of Zeus", "The Great Lighthouse", "Hanging Gardens", "Terracotta Army", "The Oracle", "Petra", "Great Wall", "Colossus"], + "baseUnitBuyCost": 300, "iconRGB": [104, 58, 183] }, { @@ -67,6 +71,7 @@ "startingObsoleteWonders": ["Temple of Artemis", "Stonehenge", "The Great Library", "Mausoleum of Halicarnassus", "The Pyramids", "Statue of Zeus", "The Great Lighthouse", "Hanging Gardens", "Terracotta Army", "The Oracle", "Petra", "Great Wall", "Colossus", "Hagia Sophia", "Chichen Itza", "Machu Picchu", "Angkor Wat", "Alhambra", "Notre Dame"], + "baseUnitBuyCost": 400, "iconRGB": [63, 81, 182] }, { @@ -84,6 +89,7 @@ "The Great Lighthouse", "Hanging Gardens", "Terracotta Army", "The Oracle", "Petra", "Great Wall", "Colossus", "Hagia Sophia", "Chichen Itza", "Machu Picchu", "Angkor Wat", "Alhambra", "Notre Dame", "Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin"], + "baseUnitBuyCost": 600, "iconRGB": [33, 150, 243] }, { @@ -102,6 +108,7 @@ "Hagia Sophia", "Chichen Itza", "Machu Picchu", "Angkor Wat", "Alhambra", "Notre Dame", "Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin", "The Louvre", "Big Ben", "Brandenburg Gate"], + "baseUnitBuyCost": 800, "iconRGB": [0, 150, 136] }, { @@ -121,6 +128,7 @@ "Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin", "The Louvre", "Big Ben", "Brandenburg Gate", "Eiffel Tower", "Statue of Liberty", "Neuschwanstein", "Cristo Redentor"], + "baseUnitBuyCost": 1000, // So theoretically this is always just all the wonders at least 2 eras old. So we could just use that. // But where is the modularity? The excluding of very specific wonders? That is no fun. // So we just write down the entire long list (sorted by era!) instead. @@ -145,6 +153,7 @@ "Sistine Chapel", "Forbidden Palace", "Leaning Tower of Pisa", "Himeji Castle", "Taj Mahal", "Porcelain Tower", "Kremlin", "The Louvre", "Big Ben", "Brandenburg Gate", "Eiffel Tower", "Statue of Liberty", "Neuschwanstein", "Cristo Redentor"], + "baseUnitBuyCost": 1000, "iconRGB": [76, 176, 81] } ] \ No newline at end of file diff --git a/android/assets/jsons/Civ V - Vanilla/Nations.json b/android/assets/jsons/Civ V - Vanilla/Nations.json index 822bf81546..2db30bd6e1 100644 --- a/android/assets/jsons/Civ V - Vanilla/Nations.json +++ b/android/assets/jsons/Civ V - Vanilla/Nations.json @@ -712,7 +712,7 @@ "innerColor": [255, 255, 255], "uniqueName": "Dutch East India Company", "uniques": ["Retain [50]% of the happiness from a luxury after the last copy has been traded away"], - "cities": ["Amsterdam", "Rotterdam", "Utrecht", "Groningen", "Breda", "Nijmegen", "Den Haag", "Haarlem", "Arnhem", "Zutphen", "Maastricht", "Tilburg", "Eindhoven", "Dordrecht", "Leiden", "Hertogenbosch", "Almere", "Alkmaar", "Brielle", "Vlissingen", "Apeldoorn", "Enschede", "Amersfoort", "Zwolle", "Venlo", "Uden", "Grave", "Delft", "Gouda", "Nieuwstadt", "Weesp", "Coevorden", "Kerkrade"] + "cities": ["Amsterdam", "Rotterdam", "Utrecht", "Groningen", "Breda", "Nijmegen", "Den Haag", "Haarlem", "Arnhem", "Zutphen", "Maastricht", "Tilburg", "Eindhoven", "Dordrecht", "Leiden", "'s Hertogenbosch", "Almere", "Alkmaar", "Brielle", "Vlissingen", "Apeldoorn", "Enschede", "Amersfoort", "Zwolle", "Venlo", "Uden", "Grave", "Delft", "Gouda", "Nieuwstadt", "Weesp", "Coevorden", "Kerkrade"] }, { diff --git a/android/assets/jsons/Civ V - Vanilla/Policies.json b/android/assets/jsons/Civ V - Vanilla/Policies.json index 728967fe8c..73d2dfcba3 100644 --- a/android/assets/jsons/Civ V - Vanilla/Policies.json +++ b/android/assets/jsons/Civ V - Vanilla/Policies.json @@ -260,7 +260,7 @@ }, { "name": "Mercantilism", - "uniques": ["Cost of purchasing items in cities reduced by [25]%", "[+1 Science] from every [Mint]", "[+1 Science] from every [Market]", + "uniques": ["[Gold] cost of purchasing items in cities [-25]%", "[+1 Science] from every [Mint]", "[+1 Science] from every [Market]", "[+1 Science] from every [Bank]", "[+1 Science] from every [Stock Exchange]"], "requires": ["Trade Unions"], "row": 2, @@ -381,7 +381,7 @@ }, { "name": "Militarism", - "uniques": ["Gold cost of purchasing [All] units -[33]%"], + "uniques": ["[Gold] cost of purchasing [All] units [-33]%"], "row": 1, "column": 5 }, diff --git a/android/assets/jsons/Civ V - Vanilla/Units.json b/android/assets/jsons/Civ V - Vanilla/Units.json index 7cffd0fad5..c70cf17877 100644 --- a/android/assets/jsons/Civ V - Vanilla/Units.json +++ b/android/assets/jsons/Civ V - Vanilla/Units.json @@ -1176,9 +1176,9 @@ "May withdraw before melee ([80]%)", "+[100]% Strength vs [submarine units]"], "attackSound": "shipguns" }, - + // Atomic Era - + { "name": "Marine", "unitType": "Gunpowder", @@ -1375,7 +1375,7 @@ "cost": 425, "requiredTech": "Computers", "requiredResource": "Aluminum", - "uniques": ["+[100]% Strength vs [Armored]", "No defensive terrain bonus", "Can move after attacking", + "uniques": ["+[100]% Strength vs [Armored]", "No defensive terrain bonus", "Can move after attacking", "All tiles cost 1 movement", "Unable to capture cities"], "attackSound": "machinegun" }, @@ -1390,7 +1390,7 @@ "rangedStrength": 85, "cost": 425, "requiredTech": "Telecommunications", - "uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "Can attack submarines", + "uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "Can attack submarines", "[+1] Visibility Range", "Can carry [2] [Missile] units"], "attackSound": "torpedo" }, @@ -1500,7 +1500,7 @@ "name": "Great Prophet", "unitType": "Civilian", "uniques": ["Can construct [Holy site] if it hasn't spread religion yet", "Can spread religion [4] times", - "May found a religion", "Great Person - [Faith]", "Unbuildable", "Hidden when religion is disabled"], + "May found a religion", "Great Person - [Faith]", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"], "movement": 2 }, { @@ -1518,6 +1518,16 @@ "uniques": ["Can start an [8]-turn golden age","Bonus for units in 2 tile radius 15%", "Heal adjacent units for an additional 15 HP per turn", "Can construct [Citadel]", "Great Person - [War]", "Unbuildable"], "movement": 5 + }, + + /* Religious units */ + + { + "name": "Missionary", + "unitType": "Civilian", + "uniques": ["Can spread religion [2] times", "Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]", + "Unbuildable", "Religious Unit"], + "movement": 4 } /* Spaceship Parts */ diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index fa35013dfa..744e302582 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -1,6 +1,6 @@ - -# Tutorial tasks - + +# Tutorial tasks + Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup = Found a city!\nSelect the Settler (flag unit) > Click on 'Found city' (bottom-left corner) = Enter the city screen!\nClick the city button twice = @@ -15,24 +15,24 @@ Create a trade route!\nConstruct roads between your capital and another city\nOr Conquer a city!\nBring an enemy city down to low health > \nEnter the city with a melee unit = Move an air unit!\nSelect an air unit > select another city within range > \nMove the unit to the other city = See your stats breakdown!\nEnter the Overview screen (top right corner) >\nClick on 'Stats' = - + Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send me (yairm210@hotmail.com) an email with the game information (menu -> save game -> copy game info -> paste into email) and I'll try to fix it as fast as I can! = Oh no! It looks like something went DISASTROUSLY wrong! This is ABSOLUTELY not supposed to happen! Please send us an report and we'll try to fix it as fast as we can! = - -# Buildings - + +# Buildings + Unsellable = Not displayed as an available construction unless [building] is built = Not displayed as an available construction without [resource] = - + Choose a free great person = Get [unitName] = - + Hydro Plant = [buildingName] obsoleted = - -# Diplomacy,Trade,Nations - + +# Diplomacy,Trade,Nations + Requires [buildingName] to be built in the city = Requires [buildingName] to be built in all cities = Provides a free [buildingName] in the city = @@ -46,14 +46,16 @@ Cannot be built with [buildingName] = Consumes 1 [resource] = Consumes [amount] [resource] = Required tech: [requiredTech] = -Requires [PolicyOrNationalWonder] = +Requires [PolicyOrNationalWonder] = Cannot be purchased = +Can only be purchased = See also = + Requires at least one of the following: = Requires all of the following: = Leads to [techName] = Leads to: = - + Current construction = Construction queue = Pick a construction = @@ -64,7 +66,7 @@ Show stats drilldown = Show construction queue = Save = Cancel = - + Diplomacy = War = Peace = @@ -92,7 +94,7 @@ Indeed! = Denounce [civName]? = Denounce ([numberOfTurns] turns) = We will remember this. = - + [civName] has declared war on [targetCivName]! = [civName] and [targetCivName] have signed a Peace Treaty! = [civName] and [targetCivName] have signed the Declaration of Friendship! = @@ -100,7 +102,7 @@ We will remember this. = Do you want to break your promise to [leaderName]? = We promised not to settle near them ([count] turns remaining) = They promised not to settle near us ([count] turns remaining) = - + Unforgivable = Enemy = Competitor = @@ -108,12 +110,12 @@ Neutral = Favorable = Friend = Ally = - + [questName] (+[influenceAmount] influence) = [remainingTurns] turns remaining = - -## Diplomatic modifiers - + +## Diplomatic modifiers + You declared war on us! = Your warmongering ways are unacceptable to us. = You have captured our cities! = @@ -136,15 +138,15 @@ Your arrogant demands are in bad taste = Your use of nuclear weapons is disgusting! = You have stolen our lands! = You gave us units! = - + Demands = Please don't settle new cities near us. = Very well, we shall look for new lands to settle. = We shall do as we please. = We noticed your new city near our borders, despite your promise. This will have....implications. = - -# City-States - + +# City-States + Provides [amountOfCulture] culture at 30 Influence = Provides 3 food in capital and 1 food in other cities at 30 Influence = Provides 3 happiness at 30 Influence = @@ -156,7 +158,7 @@ Protected by = Revoke Protection = Pledge to protect = Declare Protection of [cityStateName]? = - + Cultured = Maritime = Mercantile = @@ -170,9 +172,9 @@ Personality = Influence = Reach 30 for friendship. = Reach highest influence above 60 for alliance. = - + # Trades - + Trade = Offer trade = Retract offer = @@ -199,17 +201,17 @@ Declare war on [nation] = Luxury resources = Strategic resources = Owned: [amountOwned] = - + # Nation picker - + [resourceName] not required = Lost ability = National ability = [firstValue] vs [secondValue] = - - -# New game screen - + + +# New game screen + Uniques = Promotions = Load copied data = @@ -240,7 +242,7 @@ Victory Conditions = Scientific = Domination = Cultural = - + Map Shape = Hexagonal = Rectangular = @@ -248,8 +250,9 @@ Height = Width = Radius = Enable Religion = - -Advanced Settings = + +Show advanced settings = +Hide advanced settings = RNG Seed = Map Height = Temperature extremeness = @@ -260,9 +263,9 @@ Max Coast extension = Biome areas extension = Water level = Reset to default = - + Online Multiplayer = - + World Size = Tiny = Small = @@ -273,9 +276,9 @@ World wrap requires a minimum width of 32 tiles = The provided map dimensions were too small = The provided map dimensions were too big = The provided map dimensions had an unacceptable aspect ratio = - + Difficulty = - + AI = Remove = Random = @@ -283,14 +286,14 @@ Human = Hotseat = User ID = Click to copy = - - + + Game Speed = Quick = Standard = Epic = Marathon = - + Starting Era = It looks like we can't make a map with the parameters you requested! = Maybe you put too many players into too small a map? = @@ -298,14 +301,14 @@ No human players selected! = Mods: = Base ruleset mods: = Extension mods: = - + World Wrap = World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes! = Anything above 80 by 50 may work very slowly on Android! = Anything above 40 may work very slowly on Android! = - -# Multiplayer - + +# Multiplayer + Username = Multiplayer = Could not download game! = @@ -341,9 +344,9 @@ Resign = Are you sure you want to resign? = You can only resign if it's your turn = [civName] resigned and is now controlled by AI = - -# Save game menu - + +# Save game menu + Current saves = Show autosaves = Saved game name = @@ -370,9 +373,9 @@ Load from custom location = Could not load game from custom location! = Save to custom location = Could not save game to custom location! = - -# Options - + +# Options + Options = Display options = Gameplay options = @@ -410,9 +413,9 @@ Enable portrait orientation = Generate translation files = Translation files are generated successfully. = Locate mod errors = - -# Notifications - + +# Notifications + Research of [technologyName] has completed! = [construction] has become obsolete and was removed from the queue in [cityName]! = [construction] has become obsolete and was removed from the queue in [amount] cities! = @@ -516,9 +519,9 @@ Our [name] took [tileDamage] tile damage = [civName] has adopted the [policyName] policy = An unknown civilization has adopted the [policyName] policy = Our influence with City-States has started dropping faster! = - -# World Screen UI - + +# World Screen UI + Working... = Waiting for other players... = in = @@ -571,7 +574,7 @@ Yes = No = Acquire = Under construction = - + Food = Production = Gold = @@ -579,7 +582,7 @@ Happiness = Culture = Science = Faith = - + Crop Yield = Territory = Force = @@ -588,7 +591,7 @@ Golden Age = [year] BC = [year] AD = Civilopedia = - + Start new game = Save game = Load game = @@ -604,9 +607,9 @@ Close = Do you want to exit the game? = Start bias: = Avoid [terrain] = - -# City screen - + +# City screen + Exit city = Raze city = Stop razing city = @@ -654,9 +657,9 @@ Worked by [cityName] = Lock = Unlock = Move to city = - -# Technology UI - + +# Technology UI + Pick a tech = Pick a free tech = Research [technology] = @@ -679,9 +682,9 @@ Attack = Bombard = NUKE = Captured! = - -# Battle modifier categories - + +# Battle modifier categories + defence vs ranged = [percentage] to unit defence = Attacker Bonus = @@ -703,11 +706,11 @@ defence vs [unitType] = [tileFilter] defence = Defensive Bonus = Stacked with [unitType] = - + The following improvements [stats]: = The following improvements on [tileType] tiles [stats]: = - - + + Hurry Research = Conduct Trade Mission = Your trade mission to [civName] has earned you [goldAmount] gold and [influenceAmount] influence! = @@ -726,9 +729,9 @@ Policies = Base happiness = Occupied City = Buildings = - -# terrainFilters (so for uniques like: "[stats] from [terrainFilter] tiles") - + +# terrainFilters (so for uniques like: "[stats] from [terrainFilter] tiles") + All = Water = Land = @@ -747,15 +750,15 @@ Strategic resource = Fresh water = non-fresh water = Natural Wonder = - -# improvementFilters - + +# improvementFilters + All = All Road = Great Improvement = Great = - - + + Wonders = Base values = Bonuses = @@ -781,9 +784,9 @@ Known and defeated ([numberOfCivs]) = Tiles = Natural Wonders = Treasury deficit = - -# Victory - + +# Victory + Science victory = Cultural victory = Conquest victory = @@ -802,7 +805,7 @@ The world has been convulsed by war. Many great and powerful civilizations have You have achieved victory through mastery of Science! You have conquered the mysteries of nature and led your people on a voyage to a brave new world! Your triumph will be remembered as long as the stars burn in the night sky! = Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself! = You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory! = -You have triumphed over your foes through the art of diplomacy! Your cunning and wisdom have earned you great friends - and divided and sown confusion among your enemies! Forever will you be remembered as the leader who brought peace to this weary world! = +You have triumphed over your foes through the art of diplomacy! Your cunning and wisdom have earned you great friends - and divided and sown confusion among your enemies! Forever will you be remembered as the leader who brought peace to this weary world! = One more turn...! = Built Apollo Program = Destroy [civName] = @@ -812,8 +815,8 @@ Rankings = Spaceship parts remaining = Branches completed = Undefeated civs = - # The \n here means: put a newline (enter) here. If this is omitted, the sidebox in the diplomacy overview will become _really_ wide. - # Feel free to replace it with a space and put it somewhere else in your translation + # The \n here means: put a newline (enter) here. If this is omitted, the sidebox in the diplomacy overview will become _really_ wide. + # Feel free to replace it with a space and put it somewhere else in your translation Turns until the next\ndiplomacy victory vote: [amount] = Choose a civ to vote for = Choose who should become the world leader and win a diplomatic victory! = @@ -822,9 +825,9 @@ Vote for [civilizationName] = Continue = Abstained = Vote for World Leader = - -# Capturing a city - + +# Capturing a city + What would you like to do with the city? = Annex = Annexed cities become part of your regular empire. = @@ -844,14 +847,14 @@ Destroying the city instantly razes the city to the ground. = Remove your troops in our border immediately! = Sorry. = Never! = - + Offer Declaration of Friendship ([30] turns) = My friend, shall we declare our friendship to the world? = Sign Declaration of Friendship ([30] turns) = We are not interested. = We have signed a Declaration of Friendship with [otherCiv]! = [otherCiv] has denied our Declaration of Friendship! = - + Basics = Resources = Terrains = @@ -920,8 +923,8 @@ Terrain feature [feature] does not exist in ruleset! = Resource [resource] does not exist in ruleset! = Improvement [improvement] does not exist in ruleset! = Change map to fit selected ruleset? = - -# Civilopedia difficulty levels + +# Civilopedia difficulty levels Player settings = Base Happiness = Happiness per luxury = @@ -931,7 +934,7 @@ Building cost modifier = Policy cost modifier = Unhappiness modifier = Bonus vs. Barbarians = - + AI settings = AI city growth modifier = AI unit cost modifier = @@ -940,14 +943,14 @@ AI wonder cost modifier = AI building maintenance modifier = AI unit maintenance modifier = AI unhappiness modifier = - + Turns until barbarians enter player tiles = Gold reward for clearing barbarian camps = - -# Other civilopedia things + +# Other civilopedia things Nations = Available for [unitTypes] = -Available for: = +Available for: = Free promotion: = Free promotions: = Free for [units] = @@ -956,17 +959,17 @@ Granted by [param] = Granted by: = [bonus] with [tech] = Difficulty levels = - -# Policies - + +# Policies + Adopt policy = Adopt free policy = Unlocked at = Gain 2 free technologies = All policies adopted = - -# Religions - + +# Religions + Choose an Icon and name for your Religion = Choose a [beliefType] belief! = Found [religionName] = @@ -975,14 +978,14 @@ Found Religion = Found Pantheon = Follow [belief] = Religions and Beliefs = - -# Terrains - + +# Terrains + Impassable = Rare feature = - -# Resources - + +# Resources + Bison = Copper = Cocoa = @@ -992,9 +995,9 @@ Truffles = Strategic = Bonus = Luxury = - -# Unit types - + +# Unit types + City = Civilian = Melee = @@ -1003,21 +1006,21 @@ Scout = Mounted = Armor = Siege = - + WaterCivilian = WaterMelee = WaterRanged = WaterSubmarine = WaterAircraftCarrier = - + Fighter = Bomber = AtomicBomber = Missile = - - + + # Unit filters and other unit related things - + Air = air units = All = @@ -1038,13 +1041,13 @@ Water = water units = wounded units = Wounded = - -# For the All "newly-trained [relevant] units in this city receive the [] promotion" translation. Relevant as in 'units that can receive' + +# For the All "newly-trained [relevant] units in this city receive the [] promotion" translation. Relevant as in 'units that can receive' relevant = - - -# Promotions - + + +# Promotions + Pick promotion = OR = units in open terrain = @@ -1058,9 +1061,9 @@ Dogfighting II = Dogfighting III = Choose name for [unitName] = [unitFilter] units gain the [promotion] promotion = - -# Multiplayer Turn Checker Service - + +# Multiplayer Turn Checker Service + Multiplayer options = Enable out-of-game turn notifications = Time between turn checks out-of-game (in minutes) = @@ -1069,10 +1072,10 @@ Take user ID from clipboard = Doing this will reset your current user ID to the clipboard contents - are you sure? = ID successfully set! = Invalid ID! = - - -# Mods - + + +# Mods + Mods = Download [modName] = Update [modName] = @@ -1100,9 +1103,9 @@ No description provided = Author: [author] = Size: [size] kB = The mod you selected is incompatible with the defined ruleset! = - -# Uniques that are relevant to more than one type of game object - + +# Uniques that are relevant to more than one type of game object + [stats] from every [param] = [stats] from [param] tiles in this city = [stats] from every [param] on [tileFilter] tiles = @@ -1116,8 +1119,8 @@ Can only be built on [tileFilter] tiles = Cannot be built on [tileFilter] tiles = Does not need removal of [feature] = Gain a free [building] [cityFilter] = - -# City filters + +# City filters in this city = in all cities = in all coastal cities = @@ -1127,3 +1130,4 @@ in all cities with a world wonder = in all cities connected to capital = in all cities with a garrison = + diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index fcf47cdb5c..54b8313e9e 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -2,6 +2,9 @@ package com.unciv.logic.automation import com.unciv.Constants import com.unciv.logic.city.CityInfo +import com.unciv.logic.city.IConstruction +import com.unciv.logic.city.INonPerpetualConstruction +import com.unciv.logic.city.PerpetualConstruction import com.unciv.logic.civilization.* import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers @@ -15,6 +18,7 @@ import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.tech.Technology import com.unciv.models.ruleset.unit.BaseUnit +import com.unciv.models.stats.Stat import com.unciv.models.translations.tr import kotlin.math.min @@ -136,8 +140,9 @@ object NextTurnAutomation { for (city in civInfo.cities.sortedByDescending { it.population.population }) { val construction = city.cityConstructions.getCurrentConstruction() - if (construction.canBePurchased() - && city.civInfo.gold / 3 >= construction.getGoldCost(civInfo)) { + if (construction is PerpetualConstruction) continue + if ((construction as INonPerpetualConstruction).canBePurchasedWithStat(city, Stat.Gold) + && city.civInfo.gold / 3 >= construction.getStatBuyCost(city, Stat.Gold)!!) { city.cityConstructions.purchaseConstruction(construction.name, 0, true) } } diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index 458a21628c..e134c25d32 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -8,6 +8,7 @@ import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.UniqueMap import com.unciv.models.ruleset.unit.BaseUnit +import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaCategories @@ -152,15 +153,19 @@ class CityConstructions { } + /** @constructionName needs to be a non-perpetual construction, else a cost of -1 is inferred */ internal fun getTurnsToConstructionString(constructionName: String, useStoredProduction:Boolean = true): String { val construction = getConstruction(constructionName) - val cost = construction.getProductionCost(cityInfo.civInfo) + val cost = + if (construction is INonPerpetualConstruction) construction.getProductionCost(cityInfo.civInfo) + else -1 // This could _should_ never be reached val turnsToConstruction = turnsToConstruction(constructionName, useStoredProduction) val currentProgress = if (useStoredProduction) getWorkDone(constructionName) else 0 if (currentProgress == 0) return "\n$cost${Fonts.production} $turnsToConstruction${Fonts.turn}" else return "\n$currentProgress/$cost${Fonts.production}\n$turnsToConstruction${Fonts.turn}" } + // This function appears unused, can it be removed? fun getProductionForTileInfo(): String { /* this is because there were rare errors that I assume were caused because currentConstruction changed on another thread */ @@ -246,8 +251,8 @@ class CityConstructions { val constr = getConstruction(constructionName) return when { constr is PerpetualConstruction -> 0 - useStoredProduction -> constr.getProductionCost(cityInfo.civInfo) - getWorkDone(constructionName) - else -> constr.getProductionCost(cityInfo.civInfo) + useStoredProduction -> (constr as INonPerpetualConstruction).getProductionCost(cityInfo.civInfo) - getWorkDone(constructionName) + else -> (constr as INonPerpetualConstruction).getProductionCost(cityInfo.civInfo) } } @@ -315,7 +320,7 @@ class CityConstructions { val construction = getConstruction(currentConstructionFromQueue) if (construction is PerpetualConstruction) chooseNextConstruction() // check every turn if we could be doing something better, because this doesn't end by itself else { - val productionCost = construction.getProductionCost(cityInfo.civInfo) + val productionCost = (construction as INonPerpetualConstruction).getProductionCost(cityInfo.civInfo) if (inProgressConstructions.containsKey(currentConstructionFromQueue) && inProgressConstructions[currentConstructionFromQueue]!! >= productionCost) { productionOverflow = inProgressConstructions[currentConstructionFromQueue]!! - productionCost @@ -481,14 +486,25 @@ class CityConstructions { * Note: -1 does not guarantee queue will remain unchanged (validation) * @param automatic Flag whether automation should try to choose what next to build (not coming from UI) * Note: settings.autoAssignCityProduction is handled later + * @param stat Stat object of the stat with which was paid for the construction * @return Success (false e.g. unit cannot be placed */ - fun purchaseConstruction(constructionName: String, queuePosition: Int, automatic: Boolean): Boolean { + fun purchaseConstruction( + constructionName: String, + queuePosition: Int, + automatic: Boolean, + stat: Stat = Stat.Gold + ): Boolean { if (!getConstruction(constructionName).postBuildEvent(this, true)) return false // nothing built - no pay - if (!cityInfo.civInfo.gameInfo.gameParameters.godMode) - cityInfo.civInfo.addGold(-getConstruction(constructionName).getGoldCost(cityInfo.civInfo)) + if (!cityInfo.civInfo.gameInfo.gameParameters.godMode) { + val construction = getConstruction(constructionName) + if (construction is PerpetualConstruction) return false + val constructionCost = (construction as INonPerpetualConstruction).getStatBuyCost(cityInfo, stat) + if (constructionCost == null) return false // We should never end up here anyway, so things have already gone _way_ wrong + cityInfo.addStat(stat, -1 * constructionCost) + } if (queuePosition in 0 until constructionQueue.size) removeFromQueue(queuePosition, automatic) diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 27451789fa..5a00b21383 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -12,6 +12,7 @@ import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unit.BaseUnit +import com.unciv.models.stats.Stat import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -398,6 +399,21 @@ class CityInfo { gppCounter.add(entry) return gppCounter } + + fun addStat(stat: Stat, amount: Int) { + when (stat) { + Stat.Production -> cityConstructions.addProductionPoints(amount) + Stat.Food -> population.foodStored += amount + else -> civInfo.addStat(stat, amount) + } + } + + fun getStatReserve(stat: Stat): Int { + return when (stat) { + Stat.Food -> population.foodStored + else -> civInfo.getStatReserve(stat) + } + } internal fun getMaxHealth() = 200 + cityConstructions.getBuiltBuildings().sumBy { it.cityHealth } @@ -554,7 +570,7 @@ class CityInfo { otherCiv.getDiplomacyManager(civInfo).setFlag(DiplomacyFlags.SettledCitiesNearUs, 30) } - fun canPurchase(construction: IConstruction): Boolean { + fun canPurchase(construction: INonPerpetualConstruction): Boolean { if (construction is BaseUnit) { val tile = getCenterTile() if (construction.isCivilian()) @@ -576,6 +592,9 @@ class CityInfo { "in all cities with a world wonder" -> cityConstructions.getBuiltBuildings().any { it.isWonder } "in all cities connected to capital" -> isConnectedToCapital() "in all cities with a garrison" -> getCenterTile().militaryUnit != null + "in all cities in which the majority religion is a major religion" -> + religion.getMajorityReligion() != null + && civInfo.gameInfo.religions[religion.getMajorityReligion()]!!.isMajorReligion() else -> false } } diff --git a/core/src/com/unciv/logic/city/CityReligion.kt b/core/src/com/unciv/logic/city/CityReligion.kt index 9940c7ca21..f94db989b9 100644 --- a/core/src/com/unciv/logic/city/CityReligion.kt +++ b/core/src/com/unciv/logic/city/CityReligion.kt @@ -43,8 +43,8 @@ class CityInfoReligionManager: Counter() { val followersPerReligion = getNumberOfFollowers() if (followersPerReligion.isEmpty()) return null val religionWithMaxFollowers = followersPerReligion.maxByOrNull { it.value }!! - if (religionWithMaxFollowers.value >= cityInfo.population.population) return religionWithMaxFollowers.key - else return null + return if (religionWithMaxFollowers.value >= cityInfo.population.population) religionWithMaxFollowers.key + else null } fun getAffectedBySurroundingCities() { diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index 5101a79bae..a58adfb427 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -1,23 +1,78 @@ package com.unciv.logic.city import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.models.ruleset.Unique import com.unciv.models.stats.INamed +import com.unciv.models.stats.Stat import com.unciv.ui.utils.Fonts +import kotlin.math.pow import kotlin.math.roundToInt interface IConstruction : INamed { - fun getProductionCost(civInfo: CivilizationInfo): Int - fun getGoldCost(civInfo: CivilizationInfo): Int fun isBuildable(cityConstructions: CityConstructions): Boolean fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean = false): Boolean // Yes I'm hilarious. fun getResourceRequirements(): HashMap - fun canBePurchased(): Boolean +} + +interface INonPerpetualConstruction : IConstruction, INamed { + val hurryCostModifier: Int + val uniqueObjects: List + val uniques: List + + fun getProductionCost(civInfo: CivilizationInfo): Int + fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? + + private fun getMatchingUniques(uniqueTemplate: String): Sequence { + return uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate } + } + + fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean = false): Boolean { + if (stat in listOf(Stat.Production, Stat.Happiness)) return false + if ("Cannot be purchased" in uniques) return false + if (stat == Stat.Gold) return !uniques.contains("Unbuildable") + // Can be purchased with [Stat] [cityFilter] + if (getMatchingUniques("Can be purchased with [] []") + .any { it.params[0] == stat.name && (ignoreCityRequirements || cityInfo.matchesFilter(it.params[1])) } + ) return true + // Can be purchased for [amount] [Stat] [cityFilter] + if (getMatchingUniques("Can be purchased for [] [] []") + .any { it.params[1] == stat.name && ( ignoreCityRequirements || cityInfo.matchesFilter(it.params[2])) } + ) return true + return false + } + + fun canBePurchasedWithAnyStat(cityInfo: CityInfo): Boolean { + return Stat.values().any { canBePurchasedWithStat(cityInfo, it) } + } + + fun getBaseGoldCost(civInfo: CivilizationInfo): Double { + // https://forums.civfanatics.com/threads/rush-buying-formula.393892/ + return (30.0 * getProductionCost(civInfo)).pow(0.75) * (1 + hurryCostModifier / 100f) + } + + // I can't make this function protected or private :( + fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { + if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt() + + // Can be purchased for [amount] [Stat] [cityFilter] + val lowestCostUnique = getMatchingUniques("Can be purchased for [] [] []") + .filter { it.params[1] == stat.name && cityInfo.matchesFilter(it.params[2]) } + .minByOrNull { it.params[0].toInt() } + if (lowestCostUnique != null) return lowestCostUnique.params[0].toInt() + + // Can be purchased with [Stat] [cityFilter] + if (getMatchingUniques("Can be purchased with [] []") + .any { it.params[0] == stat.name && cityInfo.matchesFilter(it.params[1])} + ) return cityInfo.civInfo.gameInfo.ruleSet.eras[cityInfo.civInfo.getEra()]!!.baseUnitBuyCost + return null + } } open class PerpetualConstruction(override var name: String, val description: String) : IConstruction { + override fun shouldBeDisplayed(cityConstructions: CityConstructions) = isBuildable(cityConstructions) open fun getProductionTooltip(cityInfo: CityInfo) : String = "\r\n${(cityInfo.cityStats.currentCityStats.production / CONVERSION_RATE).roundToInt()}/${Fonts.turn}" @@ -50,12 +105,6 @@ open class PerpetualConstruction(override var name: String, val description: Str = mapOf(science.name to science, gold.name to gold, idle.name to idle) } - override fun canBePurchased() = false - - override fun getProductionCost(civInfo: CivilizationInfo) = throw Exception("Impossible!") - - override fun getGoldCost(civInfo: CivilizationInfo) = throw Exception("Impossible!") - override fun isBuildable(cityConstructions: CityConstructions): Boolean = throw Exception("Impossible!") diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 348841961e..2af535de0f 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -705,6 +705,19 @@ class CivilizationInfo { // Happiness cannot be added as it is recalculated again, use a unique instead } } + + fun getStatReserve(stat: Stat): Int { + return when (stat) { + Stat.Culture -> policies.storedCulture + Stat.Science -> { + if (tech.currentTechnology() == null) 0 + else tech.remainingScienceToTech(tech.currentTechnology()!!.name) + } + Stat.Gold -> gold + Stat.Faith -> religionManager.storedFaith + else -> 0 + } + } fun getGreatPersonPointsForNextTurn(): Counter { val greatPersonPoints = Counter() diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index c375e99701..33b84b0fd6 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -1,8 +1,9 @@ package com.unciv.models.ruleset +import com.unciv.UncivGame import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.CityInfo -import com.unciv.logic.city.IConstruction +import com.unciv.logic.city.INonPerpetualConstruction import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.Counter import com.unciv.models.ruleset.tile.TileImprovement @@ -17,10 +18,9 @@ import com.unciv.ui.utils.Fonts import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap -import kotlin.math.pow -class Building : NamedStats(), IConstruction, ICivilopediaText { +class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { var requiredTech: String? = null @@ -45,7 +45,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { var greatPersonPoints= Counter() /** Extra cost percentage when purchasing */ - private var hurryCostModifier = 0 + override var hurryCostModifier = 0 var isWonder = false var isNationalWonder = false fun isAnyWonder() = isWonder || isNationalWonder @@ -68,9 +68,9 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { var quote: String = "" @Deprecated("As of 3.15.16 - replaced with 'Provides a free [buildingName] [cityFilter]'") var providesFreeBuilding: String? = null - var uniques = ArrayList() + override var uniques = ArrayList() var replacementTextForUniques = "" - val uniqueObjects: List by lazy { uniques.map { Unique(it) } } + override val uniqueObjects: List by lazy { uniques.map { Unique(it) } } override var civilopediaText = listOf() @@ -101,7 +101,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { if (!tileBonusHashmap.containsKey(stats)) tileBonusHashmap[stats] = ArrayList() tileBonusHashmap[stats]!!.add(unique.params[1]) } - unique.placeholderText == "Consumes [] []" -> Unit // skip these, + unique.placeholderText == "Consumes [] []" -> Unit // skip these, else -> yield(unique.text) } for ((key, value) in tileBonusHashmap) @@ -173,7 +173,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { if (!isWonder) for (unique in city.getMatchingUniques("[] from all [] buildings")) { - if (isStatRelated(Stat.valueOf(unique.params[1]))) + if (matchesFilter(unique.params[1])) stats.add(unique.stats) } else @@ -199,13 +199,19 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { return stats } + override fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean): Boolean { + if (stat == Stat.Gold && isAnyWonder()) return false + return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) + } + + override fun getCivilopediaTextHeader() = FormattedLine(name, header=2, icon=makeLink()) override fun makeLink() = if (isAnyWonder()) "Wonder/$name" else "Building/$name" override fun hasCivilopediaTextLines() = true override fun replacesCivilopediaDescription() = true override fun getCivilopediaTextLines(ruleset: Ruleset): List { fun Float.formatSignedInt() = (if (this > 0f) "+" else "") + this.toInt().toString() - + val textList = ArrayList() if (isAnyWonder()) { @@ -223,7 +229,9 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { if (cost > 0) { val stats = mutableListOf("$cost${Fonts.production}") - if (canBePurchased()) stats += "${(getBaseGoldCost()*0.1).toInt()*10}${Fonts.gold}" + if (canBePurchasedWithStat(CityInfo(), Stat.Gold, true)) { + stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}" + } textList += FormattedLine(stats.joinToString(", ", "{Cost}: ")) } @@ -323,12 +331,6 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { return textList } - - override fun canBePurchased(): Boolean { - return !isAnyWonder() && "Cannot be purchased" !in uniques - } - - override fun getProductionCost(civInfo: CivilizationInfo): Int { var productionCost = cost.toFloat() @@ -351,24 +353,34 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { return productionCost.toInt() } - private fun getBaseGoldCost() = (30.0 * cost).pow(0.75) * (1 + hurryCostModifier / 100.0) + override fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? { + var cost = getBaseBuyCost(cityInfo, stat)?.toDouble() + if (cost == null) return null - override fun getGoldCost(civInfo: CivilizationInfo): Int { - // https://forums.civfanatics.com/threads/rush-buying-formula.393892/ - var cost = (30 * getProductionCost(civInfo)).toDouble().pow(0.75) * (1 + hurryCostModifier / 100f) + // Deprecated since 3.15.15 + if (stat == Stat.Gold) { + for (unique in cityInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%")) + cost *= 1 - (unique.params[0].toFloat() / 100) - for (unique in civInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%")) - cost *= 1 - (unique.params[0].toFloat() / 100) + for (unique in cityInfo.getMatchingUniques("Cost of purchasing [] buildings reduced by []%")) { + if (matchesFilter(unique.params[0])) + cost *= 1 - (unique.params[1].toFloat() / 100) + } + } + // - for (unique in civInfo.getMatchingUniques("Cost of purchasing [] buildings reduced by []%")) { - if (isStatRelated(Stat.valueOf(unique.params[0]))) - cost *= 1 - (unique.params[1].toFloat() / 100) + for (unique in cityInfo.getMatchingUniques("[] cost of purchasing items in cities []%")) + if (stat.name == unique.params[0]) + cost *= 1 + (unique.params[1].toFloat() / 100) + + for (unique in cityInfo.getMatchingUniques("[] cost of purchasing [] buildings []%")) { + if (stat.name == unique.params[0] && matchesFilter(unique.params[1])) + cost *= 1 + (unique.params[2].toFloat() / 100) } - return (cost / 10).toInt() * 10 + return (cost / 10f).toInt() * 10 } - override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean { if (cityConstructions.isBeingConstructedOrEnqueued(name)) return false @@ -377,22 +389,29 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { || rejectionReason.startsWith("Requires") || rejectionReason.startsWith("Consumes") || rejectionReason.endsWith("Wonder is being built elsewhere") + || rejectionReason == "Can only be purchased" } fun getRejectionReason(construction: CityConstructions): String { if (construction.isBuilt(name)) return "Already built" // for buildings that are created as side effects of other things, and not directly built - if (uniques.contains("Unbuildable")) return "Unbuildable" + // unless they can be bought with faith + if (uniques.contains("Unbuildable")) { + if (canBePurchasedWithAnyStat(construction.cityInfo)) + return "Can only be purchased" + return "Unbuildable" + } val cityCenter = construction.cityInfo.getCenterTile() val civInfo = construction.cityInfo.civInfo // This overrides the others - if (uniqueObjects.any { - it.placeholderText == "Not displayed as an available construction unless [] is built" - && !construction.containsBuildingOrEquivalent(it.params[0]) - }) - return "Should not be displayed" + if (uniqueObjects + .any { + it.placeholderText == "Not displayed as an available construction unless [] is built" + && !construction.containsBuildingOrEquivalent(it.params[0]) + } + ) return "Should not be displayed" for (unique in uniqueObjects.filter { it.placeholderText == "Not displayed as an available construction without []" }) { val filter = unique.params[0] @@ -437,7 +456,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { if (civInfo.isCityState()) return "No world wonders for city-states" - + val ruleSet = civInfo.gameInfo.ruleSet val startingEra = civInfo.gameInfo.gameParameters.startingEra if (startingEra in ruleSet.eras && name in ruleSet.eras[startingEra]!!.startingObsoleteWonders) @@ -602,7 +621,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { return true } - + fun matchesFilter(filter: String): Boolean { return when (filter) { "All" -> true @@ -612,6 +631,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { replaces -> true else -> { if (uniques.contains(filter)) return true + if (isStats(filter) && isStatRelated(Stat.valueOf(filter))) return true return false } } diff --git a/core/src/com/unciv/models/ruleset/Era.kt b/core/src/com/unciv/models/ruleset/Era.kt index 59031f1b0e..679c21e74c 100644 --- a/core/src/com/unciv/models/ruleset/Era.kt +++ b/core/src/com/unciv/models/ruleset/Era.kt @@ -18,6 +18,7 @@ class Era : INamed { var settlerPopulation = 1 var settlerBuildings = ArrayList() var startingObsoleteWonders = ArrayList() + var baseUnitBuyCost = 200 var iconRGB: List? = null fun getStartingUnits(): List { diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 57e0114ab7..34424ee0ba 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -461,7 +461,7 @@ object RulesetCache : HashMap() { } - fun getBaseRuleset() = this[BaseRuleset.Civ_V_Vanilla.fullName]!!.clone() // safeguard, o no-one edits the base ruleset by mistake + fun getBaseRuleset() = this[BaseRuleset.Civ_V_Vanilla.fullName]!!.clone() // safeguard, so no-one edits the base ruleset by mistake fun getComplexRuleset(mods: LinkedHashSet): Ruleset { val newRuleset = Ruleset() diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 4881c6dff2..aa4af5af83 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -1,15 +1,18 @@ package com.unciv.models.ruleset.unit import com.unciv.Constants +import com.unciv.UncivGame import com.unciv.logic.city.CityConstructions -import com.unciv.logic.city.IConstruction +import com.unciv.logic.city.CityInfo +import com.unciv.logic.city.INonPerpetualConstruction import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Unique import com.unciv.models.stats.INamed +import com.unciv.models.stats.Stat import com.unciv.models.translations.tr -import com.unciv.ui.civilopedia.ICivilopediaText +import com.unciv.ui.civilopedia.CivilopediaText import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts import java.util.* @@ -22,11 +25,11 @@ import kotlin.math.pow /** This is the basic info of the units, as specified in Units.json, in contrast to MapUnit, which is a specific unit of a certain type that appears on the map */ -class BaseUnit : INamed, IConstruction, ICivilopediaText { +class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() { override lateinit var name: String var cost: Int = 0 - var hurryCostModifier: Int = 0 + override var hurryCostModifier: Int = 0 var movement: Int = 0 var strength: Int = 0 var rangedStrength: Int = 0 @@ -36,8 +39,8 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { fun getType() = ruleset.unitTypes[unitType]!! var requiredTech: String? = null var requiredResource: String? = null - var uniques = HashSet() - val uniqueObjects: List by lazy { uniques.map { Unique(it) } } + override var uniques = ArrayList() // Can not be a hashset as that would remove doubles + override val uniqueObjects: List by lazy { uniques.map { Unique(it) } } var replacementTextForUniques = "" var promotions = HashSet() var obsoleteTech: String? = null @@ -45,7 +48,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { var replaces: String? = null var uniqueTo: String? = null var attackSound: String? = null - + lateinit var ruleset: Ruleset override var civilopediaText = listOf() @@ -110,7 +113,8 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { if (cost > 0) { stats.clear() stats += "$cost${Fonts.production}" - if (canBePurchased()) stats += "${(getBaseGoldCost()*0.1).toInt()*10}${Fonts.gold}" + if (canBePurchasedWithStat(CityInfo(), Stat.Gold, true)) + stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}" textList += FormattedLine(stats.joinToString(", ", "{Cost}: ")) } @@ -174,7 +178,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { return textList } - + fun getMapUnit(ruleset: Ruleset): MapUnit { val unit = MapUnit() unit.name = name @@ -184,8 +188,6 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { return unit } - override fun canBePurchased() = "Cannot be purchased" !in uniques - override fun getProductionCost(civInfo: CivilizationInfo): Int { var productionCost = cost.toFloat() if (civInfo.isCityState()) @@ -198,33 +200,44 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { return productionCost.toInt() } - private fun getBaseGoldCost() = (30.0 * cost).pow(0.75) * (1 + hurryCostModifier / 100.0) - private fun getGoldCostWithGameSpeed(civInfo: CivilizationInfo) = - getBaseGoldCost() * civInfo.gameInfo.gameParameters.gameSpeed.modifier - - override fun getGoldCost(civInfo: CivilizationInfo): Int { - var cost = getGoldCostWithGameSpeed(civInfo) - for (unique in civInfo.getMatchingUniques("Gold cost of purchasing [] units -[]%")) { - if (matchesFilter(unique.params[0])) - cost *= 1f - unique.params[1].toFloat() / 100f - } + override fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? { + var cost = getBaseBuyCost(cityInfo, stat)?.toDouble() + if (cost == null) return null // Deprecated since 3.15 - if (civInfo.hasUnique("Gold cost of purchasing units -33%")) cost *= 0.67f + if (stat == Stat.Gold && cityInfo.civInfo.hasUnique("Gold cost of purchasing units -33%")) cost *= 0.67f // - for (unique in civInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%")) - cost *= 1f - (unique.params[0].toFloat() / 100f) - return (cost / 10).toInt() * 10 // rounded down to nearest ten + // Deprecated since 3.15.15 + if (stat == Stat.Gold) { + for (unique in cityInfo.getMatchingUniques("Gold cost of purchasing [] units -[]%")) { + if (matchesFilter(unique.params[0])) + cost *= 1f - unique.params[1].toFloat() / 100f + } + for (unique in cityInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%")) + cost *= 1f - (unique.params[0].toFloat() / 100f) + } + // + + for (unique in cityInfo.getMatchingUniques("[] cost of purchasing [] units []%")) { + if (stat.name == unique.params[0] && matchesFilter(unique.params[1])) + cost *= 1f + unique.params[2].toFloat() / 100f + } + for (unique in cityInfo.getMatchingUniques("[] cost of purchasing items in cities []%")) + if (stat.name == unique.params[0]) + cost *= 1f + (unique.params[1].toFloat() / 100f) + + return (cost / 10f).toInt() * 10 } - fun getDisbandGold(civInfo: CivilizationInfo) = getGoldCostWithGameSpeed(civInfo).toInt() / 20 + fun getDisbandGold(civInfo: CivilizationInfo) = getBaseGoldCost(civInfo).toInt() / 20 - override fun shouldBeDisplayed(construction: CityConstructions): Boolean { - val rejectionReason = getRejectionReason(construction) + override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean { + val rejectionReason = getRejectionReason(cityConstructions) return rejectionReason == "" || rejectionReason.startsWith("Requires") || rejectionReason.startsWith("Consumes") + || rejectionReason == "Can only be purchased" } fun getRejectionReason(cityConstructions: CityConstructions): String { @@ -238,7 +251,11 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { return "Should not be displayed" } val civRejectionReason = getRejectionReason(civInfo) - if (civRejectionReason != "") return civRejectionReason + if (civRejectionReason != "") { + if (civRejectionReason == "Unbuildable" && canBePurchasedWithAnyStat(cityConstructions.cityInfo)) + return "Can only be purchased" + return civRejectionReason + } for (unique in uniqueObjects.filter { it.placeholderText == "Requires at least [] population" }) if (unique.params[0].toInt() > cityConstructions.cityInfo.population.population) return unique.text @@ -293,6 +310,10 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { if (wasBought && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique("Can move immediately once bought")) unit.currentMovement = 0f + if (unit.hasUnique("Religious Unit")) { + unit.religion = cityConstructions.cityInfo.religion.getMajorityReligion() + } + if (this.isCivilian()) return true // tiny optimization makes save files a few bytes smaller var XP = cityConstructions.getBuiltBuildings().sumBy { it.xpForNewUnits } @@ -313,7 +334,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { } unit.promotions.XP = XP - for (unique in + for (unique in cityConstructions.cityInfo.getMatchingUniques("All newly-trained [] units [] receive the [] promotion") .filter { cityConstructions.cityInfo.matchesFilter(it.params[1]) } + // Deprecated since 3.15.9 @@ -323,13 +344,13 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { val filter = unique.params[0] val promotion = unique.params.last() - if (unit.matchesFilter(filter) || + if (unit.matchesFilter(filter) || ( - filter == "relevant" && + filter == "relevant" && civInfo.gameInfo.ruleSet.unitPromotions.values .any { - it.name == promotion - && unit.type.name in it.unitTypes + it.name == promotion + && unit.type.name in it.unitTypes } ) ) { @@ -353,12 +374,12 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { } fun matchesFilter(filter: String): Boolean { - + return when (filter) { unitType -> true name -> true "All" -> true - + "Melee" -> isMelee() "Ranged" -> isRanged() "Civilian" -> isCivilian() @@ -367,7 +388,7 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { "Water" -> isWaterUnit() "Air" -> isAirUnit() "non-air" -> !movesLikeAirUnits() - + "Nuclear Weapon" -> isNuclearWeapon() // Deprecated as of 3.15.2 "military water" -> isMilitary() && isWaterUnit() @@ -378,8 +399,8 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { // "military units" --> "Military" && matchesFilter(filter.removeSuffix(" units").toLowerCase(Locale.ENGLISH).capitalize(Locale.ENGLISH)) ) return true - return uniques.contains(filter) - } + return uniques.contains(filter) + } } } @@ -403,12 +424,12 @@ class BaseUnit : INamed, IConstruction, ICivilopediaText { fun isMelee() = !isRanged() && strength > 0 fun isMilitary() = isRanged() || isMelee() fun isCivilian() = !isMilitary() - + fun isLandUnit() = getType().isLandUnit() fun isWaterUnit() = getType().isWaterUnit() fun isAirUnit() = getType().isAirUnit() - fun isProbablySiegeUnit() = + fun isProbablySiegeUnit() = ( isRanged() && (uniqueObjects + getType().uniqueObjects) diff --git a/core/src/com/unciv/models/stats/Stat.kt b/core/src/com/unciv/models/stats/Stat.kt index 0b4b95cb00..4855f4c902 100644 --- a/core/src/com/unciv/models/stats/Stat.kt +++ b/core/src/com/unciv/models/stats/Stat.kt @@ -1,11 +1,13 @@ package com.unciv.models.stats -enum class Stat{ - Production, - Food, - Gold, - Science, - Culture, - Happiness, - Faith +import com.unciv.models.UncivSound + +enum class Stat (val sound: UncivSound) { + Production(UncivSound.Click), + Food(UncivSound.Click), + Gold(UncivSound.Coin), + Science(UncivSound.Chimes), + Culture(UncivSound.Paper), + Happiness(UncivSound.Click), + Faith(UncivSound.Choir), } \ No newline at end of file diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index 8eec07b07d..894648688a 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -224,7 +224,8 @@ object TranslationFileWriter { "in all non-occupied cities", "in all cities with a world wonder", "in all cities connected to capital", - "in all cities with a garrison" + "in all cities with a garrison", + "in all cities in which the majority religion is a major religion" ) val startMillis = System.currentTimeMillis() diff --git a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt index 2f38026441..d7a962ebf5 100644 --- a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt @@ -7,10 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align -import com.unciv.logic.city.CityConstructions -import com.unciv.logic.city.CityInfo -import com.unciv.logic.city.IConstruction -import com.unciv.logic.city.PerpetualConstruction +import com.unciv.logic.city.* import com.unciv.models.UncivSound import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.unit.BaseUnit @@ -70,7 +67,9 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase private fun updateButtons(construction: IConstruction?) { buttons.clear() buttons.add(getQueueButton(construction)).padRight(5f) - buttons.add(getBuyButton(construction)) + if (construction != null && construction !is PerpetualConstruction) + for (button in getBuyButtons(construction as INonPerpetualConstruction)) + buttons.add(button).padRight(5f) } private fun updateConstructionQueue() { @@ -262,7 +261,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase if (cityConstructions.getWorkDone(constructionName) == 0) return Table() val constructionPercentage = cityConstructions.getWorkDone(constructionName) / - construction.getProductionCost(cityConstructions.cityInfo.civInfo).toFloat() + (construction as INonPerpetualConstruction).getProductionCost(cityConstructions.cityInfo.civInfo).toFloat() return ImageGetter.getProgressBarVertical(2f, 30f, constructionPercentage, Color.BROWN.cpy().lerp(Color.WHITE, 0.5f), Color.WHITE) } @@ -377,9 +376,9 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase } } - fun purchaseConstruction(construction: IConstruction) { + fun purchaseConstruction(construction: INonPerpetualConstruction, stat: Stat = Stat.Gold) { val city = cityScreen.city - if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false)) { + if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false, stat)) { Popup(cityScreen).apply { add("No space available to place [${construction.name}] near [${city.name}]".tr()).row() addCloseButton() @@ -393,38 +392,47 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase } cityScreen.update() } + + private fun getBuyButtons(construction: INonPerpetualConstruction?): List { + return listOf(Stat.Gold, Stat.Faith, Stat.Culture, Stat.Science, Stat.Food) + .mapNotNull { getBuyButton(construction, it) } + } - private fun getBuyButton(construction: IConstruction?): TextButton { + private fun getBuyButton(construction: INonPerpetualConstruction?, stat: Stat = Stat.Gold): TextButton? { + if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Culture, Stat.Science, Stat.Food)) + return null val city = cityScreen.city - val cityConstructions = city.cityConstructions val button = "".toTextButton() if (construction == null || construction is PerpetualConstruction || - (!construction.canBePurchased() && !city.civInfo.gameInfo.gameParameters.godMode)) { + (!construction.canBePurchasedWithStat(city, stat) && !city.civInfo.gameInfo.gameParameters.godMode)) { // fully disable a "buy" button only for "priceless" buildings such as wonders // for all other cases, the price should be displayed - button.setText("Buy".tr()) - button.disable() + if (stat == Stat.Gold) { + button.setText("Buy".tr()) + button.disable() + } + else return null } else { - val constructionGoldCost = construction.getGoldCost(city.civInfo) - button.setText("Buy".tr() + " " + constructionGoldCost) - button.add(ImageGetter.getStatIcon(Stat.Gold.name)).size(20f).padBottom(2f) + val constructionBuyCost = construction.getStatBuyCost(city, stat)!! + button.setText("Buy".tr() + " " + constructionBuyCost) + button.add(ImageGetter.getStatIcon(stat.name)).size(20f).padBottom(2f) - button.onClick(UncivSound.Coin) { + button.onClick(stat.sound) { button.disable() cityScreen.closeAllPopups() - - val purchasePrompt = "Currently you have [${city.civInfo.gold}] gold.".tr() + "\n" + - "Would you like to purchase [${construction.name}] for [$constructionGoldCost] gold?".tr() - YesNoPopup(purchasePrompt, { purchaseConstruction(construction) }, cityScreen, { cityScreen.update() }).open() + + val purchasePrompt = "Currently you have [${city.getStatReserve(stat)}] [${stat.name}].".tr() + "\n" + + "Would you like to purchase [${construction.name}] for [$constructionBuyCost] [${stat.name}]?".tr() + YesNoPopup(purchasePrompt, { purchaseConstruction(construction, stat) }, cityScreen, { cityScreen.update() }).open() } - - if (!construction.isBuildable(cityConstructions) - || !cityScreen.canChangeState - || city.isPuppet || city.isInResistance() + + if (!cityScreen.canChangeState + || city.isPuppet + || city.isInResistance() || !city.canPurchase(construction) - || (constructionGoldCost > city.civInfo.gold && !city.civInfo.gameInfo.gameParameters.godMode)) + || (constructionBuyCost > city.getStatReserve(stat) && !city.civInfo.gameInfo.gameParameters.godMode)) button.disable() } @@ -432,7 +440,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase return button } - + private fun getRaisePriorityButton(constructionQueueIndex: Int, name: String, city: CityInfo): Table { val tab = Table() tab.add(ImageGetter.getImage("OtherIcons/Up").surroundWithCircle(40f)) diff --git a/core/src/com/unciv/ui/tilegroups/CityButton.kt b/core/src/com/unciv/ui/tilegroups/CityButton.kt index 6e2abb0d55..758e678aae 100644 --- a/core/src/com/unciv/ui/tilegroups/CityButton.kt +++ b/core/src/com/unciv/ui/tilegroups/CityButton.kt @@ -13,6 +13,7 @@ import com.badlogic.gdx.utils.Align import com.unciv.logic.battle.CityCombatant import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.CityInfo +import com.unciv.logic.city.INonPerpetualConstruction import com.unciv.logic.city.PerpetualConstruction import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.ui.cityscreen.CityScreen @@ -386,7 +387,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab group.addActor(label) val constructionPercentage = cityConstructions.getWorkDone(cityCurrentConstruction.name) / - cityCurrentConstruction.getProductionCost(cityConstructions.cityInfo.civInfo).toFloat() + (cityCurrentConstruction as INonPerpetualConstruction).getProductionCost(cityConstructions.cityInfo.civInfo).toFloat() val productionBar = ImageGetter.getProgressBarVertical(2f, groupHeight, constructionPercentage, Color.BROWN.cpy().lerp(Color.WHITE, 0.5f), Color.BLACK) productionBar.x = 10f diff --git a/core/src/com/unciv/ui/utils/Fonts.kt b/core/src/com/unciv/ui/utils/Fonts.kt index 66ef46b7cc..f340bfe47a 100644 --- a/core/src/com/unciv/ui/utils/Fonts.kt +++ b/core/src/com/unciv/ui/utils/Fonts.kt @@ -12,6 +12,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Disposable import com.unciv.UncivGame +import com.unciv.models.stats.Stat interface NativeFontImplementation { fun getFontSize(): Int @@ -151,7 +152,6 @@ object Fonts { return pixmap } - const val turn = '⏳' // U+23F3 'hourglass' const val strength = '†' // U+2020 'dagger' const val rangedStrength = '‡' // U+2021 'double dagger' @@ -164,4 +164,16 @@ object Fonts { const val culture = '♪' // U+266A 'eighth note' (🎵 U+1F3B5 'musical note') const val happiness = '⌣' // U+2323 'smile' (😀 U+1F600 'grinning face') const val faith = '☮' // U+262E 'peace symbol' (🕊 U+1F54A 'dove of peace') + + fun statToChar(stat: Stat): Char { + return when (stat) { + Stat.Food -> food + Stat.Production -> production + Stat.Gold -> gold + Stat.Happiness -> happiness + Stat.Culture -> culture + Stat.Science -> science + Stat.Faith -> faith + } + } } diff --git a/docs/Credits.md b/docs/Credits.md index c0d17f650b..09138138bb 100644 --- a/docs/Credits.md +++ b/docs/Credits.md @@ -125,13 +125,14 @@ Unless otherwise specified, all the following are from [the Noun Project](https: * [Robot](https://thenounproject.com/term/robot/1182459/) by Lluisa Iborra, ES for Giant Death Robot -### Great People +### All Eras * [Pallet](https://thenounproject.com/search/?q=Pallet&i=6862) By James Keuning for Great Artist * [Gear](https://thenounproject.com/search/?q=Gear&i=17369) By Melvin Salas for Great Engineer * [Beaker](https://thenounproject.com/search/?q=Beaker&i=621510) By Delwar Hossain for Great Scientist -* [Dove](https://thenounproject.com/search/?q=dove&i=1344088) by sandra for Great Prophet* +* [Dove](https://thenounproject.com/search/?q=dove&i=1344088) by sandra for Great Prophet * [General](https://thenounproject.com/search/?q=general&i=933566) By anbileru adaleru for Great General +* [Religion](https://thenounproject.com/search/?q=preach&i=53064) by Bruno Gätjens González adapted for Missionary ## Resources