From 29bc82e1bdaea9b94ab76e30ac81a9e882a30476 Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Fri, 15 Mar 2019 12:14:19 +0200 Subject: [PATCH] Diplomacy screen redesigned, to allow for more diplomacy options in the future --- .../automation/SpecificUnitAutomation.kt | 150 ++++++++++++++++++ .../unciv/logic/automation/UnitAutomation.kt | 148 +---------------- .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 109 ++++++++----- 3 files changed, 217 insertions(+), 190 deletions(-) create mode 100644 core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt diff --git a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt new file mode 100644 index 0000000000..29e7579165 --- /dev/null +++ b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt @@ -0,0 +1,150 @@ +package com.unciv.logic.automation + +import com.unciv.UnCivGame +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.civilization.GreatPersonManager +import com.unciv.logic.map.MapUnit +import com.unciv.logic.map.TileInfo +import com.unciv.models.stats.Stats +import com.unciv.ui.worldscreen.unit.UnitActions + +class SpecificUnitAutomation{ + + private fun hasWorkableSeaResource(tileInfo: TileInfo, civInfo: CivilizationInfo): Boolean { + return tileInfo.hasViewableResource(civInfo) && tileInfo.isWater() && tileInfo.improvement==null + } + + fun automateWorkBoats(unit: MapUnit) { + val seaResourcesInCities = unit.civInfo.cities.flatMap { it.getTilesInRange() }.asSequence() + .filter { hasWorkableSeaResource(it, unit.civInfo) && (unit.canMoveTo(it) || unit.currentTile == it) } + val closestReachableResource = seaResourcesInCities.sortedBy { it.arialDistanceTo(unit.currentTile) } + .firstOrNull { unit.movementAlgs().canReach(it) } + + if (closestReachableResource != null) { + unit.movementAlgs().headTowards(closestReachableResource) + if (unit.currentMovement > 0 && unit.currentTile == closestReachableResource) { + val createImprovementAction = UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen) + .firstOrNull { it.name.startsWith("Create") } // could be either fishing boats or oil well + if (createImprovementAction != null) + return createImprovementAction.action() // unit is already gone, can't "explore" + } + } + else UnitAutomation().explore(unit, unit.getDistanceToTiles()) + } + + fun automateGreatGeneral(unit: MapUnit){ + //try to follow nearby units. Do not garrison in city if possible + val militaryUnitTilesInDistance = unit.getDistanceToTiles().map { it.key } + .filter {val militant = it.militaryUnit + militant != null && militant.civInfo == unit.civInfo + && (it.civilianUnit == null || it.civilianUnit == unit) + && militant.getMaxMovement() <= 2 && !it.isCityCenter()} + + if(militaryUnitTilesInDistance.isNotEmpty()) { + val tilesSortedByAffectedTroops = militaryUnitTilesInDistance + .sortedByDescending { it.getTilesInDistance(2).count { + val militaryUnit = it.militaryUnit + militaryUnit!=null && militaryUnit.civInfo==unit.civInfo + } } + unit.movementAlgs().headTowards(tilesSortedByAffectedTroops.first()) + return + } + + //if no unit to follow, take refuge in city. + val cityToGarrison = unit.civInfo.cities.map {it.getCenterTile()} + .sortedBy { it.arialDistanceTo(unit.currentTile) } + .firstOrNull { it.civilianUnit == null && unit.canMoveTo(it) && unit.movementAlgs().canReach(it)} + + if (cityToGarrison != null) { + unit.movementAlgs().headTowards(cityToGarrison) + return + } + } + + fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map): Float { + val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2) + .asSequence() + .sortedByDescending { nearbyTileRankings[it] }.take(2) + .toList() + val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer) + .asSequence() + .sortedByDescending { nearbyTileRankings[it] } + .take(5) + .toList() + var rank = top5Tiles.asSequence().map { nearbyTileRankings[it]!! }.sum() + if(tileInfo.neighbors.any{it.baseTerrain == "Coast"}) rank += 5 + return rank + } + + + fun automateSettlerActions(unit: MapUnit) { + if(unit.getTile().militaryUnit==null) return // Don't move until you're accompanied by a military unit + + val tilesNearCities = unit.civInfo.gameInfo.civilizations.flatMap { it.cities } + .flatMap { it.getCenterTile().getTilesInDistance(3) }.toHashSet() + + // This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once. + val nearbyTileRankings = unit.getTile().getTilesInDistance(7) + .associateBy ( {it},{ Automation().rankTile(it,unit.civInfo) }) + + val possibleCityLocations = unit.getTile().getTilesInDistance(5) + .filter { (unit.canMoveTo(it) || unit.currentTile==it) && it !in tilesNearCities && it.isLand() } + + val bestCityLocation: TileInfo? = possibleCityLocations + .asSequence() + .sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings) } + .firstOrNull { unit.movementAlgs().canReach(it) } + + if(bestCityLocation==null) // We got a badass over here, all tiles within 5 are taken? Screw it, random walk. + return UnitAutomation().explore(unit, unit.getDistanceToTiles()) + + if(bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() }) + throw Exception("City within distance") + + if (unit.getTile() == bestCityLocation) + UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action() + else { + unit.movementAlgs().headTowards(bestCityLocation) + if (unit.currentMovement > 0 && unit.getTile() == bestCityLocation) + UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action() + } + } + + fun automateGreatPerson(unit: MapUnit) { + if(unit.getTile().militaryUnit==null) return // Don't move until you're accompanied by a military unit + + val relatedStat = GreatPersonManager().statToGreatPersonMapping.entries.first { it.value==unit.name }.key + + val citiesByStatBoost = unit.civInfo.cities.sortedByDescending{ + val stats = Stats() + for (bonus in it.cityStats.statPercentBonusList.values) stats.add(bonus) + stats.toHashMap()[relatedStat]!! + } + for(city in citiesByStatBoost){ + val pathToCity =unit.movementAlgs().getShortestPath(city.getCenterTile()) + if(pathToCity.isEmpty()) continue + if(pathToCity.size>2){ + unit.movementAlgs().headTowards(city.getCenterTile()) + return + } + + // if we got here, we're pretty close, start looking! + val tiles = city.getTiles().asSequence() + .filter { (unit.canMoveTo(it) || unit.currentTile==it) + && it.isLand() + && !it.isCityCenter() + && it.resource==null } + .sortedByDescending { Automation().rankTile(it,unit.civInfo) }.toList() + val chosenTile = tiles.firstOrNull { unit.movementAlgs().canReach(it) } + if(chosenTile==null) continue // to another city + + unit.movementAlgs().headTowards(chosenTile) + if(unit.currentTile==chosenTile && unit.currentMovement>0) + UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen) + .first { it.name.startsWith("Create") }.action() + return + } + + } + +} \ No newline at end of file diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index 33780a8061..6041dc4371 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -3,13 +3,11 @@ package com.unciv.logic.automation import com.unciv.UnCivGame import com.unciv.logic.battle.* import com.unciv.logic.city.CityInfo -import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.GreatPersonManager import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo import com.unciv.models.gamebasics.GameBasics -import com.unciv.models.stats.Stats import com.unciv.ui.utils.getRandom import com.unciv.ui.worldscreen.unit.UnitAction import com.unciv.ui.worldscreen.unit.UnitActions @@ -215,9 +213,8 @@ class UnitAutomation{ if (unit.baseUnit().upgradesTo != null) { val upgradedUnit = GameBasics.Units[unit.baseUnit().upgradesTo!!]!! if (upgradedUnit.isBuildable(unit.civInfo)) { - val goldCostOfUpgrade = (upgradedUnit.cost - unit.baseUnit().cost) * 2 + 10 val upgradeAction = unitActions.firstOrNull { it.name.startsWith("Upgrade to") } - if (upgradeAction != null && unit.civInfo.gold > goldCostOfUpgrade) { + if (upgradeAction != null && upgradeAction.canAct) { upgradeAction.action() return true } @@ -444,145 +441,4 @@ class UnitAutomation{ } } -} - -class SpecificUnitAutomation{ - - private fun hasWorkableSeaResource(tileInfo: TileInfo, civInfo: CivilizationInfo): Boolean { - return tileInfo.hasViewableResource(civInfo) && tileInfo.isWater() && tileInfo.improvement==null - } - - fun automateWorkBoats(unit: MapUnit) { - val seaResourcesInCities = unit.civInfo.cities.flatMap { it.getTilesInRange() }.asSequence() - .filter { hasWorkableSeaResource(it, unit.civInfo) && (unit.canMoveTo(it) || unit.currentTile == it) } - val closestReachableResource = seaResourcesInCities.sortedBy { it.arialDistanceTo(unit.currentTile) } - .firstOrNull { unit.movementAlgs().canReach(it) } - - if (closestReachableResource != null) { - unit.movementAlgs().headTowards(closestReachableResource) - if (unit.currentMovement > 0 && unit.currentTile == closestReachableResource) { - val createImprovementAction = UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen) - .firstOrNull { it.name.startsWith("Create") } // could be either fishing boats or oil well - if (createImprovementAction != null) - return createImprovementAction.action() // unit is already gone, can't "explore" - } - } - else UnitAutomation().explore(unit, unit.getDistanceToTiles()) - } - - fun automateGreatGeneral(unit: MapUnit){ - //try to follow nearby units. Do not garrison in city if possible - val militaryUnitTilesInDistance = unit.getDistanceToTiles().map { it.key } - .filter {val militant = it.militaryUnit - militant != null && militant.civInfo == unit.civInfo - && (it.civilianUnit == null || it.civilianUnit == unit) - && militant.getMaxMovement() <= 2 && !it.isCityCenter()} - - if(militaryUnitTilesInDistance.isNotEmpty()) { - val tilesSortedByAffectedTroops = militaryUnitTilesInDistance - .sortedByDescending { it.getTilesInDistance(2).count { - val militaryUnit = it.militaryUnit - militaryUnit!=null && militaryUnit.civInfo==unit.civInfo - } } - unit.movementAlgs().headTowards(tilesSortedByAffectedTroops.first()) - return - } - - //if no unit to follow, take refuge in city. - val cityToGarrison = unit.civInfo.cities.map {it.getCenterTile()} - .sortedBy { it.arialDistanceTo(unit.currentTile) } - .firstOrNull { it.civilianUnit == null && unit.canMoveTo(it) && unit.movementAlgs().canReach(it)} - - if (cityToGarrison != null) { - unit.movementAlgs().headTowards(cityToGarrison) - return - } - } - - fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map): Float { - val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2) - .asSequence() - .sortedByDescending { nearbyTileRankings[it] }.take(2) - .toList() - val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer) - .asSequence() - .sortedByDescending { nearbyTileRankings[it] } - .take(5) - .toList() - var rank = top5Tiles.asSequence().map { nearbyTileRankings[it]!! }.sum() - if(tileInfo.neighbors.any{it.baseTerrain == "Coast"}) rank += 5 - return rank - } - - - fun automateSettlerActions(unit: MapUnit) { - if(unit.getTile().militaryUnit==null) return // Don't move until you're accompanied by a military unit - - val tilesNearCities = unit.civInfo.gameInfo.civilizations.flatMap { it.cities } - .flatMap { it.getCenterTile().getTilesInDistance(3) }.toHashSet() - - // This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once. - val nearbyTileRankings = unit.getTile().getTilesInDistance(7) - .associateBy ( {it},{ Automation().rankTile(it,unit.civInfo) }) - - val possibleCityLocations = unit.getTile().getTilesInDistance(5) - .filter { (unit.canMoveTo(it) || unit.currentTile==it) && it !in tilesNearCities && it.isLand() } - - val bestCityLocation: TileInfo? = possibleCityLocations - .asSequence() - .sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings) } - .firstOrNull { unit.movementAlgs().canReach(it) } - - if(bestCityLocation==null) // We got a badass over here, all tiles within 5 are taken? Screw it, random walk. - return UnitAutomation().explore(unit, unit.getDistanceToTiles()) - - if(bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() }) - throw Exception("City within distance") - - if (unit.getTile() == bestCityLocation) - UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action() - else { - unit.movementAlgs().headTowards(bestCityLocation) - if (unit.currentMovement > 0 && unit.getTile() == bestCityLocation) - UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action() - } - } - - fun automateGreatPerson(unit: MapUnit) { - if(unit.getTile().militaryUnit==null) return // Don't move until you're accompanied by a military unit - - val relatedStat = GreatPersonManager().statToGreatPersonMapping.entries.first { it.value==unit.name }.key - - val citiesByStatBoost = unit.civInfo.cities.sortedByDescending{ - val stats = Stats() - for (bonus in it.cityStats.statPercentBonusList.values) stats.add(bonus) - stats.toHashMap()[relatedStat]!! - } - for(city in citiesByStatBoost){ - val pathToCity =unit.movementAlgs().getShortestPath(city.getCenterTile()) - if(pathToCity.isEmpty()) continue - if(pathToCity.size>2){ - unit.movementAlgs().headTowards(city.getCenterTile()) - return - } - - // if we got here, we're pretty close, start looking! - val tiles = city.getTiles().asSequence() - .filter { (unit.canMoveTo(it) || unit.currentTile==it) - && it.isLand() - && !it.isCityCenter() - && it.resource==null } - .sortedByDescending { Automation().rankTile(it,unit.civInfo) }.toList() - val chosenTile = tiles.firstOrNull { unit.movementAlgs().canReach(it) } - if(chosenTile==null) continue // to another city - - unit.movementAlgs().headTowards(chosenTile) - if(unit.currentTile==chosenTile && unit.currentMovement>0) - UnitActions().getUnitActions(unit,UnCivGame.Current.worldScreen) - .first { it.name.startsWith("Create") }.action() - return - } - - } - -} +} \ No newline at end of file diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index e03ad18554..40e904d6a1 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -1,21 +1,25 @@ package com.unciv.ui.trade import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane +import com.badlogic.gdx.scenes.scene2d.ui.SplitPane +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.UnCivGame +import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.gamebasics.tr import com.unciv.ui.utils.* import com.unciv.ui.worldscreen.optionstable.PopupTable import com.unciv.ui.worldscreen.optionstable.YesNoPopupTable -class DiplomacyScreen:CameraStageBaseScreen(){ +class DiplomacyScreen:CameraStageBaseScreen() { val leftSideTable = Table().apply { defaults().pad(10f) } val rightSideTable = Table() - init{ + init { onBackButtonClicked { UnCivGame.Current.setWorldScreen() } - val splitPane = SplitPane(ScrollPane(leftSideTable),rightSideTable,false, skin) + val splitPane = SplitPane(ScrollPane(leftSideTable), rightSideTable, false, skin) splitPane.splitAmount = 0.2f updateLeftSideTable() @@ -37,49 +41,66 @@ class DiplomacyScreen:CameraStageBaseScreen(){ for (civ in UnCivGame.Current.gameInfo.civilizations .filterNot { it.isDefeated() || it.isPlayerCivilization() || it.isBarbarianCivilization() }) { if (!currentPlayerCiv.diplomacy.containsKey(civ.civName)) continue - val civDiplomacy = currentPlayerCiv.diplomacy[civ.civName]!! - val civTable = Table().apply { background = ImageGetter.getBackground(civ.getNation().getColor()) } - civTable.pad(10f) - civTable.defaults().pad(10f) - val peaceWarStatus = civDiplomacy.diplomaticStatus.toString() - civTable.add(Label(civ.civName.tr() + " ({$peaceWarStatus})".tr(), skin) - .setFontSize(22).setFontColor(civ.getNation().getSecondaryColor())).row() - civTable.addSeparator() + val civIndicator = ImageGetter.getCircle().apply { color = civ.getNation().getSecondaryColor() } + .surroundWithCircle(100f).apply { circle.color = civ.getNation().getColor() } + val relationship = ImageGetter.getCircle() + if(currentPlayerCiv.isAtWarWith(civ)) relationship.color = Color.RED + else relationship.color = Color.GREEN + relationship.setSize(30f,30f) + civIndicator.addActor(relationship) - val tradeButton = TextButton("Trade".tr(), skin) - tradeButton.onClick { + leftSideTable.add(civIndicator).row() + + civIndicator.onClick { rightSideTable.clear() - rightSideTable.add(TradeTable(civ, stage){updateLeftSideTable()}) + rightSideTable.add(getDiplomacyTable(civ)) } - civTable.add(tradeButton).row() - - if (!currentPlayerCiv.isAtWarWith(civ)) { - val declareWarButton = TextButton("Declare war".tr(), skin) - declareWarButton.color = Color.RED - val turnsToPeaceTreaty = civDiplomacy.turnsToPeaceTreaty() - if(turnsToPeaceTreaty>0){ - declareWarButton.disable() - declareWarButton.setText(declareWarButton.text.toString() + " ($turnsToPeaceTreaty)") - } - declareWarButton.onClick { - YesNoPopupTable("Declare war on [${civ.civName}]?".tr(), { - civDiplomacy.declareWar() - - val responsePopup = PopupTable(this) - val otherCivLeaderName = civ.getNation().leaderName + " of " + civ.civName - responsePopup.add(otherCivLeaderName.toLabel()) - responsePopup.addSeparator() - responsePopup.addGoodSizedLabel(civ.getNation().attacked).row() - responsePopup.addButton("Very well.".tr()) { responsePopup.remove() } - responsePopup.open() - - updateLeftSideTable() - }, this) - } - civTable.add(declareWarButton).row() - } - leftSideTable.add(civTable).row() } } -} + + private fun getDiplomacyTable(civ: CivilizationInfo): Table { + val diplomacyTable = Table() + diplomacyTable.defaults().pad(10f) + val leaderName = "[" + civ.getNation().leaderName + "] of [" + civ.civName + "]" + diplomacyTable.add(leaderName.toLabel()) + diplomacyTable.addSeparator() + + val tradeButton = TextButton("Trade".tr(), skin) + tradeButton.onClick { + rightSideTable.clear() + rightSideTable.add(TradeTable(civ, stage) { updateLeftSideTable() }) + } + diplomacyTable.add(tradeButton).row() + + val currentPlayerCiv = UnCivGame.Current.gameInfo.getCurrentPlayerCivilization() + val civDiplomacy = currentPlayerCiv.diplomacy[civ.civName]!! + + if (!currentPlayerCiv.isAtWarWith(civ)) { + val declareWarButton = TextButton("Declare war".tr(), skin) + declareWarButton.color = Color.RED + val turnsToPeaceTreaty = civDiplomacy.turnsToPeaceTreaty() + if (turnsToPeaceTreaty > 0) { + declareWarButton.disable() + declareWarButton.setText(declareWarButton.text.toString() + " ($turnsToPeaceTreaty)") + } + declareWarButton.onClick { + YesNoPopupTable("Declare war on [${civ.civName}]?".tr(), { + civDiplomacy.declareWar() + + val responsePopup = PopupTable(this) + val otherCivLeaderName = civ.getNation().leaderName + " of " + civ.civName + responsePopup.add(otherCivLeaderName.toLabel()) + responsePopup.addSeparator() + responsePopup.addGoodSizedLabel(civ.getNation().attacked).row() + responsePopup.addButton("Very well.".tr()) { responsePopup.remove() } + responsePopup.open() + + updateLeftSideTable() + }, this) + } + diplomacyTable.add(declareWarButton).row() + } + return diplomacyTable + } +} \ No newline at end of file