Hoh boy so many changes to ensure the regular game works with multiplayer...

This commit is contained in:
Yair Morgenstern
2019-08-30 14:46:52 +03:00
parent bc1592bafe
commit 015b8343c7
22 changed files with 169 additions and 144 deletions

View File

@ -21,8 +21,8 @@ android {
applicationId "com.unciv.app" applicationId "com.unciv.app"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 28 targetSdkVersion 28
versionCode 290 versionCode 292
versionName "2.19.9" versionName "2.19.10"
} }
// Had to add this crap for Travis to build, it wanted to sign the app // Had to add this crap for Travis to build, it wanted to sign the app

View File

@ -1,5 +1,6 @@
package com.unciv package com.unciv
import com.badlogic.gdx.Application
import com.badlogic.gdx.Game import com.badlogic.gdx.Game
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input import com.badlogic.gdx.Input
@ -12,6 +13,7 @@ import com.unciv.models.metadata.GameSettings
import com.unciv.ui.LanguagePickerScreen import com.unciv.ui.LanguagePickerScreen
import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.worldscreen.WorldScreen
import java.util.*
class UnCivGame(val version: String) : Game() { class UnCivGame(val version: String) : Game() {
var gameInfo: GameInfo = GameInfo() var gameInfo: GameInfo = GameInfo()
@ -21,18 +23,25 @@ class UnCivGame(val version: String) : Game() {
* This exists so that when debugging we can see the entire map. * This exists so that when debugging we can see the entire map.
* Remember to turn this to false before commit and upload! * Remember to turn this to false before commit and upload!
*/ */
val viewEntireMapForDebug = false var viewEntireMapForDebug = false
/** For when you need to test something in an advanced game and don't have time to faff around */ /** For when you need to test something in an advanced game and don't have time to faff around */
val superchargedForDebug = false val superchargedForDebug = false
val mutiplayerEnabled = false
lateinit var worldScreen: WorldScreen lateinit var worldScreen: WorldScreen
override fun create() { override fun create() {
Current = this Current = this
if(Gdx.app.type!= Application.ApplicationType.Desktop)
viewEntireMapForDebug=false
Gdx.input.setCatchKey(Input.Keys.BACK, true) Gdx.input.setCatchKey(Input.Keys.BACK, true)
GameBasics.run { } // just to initialize the GameBasics GameBasics.run { } // just to initialize the GameBasics
settings = GameSaver().getGeneralSettings() settings = GameSaver().getGeneralSettings()
if(settings.userId=="") { // assign permanent user id
settings.userId = UUID.randomUUID().toString()
settings.save()
}
if (GameSaver().getSave("Autosave").exists()) { if (GameSaver().getSave("Autosave").exists()) {
try { try {
loadGame("Autosave") loadGame("Autosave")
@ -45,7 +54,8 @@ class UnCivGame(val version: String) : Game() {
fun loadGame(gameInfo:GameInfo){ fun loadGame(gameInfo:GameInfo){
this.gameInfo = gameInfo this.gameInfo = gameInfo
worldScreen = WorldScreen(gameInfo.currentPlayerCiv)
worldScreen = WorldScreen(gameInfo.getPlayerToViewAs())
setWorldScreen() setWorldScreen()
} }
@ -55,10 +65,7 @@ class UnCivGame(val version: String) : Game() {
fun startNewGame() { fun startNewGame() {
val newGame = GameStarter().startNewGame(GameParameters().apply { difficulty="Chieftain" }) val newGame = GameStarter().startNewGame(GameParameters().apply { difficulty="Chieftain" })
gameInfo = newGame loadGame(newGame)
worldScreen = WorldScreen(gameInfo.currentPlayerCiv)
setWorldScreen()
} }
fun setWorldScreen() { fun setWorldScreen() {
@ -80,8 +87,7 @@ class UnCivGame(val version: String) : Game() {
return create() return create()
if(::worldScreen.isInitialized) worldScreen.dispose() // I hope this will solve some of the many OuOfMemory exceptions... if(::worldScreen.isInitialized) worldScreen.dispose() // I hope this will solve some of the many OuOfMemory exceptions...
worldScreen = WorldScreen(gameInfo.currentPlayerCiv) loadGame(gameInfo)
setWorldScreen()
} }
// Maybe this will solve the resume error on chrome OS, issue 322? Worth a shot // Maybe this will solve the resume error on chrome OS, issue 322? Worth a shot

View File

@ -2,6 +2,7 @@ package com.unciv.logic
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UnCivGame
import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.CityConstructions
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
@ -39,9 +40,17 @@ class GameInfo {
toReturn.turns = turns toReturn.turns = turns
toReturn.difficulty=difficulty toReturn.difficulty=difficulty
toReturn.gameParameters = gameParameters toReturn.gameParameters = gameParameters
toReturn.gameId = gameId
return toReturn return toReturn
} }
fun getPlayerToViewAs(): CivilizationInfo {
if (!gameParameters.isOnlineMultiplayer) return currentPlayerCiv // non-online, play as human player
val userId = UnCivGame.Current.settings.userId
if (civilizations.any { it.playerId == userId}) return civilizations.first { it.playerId == userId }
else return getBarbarianCivilization()// you aren't anyone. How did you even get this game? Can you spectate?
}
fun getCivilization(civName:String) = civilizations.first { it.civName==civName } fun getCivilization(civName:String) = civilizations.first { it.civName==civName }
fun getCurrentPlayerCivilization() = currentPlayerCiv fun getCurrentPlayerCivilization() = currentPlayerCiv
fun getBarbarianCivilization() = getCivilization("Barbarians") fun getBarbarianCivilization() = getCivilization("Barbarians")

View File

@ -76,13 +76,14 @@ class CivilizationInfo {
val toReturn = CivilizationInfo() val toReturn = CivilizationInfo()
toReturn.gold = gold toReturn.gold = gold
toReturn.playerType = playerType toReturn.playerType = playerType
toReturn.playerId = playerId
toReturn.civName = civName toReturn.civName = civName
toReturn.tech = tech.clone() toReturn.tech = tech.clone()
toReturn.policies = policies.clone() toReturn.policies = policies.clone()
toReturn.goldenAges = goldenAges.clone() toReturn.goldenAges = goldenAges.clone()
toReturn.greatPeople = greatPeople.clone() toReturn.greatPeople = greatPeople.clone()
toReturn.victoryManager = victoryManager.clone() toReturn.victoryManager = victoryManager.clone()
for(diplomacyManager in diplomacy.values.map { it.clone() }) for (diplomacyManager in diplomacy.values.map { it.clone() })
toReturn.diplomacy.put(diplomacyManager.otherCivName, diplomacyManager) toReturn.diplomacy.put(diplomacyManager.otherCivName, diplomacyManager)
toReturn.cities = cities.map { it.clone() } toReturn.cities = cities.map { it.clone() }

View File

@ -310,10 +310,10 @@ open class TileInfo {
turnsToImprovement = improvement.getTurnsToBuild(civInfo) turnsToImprovement = improvement.getTurnsToBuild(civInfo)
} }
fun hasEnemySubmarine(): Boolean { fun hasEnemySubmarine(viewingCiv:CivilizationInfo): Boolean {
val unitsInTile = getUnits() val unitsInTile = getUnits()
if (unitsInTile.isEmpty()) return false if (unitsInTile.isEmpty()) return false
if (!unitsInTile.first().civInfo.isPlayerCivilization() && if (unitsInTile.first().civInfo!=viewingCiv &&
unitsInTile.firstOrNull { it.isInvisible() } != null) { unitsInTile.firstOrNull { it.isInvisible() } != null) {
return true return true
} }

View File

@ -135,44 +135,45 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
} }
} }
fun getTileToMoveToThisTurn(finalDestination:TileInfo): TileInfo {
val currentTile = unit.getTile()
if (currentTile == finalDestination) return currentTile
if(unit.type.isAirUnit()){
return finalDestination // head there directly
}
val distanceToTiles = getDistanceToTiles()
if (distanceToTiles.containsKey(finalDestination)) { // we should be able to get there this turn
if (canMoveTo(finalDestination))
return finalDestination
// Someone is blocking to the path to the final tile...
val destinationNeighbors = finalDestination.neighbors
if (destinationNeighbors.contains(currentTile)) // We're right nearby anyway, no need to move
return currentTile
val reachableDestinationNeighbors = destinationNeighbors
.filter { distanceToTiles.containsKey(it) && canMoveTo(it) }
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
return currentTile
return reachableDestinationNeighbors.minBy { distanceToTiles[it]!!.totalDistance }!! // we can get a little closer
} // If the tile is far away, we need to build a path how to get there, and then take the first step
val path = getShortestPath(finalDestination)
class UnreachableDestinationException : Exception()
if (path.isEmpty()) throw UnreachableDestinationException()
return path.first()
}
/** /**
* @return The tile that we reached this turn * @return The tile that we reached this turn
*/ */
fun headTowards(destination: TileInfo): TileInfo { fun headTowards(destination: TileInfo): TileInfo {
val currentTile = unit.getTile() val destinationTileThisTurn = getTileToMoveToThisTurn(destination)
if (currentTile == destination) return currentTile
if(unit.type.isAirUnit()){
moveToTile(destination)
return destination
}
val distanceToTiles = getDistanceToTiles()
val destinationTileThisTurn: TileInfo
if (distanceToTiles.containsKey(destination)) { // we can get there this turn
if (canMoveTo(destination))
destinationTileThisTurn = destination
else // Someone is blocking to the path to the final tile...
{
val destinationNeighbors = destination.neighbors
if (destinationNeighbors.contains(currentTile)) // We're right nearby anyway, no need to move
return currentTile
val reachableDestinationNeighbors = destinationNeighbors
.filter { distanceToTiles.containsKey(it) && canMoveTo(it) }
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
return currentTile
destinationTileThisTurn = reachableDestinationNeighbors.minBy { distanceToTiles[it]!!.totalDistance }!!
}
} else { // If the tile is far away, we need to build a path how to get there, and then take the first step
val path = getShortestPath(destination)
class UnreachableDestinationException : Exception()
if (path.isEmpty()) throw UnreachableDestinationException()
destinationTileThisTurn = path.first()
}
moveToTile(destinationTileThisTurn) moveToTile(destinationTileThisTurn)
return destinationTileThisTurn return destinationTileThisTurn
} }

View File

@ -1,8 +1,6 @@
package com.unciv.models.metadata package com.unciv.models.metadata
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import java.util.*
import kotlin.collections.ArrayList
class GameSettings { class GameSettings {
var showWorkedTiles: Boolean = false var showWorkedTiles: Boolean = false
@ -20,7 +18,7 @@ class GameSettings {
var autoAssignCityProduction: Boolean = true var autoAssignCityProduction: Boolean = true
var userName:String="" var userName:String=""
var userId = UUID.randomUUID().toString() var userId = ""
fun save(){ fun save(){
GameSaver().setGeneralSettings(this) GameSaver().setGeneralSettings(this)

View File

@ -2,7 +2,6 @@ package com.unciv.ui.cityscreen
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.UnCivGame
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.ui.tilegroups.TileGroup import com.unciv.ui.tilegroups.TileGroup
@ -26,12 +25,7 @@ class CityTileGroup(private val city: CityInfo, tileInfo: TileInfo, tileSetStrin
} }
fun update() { fun update() {
val canSeeTile = UnCivGame.Current.viewEntireMapForDebug super.update(city.civInfo,true)
|| city.civInfo.viewableTiles.contains(tileInfo)
val showSubmarine = UnCivGame.Current.viewEntireMapForDebug
|| city.civInfo.viewableInvisibleUnitsTiles.contains(tileInfo)
|| (!tileInfo.hasEnemySubmarine())
super.update(canSeeTile,true, showSubmarine)
// this needs to happen on update, because we can buy tiles, which changes the definition of the bought tiles... // this needs to happen on update, because we can buy tiles, which changes the definition of the bought tiles...
when { when {

View File

@ -37,7 +37,7 @@ class MapEditorOptionsTable(mapEditorScreen: MapEditorScreen): PopupTable(mapEdi
tile.roadStatus=RoadStatus.None tile.roadStatus=RoadStatus.None
tile.setTransients() tile.setTransients()
tileGroup.update(true,true,true) tileGroup.update()
} }
} }
add(clearCurrentMapButton).row() add(clearCurrentMapButton).row()

View File

@ -66,13 +66,13 @@ class MapEditorScreen(): CameraStageBaseScreen(){
val tileGroups = tileMap.values.map { TileGroup(it, tileSetStrings) } val tileGroups = tileMap.values.map { TileGroup(it, tileSetStrings) }
for (tileGroup in tileGroups) { for (tileGroup in tileGroups) {
tileGroup.showEntireMap = true tileGroup.showEntireMap = true
tileGroup.update(true, true, true) tileGroup.update()
tileGroup.onClick { tileGroup.onClick {
val tileInfo = tileGroup.tileInfo val tileInfo = tileGroup.tileInfo
tileEditorOptions.updateTileWhenClicked(tileInfo) tileEditorOptions.updateTileWhenClicked(tileInfo)
tileGroup.tileInfo.setTransients() tileGroup.tileInfo.setTransients()
tileGroup.update(true, true, true) tileGroup.update()
} }
} }

View File

@ -122,7 +122,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
val group = TileGroup(tileInfo, TileSetStrings()) val group = TileGroup(tileInfo, TileSetStrings())
group.showEntireMap=true group.showEntireMap=true
group.forMapEditorIcon=true group.forMapEditorIcon=true
group.update(true,true,true) group.update()
group.onClick { group.onClick {
clearSelection() clearSelection()
@ -249,7 +249,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
.apply { .apply {
showEntireMap=true showEntireMap=true
forMapEditorIcon=true forMapEditorIcon=true
update(true,true,true) update()
} }
setCurrentHex(tileGroup) setCurrentHex(tileGroup)
} }

View File

@ -6,6 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Skin
import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Array
import com.unciv.UnCivGame import com.unciv.UnCivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.GameStarter import com.unciv.logic.GameStarter
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.models.gamebasics.tr import com.unciv.models.gamebasics.tr
@ -66,8 +67,10 @@ class NewGameScreen: PickerScreen(){
try { try {
newGame = GameStarter().startNewGame(newGameParameters) newGame = GameStarter().startNewGame(newGameParameters)
if (newGameParameters.isOnlineMultiplayer) { if (newGameParameters.isOnlineMultiplayer) {
newGame!!.isUpToDate=true // So we don't try to download it from dropbox the second after we upload it - the file is not yet ready for loading!
try { try {
OnlineMultiplayer().tryUploadGame(newGame!!) OnlineMultiplayer().tryUploadGame(newGame!!)
GameSaver().autoSave(newGame!!){}
} catch (ex: Exception) { } catch (ex: Exception) {
val cantUploadNewGamePopup = PopupTable(this) val cantUploadNewGamePopup = PopupTable(this)
cantUploadNewGamePopup.addGoodSizedLabel("Can't upload the new game!") cantUploadNewGamePopup.addGoodSizedLabel("Can't upload the new game!")

View File

@ -6,6 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Array
import com.unciv.UnCivGame
import com.unciv.logic.MapSaver import com.unciv.logic.MapSaver
import com.unciv.logic.map.MapType import com.unciv.logic.map.MapType
import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.GameBasics
@ -25,7 +26,9 @@ class NewGameScreenOptionsTable(val newGameParameters: GameParameters, val onMul
addCityStatesSelectBox() addCityStatesSelectBox()
addVictoryTypeCheckboxes() addVictoryTypeCheckboxes()
addBarbariansCheckbox() addBarbariansCheckbox()
//addIsOnlineMultiplayerCheckbox()
if(UnCivGame.Current.mutiplayerEnabled)
addIsOnlineMultiplayerCheckbox()
pack() pack()
} }

View File

@ -59,7 +59,7 @@ class PlayerPickerTable(val newGameScreen: NewGameScreen, val newGameParameters:
val playerIdTable = Table() val playerIdTable = Table()
playerIdTable.add("Player ID:".toLabel()) playerIdTable.add("Player ID:".toLabel())
val playerIdTextfield = TextField("", CameraStageBaseScreen.skin) val playerIdTextfield = TextField(player.playerId, CameraStageBaseScreen.skin)
playerIdTable.add(playerIdTextfield) playerIdTable.add(playerIdTextfield)
val errorLabel = "Not a valid user id!".toLabel().setFontColor(Color.RED) val errorLabel = "Not a valid user id!".toLabel().setFontColor(Color.RED)
errorLabel.isVisible=false errorLabel.isVisible=false

View File

@ -185,33 +185,42 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings)
} }
} }
fun showMilitaryUnit(viewingCiv: CivilizationInfo) = showEntireMap
|| viewingCiv.viewableInvisibleUnitsTiles.contains(tileInfo)
|| (!tileInfo.hasEnemySubmarine(viewingCiv))
open fun update(isViewable: Boolean, showResourcesAndImprovements:Boolean, showSubmarine: Boolean) { fun isViewable(viewingCiv: CivilizationInfo) = showEntireMap
|| viewingCiv.viewableTiles.contains(tileInfo)
open fun update(viewingCiv:CivilizationInfo?=null, showResourcesAndImprovements: Boolean=true) {
hideCircle() hideCircle()
if (!showEntireMap if (viewingCiv!=null && !showEntireMap
&& !tileInfo.tileMap.gameInfo.getCurrentPlayerCivilization().exploredTiles.contains(tileInfo.position)) { && !viewingCiv.exploredTiles.contains(tileInfo.position)) {
tileBaseImage.color = Color.DARK_GRAY tileBaseImage.color = Color.DARK_GRAY
return return
} }
val tileIsViewable = viewingCiv==null || isViewable(viewingCiv)
val showMilitaryUnit = viewingCiv==null || showMilitaryUnit(viewingCiv)
updateTileImage(true) updateTileImage(true)
updateTerrainBaseImage() updateTerrainBaseImage()
updateTerrainFeatureImage() updateTerrainFeatureImage()
updateCityImage() updateCityImage()
updateTileColor(isViewable) updateTileColor(tileIsViewable)
updateResourceImage(showResourcesAndImprovements) updateResourceImage(showResourcesAndImprovements)
updateImprovementImage(showResourcesAndImprovements) updateImprovementImage(showResourcesAndImprovements)
civilianUnitImage = newUnitImage(tileInfo.civilianUnit, civilianUnitImage, isViewable, -20f) civilianUnitImage = newUnitImage(tileInfo.civilianUnit, civilianUnitImage, tileIsViewable, -20f)
militaryUnitImage = newUnitImage(tileInfo.militaryUnit, militaryUnitImage, isViewable && showSubmarine, 20f) militaryUnitImage = newUnitImage(tileInfo.militaryUnit, militaryUnitImage, tileIsViewable && showMilitaryUnit, 20f)
updateRoadImages() updateRoadImages()
updateBorderImages() updateBorderImages()
crosshairImage.isVisible = false crosshairImage.isVisible = false
fogImage.isVisible = !(isViewable || showEntireMap) fogImage.isVisible = !(tileIsViewable || showEntireMap)
} }
private fun updateTerrainBaseImage() { private fun updateTerrainBaseImage() {

View File

@ -2,6 +2,7 @@ package com.unciv.ui.tilegroups
import com.unciv.UnCivGame import com.unciv.UnCivGame
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.ui.utils.CameraStageBaseScreen import com.unciv.ui.utils.CameraStageBaseScreen
@ -20,21 +21,21 @@ class WorldTileGroup(internal val worldScreen: WorldScreen, tileInfo: TileInfo,
unitImage?.selectUnit() unitImage?.selectUnit()
} }
fun update(isViewable: Boolean, showSubmarine: Boolean) { fun update(viewingCiv: CivilizationInfo) {
val city = tileInfo.getCity() val city = tileInfo.getCity()
removePopulationIcon() removePopulationIcon()
if (isViewable && tileInfo.isWorked() && UnCivGame.Current.settings.showWorkedTiles val tileIsViewable = isViewable(viewingCiv)
if (tileIsViewable && tileInfo.isWorked() && UnCivGame.Current.settings.showWorkedTiles
&& city!!.civInfo.isPlayerCivilization()) && city!!.civInfo.isPlayerCivilization())
addPopulationIcon() addPopulationIcon()
val currentPlayerCiv = worldScreen.viewingCiv val currentPlayerCiv = worldScreen.viewingCiv
if (UnCivGame.Current.viewEntireMapForDebug if (UnCivGame.Current.viewEntireMapForDebug
|| currentPlayerCiv.exploredTiles.contains(tileInfo.position)) || currentPlayerCiv.exploredTiles.contains(tileInfo.position))
updateCityButton(city, isViewable || UnCivGame.Current.viewEntireMapForDebug) // needs to be before the update so the units will be above the city button updateCityButton(city, tileIsViewable || UnCivGame.Current.viewEntireMapForDebug) // needs to be before the update so the units will be above the city button
super.update(isViewable || UnCivGame.Current.viewEntireMapForDebug, super.update(viewingCiv, UnCivGame.Current.settings.showResourcesAndImprovements)
UnCivGame.Current.settings.showResourcesAndImprovements, showSubmarine)
// order by z index! // order by z index!

View File

@ -32,7 +32,6 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
val tileGroups = HashMap<TileInfo, WorldTileGroup>() val tileGroups = HashMap<TileInfo, WorldTileGroup>()
var unitActionOverlay :Actor?=null var unitActionOverlay :Actor?=null
var removeUnitActionOverlay=false
// Used to transfer data on the "move here" button that should be created, from the side thread to the main thread // Used to transfer data on the "move here" button that should be created, from the side thread to the main thread
class MoveHereButtonDto(val unit: MapUnit, val tileInfo: TileInfo, val turnsToGetThere: Int) class MoveHereButtonDto(val unit: MapUnit, val tileInfo: TileInfo, val turnsToGetThere: Int)
@ -170,7 +169,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
worldScreen.bottomBar.unitTable.selectedUnit = unit worldScreen.bottomBar.unitTable.selectedUnit = unit
worldScreen.bottomBar.unitTable.selectedCity = null worldScreen.bottomBar.unitTable.selectedCity = null
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
removeUnitActionOverlay = true unitActionOverlay?.remove()
} }
table.add(unitGroup) table.add(unitGroup)
} }
@ -238,32 +237,22 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
} }
internal fun updateTiles(civInfo: CivilizationInfo) { internal fun updateTiles(viewingCiv: CivilizationInfo) {
if(removeUnitActionOverlay){
removeUnitActionOverlay=false
unitActionOverlay?.remove()
}
val playerViewableTilePositions = civInfo.viewableTiles.map { it.position }.toHashSet() val playerViewableTilePositions = viewingCiv.viewableTiles.map { it.position }.toHashSet()
val playerViewableInvisibleUnitsTilePositions = civInfo.viewableInvisibleUnitsTiles.map { it.position }.toHashSet() val playerViewableInvisibleUnitsTilePositions = viewingCiv.viewableInvisibleUnitsTiles.map { it.position }.toHashSet()
for (tileGroup in tileGroups.values){ for (tileGroup in tileGroups.values){
val canSeeTile = UnCivGame.Current.viewEntireMapForDebug tileGroup.update(viewingCiv)
|| playerViewableTilePositions.contains(tileGroup.tileInfo.position)
val showSubmarine = UnCivGame.Current.viewEntireMapForDebug
|| playerViewableInvisibleUnitsTilePositions.contains(tileGroup.tileInfo.position)
|| (!tileGroup.tileInfo.hasEnemySubmarine())
tileGroup.update(canSeeTile, showSubmarine)
if(tileGroup.tileInfo.improvement==Constants.barbarianEncampment if(tileGroup.tileInfo.improvement==Constants.barbarianEncampment
&& tileGroup.tileInfo.position in civInfo.exploredTiles) && tileGroup.tileInfo.position in viewingCiv.exploredTiles)
tileGroup.showCircle(Color.RED) tileGroup.showCircle(Color.RED)
val unitsInTile = tileGroup.tileInfo.getUnits() val unitsInTile = tileGroup.tileInfo.getUnits()
val canSeeEnemy = unitsInTile.isNotEmpty() && unitsInTile.first().civInfo.isAtWarWith(civInfo) val canSeeEnemy = unitsInTile.isNotEmpty() && unitsInTile.first().civInfo.isAtWarWith(viewingCiv)
&& (showSubmarine || unitsInTile.firstOrNull {!it.isInvisible()}!=null) && tileGroup.showMilitaryUnit(viewingCiv)
if(canSeeTile && canSeeEnemy) if(tileGroup.isViewable(viewingCiv) && canSeeEnemy)
tileGroup.showCircle(Color.RED) // Display ALL viewable enemies with a red circle so that users don't need to go "hunting" for enemy units tileGroup.showCircle(Color.RED) // Display ALL viewable enemies with a red circle so that users don't need to go "hunting" for enemy units
} }

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.Button import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextButton
@ -113,23 +114,36 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
update() update()
if(gameInfo.gameParameters.isOnlineMultiplayer && !gameInfo.isUpToDate) { if(gameInfo.gameParameters.isOnlineMultiplayer && !gameInfo.isUpToDate)
isPlayersTurn = false // until we're up to date, don't let the player do anything isPlayersTurn = false // until we're up to date, don't let the player do anything
val loadingGamePopup = PopupTable(this) if(gameInfo.gameParameters.isOnlineMultiplayer && !isPlayersTurn) {
loadingGamePopup.add("Loading latest game state...") stage.addAction(Actions.forever(Actions.sequence(Actions.run {
loadingGamePopup.open() loadLatestMultiplayerState()
thread { }, Actions.delay(10f)))) // delay is in seconds
try { }
val latestGame = OnlineMultiplayer().tryDownloadGame(gameInfo.gameId) }
latestGame.isUpToDate=true
game.loadGame(latestGame) fun loadLatestMultiplayerState(){
} catch (ex: Exception) { val loadingGamePopup = PopupTable(this)
loadingGamePopup.add("Loading latest game state...")
loadingGamePopup.open()
thread {
try {
val latestGame = OnlineMultiplayer().tryDownloadGame(gameInfo.gameId)
if(gameInfo.isUpToDate && gameInfo.currentPlayer==latestGame.currentPlayer) { // we were trying to download this to see when it's our turn...nothing changed
loadingGamePopup.close() loadingGamePopup.close()
val couldntDownloadLatestGame = PopupTable(this) return@thread
couldntDownloadLatestGame.addGoodSizedLabel("Couldn't download the latest game state!")
couldntDownloadLatestGame.addCloseButton()
couldntDownloadLatestGame.open()
} }
latestGame.isUpToDate=true
// Since we're making a screen this needs to run from the man thread which has a GL context
Gdx.app.postRunnable { game.loadGame(latestGame) }
} catch (ex: Exception) {
loadingGamePopup.close()
val couldntDownloadLatestGame = PopupTable(this)
couldntDownloadLatestGame.addGoodSizedLabel("Couldn't download the latest game state!")
couldntDownloadLatestGame.addCloseButton()
couldntDownloadLatestGame.open()
} }
} }
} }
@ -138,23 +152,13 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
// That way, not only do we save a lot of unnecessary updates, we also ensure that all updates are called from the main GL thread // That way, not only do we save a lot of unnecessary updates, we also ensure that all updates are called from the main GL thread
// and we don't get any silly concurrency problems! // and we don't get any silly concurrency problems!
private fun update() { private fun update() {
// many of the display functions will be called with the game clone and not the actual game,
// because that's guaranteed to stay the exact same and so we won't get any concurrent modification exceptions
val gameClone = gameInfo.clone()
gameClone.setTransients()
val cloneCivilization = gameClone.getCurrentPlayerCivilization()
thread {
gameInfo.civilizations.forEach { it.setCitiesConnectedToCapitalTransients() }
}
displayTutorialsOnUpdate(cloneCivilization, gameClone)
displayTutorialsOnUpdate(viewingCiv, gameInfo)
bottomBar.update(tileMapHolder.selectedTile) // has to come before tilemapholder update because the tilemapholder actions depend on the selected unit! bottomBar.update(tileMapHolder.selectedTile) // has to come before tilemapholder update because the tilemapholder actions depend on the selected unit!
battleTable.update() battleTable.update()
minimapWrapper.update(cloneCivilization) minimapWrapper.update(viewingCiv)
minimapWrapper.y = bottomBar.height // couldn't be bothered to create a separate val for minimap wrapper minimapWrapper.y = bottomBar.height // couldn't be bothered to create a separate val for minimap wrapper
unitActionsTable.update(bottomBar.unitTable.selectedUnit) unitActionsTable.update(bottomBar.unitTable.selectedUnit)
@ -165,12 +169,12 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
// it causes a bug when we move a unit to an unexplored tile (for instance a cavalry unit which can move far) // it causes a bug when we move a unit to an unexplored tile (for instance a cavalry unit which can move far)
tileMapHolder.updateTiles(viewingCiv) tileMapHolder.updateTiles(viewingCiv)
topBar.update(cloneCivilization) topBar.update(viewingCiv)
updateTechButton(cloneCivilization) updateTechButton(viewingCiv)
techPolicyandVictoryHolder.pack() techPolicyandVictoryHolder.pack()
techPolicyandVictoryHolder.setPosition(10f, topBar.y - techPolicyandVictoryHolder.height - 5f) techPolicyandVictoryHolder.setPosition(10f, topBar.y - techPolicyandVictoryHolder.height - 5f)
updateDiplomacyButton(cloneCivilization) updateDiplomacyButton(viewingCiv)
notificationsScroll.update(viewingCiv.notifications) notificationsScroll.update(viewingCiv.notifications)
notificationsScroll.setPosition(stage.width - notificationsScroll.width - 5f, notificationsScroll.setPosition(stage.width - notificationsScroll.width - 5f,
@ -309,7 +313,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
shouldUpdate = true shouldUpdate = true
thread { thread { // on a separate thread so the user can explore their world while we're passing the turn
val gameInfoClone = gameInfo.clone() val gameInfoClone = gameInfo.clone()
gameInfoClone.setTransients() gameInfoClone.setTransients()
try { try {
@ -342,7 +346,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
Gdx.app.postRunnable { Gdx.app.postRunnable {
fun createNewWorldScreen(){ fun createNewWorldScreen(){
val newWorldScreen = WorldScreen(gameInfoClone.currentPlayerCiv) val newWorldScreen = WorldScreen(gameInfoClone.getPlayerToViewAs())
newWorldScreen.tileMapHolder.scrollX = tileMapHolder.scrollX newWorldScreen.tileMapHolder.scrollX = tileMapHolder.scrollX
newWorldScreen.tileMapHolder.scrollY = tileMapHolder.scrollY newWorldScreen.tileMapHolder.scrollY = tileMapHolder.scrollY
newWorldScreen.tileMapHolder.scaleX = tileMapHolder.scaleX newWorldScreen.tileMapHolder.scaleX = tileMapHolder.scaleX

View File

@ -167,7 +167,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
attackButton.onClick { attackButton.onClick {
try { try {
battle.moveAndAttack(attacker, attackableEnemy) battle.moveAndAttack(attacker, attackableEnemy)
worldScreen.tileMapHolder.removeUnitActionOverlay = true // the overlay was one of attacking worldScreen.tileMapHolder.unitActionOverlay?.remove() // the overlay was one of attacking
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
} }
catch (ex:Exception){ catch (ex:Exception){

View File

@ -58,9 +58,10 @@ class DropBox(){
return response return response
} }
fun uploadFile(fileName: String, data: String){ fun uploadFile(fileName: String, data: String, overwrite:Boolean=false){
val overwriteModeString = if(!overwrite) "" else ""","mode":{".tag":"overwrite"}"""
val response = dropboxApi("https://content.dropboxapi.com/2/files/upload", val response = dropboxApi("https://content.dropboxapi.com/2/files/upload",
data,"application/octet-stream","{\"path\":\"$fileName\"}") data,"application/octet-stream", """{"path":"$fileName"$overwriteModeString}""")
} }
@ -80,11 +81,11 @@ class OnlineMultiplayer(){
fun tryUploadGame(gameInfo: GameInfo){ fun tryUploadGame(gameInfo: GameInfo){
val zippedGameInfo = Gzip.zip(GameSaver().json().toJson(gameInfo)) val zippedGameInfo = Gzip.zip(GameSaver().json().toJson(gameInfo))
DropBox().uploadFile(getGameLocation(gameInfo.gameId),zippedGameInfo) DropBox().uploadFile(getGameLocation(gameInfo.gameId),zippedGameInfo,true)
} }
fun tryDownloadGame(gameId: String): GameInfo { fun tryDownloadGame(gameId: String): GameInfo {
val zippedGameInfo = DropBox().downloadFile(gameId) val zippedGameInfo = DropBox().downloadFile(getGameLocation(gameId))
return GameSaver().gameInfoFromString(Gzip.unzip(zippedGameInfo)) return GameSaver().gameInfoFromString(Gzip.unzip(zippedGameInfo))
} }
} }

View File

@ -53,10 +53,11 @@ class WorldScreenMenuTable(val worldScreen: WorldScreen) : PopupTable(worldScree
addButton("Start new game".tr()){ UnCivGame.Current.screen = NewGameScreen() } addButton("Start new game".tr()){ UnCivGame.Current.screen = NewGameScreen() }
if(worldScreen.gameInfo.gameParameters.isOnlineMultiplayer){ if(worldScreen.gameInfo.gameParameters.isOnlineMultiplayer){
addButton("Copy game ID to clipboard".tr()){ Gdx.app.clipboard.contents = worldScreen.gameInfo.gameId } addButton("Copy game ID".tr()){ Gdx.app.clipboard.contents = worldScreen.gameInfo.gameId }
} }
// addJoinMultiplayerButton() if(UnCivGame.Current.mutiplayerEnabled)
addJoinMultiplayerButton()
addButton("Victory status".tr()) { UnCivGame.Current.screen = VictoryScreen() } addButton("Victory status".tr()) { UnCivGame.Current.screen = VictoryScreen() }

View File

@ -1,5 +1,6 @@
package com.unciv.ui.worldscreen.unit package com.unciv.ui.worldscreen.unit
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Button import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup
@ -39,7 +40,7 @@ class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUni
addButton(icon, label) { addButton(icon, label) {
selectedUnit.mapUnitAction = action selectedUnit.mapUnitAction = action
selectedUnit.mapUnitAction?.doPreTurnAction() selectedUnit.mapUnitAction?.doPreTurnAction()
tileMapHolder.removeUnitActionOverlay = true tileMapHolder.unitActionOverlay?.remove()
tileMapHolder.worldScreen.shouldUpdate = true tileMapHolder.worldScreen.shouldUpdate = true
} }
} }
@ -57,28 +58,32 @@ class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUni
fun onMoveButtonClick() { fun onMoveButtonClick() {
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
thread { thread {
if (selectedUnit.movement.canReach(targetTile)) { // these are the heavy parts, finding where we want to go
if (!selectedUnit.movement.canReach(targetTile)) return@thread // can't move here
val tileToMoveTo = selectedUnit.movement.getTileToMoveToThisTurn(targetTile)
Gdx.app.postRunnable {
try { try {
// Because this is darned concurrent (as it MUST be to avoid ANRs), // Because this is darned concurrent (as it MUST be to avoid ANRs),
// there are edge cases where the canReach is true, // there are edge cases where the canReach is true,
// but until it reaches the headTowards the board has changed and so the headTowards fails. // but until it reaches the headTowards the board has changed and so the headTowards fails.
// I can't think of any way to avoid this, // I can't think of any way to avoid this,
// but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch // but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch
selectedUnit.movement.headTowards(targetTile) selectedUnit.movement.moveToTile(tileToMoveTo)
Sounds.play("whoosh") Sounds.play("whoosh")
if (selectedUnit.currentTile != targetTile) if (selectedUnit.currentTile != targetTile)
selectedUnit.action = "moveTo " + targetTile.position.x.toInt() + "," + targetTile.position.y.toInt() selectedUnit.action = "moveTo " + targetTile.position.x.toInt() + "," + targetTile.position.y.toInt()
if(selectedUnit.currentMovement>0){ if (selectedUnit.currentMovement > 0) {
tileMapHolder.worldScreen.bottomBar.unitTable.selectedUnit=selectedUnit tileMapHolder.worldScreen.bottomBar.unitTable.selectedUnit = selectedUnit
} }
// we don't update it directly because we're on a different thread; instead, we tell it to update itself
tileMapHolder.worldScreen.shouldUpdate = true
tileMapHolder.unitActionOverlay?.remove()
} catch (e: Exception) { } catch (e: Exception) {
} }
} }
// we don't update it directly because we're on a different thread; instead, we tell it to update itself
tileMapHolder.worldScreen.shouldUpdate = true
tileMapHolder.removeUnitActionOverlay=true
} }
} }