Split CityInfo into areas of concern - separate files for Stats, Expansion and Population

This commit is contained in:
Yair Morgenstern 2018-01-13 23:11:53 +02:00
parent 3bb849726a
commit a992dff6fe
12 changed files with 426 additions and 276 deletions

View File

@ -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(){

View File

@ -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<Vector2> possibleNewTileVectors = new Linq<Vector2>();
for (int i = 2; i <4 ; i++) {
Linq<TileInfo> tiles = CivilizationInfo.current().tileMap.getTilesInDistance(cityInfo.cityLocation,i);
tiles = tiles.where(new Predicate<TileInfo>() {
@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);
}
}
}

View File

@ -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<String,FullStats> buildingsSpecialists = new LinqHashMap<String, FullStats>();
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<TileResource> 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<TileInfo>() {
@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<String>() {
@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<Vector2> possibleNewTileVectors = new Linq<Vector2>();
for (int i = 2; i <4 ; i++) {
Linq<TileInfo> tiles = getTileMap().getTilesInDistance(cityLocation,i);
tiles = tiles.where(new Predicate<TileInfo>() {
@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<TileInfo> tilesReached = new Linq<TileInfo>();
@ -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<String> getBuildingUniques(){
return cityConstructions.getBuiltBuildings().select(new Linq.Func<Building, String>() {
@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;
}
}

View File

@ -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<String>() {
@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;
}
}

View File

@ -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<String,FullStats> buildingsSpecialists = new LinqHashMap<String, FullStats>();
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<TileInfo>() {
@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);
}
}
}

View File

@ -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<String>() {

View File

@ -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);

View File

@ -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();

View File

@ -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();
}

View File

@ -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());

View File

@ -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();
}

View File

@ -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;
}
}
}