Performance improvement - Moved all autosaving to save a *clone* of the current game in another thread, thus allowing the user to continue his game without having to wait for the game to save

This commit is contained in:
Yair Morgenstern 2018-08-16 23:33:56 +03:00
parent b4fece29e0
commit fe67fda906
28 changed files with 196 additions and 42 deletions

View File

@ -98,5 +98,14 @@ class GameInfo {
for (tile in city.getTiles()) tilesToCities.put(tile,city)
}
}
fun clone():GameInfo{
val toReturn = GameInfo()
toReturn.civilizations.addAll(civilizations.map { it.clone() })
toReturn.tileMap=tileMap.clone()
toReturn.notifications.addAll(notifications)
toReturn.turns=turns
return toReturn
}
}

View File

@ -23,7 +23,7 @@ class GameSaver {
}
fun loadGame(GameName: String) : GameInfo {
val game = Json().fromJson(GameInfo::class.java, getSave(GameName).readString())
val game = UnCivGame.Current.json.fromJson(GameInfo::class.java, getSave(GameName).readString())
game.setTransients()
return game
}

View File

@ -129,6 +129,7 @@ class UnitAutomation{
}
private fun tryAdvanceTowardsCloseEnemy(unit: MapUnit): Boolean {
// this can be sped up if we check each layer separately
var closeEnemies = unit.getTile().getTilesInDistance(5)
.filter{ containsAttackableEnemy(it, unit.civInfo) && unit.movementAlgs().canReach(it)}
if(unit.baseUnit().unitType.isRanged())
@ -171,8 +172,8 @@ class UnitAutomation{
private fun tryHeadTowardsEnemyCity(unit: MapUnit): Boolean {
if(unit.civInfo.cities.isEmpty()) return false
var enemyCities = unit.civInfo.exploredTiles.map { unit.civInfo.gameInfo.tileMap[it] }
.filter { it.isCityCenter() && it.getOwner() != unit.civInfo }
var enemyCities = unit.civInfo.gameInfo.civilizations.filter { unit.civInfo.isAtWarWith(it) }
.flatMap { it.cities }.filter { it.location in unit.civInfo.exploredTiles }.map { it.getCenterTile() }
if(unit.baseUnit().unitType.isRanged())
enemyCities = enemyCities.filterNot { it.getCity()!!.health==1 }

View File

@ -29,7 +29,7 @@ class CityConstructions {
val stats = Stats()
for (building in getBuiltBuildings())
stats.add(building.getStats(cityInfo.civInfo.policies.adoptedPolicies))
stats.science += (cityInfo.buildingUniques.count({ it == "+1 Science Per 2 Population" }) * cityInfo.population.population / 2).toFloat()
stats.science += (cityInfo.getBuildingUniques().count({ it == "+1 Science Per 2 Population" }) * cityInfo.population.population / 2).toFloat()
return stats
}
@ -170,4 +170,12 @@ class CityConstructions {
Automation().chooseNextConstruction(this)
}
fun clone(): CityConstructions {
val toReturn = CityConstructions()
toReturn.currentConstruction=currentConstruction
toReturn.builtBuildings.addAll(builtBuildings)
toReturn.inProgressConstructions.putAll(inProgressConstructions)
return toReturn
}
} // for json parsing, we need to have a default constructor

View File

@ -24,7 +24,7 @@ class CityExpansionManager {
fun getCultureToNextTile(): Int {
val numTilesClaimed = cityInfo.tiles.size - 7
var cultureToNextTile = 6 * Math.pow(numTilesClaimed + 1.4813, 1.3)
if (cityInfo.civInfo.buildingUniques.contains("Cost of acquiring new tiles reduced by 25%")) cultureToNextTile *= 0.75 //Speciality of Angkor Wat
if (cityInfo.civInfo.getBuildingUniques().contains("Cost of acquiring new tiles reduced by 25%")) cultureToNextTile *= 0.75 //Speciality of Angkor Wat
if (cityInfo.civInfo.policies.isAdopted("Tradition")) cultureToNextTile *= 0.75
return Math.round(cultureToNextTile).toInt()
}
@ -69,4 +69,10 @@ class CityExpansionManager {
}
}
fun clone(): CityExpansionManager {
val toReturn = CityExpansionManager()
toReturn.cultureStored=cultureStored
return toReturn
}
}

View File

@ -58,8 +58,7 @@ class CityInfo {
return cityResources
}
val buildingUniques: List<String?>
get() = cityConstructions.getBuiltBuildings().filter { it.unique!=null }.map { it.unique }
fun getBuildingUniques(): List<String?> = cityConstructions.getBuiltBuildings().filter { it.unique != null }.map { it.unique }
fun getGreatPersonPoints(): Stats {
var greatPersonPoints = population.getSpecialists().times(3f)
@ -68,7 +67,7 @@ class CityInfo {
if (building.greatPersonPoints != null)
greatPersonPoints.add(building.greatPersonPoints!!)
if (civInfo.buildingUniques.contains("+33% great person generation in all cities"))
if (civInfo.getBuildingUniques().contains("+33% great person generation in all cities"))
greatPersonPoints = greatPersonPoints.times(1.33f)
if (civInfo.policies.isAdopted("Entrepreneurship"))
greatPersonPoints.gold *= 1.25f
@ -173,4 +172,18 @@ class CityInfo {
}
override fun toString(): String {return name} // for debug
fun clone(): CityInfo {
val toReturn = CityInfo()
toReturn.population = population.clone()
toReturn.health=health
toReturn.name=name
toReturn.tiles.addAll(tiles)
toReturn.workedTiles.addAll(workedTiles)
toReturn.cityConstructions=cityConstructions.clone()
toReturn.expansion = expansion.clone()
toReturn.isBeingRazed=isBeingRazed
toReturn.location=location
return toReturn
}
}

View File

@ -32,7 +32,7 @@ class CityStats {
val civInfo = cityInfo.civInfo
var goldFromTradeRoute = civInfo.getCapital().population.population * 0.15 + cityInfo.population.population * 1.1 - 1 // Calculated by http://civilization.wikia.com/wiki/Trade_route_(Civ5)
if (civInfo.policies.isAdopted("Trade Unions")) goldFromTradeRoute += 2.0
if (civInfo.buildingUniques.contains("Gold from all trade routes +25%")) goldFromTradeRoute *= 1.25 // Machu Pichu speciality
if (civInfo.getBuildingUniques().contains("Gold from all trade routes +25%")) goldFromTradeRoute *= 1.25 // Machu Pichu speciality
stats.gold += goldFromTradeRoute.toFloat()
}
return stats
@ -46,7 +46,7 @@ class CityStats {
"Gold" -> stats.gold += production / 4
"Science" -> {
var scienceProduced = production / 4
if (cityInfo.civInfo.buildingUniques.contains("ScienceConversionIncrease")) scienceProduced *= 1.33f
if (cityInfo.civInfo.getBuildingUniques().contains("ScienceConversionIncrease")) scienceProduced *= 1.33f
if (cityInfo.civInfo.policies.isAdopted("Rationalism")) scienceProduced *= 1.33f
stats.science += scienceProduced
}
@ -109,7 +109,7 @@ class CityStats {
var unhappinessFromCitizens = cityInfo.population.population.toFloat()
if (civInfo.policies.isAdopted("Democracy"))
unhappinessFromCitizens -= cityInfo.population.getNumberOfSpecialists() * 0.5f
if (civInfo.buildingUniques.contains("Unhappiness from population decreased by 10%"))
if (civInfo.getBuildingUniques().contains("Unhappiness from population decreased by 10%"))
unhappinessFromCitizens *= 0.9f
if (civInfo.policies.isAdopted("Meritocracy"))
unhappinessFromCitizens *= 0.95f
@ -181,7 +181,7 @@ class CityStats {
private fun getStatPercentBonusesFromWonders(): Stats {
val stats = Stats()
val civUniques = cityInfo.civInfo.buildingUniques
val civUniques = cityInfo.civInfo.getBuildingUniques()
if (civUniques.contains("Culture in all cities increased by 25%")) stats.culture += 25f
return stats
}

View File

@ -61,7 +61,7 @@ class PopulationManager {
// growth!
{
foodStored -= getFoodToNextPopulation()
if (cityInfo.buildingUniques.contains("40% of food is carried over after a new citizen is born")) foodStored += (0.4f * getFoodToNextPopulation()).toInt() // Aqueduct special
if (cityInfo.getBuildingUniques().contains("40% of food is carried over after a new citizen is born")) foodStored += (0.4f * getFoodToNextPopulation()).toInt() // Aqueduct special
population++
autoAssignPopulation()
cityInfo.civInfo.addNotification(cityInfo.name + " {has grown}!", cityInfo.location, Color.GREEN)
@ -89,4 +89,11 @@ class PopulationManager {
}
}
fun clone(): PopulationManager {
val toReturn = PopulationManager()
toReturn.population=population
toReturn.foodStored=foodStored
return toReturn
}
}

View File

@ -136,7 +136,7 @@ class CivilizationInfo {
}
}
if (buildingUniques.contains("Provides 1 happiness per social policy"))
if (getBuildingUniques().contains("Provides 1 happiness per social policy"))
statMap["Policies"] = policies.getAdoptedPolicies().count { !it.endsWith("Complete") }.toFloat()
return statMap
@ -151,8 +151,7 @@ class CivilizationInfo {
return civResources
}
val buildingUniques: List<String>
get() = cities.flatMap{ it.cityConstructions.getBuiltBuildings().map { it.unique }.filterNotNull() }.distinct()
fun getBuildingUniques(): List<String> = cities.flatMap { it.cityConstructions.getBuiltBuildings().map { it.unique }.filterNotNull() }.distinct()
constructor()
@ -305,6 +304,22 @@ class CivilizationInfo {
if(isAtWarWith(otherCiv)) return true
return false
}
fun clone(): CivilizationInfo {
val toReturn = CivilizationInfo()
toReturn.exploredTiles=exploredTiles.toHashSet()
toReturn.diplomacy.putAll(diplomacy.values.map { it.clone() }.associateBy { it.otherCivName })
toReturn.cities.addAll(cities.map { it.clone() })
toReturn.tech = tech.clone()
toReturn.difficulty=difficulty
toReturn.policies = policies.clone()
toReturn.happiness=happiness
toReturn.greatPeople=greatPeople.clone()
toReturn.gold = gold
toReturn.goldenAges = goldenAges.clone()
toReturn.civName=civName
return toReturn
}
}

View File

@ -94,4 +94,11 @@ class DiplomacyManager() {
}
fun canDeclareWar() = turnsToPeaceTreaty()==0
fun clone(): DiplomacyManager {
val toReturn = DiplomacyManager()
toReturn.otherCivName=otherCivName
toReturn.diplomaticStatus=diplomaticStatus
toReturn.trades.addAll(trades.map { it.clone() })
return toReturn
}
}

View File

@ -2,7 +2,7 @@ package com.unciv.logic.civilization
import com.badlogic.gdx.graphics.Color
class GoldenAgeManager {
class GoldenAgeManager{
@Transient
lateinit var civInfo: CivilizationInfo
@ -18,7 +18,7 @@ class GoldenAgeManager {
fun enterGoldenAge() {
var turnsToGoldenAge = 10.0
if (civInfo.buildingUniques.contains("Golden Age length increases +50%")) turnsToGoldenAge *= 1.5
if (civInfo.getBuildingUniques().contains("Golden Age length increases +50%")) turnsToGoldenAge *= 1.5
if (civInfo.policies.isAdopted("Freedom Complete")) turnsToGoldenAge *= 1.5
turnsLeftForCurrentGoldenAge += turnsToGoldenAge.toInt()
civInfo.addNotification("{You have entered a golden age}!", null, Color.GOLD)
@ -35,4 +35,12 @@ class GoldenAgeManager {
numberOfGoldenAges++
}
}
fun clone(): GoldenAgeManager {
val toReturn = GoldenAgeManager()
toReturn.numberOfGoldenAges=numberOfGoldenAges
toReturn.storedHappiness=storedHappiness
toReturn.turnsLeftForCurrentGoldenAge=turnsLeftForCurrentGoldenAge
return toReturn
}
}

View File

@ -4,7 +4,7 @@ import com.unciv.models.stats.Stats
class GreatPersonManager {
private var pointsForNextGreatPerson = 100
private val greatPersonPoints = Stats()
private var greatPersonPoints = Stats()
var freeGreatPeople=0
fun getNewGreatPerson(): String? {
@ -27,4 +27,12 @@ class GreatPersonManager {
greatPersonPoints.add(greatPersonPoints)
}
fun clone(): GreatPersonManager {
val toReturn = GreatPersonManager()
toReturn.freeGreatPeople=freeGreatPeople
toReturn.greatPersonPoints=greatPersonPoints.clone()
toReturn.pointsForNextGreatPerson=pointsForNextGreatPerson
return toReturn
}
}

View File

@ -23,7 +23,7 @@ class PolicyManager {
var cityModifier = 0.3 * (civInfo.cities.size - 1)
if (isAdopted("Representation")) cityModifier *= (2 / 3f).toDouble()
if (isAdopted("Piety Complete")) baseCost *= 0.9
if (civInfo.buildingUniques.contains("Culture cost of adopting new Policies reduced by 10%")) baseCost *= 0.9
if (civInfo.getBuildingUniques().contains("Culture cost of adopting new Policies reduced by 10%")) baseCost *= 0.9
val cost: Int = Math.round(baseCost * (1 + cityModifier)).toInt()
return cost - (cost % 5)
}
@ -84,4 +84,14 @@ class PolicyManager {
shouldOpenPolicyPicker = true
}
fun clone(): PolicyManager {
val toReturn = PolicyManager()
toReturn.numberOfAdoptedPolicies=numberOfAdoptedPolicies
toReturn.adoptedPolicies.addAll(adoptedPolicies)
toReturn.freePolicies=freePolicies
toReturn.shouldOpenPolicyPicker=shouldOpenPolicyPicker
toReturn.storedCulture=storedCulture
return toReturn
}
}

View File

@ -92,6 +92,15 @@ class TechManager {
city.cityConstructions.currentConstruction = currentConstructionUnit.upgradesTo!!
}
}
fun clone(): TechManager {
val toReturn = TechManager()
toReturn.techsResearched.addAll(techsResearched)
toReturn.freeTechs=freeTechs
toReturn.techsInProgress.putAll(techsInProgress)
toReturn.techsToResearch.addAll(techsToResearch)
return toReturn
}
}

View File

@ -14,7 +14,6 @@ class MapUnit {
lateinit var owner: String
lateinit var name: String
var maxMovement: Int = 0
var currentMovement: Float = 0f
var health:Int = 100
var action: String? = null // work, automation, fortifying, I dunno what.
@ -23,11 +22,12 @@ class MapUnit {
@Transient lateinit var baseUnit: BaseUnit
fun baseUnit(): BaseUnit = baseUnit
fun getMovementString(): String = DecimalFormat("0.#").format(currentMovement.toDouble()) + "/" + maxMovement
fun getMovementString(): String = DecimalFormat("0.#").format(currentMovement.toDouble()) + "/" + getMaxMovement()
@Transient
internal lateinit var currentTile :TileInfo
fun getTile(): TileInfo = currentTile
fun getMaxMovement() = baseUnit.movement
fun getDistanceToTiles(): HashMap<TileInfo, Float> {
val tile = getTile()
@ -63,7 +63,7 @@ class MapUnit {
private fun doPostTurnAction() {
if (name == "Worker" && getTile().improvementInProgress != null) workOnImprovement()
if(currentMovement==maxMovement.toFloat()
if(currentMovement== getMaxMovement().toFloat()
&& isFortified()){
val currentTurnsFortified = getFortificationTurns()
if(currentTurnsFortified<2) action = "Fortify ${currentTurnsFortified+1}"
@ -76,7 +76,7 @@ class MapUnit {
if (tile.turnsToImprovement != 0) return
when {
tile.improvementInProgress!!.startsWith("Remove") -> {
val tileImprovement = tile.tileImprovement
val tileImprovement = tile.getTileImprovement()
if(tileImprovement!=null
&& tileImprovement.terrainsCanBeBuiltOn.contains(tile.terrainFeature)
&& !tileImprovement.terrainsCanBeBuiltOn.contains(tile.baseTerrain)) {
@ -124,14 +124,14 @@ class MapUnit {
fun endTurn() {
doPostTurnAction()
if(currentMovement==maxMovement.toFloat() // didn't move this turn
if(currentMovement== getMaxMovement().toFloat() // didn't move this turn
|| getSpecialAbilities().contains("Unit will heal every turn, even if it performs an action")){
heal()
}
}
fun startTurn(){
currentMovement = maxMovement.toFloat()
currentMovement = getMaxMovement().toFloat()
attacksThisTurn=0
val tileOwner = getTile().getOwner()
if(tileOwner!=null && !civInfo.canEnterTiles(tileOwner)) // if an enemy city expanded onto this tile while I was in it
@ -226,4 +226,16 @@ class MapUnit {
if(hasUnique("+1 Range")) range++
return range
}
fun clone(): MapUnit {
val toReturn = MapUnit()
toReturn.action=action
toReturn.currentMovement=currentMovement
toReturn.name=name
toReturn.promotions=promotions.clone()
toReturn.health=health
toReturn.attacksThisTurn=attacksThisTurn
toReturn.owner=owner
return toReturn
}
}

View File

@ -43,8 +43,7 @@ open class TileInfo {
fun isCityCenter(): Boolean = getCity()?.location == position
val tileImprovement: TileImprovement?
get() = if (improvement == null) null else GameBasics.TileImprovements[improvement!!]
fun getTileImprovement(): TileImprovement? = if (improvement == null) null else GameBasics.TileImprovements[improvement!!]
// This is for performance - since we access the neighbors of a tile ALL THE TIME,
@ -103,7 +102,7 @@ open class TileInfo {
}
}
val improvement = tileImprovement
val improvement = getTileImprovement()
if (improvement != null) {
if (resource != null && getTileResource().improvement == improvement.name)
stats.add(getTileResource().improvementStats!!) // resource-specifc improvement
@ -125,7 +124,7 @@ open class TileInfo {
if (stats.production < 0) stats.production = 0f
if ("Jungle" == terrainFeature && city != null
&& city.buildingUniques.contains("Jungles provide +2 science"))
&& city.getBuildingUniques().contains("Jungles provide +2 science"))
stats.science += 2f
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
stats.gold++
@ -211,4 +210,18 @@ open class TileInfo {
}
fun arialDistanceTo(otherTile:TileInfo) = abs(position.x-otherTile.position.x) + abs(position.y-otherTile.position.y)
fun clone(): TileInfo {
val toReturn = TileInfo()
if(civilianUnit!=null) toReturn.civilianUnit=civilianUnit!!.clone()
if(militaryUnit!=null) toReturn.militaryUnit=militaryUnit!!.clone()
toReturn.improvement=improvement
toReturn.position=position
toReturn.baseTerrain=baseTerrain
toReturn.terrainFeature=terrainFeature
toReturn.improvementInProgress=improvementInProgress
toReturn.resource=resource
toReturn.roadStatus=roadStatus
toReturn.turnsToImprovement=turnsToImprovement
return toReturn
}
}

View File

@ -81,4 +81,10 @@ class TileMap {
return path
}
fun clone(): TileMap {
val toReturn = TileMap()
toReturn.tiles.putAll(tiles.values.map { it.clone() }.associateBy{it.position.toString()})
return toReturn
}
}

View File

@ -78,7 +78,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
while (true) {
val newTilesToCheck = ArrayList<TileInfo>()
val distanceToDestination = HashMap<TileInfo, Float>()
val movementThisTurn = if (distance == 1) unit.currentMovement else unit.maxMovement.toFloat()
val movementThisTurn = if (distance == 1) unit.currentMovement else unit.getMaxMovement().toFloat()
for (tileToCheck in tilesToCheck) {
val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn)
for (reachableTile in distanceToTilesThisTurn.keys) {

View File

@ -23,4 +23,12 @@ class UnitPromotions{
.filter { unit.baseUnit().unitType.toString() in it.unitTypes && it.name !in promotions }
.filter { it.prerequisites.isEmpty() || it.prerequisites.any { p->p in promotions } }
}
fun clone(): UnitPromotions {
val toReturn = UnitPromotions()
toReturn.XP=XP
toReturn.promotions.addAll(promotions)
toReturn.numberOfPromotions=numberOfPromotions
return toReturn
}
}

View File

@ -24,4 +24,11 @@ class Trade{
return false
return true
}
fun clone():Trade{
val toReturn = Trade()
toReturn.theirOffers.addAll(theirOffers)
toReturn.ourOffers.addAll(ourOffers)
return toReturn
}
}

View File

@ -13,4 +13,5 @@ class TradeOffersList: ArrayList<TradeOffer>(){
if(equivalentOffer.amount==0) remove(equivalentOffer)
return true
}
}

View File

@ -186,7 +186,7 @@ class Building : NamedStats(), IConstruction{
}
if ("Spaceship part" == unique) {
if (!civInfo.buildingUniques.contains("Enables construction of Spaceship parts")) return false
if (!civInfo.getBuildingUniques().contains("Enables construction of Spaceship parts")) return false
if (civInfo.scienceVictory.unconstructedParts()[name] == 0) return false // Don't need to build any more of these!
}
return true

View File

@ -18,7 +18,7 @@ class TileImprovement : NamedStats(), ICivilopedia {
private val turnsToBuild: Int = 0 // This is the base cost.
fun getTurnsToBuild(civInfo: CivilizationInfo): Int {
var realTurnsToBuild = turnsToBuild.toFloat()
if (civInfo.buildingUniques.contains("Worker construction increased 25%, provides 2 free workers"))
if (civInfo.getBuildingUniques().contains("Worker construction increased 25%, provides 2 free workers"))
realTurnsToBuild *= 0.75f
if (civInfo.policies.isAdopted("Citizenship"))
realTurnsToBuild *= 0.75f

View File

@ -76,7 +76,6 @@ class BaseUnit : INamed, IConstruction, ICivilopedia {
fun getMapUnit(): MapUnit {
val unit = MapUnit()
unit.name = name
unit.maxMovement = movement
unit.currentMovement = movement.toFloat()
unit.setTransients()
return unit

View File

@ -213,7 +213,7 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){
table.add(unit.name.tr())
if(baseUnit.strength>0) table.add(baseUnit.strength.toString()) else table.add()
if(baseUnit.rangedStrength>0) table.add(baseUnit.rangedStrength.toString()) else table.add()
table.add(unit.currentMovement.toString()+"/"+unit.maxMovement)
table.add(unit.currentMovement.toString()+"/"+unit.getMaxMovement())
val closestCity = unit.getTile().getTilesInDistance(3).firstOrNull{it.isCityCenter()}
if (closestCity!=null) table.add(closestCity.getCity()!!.name) else table.add()
table.row()

View File

@ -58,7 +58,7 @@ class VictoryScreen : PickerScreen() {
fun scienceVictoryColumn():Table{
val t = Table()
t.defaults().pad(5f)
t.add(getMilestone("Built Apollo Program",civInfo.buildingUniques.contains("Enables construction of Spaceship parts"))).row()
t.add(getMilestone("Built Apollo Program",civInfo.getBuildingUniques().contains("Enables construction of Spaceship parts"))).row()
val scienceVictory = civInfo.scienceVictory

View File

@ -177,15 +177,23 @@ class WorldScreen : CameraStageBaseScreen() {
kotlin.concurrent.thread {
try {
game.gameInfo.nextTurn()
GameSaver().saveGame(game.gameInfo, "Autosave")
gameInfo.nextTurn()
}
catch (ex:Exception){
UnCivGame.Current.settings.hasCrashedRecently=true
UnCivGame.Current.settings.save()
game.settings.hasCrashedRecently=true
game.settings.save()
throw ex
}
val gameInfoClone = gameInfo.clone()
kotlin.concurrent.thread {
// the save takes a long time( up to a second!) and we can do it while the player continues his game.
// On the other hand if we alter the game data while it's being serialized we could get a concurrent modification exception.
// So what we do is we clone all the game data and serialize the clone.
GameSaver().saveGame(gameInfoClone, "Autosave")
nextTurnButton.enable() // only enable the user to next turn once we've saved the current one
}
// If we put this BEFORE the save game, then we try to save the game...
// but the main thread does other stuff, including showing tutorials which guess what? Changes the game data
// BOOM! Exception!
@ -193,7 +201,6 @@ class WorldScreen : CameraStageBaseScreen() {
shouldUpdate=true
nextTurnButton.setText("Next turn".tr())
nextTurnButton.enable()
Gdx.input.inputProcessor = stage
}
}

View File

@ -78,7 +78,7 @@ class UnitActions {
newunit.currentMovement=0f
},
unit.civInfo.gold >= goldCostOfUpgrade
&& unit.currentMovement == unit.maxMovement.toFloat() )
&& unit.currentMovement == unit.getMaxMovement().toFloat() )
}
}