This commit is contained in:
Yair Morgenstern 2020-04-13 11:09:21 +03:00
commit 935071e206
14 changed files with 404 additions and 78 deletions

View file

@ -5,6 +5,7 @@ object Constants {
const val settler = "Settler" const val settler = "Settler"
const val greatGeneral = "Great General" const val greatGeneral = "Great General"
const val impassable = "Impassable"
const val ocean = "Ocean" const val ocean = "Ocean"
const val coast = "Coast" const val coast = "Coast"
const val mountain = "Mountain" const val mountain = "Mountain"

View file

@ -210,7 +210,8 @@ class CityConstructions {
//region state changing functions //region state changing functions
fun setTransients(){ fun setTransients(){
builtBuildingObjects = ArrayList(builtBuildings.map { cityInfo.getRuleset().buildings[it]!! }) builtBuildingObjects = ArrayList(builtBuildings.map { cityInfo.getRuleset().buildings[it]
?: throw java.lang.Exception("Building $it is not found!")})
} }
fun addProductionPoints(productionToAdd: Int) { fun addProductionPoints(productionToAdd: Int) {

View file

@ -280,9 +280,9 @@ class CivilizationInfo {
fun isAtWarWith(otherCiv:CivilizationInfo): Boolean { fun isAtWarWith(otherCiv:CivilizationInfo): Boolean {
if (otherCiv.civName == civName) return false // never at war with itself if (otherCiv.civName == civName) return false // never at war with itself
if (otherCiv.isBarbarian() || isBarbarian()) return true if (otherCiv.isBarbarian() || isBarbarian()) return true
if (!diplomacy.containsKey(otherCiv.civName)) // not encountered yet val diplomacyManager = diplomacy[otherCiv.civName]
return false ?: return false // not encountered yet
return getDiplomacyManager(otherCiv).diplomaticStatus == DiplomaticStatus.War return diplomacyManager.diplomaticStatus == DiplomaticStatus.War
} }
fun isAtWar() = diplomacy.values.any { it.diplomaticStatus== DiplomaticStatus.War && !it.otherCiv().isDefeated() } fun isAtWar() = diplomacy.values.any { it.diplomaticStatus== DiplomaticStatus.War && !it.otherCiv().isDefeated() }
@ -338,7 +338,8 @@ class CivilizationInfo {
* And if they civs on't yet know who they are then they don;t know if they're barbarians =\ * And if they civs on't yet know who they are then they don;t know if they're barbarians =\
* */ * */
fun setNationTransient(){ fun setNationTransient(){
nation = gameInfo.ruleSet.nations[civName]!! nation = gameInfo.ruleSet.nations[civName]
?: throw java.lang.Exception("Nation $civName is not found!")
} }
fun setTransients() { fun setTransients() {
@ -452,12 +453,12 @@ class CivilizationInfo {
fun canEnterTiles(otherCiv: CivilizationInfo): Boolean { fun canEnterTiles(otherCiv: CivilizationInfo): Boolean {
if (otherCiv==this) return true if (otherCiv==this) return true
if(nation.isBarbarian() && gameInfo.turns >= gameInfo.difficultyObject.turnBarbariansCanEnterPlayerTiles) return true if (otherCiv.isBarbarian()) return true
if(!diplomacy.containsKey(otherCiv.civName)) // not encountered yet if (nation.isBarbarian() && gameInfo.turns >= gameInfo.difficultyObject.turnBarbariansCanEnterPlayerTiles)
return false return true
if(isAtWarWith(otherCiv)) return true val diplomacyManager = diplomacy[otherCiv.civName]
if(getDiplomacyManager(otherCiv).hasOpenBorders) return true ?: return false // not encountered yet
return false return (diplomacyManager.hasOpenBorders || diplomacyManager.diplomaticStatus == DiplomaticStatus.War)
} }
fun addNotification(text: String, location: Vector2?, color: Color) { fun addNotification(text: String, location: Vector2?, color: Color) {

View file

@ -39,6 +39,9 @@ class MapUnit {
@Transient var doubleMovementInCoast = false @Transient var doubleMovementInCoast = false
@Transient var doubleMovementInForestAndJungle = false @Transient var doubleMovementInForestAndJungle = false
@Transient var doubleMovementInSnowTundraAndHills = false @Transient var doubleMovementInSnowTundraAndHills = false
@Transient var canEnterIceTiles = false
@Transient var cannotEnterOceanTiles = false
@Transient var cannotEnterOceanTilesUntilAstronomy = false
lateinit var owner: String lateinit var owner: String
lateinit var name: String lateinit var name: String
@ -134,11 +137,14 @@ class MapUnit {
uniques.addAll(promotions.promotions.map { currentTile.tileMap.gameInfo.ruleSet.unitPromotions[it]!!.effect }) uniques.addAll(promotions.promotions.map { currentTile.tileMap.gameInfo.ruleSet.unitPromotions[it]!!.effect })
tempUniques = uniques tempUniques = uniques
if("Ignores terrain cost" in uniques) ignoresTerrainCost = true ignoresTerrainCost = ("Ignores terrain cost" in uniques)
if("Rough terrain penalty" in uniques) roughTerrainPenalty = true roughTerrainPenalty = ("Rough terrain penalty" in uniques)
if("Double movement in coast" in uniques) doubleMovementInCoast = true doubleMovementInCoast = ("Double movement in coast" in uniques)
if("Double movement rate through Forest and Jungle" in uniques) doubleMovementInForestAndJungle = true doubleMovementInForestAndJungle = ("Double movement rate through Forest and Jungle" in uniques)
if("Double movement in Snow, Tundra and Hills" in uniques) doubleMovementInSnowTundraAndHills = true doubleMovementInSnowTundraAndHills = ("Double movement in Snow, Tundra and Hills" in uniques)
canEnterIceTiles = ("Can enter ice tiles" in uniques)
cannotEnterOceanTiles = ("Cannot enter ocean tiles" in uniques)
cannotEnterOceanTilesUntilAstronomy = ("Cannot enter ocean tiles until Astronomy" in uniques)
} }
fun hasUnique(unique:String): Boolean { fun hasUnique(unique:String): Boolean {
@ -307,7 +313,8 @@ class MapUnit {
fun setTransients(ruleset: Ruleset) { fun setTransients(ruleset: Ruleset) {
promotions.unit=this promotions.unit=this
mapUnitAction?.unit = this mapUnitAction?.unit = this
baseUnit=ruleset.units[name]!! baseUnit=ruleset.units[name]
?: throw java.lang.Exception("Unit $name is not found!")
updateUniques() updateUniques()
} }

View file

@ -363,7 +363,7 @@ open class TileInfo {
if(!defencePercentString.startsWith("-")) defencePercentString = "+$defencePercentString" if(!defencePercentString.startsWith("-")) defencePercentString = "+$defencePercentString"
lineList += "[$defencePercentString] to unit defence".tr() lineList += "[$defencePercentString] to unit defence".tr()
} }
if(getBaseTerrain().impassable) lineList += "Impassable".tr() if(getBaseTerrain().impassable) lineList += Constants.impassable.tr()
return lineList.joinToString("\n") return lineList.joinToString("\n")
} }

View file

@ -325,8 +325,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
&& !(tile.isCityCenter() && tile.isCoastalTile())) && !(tile.isCityCenter() && tile.isCoastalTile()))
return false return false
if (tile.terrainFeature == Constants.ice if (tile.terrainFeature == Constants.ice && !unit.canEnterIceTiles)
&& !unit.baseUnit.uniques.contains("Can enter ice tiles"))
return false return false
if (tile.isWater && unit.type.isLandUnit()) { if (tile.isWater && unit.type.isLandUnit()) {
@ -335,8 +334,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return false return false
} }
if (tile.isOcean && unit.civInfo.nation.unique != UniqueAbility.WAYFINDING) { if (tile.isOcean && unit.civInfo.nation.unique != UniqueAbility.WAYFINDING) {
if (unit.baseUnit.uniques.contains("Cannot enter ocean tiles")) return false if (unit.cannotEnterOceanTiles) return false
if (unit.baseUnit.uniques.contains("Cannot enter ocean tiles until Astronomy") if (unit.cannotEnterOceanTilesUntilAstronomy
&& !unit.civInfo.tech.isResearched("Astronomy")) && !unit.civInfo.tech.isResearched("Astronomy"))
return false return false
} }
@ -350,12 +349,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
// AIs won't enter city-state's border. // AIs won't enter city-state's border.
} }
val unitsInTile = tile.getUnits() val firstUnit = tile.getUnits().firstOrNull()
if (unitsInTile.isNotEmpty()) { if (firstUnit != null && firstUnit.civInfo != unit.civInfo && unit.civInfo.isAtWarWith(firstUnit.civInfo))
val firstUnit = unitsInTile.first()
if (firstUnit.civInfo != unit.civInfo && unit.civInfo.isAtWarWith(firstUnit.civInfo))
return false return false
}
return true return true
} }

View file

@ -1,6 +1,7 @@
package com.unciv.models.ruleset.tile package com.unciv.models.ruleset.tile
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.stats.NamedStats import com.unciv.models.stats.NamedStats
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -23,6 +24,9 @@ class Terrain : NamedStats() {
if(uniques.isNotEmpty()) if(uniques.isNotEmpty())
sb.appendln(uniques.joinToString { it.tr() }) sb.appendln(uniques.joinToString { it.tr() })
if (impassable)
sb.appendln(Constants.impassable.tr())
else
sb.appendln("{Movement cost}: $movementCost".tr()) sb.appendln("{Movement cost}: $movementCost".tr())
if (defenceBonus != 0f) if (defenceBonus != 0f)

View file

@ -3,9 +3,15 @@ package com.unciv.ui
import com.unciv.ui.utils.AutoScrollPane as ScrollPane import com.unciv.ui.utils.AutoScrollPane as ScrollPane
import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.scenes.scene2d.ui.*
import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.tilegroups.TileGroup
import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import java.util.* import java.util.*
@ -15,15 +21,19 @@ class CivilopediaScreen(ruleset: Ruleset) : CameraStageBaseScreen() {
private val categoryToEntries = LinkedHashMap<String, Collection<CivilopediaEntry>>() private val categoryToEntries = LinkedHashMap<String, Collection<CivilopediaEntry>>()
private val categoryToButtons = LinkedHashMap<String, Button>() private val categoryToButtons = LinkedHashMap<String, Button>()
private val entrySelectTable = Table().apply { defaults().pad(5f) } private val entrySelectTable = Table().apply { defaults().pad(6f) }
val description = "".toLabel() val description = "".toLabel()
fun select(category: String) { fun select(category: String) {
entrySelectTable.clear() entrySelectTable.clear()
for (entry in categoryToEntries[category]!! for (entry in categoryToEntries[category]!!
.sortedBy { it.name.tr() }){ // Alphabetical order of localized names .sortedBy { it.name.tr() }){ // Alphabetical order of localized names
val entryButton = Button(skin) val entryButton = Button(skin)
if(entry.image!=null) if(entry.image!=null)
if (category=="Terrains")
entryButton.add(entry.image).padRight(24f)
else
entryButton.add(entry.image).size(50f).padRight(10f) entryButton.add(entry.image).size(50f).padRight(10f)
entryButton.add(entry.name.toLabel()) entryButton.add(entry.name.toLabel())
entryButton.onClick { entryButton.onClick {
@ -35,29 +45,8 @@ class CivilopediaScreen(ruleset: Ruleset) : CameraStageBaseScreen() {
init { init {
onBackButtonClicked { UncivGame.Current.setWorldScreen() } onBackButtonClicked { UncivGame.Current.setWorldScreen() }
val buttonTable = Table()
buttonTable.pad(15f)
buttonTable.defaults().pad(10f)
val buttonTableScroll = ScrollPane(buttonTable)
val goToGameButton = TextButton("Close".tr(), skin) val tileSetStrings = TileSetStrings()
goToGameButton.onClick {
game.setWorldScreen()
dispose()
}
val topTable = Table()
topTable.add(goToGameButton).pad(10f)
topTable.add(buttonTableScroll)
val entryTable = Table()
val splitPane = SplitPane(topTable, entryTable, true, skin)
splitPane.splitAmount = 0.2f
splitPane.setFillParent(true)
stage.addActor(splitPane)
description.setWrap(true)
categoryToEntries["Buildings"] = ruleset.buildings.values categoryToEntries["Buildings"] = ruleset.buildings.values
.map { CivilopediaEntry(it.name,it.getDescription(false, null,ruleset), .map { CivilopediaEntry(it.name,it.getDescription(false, null,ruleset),
@ -66,7 +55,8 @@ class CivilopediaScreen(ruleset: Ruleset) : CameraStageBaseScreen() {
.map { CivilopediaEntry(it.name,it.getDescription(ruleset), .map { CivilopediaEntry(it.name,it.getDescription(ruleset),
ImageGetter.getResourceImage(it.name,50f)) } ImageGetter.getResourceImage(it.name,50f)) }
categoryToEntries["Terrains"] = ruleset.terrains.values categoryToEntries["Terrains"] = ruleset.terrains.values
.map { CivilopediaEntry(it.name,it.getDescription(ruleset)) } .map { CivilopediaEntry(it.name,it.getDescription(ruleset),
terrainImage(it, ruleset, tileSetStrings) ) }
categoryToEntries["Tile Improvements"] = ruleset.tileImprovements.values categoryToEntries["Tile Improvements"] = ruleset.tileImprovements.values
.map { CivilopediaEntry(it.name,it.getDescription(ruleset,false), .map { CivilopediaEntry(it.name,it.getDescription(ruleset,false),
ImageGetter.getImprovementIcon(it.name,50f)) } ImageGetter.getImprovementIcon(it.name,50f)) }
@ -87,6 +77,10 @@ class CivilopediaScreen(ruleset: Ruleset) : CameraStageBaseScreen() {
categoryToEntries["Tutorials"] = tutorialController.getCivilopediaTutorials() categoryToEntries["Tutorials"] = tutorialController.getCivilopediaTutorials()
.map { CivilopediaEntry(it.key.replace("_"," "), it.value.joinToString("\n\n") { line -> line.tr() }) } .map { CivilopediaEntry(it.key.replace("_"," "), it.value.joinToString("\n\n") { line -> line.tr() }) }
val buttonTable = Table()
buttonTable.pad(15f)
buttonTable.defaults().pad(10f)
for (category in categoryToEntries.keys) { for (category in categoryToEntries.keys) {
val button = TextButton(category.tr(), skin) val button = TextButton(category.tr(), skin)
button.style = TextButton.TextButtonStyle(button.style) button.style = TextButton.TextButtonStyle(button.style)
@ -94,19 +88,74 @@ class CivilopediaScreen(ruleset: Ruleset) : CameraStageBaseScreen() {
button.onClick { select(category) } button.onClick { select(category) }
buttonTable.add(button) buttonTable.add(button)
} }
select("Tutorials")
val sp = ScrollPane(entrySelectTable) buttonTable.pack()
sp.setupOverscroll(5f, 1f, 200f) buttonTable.width = stage.width
entryTable.add(sp).width(Value.percentWidth(0.25f, entryTable)).height(Value.percentHeight(0.7f, entryTable)) val buttonTableScroll = ScrollPane(buttonTable)
val goToGameButton = TextButton("Close".tr(), skin)
goToGameButton.onClick {
game.setWorldScreen()
dispose()
}
val topTable = Table()
topTable.add(goToGameButton).pad(10f)
topTable.add(buttonTableScroll)
topTable.pack()
//buttonTable.height = topTable.height
val entryTable = Table()
val splitPane = SplitPane(topTable, entryTable, true, skin)
splitPane.splitAmount = topTable.prefHeight / stage.height
entryTable.height = stage.height - topTable.prefHeight
splitPane.setFillParent(true)
stage.addActor(splitPane)
description.setWrap(true)
val entrySelectScroll = ScrollPane(entrySelectTable)
entrySelectScroll.setupOverscroll(5f, 1f, 200f)
entryTable.add(entrySelectScroll)
.width(Value.percentWidth(0.25f, entryTable))
.fillY()
.pad(Value.percentWidth(0.02f, entryTable)) .pad(Value.percentWidth(0.02f, entryTable))
entryTable.add(ScrollPane(description)).colspan(4) entryTable.add(ScrollPane(description)).colspan(4)
.width(Value.percentWidth(0.65f, entryTable)) .width(Value.percentWidth(0.65f, entryTable))
.height(Value.percentHeight(0.7f, entryTable)) .fillY()
.pad(Value.percentWidth(0.02f, entryTable)) .pad(Value.percentWidth(0.02f, entryTable))
// Simply changing these to x*width, y*height won't work // Simply changing these to x*width, y*height won't work
buttonTable.width = stage.width select("Tutorials")
} }
private fun terrainImage (terrain: Terrain, ruleset: Ruleset, tileSetStrings: TileSetStrings ): Actor? {
val tileInfo = TileInfo()
tileInfo.ruleset = ruleset
when(terrain.type) {
TerrainType.NaturalWonder -> {
tileInfo.naturalWonder = terrain.name
tileInfo.baseTerrain = terrain.turnsInto ?: Constants.grassland
}
TerrainType.TerrainFeature -> {
tileInfo.terrainFeature = terrain.name
tileInfo.baseTerrain = terrain.occursOn?.last() ?: Constants.grassland
}
else ->
tileInfo.baseTerrain = terrain.name
}
tileInfo.setTransients()
val group = TileGroup(tileInfo, TileSetStrings())
group.showEntireMap = true
group.forMapEditorIcon = true
group.update()
return group
// val wrapper = Table()
// wrapper.add(group).pad(24f)
// wrapper.pad(2f,24f,2f,24f)
// wrapper.debug = true
// return wrapper
}
} }

View file

@ -12,6 +12,8 @@ import com.unciv.logic.map.RoadStatus
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.saves.Gzip import com.unciv.ui.saves.Gzip
import com.unciv.ui.utils.Popup import com.unciv.ui.utils.Popup
import com.unciv.ui.utils.enable
import com.unciv.ui.utils.isEnabled
import com.unciv.ui.utils.onClick import com.unciv.ui.utils.onClick
import com.unciv.ui.worldscreen.mainmenu.DropBox import com.unciv.ui.worldscreen.mainmenu.DropBox
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -19,8 +21,10 @@ import kotlin.concurrent.thread
class MapEditorMenuPopup(mapEditorScreen: MapEditorScreen): Popup(mapEditorScreen){ class MapEditorMenuPopup(mapEditorScreen: MapEditorScreen): Popup(mapEditorScreen){
init{ init{
val mapNameEditor = TextField(mapEditorScreen.mapName, skin) val mapNameEditor = TextField(mapEditorScreen.mapName, skin)
mapNameEditor.addListener{ mapEditorScreen.mapName=mapNameEditor.text; true } add(mapNameEditor).fillX().row()
add(mapNameEditor).row() mapNameEditor.selectAll()
mapNameEditor.maxLength = 240 // A few under max for most filesystems
mapEditorScreen.stage.keyboardFocus = mapNameEditor
val newMapButton = TextButton("New map".tr(),skin) val newMapButton = TextButton("New map".tr(),skin)
newMapButton.onClick { newMapButton.onClick {
@ -51,10 +55,28 @@ class MapEditorMenuPopup(mapEditorScreen: MapEditorScreen): Popup(mapEditorScree
saveMapButton.onClick { saveMapButton.onClick {
mapEditorScreen.tileMap.mapParameters.name=mapEditorScreen.mapName mapEditorScreen.tileMap.mapParameters.name=mapEditorScreen.mapName
mapEditorScreen.tileMap.mapParameters.type=MapType.custom mapEditorScreen.tileMap.mapParameters.type=MapType.custom
thread ( name = "SaveMap" ) {
try {
MapSaver.saveMap(mapEditorScreen.mapName, mapEditorScreen.tileMap) MapSaver.saveMap(mapEditorScreen.mapName, mapEditorScreen.tileMap)
UncivGame.Current.setWorldScreen() UncivGame.Current.setWorldScreen()
} catch (ex: Exception) {
ex.printStackTrace()
Gdx.app.postRunnable {
val cantLoadGamePopup = Popup(mapEditorScreen)
cantLoadGamePopup.addGoodSizedLabel("It looks like your map can't be saved!").row()
cantLoadGamePopup.addCloseButton()
cantLoadGamePopup.open(force = true)
} }
}
}
}
saveMapButton.isEnabled = mapNameEditor.text.isNotEmpty()
add(saveMapButton).row() add(saveMapButton).row()
mapNameEditor.addListener {
mapEditorScreen.mapName = mapNameEditor.text
saveMapButton.isEnabled = mapNameEditor.text.isNotEmpty()
true
}
val copyMapAsTextButton = TextButton("Copy to clipboard".tr(), skin) val copyMapAsTextButton = TextButton("Copy to clipboard".tr(), skin)
copyMapAsTextButton.onClick { copyMapAsTextButton.onClick {

View file

@ -18,7 +18,7 @@ import com.unciv.ui.utils.setFontSize
class MapEditorScreen(): CameraStageBaseScreen() { class MapEditorScreen(): CameraStageBaseScreen() {
val ruleset = RulesetCache.getBaseRuleset() val ruleset = RulesetCache.getBaseRuleset()
var mapName = "My first map" var mapName = ""
var tileMap = TileMap() var tileMap = TileMap()
lateinit var mapHolder: EditorMapHolder lateinit var mapHolder: EditorMapHolder

View file

@ -42,11 +42,13 @@ class LoadGameScreen : PickerScreen() {
if (ex is UncivShowableException && ex.localizedMessage != null) { if (ex is UncivShowableException && ex.localizedMessage != null) {
// thrown exceptions are our own tests and can be shown to the user // thrown exceptions are our own tests and can be shown to the user
cantLoadGamePopup.addGoodSizedLabel(ex.localizedMessage).row() cantLoadGamePopup.addGoodSizedLabel(ex.localizedMessage).row()
cantLoadGamePopup.addCloseButton()
cantLoadGamePopup.open() cantLoadGamePopup.open()
} else { } else {
cantLoadGamePopup.addGoodSizedLabel("If you could copy your game data (\"Copy saved game to clipboard\" - ").row() cantLoadGamePopup.addGoodSizedLabel("If you could copy your game data (\"Copy saved game to clipboard\" - ").row()
cantLoadGamePopup.addGoodSizedLabel(" paste into an email to yairm210@hotmail.com)").row() cantLoadGamePopup.addGoodSizedLabel(" paste into an email to yairm210@hotmail.com)").row()
cantLoadGamePopup.addGoodSizedLabel("I could maybe help you figure out what went wrong, since this isn't supposed to happen!").row() cantLoadGamePopup.addGoodSizedLabel("I could maybe help you figure out what went wrong, since this isn't supposed to happen!").row()
cantLoadGamePopup.addCloseButton()
cantLoadGamePopup.open() cantLoadGamePopup.open()
ex.printStackTrace() ex.printStackTrace()
} }

View file

@ -113,7 +113,10 @@ fun Button.enable() {
color = Color.WHITE color = Color.WHITE
touchable = Touchable.enabled touchable = Touchable.enabled
} }
var Button.isEnabled: Boolean
//Todo: Use in PromotionPickerScreen, TradeTable, WorldScreen.updateNextTurnButton
get() = touchable == Touchable.enabled
set(value) = if (value) enable() else disable()
fun colorFromRGB(r: Int, g: Int, b: Int): Color { fun colorFromRGB(r: Int, g: Int, b: Int): Color {
return Color(r/255f, g/255f, b/255f, 1f) return Color(r/255f, g/255f, b/255f, 1f)

View file

@ -14,6 +14,7 @@ import com.unciv.logic.automation.UnitAutomation
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.* import com.unciv.logic.map.*
import com.unciv.models.AttackableTile
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
import com.unciv.ui.map.TileGroupMap import com.unciv.ui.map.TileGroupMap
@ -261,23 +262,24 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f) if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f)
} }
val unitType = unit.type val attackableTiles: List<AttackableTile> = if (unit.type.isCivilian()) listOf()
val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf()
else { else {
val tiles = BattleHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles()).map { it.tileToAttack } BattleHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
tiles.filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) } .filter { (UncivGame.Current.viewEntireMapForDebug ||
playerViewableTilePositions.contains(it.tileToAttack.position)) }
.distinctBy { it.tileToAttack }
} }
for (attackableTile in attackableTiles) { for (attackableTile in attackableTiles) {
tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57)) tileGroups[attackableTile.tileToAttack]!!.showCircle(colorFromRGB(237, 41, 57))
val distance = unit.currentTile.aerialDistanceTo(attackableTile)
if (distance > unit.getRange())
tileGroups[attackableTile]!!.showCrosshair(colorFromRGB(255, 75, 0))
else
tileGroups[attackableTile]!!.showCrosshair(Color.RED)
tileGroups[attackableTile.tileToAttack]!!.showCrosshair (
// the targets which cannot be attacked without movements shown as orange-ish
if (attackableTile.tileToAttackFrom != unit.currentTile)
colorFromRGB(255, 75, 0)
else Color.RED
)
} }
// Fade out less relevant images if a military unit is selected // Fade out less relevant images if a military unit is selected

View file

@ -0,0 +1,238 @@
// Taken from https://github.com/TomGrill/gdx-testing
package com.unciv.logic.map
import com.unciv.Constants
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.models.ruleset.Nation
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.UnitType
import com.unciv.testing.GdxTestRunner
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(GdxTestRunner::class)
class UnitMovementAlgorithmsTests {
private var tile = TileInfo()
private var civInfo = CivilizationInfo()
private var ruleSet = Ruleset()
private var unit = MapUnit()
@Before
fun initTheWorld() {
RulesetCache.loadRulesets()
ruleSet = RulesetCache.getBaseRuleset()
tile.ruleset = ruleSet
civInfo.tech.techsResearched.addAll(ruleSet.technologies.keys)
civInfo.tech.embarkedUnitsCanEnterOcean = true
civInfo.tech.unitsCanEmbark = true
civInfo.nation = Nation().apply {
name = "My nation"
cities = arrayListOf("The Capital")
}
unit.civInfo = civInfo
}
@Test
fun canPassThroughPassableTerrains() {
for (terrain in ruleSet.terrains.values) {
tile.baseTerrain = terrain.name
tile.setTransients()
unit.baseUnit = BaseUnit().apply { unitType = UnitType.Melee }
Assert.assertTrue(terrain.name, terrain.impassable != unit.movement.canPassThrough(tile))
}
}
@Test
fun unitCanEnterTheCity() {
val map = TileMap()
tile.baseTerrain = Constants.hill
tile.tileMap = map
tile.setTransients()
val otherTile = tile.clone()
otherTile.baseTerrain = Constants.coast
otherTile.position.y = 1f
map.tileMatrix.add(arrayListOf(tile, otherTile))
val city = CityInfo()
city.location = tile.position
city.civInfo = civInfo
tile.owningCity = city
for (type in UnitType.values())
{
unit.owner = civInfo.civName
unit.baseUnit = BaseUnit().apply { unitType = type }
Assert.assertTrue(type.name, unit.movement.canPassThrough(tile))
}
}
@Test
fun waterUnitCanNOTEnterLand() {
for (terrain in ruleSet.terrains.values) {
if (terrain.impassable) continue
tile.baseTerrain = terrain.name
tile.setTransients()
for (type in UnitType.values()) {
if (type == UnitType.City) continue
unit.baseUnit = BaseUnit().apply { unitType = type }
Assert.assertTrue("%s cannot be at %s".format(type, terrain.name),
(type.isWaterUnit() && tile.isLand) != unit.movement.canPassThrough(tile))
}
}
}
@Test
fun canNOTEnterIce() {
tile.baseTerrain = Constants.ocean
tile.terrainFeature = Constants.ice
tile.setTransients()
for (type in UnitType.values()) {
unit.baseUnit = BaseUnit().apply { unitType = type }
if (type == UnitType.WaterSubmarine) {
unit.baseUnit.uniques.add("Can enter ice tiles")
}
unit.updateUniques()
Assert.assertTrue("$type cannot be in Ice",
(type == UnitType.WaterSubmarine) == unit.movement.canPassThrough(tile))
}
}
@Test
fun canNOTEnterNaturalWonder() {
tile.baseTerrain = Constants.plains
tile.naturalWonder = "Wonder Thunder"
tile.setTransients()
for (type in UnitType.values()) {
unit.baseUnit = BaseUnit().apply { unitType = type }
Assert.assertFalse("$type must not enter Wonder tile", unit.movement.canPassThrough(tile))
}
}
@Test
fun canNOTEnterCoastUntilProperTechIsResearched() {
civInfo.tech.unitsCanEmbark = false
tile.baseTerrain = Constants.coast
tile.setTransients()
for (type in UnitType.values()) {
unit.baseUnit = BaseUnit().apply { unitType = type }
Assert.assertTrue("$type cannot be in Coast",
unit.type.isLandUnit() != unit.movement.canPassThrough(tile))
}
}
@Test
fun canNOTEnterOceanUntilProperTechIsResearched() {
civInfo.tech.embarkedUnitsCanEnterOcean = false
tile.baseTerrain = Constants.ocean
tile.setTransients()
for (type in UnitType.values()) {
unit.baseUnit = BaseUnit().apply { unitType = type }
Assert.assertTrue("$type cannot be in Ocean",
unit.type.isLandUnit() != unit.movement.canPassThrough(tile))
}
}
@Test
fun canNOTEnterOceanWithLimitations() {
tile.baseTerrain = Constants.ocean
tile.setTransients()
for (type in UnitType.values()) {
unit.baseUnit = BaseUnit().apply {
unitType = type
if (type == UnitType.Melee)
uniques.add("Cannot enter ocean tiles")
if (type == UnitType.Ranged)
uniques.add("Cannot enter ocean tiles until Astronomy")
}
unit.updateUniques()
Assert.assertTrue("$type cannot be in Ocean",
(type == UnitType.Melee) != unit.movement.canPassThrough(tile))
civInfo.tech.techsResearched.remove("Astronomy")
Assert.assertTrue("$type cannot be in Ocean until Astronomy",
(type == UnitType.Melee ||
type == UnitType.Ranged) != unit.movement.canPassThrough(tile))
civInfo.tech.techsResearched.add("Astronomy")
}
}
@Test
fun canNOTPassThroughTileWithEnemyUnits() {
tile.baseTerrain = Constants.grassland
tile.setTransients()
val otherCiv = CivilizationInfo()
otherCiv.civName = "Barbarians" // they are always enemies
otherCiv.nation = Nation().apply { name = "Barbarians" }
val otherUnit = MapUnit()
otherUnit.civInfo = otherCiv
tile.militaryUnit = otherUnit
for (type in UnitType.values()) {
unit.baseUnit = BaseUnit().apply { unitType = type }
Assert.assertFalse("$type must not enter occupied tile", unit.movement.canPassThrough(tile))
}
}
@Test
fun canNOTPassForeignTiles() {
tile.baseTerrain = Constants.desert
tile.setTransients()
val otherCiv = CivilizationInfo()
otherCiv.civName = "Other civ"
otherCiv.nation = Nation().apply { name = "Other nation" }
val city = CityInfo()
city.location = tile.position.cpy().add(1f,1f)
city.civInfo = otherCiv
tile.owningCity = city
unit.baseUnit = BaseUnit().apply { unitType = UnitType.Melee }
unit.owner = civInfo.civName
Assert.assertFalse("Unit must not enter other civ tile", unit.movement.canPassThrough(tile))
city.location = tile.position
Assert.assertFalse("Unit must not enter other civ city", unit.movement.canPassThrough(tile))
city.hasJustBeenConquered = true
civInfo.diplomacy["Other civ"] = DiplomacyManager(otherCiv, "Other civ")
civInfo.getDiplomacyManager(otherCiv).diplomaticStatus = DiplomaticStatus.War
Assert.assertTrue("Unit can capture other civ city", unit.movement.canPassThrough(tile))
}
}