World wrap Part 2 (#3609)

* Changed map generation for hex maps

* Adding WorldWrap UI

* Is now compileable again

* Changed map gen for rec maps

well that was easy

* Units can now move and see across wrap

* well this is smarter

* World wrap checkbox commented ou

should be available only after everything has been merged

* simplify WW gen conditions

* reduced indentation

* Refactoring for better readablity
This commit is contained in:
GGGuenni
2021-03-03 11:54:49 +01:00
committed by GitHub
parent 9a7e637891
commit 25db68fa2a
7 changed files with 88 additions and 23 deletions

View File

@ -122,7 +122,7 @@ object HexMath {
return cubic2HexCoords(roundCubicCoords(hex2CubicCoords(hexCoord)))
}
fun getVectorsAtDistance(origin: Vector2, distance: Int): List<Vector2> {
fun getVectorsAtDistance(origin: Vector2, distance: Int, maxDistance: Int, worldWrap: Boolean): List<Vector2> {
val vectors = mutableListOf<Vector2>()
if (distance == 0) {
vectors += origin.cpy()
@ -136,21 +136,23 @@ object HexMath {
}
for (i in 0 until distance) { // 8 to 10
vectors += current.cpy()
vectors += origin.cpy().scl(2f).sub(current) // Get vector on other side of clock
if (!worldWrap || distance != maxDistance)
vectors += origin.cpy().scl(2f).sub(current) // Get vector on other side of clock
current.add(1f, 1f)
}
for (i in 0 until distance) { // 10 to 12
vectors += current.cpy()
vectors += origin.cpy().scl(2f).sub(current) // Get vector on other side of clock
if (!worldWrap || distance != maxDistance || i != 0)
vectors += origin.cpy().scl(2f).sub(current) // Get vector on other side of clock
current.add(0f, 1f)
}
return vectors
}
fun getVectorsInDistance(origin: Vector2, distance: Int): List<Vector2> {
fun getVectorsInDistance(origin: Vector2, distance: Int, worldWrap: Boolean): List<Vector2> {
val hexesToReturn = mutableListOf<Vector2>()
for (i in 0 .. distance) {
hexesToReturn += getVectorsAtDistance(origin, i)
hexesToReturn += getVectorsAtDistance(origin, i, distance, worldWrap)
}
return hexesToReturn
}

View File

@ -36,6 +36,7 @@ class MapParameters {
var size: MapSize = MapSize.Medium
var noRuins = false
var noNaturalWonders = false
var worldWrap = false
/** This is used mainly for the map editor, so you can continue editing a map under the ame ruleset you started with */
var mods = LinkedHashSet<String>()

View File

@ -407,16 +407,15 @@ open class TileInfo {
/** The two tiles have a river between them */
fun isConnectedByRiver(otherTile: TileInfo): Boolean {
if (otherTile == this) throw Exception("Should not be called to compare to self!")
val xDifference = this.position.x - otherTile.position.x
val yDifference = this.position.y - otherTile.position.y
return when {
yDifference < -1f || xDifference < -1f || yDifference > 1f || xDifference > 1f ->
throw Exception("Should never call this function on a non-neighbor!")
xDifference == 1f && yDifference == 1f -> hasBottomRiver // we're directly above it
xDifference == 1f -> hasBottomRightRiver // we're to the top-left of it
yDifference == 1f -> hasBottomLeftRiver // we're to the top-right of it
else -> otherTile.isConnectedByRiver(this) // we're below it, check the other tile
return when (tileMap.getNeighborTileClockPosition(this, otherTile)) {
2 -> otherTile.hasBottomLeftRiver // we're to the bottom-left of it
4 -> hasBottomRightRiver // we're to the top-right of it
6 -> hasBottomRiver // we're directly above it
8 -> hasBottomLeftRiver // we're to the top-left of it
10 -> otherTile.hasBottomRightRiver // we're to the bottom-right of it
12 -> otherTile.hasBottomRiver // we're directly below it
else -> throw Exception("Should never call this function on a non-neighbor!")
}
}

View File

@ -44,15 +44,16 @@ class TileMap {
constructor()
/** generates an hexagonal map of given radius */
constructor(radius: Int, ruleset: Ruleset) {
for (vector in HexMath.getVectorsInDistance(Vector2.Zero, radius))
constructor(radius: Int, ruleset: Ruleset, worldWrap: Boolean = false) {
for (vector in HexMath.getVectorsInDistance(Vector2.Zero, radius, worldWrap))
tileList.add(TileInfo().apply { position = vector; baseTerrain = Constants.grassland })
setTransients(ruleset)
}
/** generates a rectangular map of given width and height*/
constructor(width: Int, height: Int, ruleset: Ruleset) {
for (x in -width / 2..width / 2)
constructor(width: Int, height: Int, ruleset: Ruleset, worldWrap: Boolean = false) {
val halfway = if(worldWrap) width/2-1 else width/2
for (x in -width / 2 .. halfway)
for (y in -height / 2..height / 2)
tileList.add(TileInfo().apply {
position = HexMath.evenQ2HexCoords(Vector2(x.toFloat(), y.toFloat()))
@ -99,8 +100,6 @@ class TileMap {
sequenceOf(get(origin))
else
sequence {
fun getIfTileExistsOrNull(x: Int, y: Int) = if (contains(x, y)) get(x, y) else null
val centerX = origin.x.toInt()
val centerY = origin.y.toInt()
@ -129,6 +128,30 @@ class TileMap {
}
}.filterNotNull()
private fun getIfTileExistsOrNull(x: Int, y: Int) : TileInfo? {
if (contains(x, y))
return get(x, y)
if (!mapParameters.worldWrap)
return null
var radius = mapParameters.size.radius
if (mapParameters.shape == MapShape.rectangular)
radius = HexMath.getEquivalentRectangularSize(radius).x.toInt() / 2
//tile is outside of the map
if (contains(x + radius, y - radius)) { //tile is on right side
//get tile wrapped around from right to left
return get(x + radius, y - radius)
} else if (contains(x - radius, y + radius)) { //tile is on left side
//get tile wrapped around from left to right
return get(x - radius, y + radius)
}
return null
}
/** Tries to place the [unitName] into the [TileInfo] closest to the given the [position]
* @param civInfo civilization to assign unit to
* @return created [MapUnit] or null if no suitable location was found
@ -141,7 +164,7 @@ class TileMap {
val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet)
fun getPassableNeighbours(tileInfo: TileInfo): Set<TileInfo> =
getTilesAtDistance(tileInfo.position, 1).filter { unit.movement.canPassThrough(it) }.toSet()
tileInfo.neighbors.filter { unit.movement.canPassThrough(it) }.toSet()
// both the civ name and actual civ need to be in here in order to calculate the canMoveTo...Darn
unit.assignOwner(civInfo, false)
@ -293,4 +316,31 @@ class TileMap {
tileInfo.setUnitTransients(setUnitCivTransients)
}
}
/**
* Returns the clockPosition of otherTile seen from tile's position
* Returns -1 if not neighbors
*/
fun getNeighborTileClockPosition(tile: TileInfo, otherTile: TileInfo): Int{
var radius = mapParameters.size.radius
if (mapParameters.shape == MapShape.rectangular)
radius = HexMath.getEquivalentRectangularSize(radius).x.toInt() / 2
val xDifference = tile.position.x - otherTile.position.x
val yDifference = tile.position.y - otherTile.position.y
val xWrapDifferenceBottom = tile.position.x - (otherTile.position.x - radius)
val yWrapDifferenceBottom = tile.position.y - (otherTile.position.y - radius)
val xWrapDifferenceTop = tile.position.x - (otherTile.position.x + radius)
val yWrapDifferenceTop = tile.position.y - (otherTile.position.y + radius)
return when {
xDifference == 1f && yDifference == 1f -> 6 // otherTile is below
xDifference == -1f && yDifference == -1f -> 12 // otherTile is above
xDifference == 1f || xWrapDifferenceBottom == 1f -> 4 // otherTile is bottom-right
yDifference == 1f || yWrapDifferenceBottom == 1f -> 8 // otherTile is bottom-left
xDifference == -1f || xWrapDifferenceTop == -1f -> 10 // otherTile is top-left
yDifference == -1f || yWrapDifferenceTop == -1f -> 2 // otherTile is top-right
else -> -1
}
}
}

View File

@ -27,10 +27,10 @@ class MapGenerator(val ruleset: Ruleset) {
if (mapParameters.shape == MapShape.rectangular) {
val size = HexMath.getEquivalentRectangularSize(mapRadius)
map = TileMap(size.x.toInt(), size.y.toInt(), ruleset)
map = TileMap(size.x.toInt(), size.y.toInt(), ruleset, mapParameters.worldWrap)
}
else
map = TileMap(mapRadius, ruleset)
map = TileMap(mapRadius, ruleset, mapParameters.worldWrap)
map.mapParameters = mapParameters
map.mapParameters.seed = seed

View File

@ -21,6 +21,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
lateinit var mapTypeSelectBox: TranslatedSelectBox
lateinit var noRuinsCheckbox: CheckBox
lateinit var noNaturalWondersCheckbox: CheckBox
lateinit var worldWrapCheckbox: CheckBox
init {
skin = CameraStageBaseScreen.skin
@ -30,6 +31,8 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
addWorldSizeSelectBox()
addNoRuinsCheckbox()
addNoNaturalWondersCheckbox()
//uncomment when whole worldWrap feature is added
//addWorldWrapCheckbox()
addAdvancedSettings()
}
@ -105,6 +108,15 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
add(noNaturalWondersCheckbox).colspan(2).row()
}
private fun addWorldWrapCheckbox() {
worldWrapCheckbox = CheckBox("World Wrap".tr(), skin)
worldWrapCheckbox.isChecked = mapParameters.worldWrap
worldWrapCheckbox.onChange {
mapParameters.worldWrap = worldWrapCheckbox.isChecked
}
add(worldWrapCheckbox).colspan(2).row()
}
private fun addAdvancedSettings() {
val advancedSettingsTable = getAdvancedSettingsTable()

View File

@ -45,6 +45,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
init {
if (Gdx.app.type == Application.ApplicationType.Desktop) this.setFlingTime(0f)
continousScrollingX = tileMap.mapParameters.worldWrap
}
// Used to transfer data on the "move here" button that should be created, from the side thread to the main thread