From a992dff6fe43e1fa1126817a5c4520d7673c24b5 Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Sat, 13 Jan 2018 23:11:53 +0200 Subject: [PATCH] Split CityInfo into areas of concern - separate files for Stats, Expansion and Population --- .../unciv/logic/city/CityConstructions.java | 6 +- .../logic/city/CityExpansionManager.java | 65 +++++ core/src/com/unciv/logic/city/CityInfo.java | 267 ++---------------- core/src/com/unciv/logic/city/CityStats.java | 244 ++++++++++++++++ .../unciv/logic/city/PopulationManager.java | 65 +++++ .../logic/civilization/CivilizationInfo.java | 6 +- core/src/com/unciv/ui/CityScreen.java | 30 +- core/src/com/unciv/ui/UnCivGame.java | 7 +- core/src/com/unciv/ui/WorldScreen.java | 2 +- core/src/com/unciv/ui/WorldTileGroup.java | 2 +- .../ConstructionPickerScreen.java | 2 +- core/src/com/unciv/ui/utils/GameSaver.java | 6 + 12 files changed, 426 insertions(+), 276 deletions(-) create mode 100644 core/src/com/unciv/logic/city/CityExpansionManager.java create mode 100644 core/src/com/unciv/logic/city/CityStats.java create mode 100644 core/src/com/unciv/logic/city/PopulationManager.java diff --git a/core/src/com/unciv/logic/city/CityConstructions.java b/core/src/com/unciv/logic/city/CityConstructions.java index 08e4d4e2d1..a291718a1e 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.java +++ b/core/src/com/unciv/logic/city/CityConstructions.java @@ -122,7 +122,7 @@ public class CityConstructions public boolean evaluate(String arg0) { return "SciencePer2Pop".equals(arg0); } - }) * getCity().population/2; // Library and public school unique (not actualy unique, though...hmm) + }) * getCity().population.population/2; // Library and public school unique (not actualy unique, though...hmm) return stats; } @@ -153,7 +153,7 @@ public class CityConstructions float workLeft = cost - workDone(constructionName); // needs to be float so that we get the cieling properly ;) - FullStats cityStats = getCity().cityStats; + FullStats cityStats = getCity().cityStats.currentCityStats; int production = Math.round(cityStats.production); if (constructionName.equals(Settler)) production += cityStats.food; @@ -164,7 +164,7 @@ public class CityConstructions CivilizationInfo.current().gold -= getConstruction(buildingName).getGoldCost(); getConstruction(buildingName).postBuildEvent(this); if(currentConstruction.equals(buildingName)) chooseNextConstruction(); - getCity().updateCityStats(); + getCity().cityStats.update(); } public String getCityProductionTextForCityButton(){ diff --git a/core/src/com/unciv/logic/city/CityExpansionManager.java b/core/src/com/unciv/logic/city/CityExpansionManager.java new file mode 100644 index 0000000000..543ba0e352 --- /dev/null +++ b/core/src/com/unciv/logic/city/CityExpansionManager.java @@ -0,0 +1,65 @@ +package com.unciv.logic.city; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Predicate; +import com.unciv.logic.civilization.CivilizationInfo; +import com.unciv.logic.map.TileInfo; +import com.unciv.models.linq.Linq; +import com.unciv.ui.UnCivGame; + +public class CityExpansionManager{ + + transient public CityInfo cityInfo; + public int cultureStored; + private int tilesClaimed; + + private void addNewTile(){ + cultureStored -= getCultureToNextTile(); + tilesClaimed++; + Linq possibleNewTileVectors = new Linq(); + + for (int i = 2; i <4 ; i++) { + Linq tiles = CivilizationInfo.current().tileMap.getTilesInDistance(cityInfo.cityLocation,i); + tiles = tiles.where(new Predicate() { + @Override + public boolean evaluate(TileInfo arg0) { + return arg0.owner == null; + } + }); + if(tiles.size()==0) continue; + + TileInfo TileChosen=null; + double TileChosenRank=0; + for(TileInfo tile : tiles){ + double rank = cityInfo.rankTile(tile); + if(rank>TileChosenRank){ + TileChosenRank = rank; + TileChosen = tile; + } + } + TileChosen.owner = UnCivGame.Current.civInfo.civName; + return; + } + } + + public int getCultureToNextTile(){ + // This one has conflicting sources - + // http://civilization.wikia.com/wiki/Mathematics_of_Civilization_V says it's 20+(10(t-1))^1.1 + // https://www.reddit.com/r/civ/comments/58rxkk/how_in_gods_name_do_borders_expand_in_civ_vi/ has it + // (per game XML files) at 6*(t+0.4813)^1.3 + // The second seems to be more based, so I'll go with that + double a = 6*Math.pow(tilesClaimed+1.4813,1.3); + if(CivilizationInfo.current().getBuildingUniques().contains("NewTileCostReduction")) a *= 0.75; //Speciality of Angkor Wat + if(CivilizationInfo.current().policies.isAdopted("Tradition")) a *= 0.75; + return (int)Math.round(a); + } + + public void nextTurn(float culture) { + + cultureStored+=culture; + if(cultureStored>=getCultureToNextTile()){ + addNewTile(); + CivilizationInfo.current().addNotification(cityInfo.name+" has expanded its borders!",cityInfo.cityLocation); + } + } +} diff --git a/core/src/com/unciv/logic/city/CityInfo.java b/core/src/com/unciv/logic/city/CityInfo.java index 178d62e69f..1b2a6ed331 100644 --- a/core/src/com/unciv/logic/city/CityInfo.java +++ b/core/src/com/unciv/logic/city/CityInfo.java @@ -9,7 +9,6 @@ import com.unciv.logic.map.TileMap; import com.unciv.ui.UnCivGame; import com.unciv.models.linq.Linq; import com.unciv.models.linq.LinqCounter; -import com.unciv.models.linq.LinqHashMap; import com.unciv.models.gamebasics.Building; import com.unciv.models.gamebasics.GameBasics; import com.unciv.models.gamebasics.TileResource; @@ -19,14 +18,10 @@ public class CityInfo { public final Vector2 cityLocation; public String name; + public PopulationManager population = new PopulationManager(); public CityConstructions cityConstructions; - public int cultureStored; - private int tilesClaimed; - public int population = 1; - public int foodStored = 0; - public LinqHashMap buildingsSpecialists = new LinqHashMap(); - - public FullStats cityStats; // This is so we won't have to calculate this multiple times - takes a lot of time, especially on phones! + public CityExpansionManager expansion = new CityExpansionManager(); + public CityStats cityStats = new CityStats(); private TileMap getTileMap(){return UnCivGame.Current.civInfo.tileMap; } @@ -48,19 +43,14 @@ public class CityInfo { cityLocation = Vector2.Zero; } // for json parsing, we need to have a default constructor - public int getCultureToNextTile(){ - // This one has conflicting sources - - // http://civilization.wikia.com/wiki/Mathematics_of_Civilization_V says it's 20+(10(t-1))^1.1 - // https://www.reddit.com/r/civ/comments/58rxkk/how_in_gods_name_do_borders_expand_in_civ_vi/ has it - // (per game XML files) at 6*(t+0.4813)^1.3 - // The second seems to be more based, so I'll go with that - double a = 6*Math.pow(tilesClaimed+1.4813,1.3); - if(CivilizationInfo.current().getBuildingUniques().contains("NewTileCostReduction")) a *= 0.75; //Speciality of Angkor Wat - if(CivilizationInfo.current().policies.isAdopted("Tradition")) a *= 0.75; - return (int)Math.round(a); - } + public CityInfo(CivilizationInfo civInfo, Vector2 cityLocation) { + + population.cityInfo = this; + expansion.cityInfo = this; + cityStats.cityInfo = this; + name = CityNames[civInfo.cities.size()]; this.cityLocation = cityLocation; civInfo.cities.add(this); @@ -83,7 +73,7 @@ public class CityInfo { tile.terrainFeature=null; autoAssignWorker(); - updateCityStats(); + cityStats.update(); } public LinqCounter getCityResources(){ @@ -105,231 +95,20 @@ public class CityInfo { } - public FullStats getSpecialists(){ - FullStats allSpecialists = new FullStats(); - for(FullStats stats : buildingsSpecialists.values()) - allSpecialists.add(stats); - return allSpecialists; - } - - public int getNumberOfSpecialists(){ - FullStats specialists = getSpecialists(); - return (int) (specialists.science+specialists.production+specialists.culture+specialists.gold); - } - - public int getFreePopulation() { - int workingPopulation = getTilesInRange().count(new Predicate() { - @Override - public boolean evaluate(TileInfo arg0) { - return name.equals(arg0.workingCity); - } - })-1; // 1 is the city center - return population - workingPopulation - getNumberOfSpecialists(); - } - - public boolean hasNonWorkingPopulation() { - return getFreePopulation() > 0; - } - - public void updateCityStats() { - CivilizationInfo civInfo = CivilizationInfo.current(); - FullStats stats = new FullStats(); - stats.science += population; - - // Working ppl - for (TileInfo cell : getTilesInRange()) - if (name.equals(cell.workingCity)) - stats.add(cell.getTileStats(this)); - - // Specialists - FullStats specialists = getSpecialists(); - stats.culture+=specialists.culture*3; - stats.production+=specialists.production*2; - stats.science+=specialists.science*3; - stats.gold+=specialists.gold*2; - if(civInfo.policies.isAdopted("Commerce Complete")) stats.gold+=getNumberOfSpecialists(); - if(civInfo.policies.isAdopted("Secularism")) stats.science+=getNumberOfSpecialists()*2; - - //idle ppl - stats.production += getFreePopulation(); - - if(!isCapital() && isConnectedToCapital(RoadStatus.Road)) { - // Calculated by http://civilization.wikia.com/wiki/Trade_route_(Civ5) - double goldFromTradeRoute = civInfo.getCapital().population * 0.15 - + population * 1.1 - 1; - if(civInfo.policies.isAdopted("Trade Unions")) goldFromTradeRoute+=2; - if(civInfo.getBuildingUniques().contains("TradeRouteGoldIncrease")) goldFromTradeRoute*=1.25; // Machu Pichu speciality - stats.gold += goldFromTradeRoute; - } - - stats.add(cityConstructions.getStats()); - if(civInfo.policies.isAdopted("Tradition") && isCapital()) - stats.culture+=3; - if(civInfo.policies.isAdopted("Landed Elite") && isCapital()) - stats.food+=2; - if(CivilizationInfo.current().policies.isAdopted("Tradition Complete")) - stats.food+=2; - if(CivilizationInfo.current().policies.isAdopted("Monarchy") && isCapital()) - stats.gold+=population/2; - if(CivilizationInfo.current().policies.isAdopted("Liberty")) - stats.culture+=1; - if(CivilizationInfo.current().policies.isAdopted("Republic")) - stats.production+=1; - if(CivilizationInfo.current().policies.isAdopted("Universal Suffrage")) - stats.production+=population/5; - if(CivilizationInfo.current().policies.isAdopted("Free Speech")) - stats.culture+=population/2; - - FullStats statPercentBonuses = cityConstructions.getStatPercentBonuses(); - if( civInfo.tech.isResearched ("Combustion") && - (isCapital() || isConnectedToCapital(RoadStatus.Railroad))) statPercentBonuses.production += 25; - if(civInfo.goldenAges.isGoldenAge()) statPercentBonuses.production+=20; - IConstruction currentConstruction = cityConstructions.getCurrentConstruction(); - if(currentConstruction instanceof Building && ((Building)currentConstruction).isWonder){ - if(civInfo.getCivResources().containsKey(GameBasics.TileResources.get("Marble"))) - statPercentBonuses.production+=15; - if(civInfo.policies.isAdopted("Aristocracy")) - statPercentBonuses.production+=15; - } - - if(civInfo.tech.isResearched("Computers")){ - statPercentBonuses.production+=10; - statPercentBonuses.science+=10; - } - - if(civInfo.policies.isAdopted("Collective Rule") && isCapital() - && "Settler".equals(cityConstructions.currentConstruction)) - statPercentBonuses.production+=50; - if(civInfo.policies.isAdopted("Republic") && currentConstruction instanceof Building) - statPercentBonuses.production+=5; - if(civInfo.policies.isAdopted("Reformation") && cityConstructions.builtBuildings.any(new Predicate() { - @Override - public boolean evaluate(String arg0) { - return GameBasics.Buildings.get(arg0).isWonder; - } - })) - statPercentBonuses.culture+=33; - if(civInfo.policies.isAdopted("Commerce") && isCapital()) - statPercentBonuses.gold+=25; - if(civInfo.policies.isAdopted("Sovereignty") && civInfo.getHappinessForNextTurn() >= 0) - statPercentBonuses.science+=15; - - stats.production*=1+statPercentBonuses.production/100; // So they get bonuses for production and gold/science - if("Gold".equals(cityConstructions.currentConstruction)) stats.gold+=stats.production/4; - if("Science".equals(cityConstructions.currentConstruction)) { - float scienceProduced=stats.production/4; - if (civInfo.getBuildingUniques().contains("ScienceConversionIncrease")) - scienceProduced*=1.33; - if(civInfo.policies.isAdopted("Rationalism")) scienceProduced*=1.33; - stats.science += scienceProduced; - } - - stats.gold*=1+statPercentBonuses.gold/100; - stats.science*=1+statPercentBonuses.science/100; - stats.culture*=1+statPercentBonuses.culture/100; - - boolean isUnhappy = civInfo.getHappinessForNextTurn() < 0; - if (!isUnhappy) stats.food*=1+statPercentBonuses.food/100; // Regular food bonus revoked when unhappy per https://forums.civfanatics.com/resources/complete-guide-to-happiness-vanilla.25584/ - stats.food -= population * 2; // Food reduced after the bonus - if(CivilizationInfo.current().policies.isAdopted("Civil Society")) - stats.food+=getNumberOfSpecialists(); - - if(isUnhappy) stats.food /= 4; // Reduce excess food to 1/4 per the same - if(civInfo.policies.isAdopted("Landed Elite") && isCapital()) - stats.food*=1.1; - if(CivilizationInfo.current().policies.isAdopted("Tradition Complete")) - stats.food*=1.15; - - stats.gold-= cityConstructions.getMaintainanceCosts(); // this is AFTER the bonus calculation! - this.cityStats = stats; - } - - public float getCityHappiness(){ // needs to be a separate function because we need to know the global happiness state - CivilizationInfo civInfo = CivilizationInfo.current(); - // in order to determine how much food is produced in a city! - float happiness = -3; // -3 happiness per city - float unhappinessFromCitizens = population; - if(civInfo.policies.isAdopted("Democracy")) unhappinessFromCitizens-=getNumberOfSpecialists()*0.5f; - if(civInfo.getBuildingUniques().contains("CitizenUnhappinessDecreased")) - unhappinessFromCitizens*=0.9; - if(civInfo.policies.isAdopted("Aristocracy")) - unhappinessFromCitizens*=0.95; - happiness-=unhappinessFromCitizens; - - if(civInfo.policies.isAdopted("Aristocracy")) - happiness+=population/10; - if(civInfo.policies.isAdopted("Monarchy") && isCapital()) - happiness+=population/2; - if(civInfo.policies.isAdopted("Meritocracy") && isConnectedToCapital(RoadStatus.Road)) - happiness+=1; - - happiness+=(int) cityConstructions.getStats().happiness; - - return happiness; - } - public void nextTurn() { - FullStats stats = cityStats; + FullStats stats = cityStats.currentCityStats; if (cityConstructions.currentConstruction.equals(CityConstructions.Settler) && stats.food > 0) { stats.production += stats.food; stats.food = 0; } - foodStored += stats.food; - if (foodStored < 0) // starvation! - { - population--; - foodStored = 0; - CivilizationInfo.current().addNotification(name+" is starving!",cityLocation); - } - if (foodStored >= foodToNextPopulation()) // growth! - { - foodStored -= foodToNextPopulation(); - if(getBuildingUniques().contains("FoodCarriesOver")) foodStored+=0.4f*foodToNextPopulation(); // Aqueduct special - population++; - autoAssignWorker(); - CivilizationInfo.current().addNotification(name+" has grown!",cityLocation); - } - + population.nextTurn(stats.food); cityConstructions.nextTurn(stats); - - cultureStored+=stats.culture; - if(cultureStored>=getCultureToNextTile()){ - addNewTile(); - CivilizationInfo.current().addNotification(name+" has expanded its borders!",cityLocation); - } + expansion.nextTurn(stats.culture); } - private void addNewTile(){ - cultureStored -= getCultureToNextTile(); - tilesClaimed++; - Linq possibleNewTileVectors = new Linq(); - for (int i = 2; i <4 ; i++) { - Linq tiles = getTileMap().getTilesInDistance(cityLocation,i); - tiles = tiles.where(new Predicate() { - @Override - public boolean evaluate(TileInfo arg0) { - return arg0.owner == null; - } - }); - if(tiles.size()==0) continue; - - TileInfo TileChosen=null; - double TileChosenRank=0; - for(TileInfo tile : tiles){ - double rank = rankTile(tile); - if(rank>TileChosenRank){ - TileChosenRank = rank; - TileChosen = tile; - } - } - TileChosen.owner = UnCivGame.Current.civInfo.civName; - return; - } - } - - private void autoAssignWorker() { + void autoAssignWorker() { double maxValue = 0; TileInfo toWork = null; for (TileInfo tileInfo : getTilesInRange()) { @@ -345,7 +124,7 @@ public class CityInfo { toWork.workingCity = name; } - private double rankTile(TileInfo tile){ + double rankTile(TileInfo tile){ FullStats stats = tile.getTileStats(this); double rank=0; if(stats.food <2) rank+=stats.food; @@ -358,9 +137,9 @@ public class CityInfo { return rank; } - private boolean isCapital(){ return CivilizationInfo.current().getCapital() == this; } + boolean isCapital(){ return CivilizationInfo.current().getCapital() == this; } - private boolean isConnectedToCapital(RoadStatus roadType){ + boolean isConnectedToCapital(RoadStatus roadType){ if(CivilizationInfo.current().getCapital()==null) return false;// first city! TileInfo capitalTile = CivilizationInfo.current().getCapital().getTile(); Linq tilesReached = new Linq(); @@ -382,12 +161,6 @@ public class CityInfo { return false; } - public int foodToNextPopulation() - { - // civ v math,civilization.wikia - return 15 + 6 * (population - 1) + (int)Math.floor(Math.pow(population - 1, 1.8f)); - } - public Linq getBuildingUniques(){ return cityConstructions.getBuiltBuildings().select(new Linq.Func() { @Override @@ -403,7 +176,7 @@ public class CityInfo { } public FullStats getGreatPersonPoints(){ - FullStats greatPersonPoints = getSpecialists().multiply(3); + FullStats greatPersonPoints = population.getSpecialists().multiply(3); CivilizationInfo civInfo = CivilizationInfo.current(); for(Building building : cityConstructions.getBuiltBuildings()) @@ -420,8 +193,4 @@ public class CityInfo { return greatPersonPoints; } - - - - } \ No newline at end of file diff --git a/core/src/com/unciv/logic/city/CityStats.java b/core/src/com/unciv/logic/city/CityStats.java new file mode 100644 index 0000000000..cc05e46723 --- /dev/null +++ b/core/src/com/unciv/logic/city/CityStats.java @@ -0,0 +1,244 @@ +package com.unciv.logic.city; + +import com.badlogic.gdx.utils.Predicate; +import com.unciv.logic.civilization.CivilizationInfo; +import com.unciv.logic.civilization.PolicyManager; +import com.unciv.logic.map.RoadStatus; +import com.unciv.logic.map.TileInfo; +import com.unciv.models.gamebasics.Building; +import com.unciv.models.gamebasics.GameBasics; +import com.unciv.models.stats.FullStats; + +/** + * Created by LENOVO on 1/13/2018. + */ + +public class CityStats{ + + public FullStats currentCityStats; // This is so we won't have to calculate this multiple times - takes a lot of time, especially on phones! + + public transient CityInfo cityInfo; + + + private FullStats getStatsFromTiles(){ + FullStats stats = new FullStats(); + for (TileInfo cell : cityInfo.getTilesInRange()) + if (cityInfo.name.equals(cell.workingCity)) + stats.add(cell.getTileStats(cityInfo)); + return stats; + } + + private FullStats getStatsFromSpecialists(){ + FullStats stats = new FullStats(); + + // Specialists + FullStats specialists = cityInfo.population.getSpecialists(); + stats.culture+=specialists.culture*3; + stats.production+=specialists.production*2; + stats.science+=specialists.science*3; + stats.gold+=specialists.gold*2; + int numOfSpecialists = cityInfo.population.getNumberOfSpecialists(); + if(CivilizationInfo.current().policies.isAdopted("Commerce Complete")) stats.gold+=numOfSpecialists; + if(CivilizationInfo.current().policies.isAdopted("Secularism")) stats.science+=numOfSpecialists*2; + + return stats; + } + + private FullStats getStatsFromTradeRoute(){ + FullStats stats = new FullStats(); + if(!cityInfo.isCapital() && cityInfo.isConnectedToCapital(RoadStatus.Road)) { + CivilizationInfo civInfo = CivilizationInfo.current(); + // Calculated by http://civilization.wikia.com/wiki/Trade_route_(Civ5) + double goldFromTradeRoute = civInfo.getCapital().population.population * 0.15 + + cityInfo.population.population * 1.1 - 1; + if(civInfo.policies.isAdopted("Trade Unions")) goldFromTradeRoute+=2; + if(civInfo.getBuildingUniques().contains("TradeRouteGoldIncrease")) goldFromTradeRoute*=1.25; // Machu Pichu speciality + stats.gold += goldFromTradeRoute; + } + return stats; + } + + private FullStats getStatsFromPolicies(){ + FullStats stats = new FullStats(); + PolicyManager policies = CivilizationInfo.current().policies; + + if(policies.isAdopted("Tradition") && cityInfo.isCapital()) + stats.culture+=3; + if(policies.isAdopted("Landed Elite") && cityInfo.isCapital()) + stats.food+=2; + if(policies.isAdopted("Tradition Complete")) + stats.food+=2; + if(policies.isAdopted("Monarchy") && cityInfo.isCapital()) + stats.gold+=cityInfo.population.population/2; + if(policies.isAdopted("Liberty")) + stats.culture+=1; + if(policies.isAdopted("Republic")) + stats.production+=1; + if(policies.isAdopted("Universal Suffrage")) + stats.production+=cityInfo.population.population/5; + if(policies.isAdopted("Free Speech")) + stats.culture+=cityInfo.population.population/2; + + return stats; + } + + + private FullStats getStatsFromProduction(){ + FullStats stats = new FullStats(); + + if("Gold".equals(cityInfo.cityConstructions.currentConstruction)) stats.gold+=stats.production/4; + if("Science".equals(cityInfo.cityConstructions.currentConstruction)) { + float scienceProduced=stats.production/4; + if (CivilizationInfo.current().getBuildingUniques().contains("ScienceConversionIncrease")) + scienceProduced*=1.33; + if(CivilizationInfo.current().policies.isAdopted("Rationalism")) scienceProduced*=1.33; + stats.science += scienceProduced; + } + return stats; + } + + + private FullStats getStatPercentBonusesFromRailroad(){ + FullStats stats = new FullStats(); + if( CivilizationInfo.current().tech.isResearched ("Combustion") && + (cityInfo.isCapital() || cityInfo.isConnectedToCapital(RoadStatus.Railroad))) + stats.production += 25; + return stats; + } + + private FullStats getStatPercentBonusesFromGoldenAge(){ + FullStats stats = new FullStats(); + if(CivilizationInfo.current().goldenAges.isGoldenAge()) + stats.production+=20; + return stats; + } + + private FullStats getStatPercentBonusesFromPolicies(){ + FullStats stats = new FullStats(); + PolicyManager policies = CivilizationInfo.current().policies; + + CityConstructions cityConstructions = cityInfo.cityConstructions; + if(policies.isAdopted("Collective Rule") && cityInfo.isCapital() + && "Settler".equals(cityConstructions.currentConstruction)) + stats.production+=50; + if(policies.isAdopted("Republic") && cityConstructions.getCurrentConstruction() instanceof Building) + stats.production+=5; + if(policies.isAdopted("Reformation") && cityConstructions.builtBuildings.any(new Predicate() { + @Override + public boolean evaluate(String arg0) { + return GameBasics.Buildings.get(arg0).isWonder; + } + })) + stats.culture+=33; + if(policies.isAdopted("Commerce") && cityInfo.isCapital()) + stats.gold+=25; + if(policies.isAdopted("Sovereignty") && CivilizationInfo.current().getHappinessForNextTurn() >= 0) + stats.science+=15; + if(policies.isAdopted("Aristocracy") + && cityConstructions.getCurrentConstruction() instanceof Building + && ((Building)cityConstructions.getCurrentConstruction()).isWonder) + stats.production+=15; + + return stats; + } + + private FullStats getStatPercentBonusesFromMarble() { + FullStats stats = new FullStats(); + IConstruction construction = cityInfo.cityConstructions.getCurrentConstruction(); + + if (construction instanceof Building + && ((Building) construction).isWonder + && CivilizationInfo.current().getCivResources().containsKey(GameBasics.TileResources.get("Marble"))) + stats.production += 15; + + return stats; + } + + private FullStats getStatPercentBonusesFromComputers() { + FullStats stats = new FullStats(); + + if (CivilizationInfo.current().tech.isResearched("Computers")) { + stats.production += 10; + stats.science += 10; + } + + return stats; + } + + private float getGrowthBonusFromPolicies(){ + float bonus = 0; + if(CivilizationInfo.current().policies.isAdopted("Landed Elite") && cityInfo.isCapital()) + bonus+=0.1; + if(CivilizationInfo.current().policies.isAdopted("Tradition Complete")) + bonus+=0.15; + return bonus; + } + + + public void update() { + CivilizationInfo civInfo = CivilizationInfo.current(); + + FullStats stats = new FullStats(); + stats.science += cityInfo.population.population; + stats.add(getStatsFromTiles()); + stats.add(getStatsFromSpecialists()); + stats.production += cityInfo.population.getFreePopulation(); + stats.add(getStatsFromTradeRoute()); + stats.add(cityInfo.cityConstructions.getStats()); + stats.add(getStatsFromPolicies()); + + FullStats statPercentBonuses = cityInfo.cityConstructions.getStatPercentBonuses(); + statPercentBonuses.add(getStatPercentBonusesFromGoldenAge()); + statPercentBonuses.add(getStatPercentBonusesFromPolicies()); + statPercentBonuses.add(getStatPercentBonusesFromRailroad()); + statPercentBonuses.add(getStatPercentBonusesFromMarble()); + statPercentBonuses.add(getStatPercentBonusesFromComputers()); + + stats.production*=1+statPercentBonuses.production/100; // So they get bonuses for production and gold/science + + stats.add(getStatsFromProduction()); + + + stats.gold*=1+statPercentBonuses.gold/100; + stats.science*=1+statPercentBonuses.science/100; + stats.culture*=1+statPercentBonuses.culture/100; + + boolean isUnhappy = civInfo.getHappinessForNextTurn() < 0; + if (!isUnhappy) stats.food*=1+statPercentBonuses.food/100; // Regular food bonus revoked when unhappy per https://forums.civfanatics.com/resources/complete-guide-to-happiness-vanilla.25584/ + stats.food -= cityInfo.population.population * 2; // Food reduced after the bonus + if(CivilizationInfo.current().policies.isAdopted("Civil Society")) + stats.food+=cityInfo.population.getNumberOfSpecialists(); + + if(isUnhappy) stats.food /= 4; // Reduce excess food to 1/4 per the same + stats.food *= (1+getGrowthBonusFromPolicies()); + + stats.gold-= cityInfo.cityConstructions.getMaintainanceCosts(); // this is AFTER the bonus calculation! + this.currentCityStats = stats; + } + + public float getCityHappiness(){ // needs to be a separate function because we need to know the global happiness state + CivilizationInfo civInfo = CivilizationInfo.current(); + // in order to determine how much food is produced in a city! + float happiness = -3; // -3 happiness per city + float unhappinessFromCitizens = cityInfo.population.population; + if(civInfo.policies.isAdopted("Democracy")) + unhappinessFromCitizens -= cityInfo.population.getNumberOfSpecialists()*0.5f; + if(civInfo.getBuildingUniques().contains("CitizenUnhappinessDecreased")) + unhappinessFromCitizens*=0.9; + if(civInfo.policies.isAdopted("Aristocracy")) + unhappinessFromCitizens*=0.95; + happiness-=unhappinessFromCitizens; + + if(civInfo.policies.isAdopted("Aristocracy")) + happiness+=cityInfo.population.population/10; + if(civInfo.policies.isAdopted("Monarchy") && cityInfo.isCapital()) + happiness+=cityInfo.population.population/2; + if(civInfo.policies.isAdopted("Meritocracy") && cityInfo.isConnectedToCapital(RoadStatus.Road)) + happiness+=1; + + happiness+=(int) cityInfo.cityConstructions.getStats().happiness; + + return happiness; + } + +} diff --git a/core/src/com/unciv/logic/city/PopulationManager.java b/core/src/com/unciv/logic/city/PopulationManager.java new file mode 100644 index 0000000000..3a39a131b1 --- /dev/null +++ b/core/src/com/unciv/logic/city/PopulationManager.java @@ -0,0 +1,65 @@ +package com.unciv.logic.city; + +import com.badlogic.gdx.utils.Predicate; +import com.unciv.logic.civilization.CivilizationInfo; +import com.unciv.logic.map.TileInfo; +import com.unciv.models.linq.LinqHashMap; +import com.unciv.models.stats.FullStats; + +public class PopulationManager { + + transient public CityInfo cityInfo; + public int population = 1; + public int foodStored = 0; + + public LinqHashMap buildingsSpecialists = new LinqHashMap(); + + public FullStats getSpecialists(){ + FullStats allSpecialists = new FullStats(); + for(FullStats stats : buildingsSpecialists.values()) + allSpecialists.add(stats); + return allSpecialists; + } + + public int getNumberOfSpecialists(){ + FullStats specialists = getSpecialists(); + return (int) (specialists.science+specialists.production+specialists.culture+specialists.gold); + } + + + public int foodToNextPopulation() + { + // civ v math,civilization.wikia + return 15 + 6 * (population - 1) + (int)Math.floor(Math.pow(population - 1, 1.8f)); + } + + + public int getFreePopulation() { + int workingPopulation = cityInfo.getTilesInRange().count(new Predicate() { + @Override + public boolean evaluate(TileInfo arg0) { + return cityInfo.name.equals(arg0.workingCity); + } + })-1; // 1 is the city center + return population - workingPopulation - getNumberOfSpecialists(); + } + + public void nextTurn(float food) { + + foodStored += food; + if (foodStored < 0) // starvation! + { + population--; + foodStored = 0; + CivilizationInfo.current().addNotification(cityInfo.name+" is starving!",cityInfo.cityLocation); + } + if (foodStored >= foodToNextPopulation()) // growth! + { + foodStored -= foodToNextPopulation(); + if(cityInfo.getBuildingUniques().contains("FoodCarriesOver")) foodStored+=0.4f*foodToNextPopulation(); // Aqueduct special + population++; + cityInfo.autoAssignWorker(); + CivilizationInfo.current().addNotification(cityInfo.name+" has grown!",cityInfo.cityLocation); + } + } +} diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.java b/core/src/com/unciv/logic/civilization/CivilizationInfo.java index 3c1490a15e..3df6460e16 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.java +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.java @@ -109,14 +109,14 @@ public class CivilizationInfo { goldenAges.nextTurn(happiness); - for (CityInfo city : cities) city.updateCityStats(); + for (CityInfo city : cities) city.cityStats.update(); turns++; } public CivStats getStatsForNextTurn() { CivStats statsForTurn = new CivStats(); for (CityInfo city : cities) { - statsForTurn.add(city.cityStats); + statsForTurn.add(city.cityStats.currentCityStats); } statsForTurn.happiness=0; @@ -145,7 +145,7 @@ public class CivilizationInfo { } }) * happinessPerUniqueLuxury; for (CityInfo city : cities) { - happiness += city.getCityHappiness(); + happiness += city.cityStats.getCityHappiness(); } if(getBuildingUniques().contains("HappinessPerSocialPolicy")) happiness+=policies.getAdoptedPolicies().count(new Predicate() { diff --git a/core/src/com/unciv/ui/CityScreen.java b/core/src/com/unciv/ui/CityScreen.java index e9569788a7..e244e9fa89 100644 --- a/core/src/com/unciv/ui/CityScreen.java +++ b/core/src/com/unciv/ui/CityScreen.java @@ -119,15 +119,15 @@ public class CityScreen extends CameraStageBaseScreen { specialist.addListener(new ClickListener(){ @Override public void clicked(InputEvent event, float x, float y) { - if(isFilled) getCity().buildingsSpecialists.get(building).add(specialistType.minus()); //unassign - else if(getCity().getFreePopulation()==0) return; + if(isFilled) getCity().population.buildingsSpecialists.get(building).add(specialistType.minus()); //unassign + else if(getCity().population.getFreePopulation()==0) return; else { - if(!getCity().buildingsSpecialists.containsKey(building)) - getCity().buildingsSpecialists.put(building,new FullStats()); - getCity().buildingsSpecialists.get(building).add(specialistType); //assign!} + if(!getCity().population.buildingsSpecialists.containsKey(building)) + getCity().population.buildingsSpecialists.put(building,new FullStats()); + getCity().population.buildingsSpecialists.get(building).add(specialistType); //assign!} } - getCity().updateCityStats(); + getCity().cityStats.update(); update(); } }); @@ -162,9 +162,9 @@ public class CityScreen extends CameraStageBaseScreen { BuildingsTable.add(new Label(building.name, skin)).pad(5); Table specialists = new Table(); specialists.row().size(20).pad(5); - if (!getCity().buildingsSpecialists.containsKey(building.name)) - getCity().buildingsSpecialists.put(building.name, new FullStats()); - FullStats currentBuildingSpecialists = getCity().buildingsSpecialists.get(building.name); + if (!getCity().population.buildingsSpecialists.containsKey(building.name)) + getCity().population.buildingsSpecialists.put(building.name, new FullStats()); + FullStats currentBuildingSpecialists = getCity().population.buildingsSpecialists.get(building.name); for (int i = 0; i < building.specialistSlots.production; i++) { specialists.add(getSpecialistIcon("StatIcons/populationBrown.png", building.name, currentBuildingSpecialists.production > i, new FullStats() {{ @@ -281,9 +281,9 @@ public class CityScreen extends CameraStageBaseScreen { group.populationImage.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { - if(tileInfo.workingCity ==null && cityInfo.getFreePopulation() > 0) tileInfo.workingCity = cityInfo.name; + if(tileInfo.workingCity ==null && cityInfo.population.getFreePopulation() > 0) tileInfo.workingCity = cityInfo.name; else if(cityInfo.name.equals(tileInfo.workingCity)) tileInfo.workingCity = null; - cityInfo.updateCityStats(); + cityInfo.cityStats.update(); update(); } }); @@ -329,7 +329,7 @@ public class CityScreen extends CameraStageBaseScreen { private void updateCityTable() { final CityInfo cityInfo = getCity(); - FullStats stats = cityInfo.cityStats; + FullStats stats = cityInfo.cityStats.currentCityStats; CityStatsTable.pad(20); CityStatsTable.columnDefaults(0).padRight(10); CityStatsTable.clear(); @@ -344,12 +344,12 @@ public class CityScreen extends CameraStageBaseScreen { CityStatsValues.put("Production",Math.round(stats.production) +cityInfo.cityConstructions.getAmountConstructedText()); CityStatsValues.put("Food",Math.round(stats.food) - +" ("+cityInfo.foodStored+"/"+cityInfo.foodToNextPopulation()+")"); + +" ("+cityInfo.population.foodStored+"/"+cityInfo.population.foodToNextPopulation()+")"); CityStatsValues.put("Gold",Math.round(stats.gold) +""); CityStatsValues.put("Science",Math.round(stats.science) +""); CityStatsValues.put("Culture",Math.round(stats.culture) - +" ("+cityInfo.cultureStored+"/"+cityInfo.getCultureToNextTile()+")"); - CityStatsValues.put("Population",cityInfo.getFreePopulation()+"/"+cityInfo.population); + +" ("+cityInfo.expansion.cultureStored+"/"+cityInfo.expansion.getCultureToNextTile()+")"); + CityStatsValues.put("Population",cityInfo.population.getFreePopulation()+"/"+cityInfo.population.population); for(String key : CityStatsValues.keySet()){ CityStatsTable.add(com.unciv.ui.utils.ImageGetter.getStatIcon(key)).align(Align.right); diff --git a/core/src/com/unciv/ui/UnCivGame.java b/core/src/com/unciv/ui/UnCivGame.java index 2f5d01372d..d28fd7d8dd 100644 --- a/core/src/com/unciv/ui/UnCivGame.java +++ b/core/src/com/unciv/ui/UnCivGame.java @@ -20,6 +20,7 @@ import com.unciv.models.gamebasics.Terrain; import com.unciv.models.gamebasics.TileImprovement; import com.unciv.models.gamebasics.TileResource; import com.unciv.models.stats.INamed; +import com.unciv.ui.utils.GameSaver; public class UnCivGame extends Game { @@ -31,11 +32,11 @@ public class UnCivGame extends Game { public void create() { SetupGameBasics(); Current = this; - if(com.unciv.ui.utils.GameSaver.GetSave("Autosave").exists()) { + if(GameSaver.GetSave("Autosave").exists()) { try { - com.unciv.ui.utils.GameSaver.LoadGame(this, "Autosave"); + GameSaver.LoadGame(this, "Autosave"); for (CityInfo city : this.civInfo.cities) { - if(city.cityStats == null) city.updateCityStats(); + if(city.cityStats == null) city.cityStats.update(); } } catch(Exception ex){ // silent fail if we can't read the autosave startNewGame(); diff --git a/core/src/com/unciv/ui/WorldScreen.java b/core/src/com/unciv/ui/WorldScreen.java index 501607e3d7..ccd661cc78 100644 --- a/core/src/com/unciv/ui/WorldScreen.java +++ b/core/src/com/unciv/ui/WorldScreen.java @@ -615,7 +615,7 @@ public class WorldScreen extends CameraStageBaseScreen { new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { - selectedTile.getCity().cityConstructions.addConstruction(300 + (30 * selectedTile.getCity().population)); //http://civilization.wikia.com/wiki/Great_engineer_(Civ5) + selectedTile.getCity().cityConstructions.addConstruction(300 + (30 * selectedTile.getCity().population.population)); //http://civilization.wikia.com/wiki/Great_engineer_(Civ5) selectedTile.unit = null; // destroy! update(); } diff --git a/core/src/com/unciv/ui/WorldTileGroup.java b/core/src/com/unciv/ui/WorldTileGroup.java index 62895aafef..cb4e2d5586 100644 --- a/core/src/com/unciv/ui/WorldTileGroup.java +++ b/core/src/com/unciv/ui/WorldTileGroup.java @@ -64,7 +64,7 @@ public class WorldTileGroup extends TileGroup { setZIndex(getParent().getChildren().size); // so this tile is rendered over neighboring tiles } - String cityButtonText = city.name +" ("+city.population+")"; + String cityButtonText = city.name +" ("+city.population.population+")"; TextButton button = cityButton.getActor(); button.setText(cityButtonText); button.setSize(button.getPrefWidth(), button.getPrefHeight()); diff --git a/core/src/com/unciv/ui/pickerscreens/ConstructionPickerScreen.java b/core/src/com/unciv/ui/pickerscreens/ConstructionPickerScreen.java index 12f7cc89e7..0f47c3dffc 100644 --- a/core/src/com/unciv/ui/pickerscreens/ConstructionPickerScreen.java +++ b/core/src/com/unciv/ui/pickerscreens/ConstructionPickerScreen.java @@ -42,7 +42,7 @@ public class ConstructionPickerScreen extends PickerScreen { @Override public void clicked(InputEvent event, float x, float y) { game.civInfo.getCurrentCity().cityConstructions.currentConstruction = selectedProduction; - game.civInfo.getCurrentCity().updateCityStats(); // Because maybe we set/removed the science or gold production options. + game.civInfo.getCurrentCity().cityStats.update(); // Because maybe we set/removed the science or gold production options. game.setScreen(new CityScreen()); dispose(); } diff --git a/core/src/com/unciv/ui/utils/GameSaver.java b/core/src/com/unciv/ui/utils/GameSaver.java index ea1a898860..4d6a3b3796 100644 --- a/core/src/com/unciv/ui/utils/GameSaver.java +++ b/core/src/com/unciv/ui/utils/GameSaver.java @@ -3,6 +3,7 @@ package com.unciv.ui.utils; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Json; +import com.unciv.logic.city.CityInfo; import com.unciv.logic.civilization.CivilizationInfo; import com.unciv.ui.UnCivGame; @@ -19,5 +20,10 @@ public class GameSaver { public static void LoadGame(UnCivGame game, String GameName) { game.civInfo = new Json().fromJson(CivilizationInfo.class, GetSave(GameName).readString()); + for (CityInfo cityInfo : game.civInfo.cities){ + cityInfo.population.cityInfo = cityInfo; + cityInfo.expansion.cityInfo = cityInfo; + cityInfo.cityStats.cityInfo = cityInfo; + } } }