New tile visibility framework!

This commit is contained in:
Yair Morgenstern 2023-01-15 10:54:41 +02:00
parent 924dbf70d0
commit d1a9caaa88
6 changed files with 98 additions and 38 deletions

View File

@ -122,7 +122,7 @@
"defenceBonus": 0.25,
"RGB": [120, 120, 120],
"uniques": ["Rough terrain",
"Has an elevation of [4] for visibility calculations",
"Has an elevation of [2] for visibility calculations",
"Occurs in chains at high elevations",
"Units ending their turn on this terrain take [50] damage",
"Always Fertility [-2] for Map Generation",
@ -158,7 +158,7 @@
"occursOn": ["Tundra","Plains","Grassland","Desert","Snow"],
"uniques": ["Rough terrain",
"[+5] Strength for cities built on this terrain",
"Has an elevation of [2] for visibility calculations",
"Has an elevation of [1] for visibility calculations",
"Occurs in groups around high elevations",
"[+1] to Fertility for Map Generation",
"A Region is formed with at least [40]% [Hill] tiles, with priority [5]",

View File

@ -122,7 +122,7 @@
"defenceBonus": 0.25,
"RGB": [120, 120, 120],
"uniques": ["Rough terrain",
"Has an elevation of [4] for visibility calculations",
"Has an elevation of [2] for visibility calculations",
"Occurs in chains at high elevations",
"Units ending their turn on this terrain take [50] damage",
"Always Fertility [-2] for Map Generation",
@ -157,7 +157,7 @@
"occursOn": ["Tundra","Plains","Grassland","Desert","Snow"],
"uniques": ["Rough terrain",
"[+5] Strength for cities built on this terrain",
"Has an elevation of [2] for visibility calculations",
"Has an elevation of [1] for visibility calculations",
"Occurs in groups around high elevations",
"[+1] to Fertility for Map Generation",
"A Region is formed with at least [40]% [Hill] tiles, with priority [5]",

View File

@ -40,7 +40,7 @@ object UnitAutomation {
unit.movement.getDistanceToTiles().keys.filter { isGoodTileToExplore(unit, it) }
if (explorableTilesThisTurn.any()) {
val bestTile = explorableTilesThisTurn
.sortedByDescending { it.height } // secondary sort is by 'how far can you see'
.sortedByDescending { it.tileHeight } // secondary sort is by 'how far can you see'
.maxByOrNull { it.aerialDistanceTo(unit.currentTile) }!! // primary sort is by 'how far can you go'
unit.movement.headTowards(bestTile)
return true

View File

@ -345,7 +345,13 @@ open class TileInfo : IsPartOfGameInfoSerialization {
}
@delegate:Transient
val height : Int by lazy {
val tileHeight : Int by lazy { // for e.g. hill+forest this is 2, since forest is visible above units
if (terrainHasUnique(UniqueType.BlocksLineOfSightAtSameElevation)) unitHeight + 1
else unitHeight
}
@delegate:Transient
val unitHeight : Int by lazy { // for e.g. hill+forest this is 1, since only hill provides height for units
allTerrains.flatMap { it.getMatchingUniques(UniqueType.VisibilityElevation) }
.map { it.params[0].toInt() }.sum()
}

View File

@ -12,6 +12,7 @@ import com.unciv.models.ruleset.Nation
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unique.UniqueType
import java.lang.Integer.max
import kotlin.math.abs
/** An Unciv map with all properties as produced by the [map editor][com.unciv.ui.mapeditor.MapEditorScreen]
@ -325,52 +326,50 @@ class TileMap : IsPartOfGameInfoSerialization {
vectorUnwrappedLeft
}
data class ViewableTile(val tile:TileInfo, val maxHeightSeenToTile:Int, val isVisible:Boolean, val isAttackable: Boolean)
/** @return List of tiles visible from location [position] for a unit with sight range [sightDistance] */
fun getViewableTiles(position: Vector2, sightDistance: Int): List<TileInfo> {
val viewableTiles = getTilesInDistance(position, 1).toMutableList()
val currentTileHeight = get(position).height
val aUnitHeight = get(position).unitHeight
val viewableTiles = mutableListOf(ViewableTile(
get(position),
aUnitHeight,
isVisible = true,
isAttackable = false
))
for (i in 1..sightDistance) { // in each layer,
// This is so we don't use tiles in the same distance to "see over",
// that is to say, the "viewableTiles.contains(it) check will return false for neighbors from the same distance
val tilesToAddInDistanceI = ArrayList<TileInfo>()
val tilesToAddInDistanceI = ArrayList<ViewableTile>()
for (cTile in getTilesAtDistance(position, i)) { // for each tile in that layer,
val cTileHeight = cTile.height
val cTileHeight = cTile.tileHeight
/*
Okay so, if we're looking at a tile from a to c with b in the middle,
Okay so, if we're looking at a tile from height a to one with height c with a MAXIMUM HEIGHT of b in the middle,
we have several scenarios:
1. a>b - - I can see everything, b does not hide c
2. a==b
2.1 c>b - c is tall enough I can see it over b!
2.2 b blocks view from same-elevation tiles - hides c
2.3 none of the above - I can see c
1. a>=b - I can see everything, b does not hide c (equals is 'flat plain' or 'string of hills' or 'hill viewing over forests')
3. a<b
3.1 b>=c - b hides c
3.2 b<c - c is tall enough I can see it over b!
3.1 b>=c - b hides c (hills hide other hills, forests, etc)
3.2 b<c - c is tall enough I can see it over b (hill+forest, mountain)
This can all be summed up as "I can see c if a>b || c>b || (a==b && b !blocks same-elevation view)"
This can all be summed up as "I can see c if a=>b || c>b"
*/
val bMinimumHighestSeenTerrainSoFar = viewableTiles.filter { it.tile in cTile.neighbors }
.minOf { it.maxHeightSeenToTile }
val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any { bNeighbor: TileInfo ->
val bNeighborHeight = bNeighbor.height
viewableTiles.contains(bNeighbor)
&& (
currentTileHeight > bNeighborHeight // a>b
|| cTileHeight > bNeighborHeight // c>b
|| (
currentTileHeight == bNeighborHeight // a==b
&& !bNeighbor.terrainHasUnique(UniqueType.BlocksLineOfSightAtSameElevation)
)
)
}
if (containsViewableNeighborThatCanSeeOver) tilesToAddInDistanceI.add(cTile)
tilesToAddInDistanceI.add(ViewableTile(
cTile,
max(cTileHeight, bMinimumHighestSeenTerrainSoFar),
aUnitHeight >= bMinimumHighestSeenTerrainSoFar || cTileHeight > bMinimumHighestSeenTerrainSoFar,
aUnitHeight >= bMinimumHighestSeenTerrainSoFar || cTile.unitHeight > bMinimumHighestSeenTerrainSoFar,
))
}
viewableTiles.addAll(tilesToAddInDistanceI)
}
return viewableTiles
return viewableTiles.filter { it.isVisible }.map { it.tile }
}
/** Strips all units from [TileMap]

View File

@ -67,7 +67,7 @@ class VisibilityTests {
fun canSeeForestOverPlains() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile("Plains", Vector2(1f,0f))
val forest = addTile(listOf("Grassland", "Forest"), Vector2(2f, 1f))
val forest = addTile(listOf("Grassland", "Forest"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(viewableTiles.contains(forest))
}
@ -75,7 +75,7 @@ class VisibilityTests {
@Test
fun cannotSeePlainsOverForest() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Forest"), Vector2(1f, 1f))
addTile(listOf("Grassland", "Forest"), Vector2(1f, 0f))
val plains = addTile("Plains", Vector2(2f,0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(!viewableTiles.contains(plains))
@ -84,7 +84,7 @@ class VisibilityTests {
@Test
fun cannotSeeForestOverForest() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Forest"), Vector2(1f, 1f))
addTile(listOf("Grassland", "Forest"), Vector2(1f, 0f))
val plains = addTile(listOf("Plains", "Forest"), Vector2(2f,0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(!viewableTiles.contains(plains))
@ -94,7 +94,7 @@ class VisibilityTests {
fun canSeeHillOverPlains() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile("Plains", Vector2(1f,0f))
val hill = addTile(listOf("Grassland", "Hill"), Vector2(2f, 1f))
val hill = addTile(listOf("Grassland", "Hill"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(viewableTiles.contains(hill))
}
@ -102,10 +102,65 @@ class VisibilityTests {
@Test
fun cannotSeePlainsOverHill() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Hill"), Vector2(1f, 1f))
addTile(listOf("Grassland", "Hill"), Vector2(1f, 0f))
val plains = addTile("Plains", Vector2(2f,0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(!viewableTiles.contains(plains))
}
@Test
fun cannotSeeHillOverHill() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Hill"), Vector2(1f,0f))
val hill = addTile(listOf("Grassland", "Hill"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(!viewableTiles.contains(hill))
}
@Test
fun cannotSeeHillOverForest() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Forest"), Vector2(1f,0f))
val hill = addTile(listOf("Grassland", "Hill"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(!viewableTiles.contains(hill))
}
@Test
fun cannotSeeForestOverHill() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Hill"), Vector2(1f,0f))
val hill = addTile(listOf("Grassland", "Forest"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(!viewableTiles.contains(hill))
}
@Test
fun canSeeHillForestOverHill() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Forest"), Vector2(1f,0f))
val hill = addTile(listOf("Grassland", "Hill", "Forest"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(viewableTiles.contains(hill))
}
@Test
fun canSeeMountainOverHill() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Hill"), Vector2(1f,0f))
val hill = addTile(listOf("Mountain"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(viewableTiles.contains(hill))
}
@Test
fun cannotSeeMountainOverHillForest() {
val grassland = addTile("Grassland", Vector2(0f,0f))
addTile(listOf("Grassland", "Hill", "Forest"), Vector2(1f,0f))
val hill = addTile(listOf("Mountain"), Vector2(2f, 0f))
val viewableTiles = grassland.getViewableTilesList(2)
assert(!viewableTiles.contains(hill))
}
}