First attempt at making Unciv Android-TV-compatible

This commit is contained in:
Yair Morgenstern
2020-12-01 23:26:01 +02:00
parent 07cee7e679
commit 0feb9bdefc
4 changed files with 123 additions and 107 deletions

View File

@ -6,12 +6,17 @@
<uses-sdk/>
<uses-permission android:name="android.permission.INTERNET" />
<!-- See https://developer.android.com/training/tv/start/start -->
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<application
android:allowBackup="true"
android:icon="@drawable/uncivicon2"
android:label="@string/app_name"
android:isGame="true"
android:appCategory="game"
android:banner="@drawable/banner"
android:theme="@style/GdxTheme" >
<activity
android:name="com.unciv.app.AndroidLauncher"
@ -25,6 +30,19 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.unciv.app.AndroidTvLauncher"
android:label="@string/app_name"
android:theme="@style/GdxTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".CopyToClipboardReceiver" android:exported="false" />
</application>

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -13,7 +13,7 @@ import com.unciv.logic.GameSaver
import com.unciv.ui.utils.ORIGINAL_FONT_SIZE
import java.io.File
class AndroidLauncher : AndroidApplication() {
open class AndroidLauncher : AndroidApplication() {
private var customSaveLocationHelper: CustomSaveLocationHelperAndroid? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -87,4 +87,6 @@ class AndroidLauncher : AndroidApplication() {
}
super.onActivityResult(requestCode, resultCode, data)
}
}
}
class AndroidTvLauncher:AndroidLauncher()

View File

@ -61,15 +61,14 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
private val diplomacyButtonHolder = Table()
private val fogOfWarButton = createFogOfWarButton()
private val nextTurnButton = createNextTurnButton()
private var nextTurnAction:()->Unit= {}
private val tutorialTaskTable = Table().apply { background=ImageGetter.getBackground(ImageGetter.getBlue().lerp(Color.BLACK, 0.5f)) }
private var nextTurnAction: () -> Unit = {}
private val tutorialTaskTable = Table().apply { background = ImageGetter.getBackground(ImageGetter.getBlue().lerp(Color.BLACK, 0.5f)) }
private val notificationsScroll: NotificationsScroll
var shouldUpdate = false
private var backButtonListener : InputListener
private var backButtonListener: InputListener
init {
@ -78,13 +77,13 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
notificationsScroll = NotificationsScroll(this)
// notifications are right-aligned, they take up only as much space as necessary.
notificationsScroll.width = stage.width/2
notificationsScroll.width = stage.width / 2
minimapWrapper.x = stage.width - minimapWrapper.width
mapHolder.addTiles()
techButtonHolder.touchable=Touchable.enabled
techButtonHolder.touchable = Touchable.enabled
techButtonHolder.onClick(UncivSound.Paper) {
game.setScreen(TechPickerScreen(viewingCiv))
}
@ -94,7 +93,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
fogOfWarButton.setPosition(10f, topBar.y - fogOfWarButton.height - 10f)
// Don't show policies until they become relevant
if(viewingCiv.policies.adoptedPolicies.isNotEmpty() || viewingCiv.policies.canAdoptPolicy()) {
if (viewingCiv.policies.adoptedPolicies.isNotEmpty() || viewingCiv.policies.canAdoptPolicy()) {
val policyScreenButton = Button(skin)
policyScreenButton.add(ImageGetter.getImage("PolicyIcons/Constitution")).size(30f).pad(15f)
policyScreenButton.onClick { game.setScreen(PolicyPickerScreen(this)) }
@ -116,8 +115,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
stage.addActor(diplomacyButtonHolder)
stage.addActor(bottomUnitTable)
stage.addActor(bottomTileInfoTable)
battleTable.width = stage.width/3
battleTable.x = stage.width/3
battleTable.width = stage.width / 3
battleTable.x = stage.width / 3
stage.addActor(battleTable)
stage.addActor(unitActionsTable)
@ -133,28 +132,28 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
// Don't select unit and change selectedCiv when centering as spectator
if (viewingCiv.isSpectator())
mapHolder.setCenterPosition(tileToCenterOn,true, false)
mapHolder.setCenterPosition(tileToCenterOn, true, false)
else
mapHolder.setCenterPosition(tileToCenterOn,true, true)
mapHolder.setCenterPosition(tileToCenterOn, true, true)
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
if(gameInfo.gameParameters.isOnlineMultiplayer && !isPlayersTurn) {
if (gameInfo.gameParameters.isOnlineMultiplayer && !isPlayersTurn) {
// restart the timer
stopMultiPlayerRefresher()
// isDaemon = true, in order to not block the app closing
multiPlayerRefresher = Timer("multiPlayerRefresh", true).apply {
schedule(object : TimerTask() { //todo maybe not use timer for web request, from timer docs "Timer tasks should complete quickly."
override fun run() { loadLatestMultiplayerState() }
override fun run() {
loadLatestMultiplayerState()
}
}, 0, 10000) // 10 seconds
}
}
tutorialController.allTutorialsShowedCallback = {
shouldUpdate = true
}
tutorialController.allTutorialsShowedCallback = { shouldUpdate = true }
backButtonListener = onBackButtonClicked { backButtonAndESCHandler() }
@ -173,63 +172,63 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
}
private fun cleanupKeyDispatcher() {
val delKeys = keyPressDispatcher.keys.filter { it!=' ' && it!='n' }
val delKeys = keyPressDispatcher.keys.filter { it != ' ' && it != 'n' }
delKeys.forEach { keyPressDispatcher.remove(it) }
}
private fun addKeyboardListener() {
stage.addListener(
object : InputListener() {
private val pressedKeys = mutableSetOf<Int>()
private var infiniteAction : RepeatAction? = null
private val amountToMove = 30 / mapHolder.scaleX
private val ALLOWED_KEYS = setOf(Input.Keys.W,Input.Keys.S,Input.Keys.A,Input.Keys.D,
Input.Keys.UP, Input.Keys.DOWN, Input.Keys.LEFT, Input.Keys.RIGHT )
object : InputListener() {
private val pressedKeys = mutableSetOf<Int>()
private var infiniteAction: RepeatAction? = null
private val amountToMove = 30 / mapHolder.scaleX
private val ALLOWED_KEYS = setOf(Input.Keys.W, Input.Keys.S, Input.Keys.A, Input.Keys.D,
Input.Keys.UP, Input.Keys.DOWN, Input.Keys.LEFT, Input.Keys.RIGHT)
override fun keyDown(event: InputEvent?, keycode: Int): Boolean {
if (keycode !in ALLOWED_KEYS) return false
override fun keyDown(event: InputEvent?, keycode: Int): Boolean {
if (keycode !in ALLOWED_KEYS) return false
pressedKeys.add(keycode)
if (infiniteAction == null) {
// create a copy of the action, because removeAction() will destroy this instance
infiniteAction = Actions.forever(Actions.delay(0.05f, Actions.run { whileKeyPressedLoop() }))
mapHolder.addAction(infiniteAction)
}
return true
}
fun whileKeyPressedLoop() {
for (keycode in pressedKeys) {
when (keycode) {
Input.Keys.W, Input.Keys.UP -> mapHolder.scrollY -= amountToMove
Input.Keys.S, Input.Keys.DOWN -> mapHolder.scrollY += amountToMove
Input.Keys.A, Input.Keys.LEFT -> mapHolder.scrollX -= amountToMove
Input.Keys.D, Input.Keys.RIGHT -> mapHolder.scrollX += amountToMove
pressedKeys.add(keycode)
if (infiniteAction == null) {
// create a copy of the action, because removeAction() will destroy this instance
infiniteAction = Actions.forever(Actions.delay(0.05f, Actions.run { whileKeyPressedLoop() }))
mapHolder.addAction(infiniteAction)
}
return true
}
mapHolder.updateVisualScroll()
}
override fun keyUp(event: InputEvent?, keycode: Int): Boolean {
if (keycode !in ALLOWED_KEYS) return false
pressedKeys.remove(keycode)
if (infiniteAction != null && pressedKeys.isEmpty()) {
// stop the loop otherwise it keeps going even after removal
infiniteAction?.finish()
// remove and nil the action
mapHolder.removeAction(infiniteAction)
infiniteAction = null
fun whileKeyPressedLoop() {
for (keycode in pressedKeys) {
when (keycode) {
Input.Keys.W, Input.Keys.UP -> mapHolder.scrollY -= amountToMove
Input.Keys.S, Input.Keys.DOWN -> mapHolder.scrollY += amountToMove
Input.Keys.A, Input.Keys.LEFT -> mapHolder.scrollX -= amountToMove
Input.Keys.D, Input.Keys.RIGHT -> mapHolder.scrollX += amountToMove
}
}
mapHolder.updateVisualScroll()
}
override fun keyUp(event: InputEvent?, keycode: Int): Boolean {
if (keycode !in ALLOWED_KEYS) return false
pressedKeys.remove(keycode)
if (infiniteAction != null && pressedKeys.isEmpty()) {
// stop the loop otherwise it keeps going even after removal
infiniteAction?.finish()
// remove and nil the action
mapHolder.removeAction(infiniteAction)
infiniteAction = null
}
return true
}
return true
}
}
)
}
private fun loadLatestMultiplayerState(){
private fun loadLatestMultiplayerState() {
// Since we're on a background thread, all the UI calls in this func need to run from the
// main thread which has a GL context
@ -243,14 +242,13 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
val latestGame = OnlineMultiplayer().tryDownloadGame(gameInfo.gameId)
// if we find it still isn't player's turn...nothing changed
if(gameInfo.isUpToDate && gameInfo.currentPlayer==latestGame.currentPlayer) {
if (gameInfo.isUpToDate && gameInfo.currentPlayer == latestGame.currentPlayer) {
Gdx.app.postRunnable { loadingGamePopup.close() }
return
}
else{ //else we found it is the player's turn again, turn off polling and load turn
} else { //else we found it is the player's turn again, turn off polling and load turn
stopMultiPlayerRefresher()
latestGame.isUpToDate=true
latestGame.isUpToDate = true
Gdx.app.postRunnable { game.loadGame(latestGame) }
}
@ -328,50 +326,50 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
}
updateNextTurnButton(hasOpenPopups()) // This must be before the notifications update, since its position is based on it
notificationsScroll.update(viewingCiv.notifications)
notificationsScroll.setPosition(stage.width - notificationsScroll.width*0.5f - 10f,
nextTurnButton.y - notificationsScroll.height*0.5f - 5f)
notificationsScroll.setPosition(stage.width - notificationsScroll.width * 0.5f - 10f,
nextTurnButton.y - notificationsScroll.height * 0.5f - 5f)
}
private fun getCurrentTutorialTask(): String {
val completedTasks = game.settings.tutorialTasksCompleted
if(!completedTasks.contains("Move unit"))
if (!completedTasks.contains("Move unit"))
return "Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup"
if(!completedTasks.contains("Found city"))
if (!completedTasks.contains("Found city"))
return "Found a city!\nSelect the Settler (flag unit) > Click on 'Found city' (bottom-left corner)"
if(!completedTasks.contains("Enter city screen"))
if (!completedTasks.contains("Enter city screen"))
return "Enter the city screen!\nClick the city button twice"
if(!completedTasks.contains("Pick technology"))
if (!completedTasks.contains("Pick technology"))
return "Pick a technology to research!\nClick on the tech button (greenish, top left) > " +
"\n select technology > click 'Research' (bottom right)"
if(!completedTasks.contains("Pick construction"))
if (!completedTasks.contains("Pick construction"))
return "Pick a construction!\nEnter city screen > Click on a unit or building (bottom left side) >" +
" \n click 'add to queue'"
if(!completedTasks.contains("Pass a turn"))
if (!completedTasks.contains("Pass a turn"))
return "Pass a turn!\nCycle through units with 'Next unit' > Click 'Next turn'"
if(!completedTasks.contains("Reassign worked tiles"))
if (!completedTasks.contains("Reassign worked tiles"))
return "Reassign worked tiles!\nEnter city screen > click the assigned (green) tile to unassign > " +
"\n click an unassigned tile to assign population"
if(!completedTasks.contains("Meet another civilization"))
if (!completedTasks.contains("Meet another civilization"))
return "Meet another civilization!\nExplore the map until you encounter another civilization!"
if(!completedTasks.contains("Open the options table"))
if (!completedTasks.contains("Open the options table"))
return "Open the options table!\nClick the menu button (top left) > click 'Options'"
if(!completedTasks.contains("Construct an improvement"))
if (!completedTasks.contains("Construct an improvement"))
return "Construct an improvement!\nConstruct a Worker unit > Move to a Plains or Grassland tile > " +
"\n Click 'Create improvement' (above the unit table, bottom left)" +
"\n > Choose the farm > \n Leave the worker there until it's finished"
if(!completedTasks.contains("Create a trade route")
&& viewingCiv.citiesConnectedToCapitalToMediums.any { it.key.civInfo==viewingCiv })
if (!completedTasks.contains("Create a trade route")
&& viewingCiv.citiesConnectedToCapitalToMediums.any { it.key.civInfo == viewingCiv })
game.settings.addCompletedTutorialTask("Create a trade route")
if(viewingCiv.cities.size>1 && !completedTasks.contains("Create a trade route"))
if (viewingCiv.cities.size > 1 && !completedTasks.contains("Create a trade route"))
return "Create a trade route!\nConstruct roads between your capital and another city" +
"\nOr, automate your worker and let him get to that eventually"
if(viewingCiv.isAtWar() && !completedTasks.contains("Conquer a city"))
if (viewingCiv.isAtWar() && !completedTasks.contains("Conquer a city"))
return "Conquer a city!\nBring an enemy city down to low health > " +
"\nEnter the city with a melee unit"
if(viewingCiv.getCivUnits().any { it.type.isAirUnit() } && !completedTasks.contains("Move an air unit"))
if (viewingCiv.getCivUnits().any { it.type.isAirUnit() } && !completedTasks.contains("Move an air unit"))
return "Move an air unit!\nSelect an air unit > select another city within range > " +
"\nMove the unit to the other city"
if(!completedTasks.contains("See your stats breakdown"))
if (!completedTasks.contains("See your stats breakdown"))
return "See your stats breakdown!\nEnter the Overview screen (top right corner) >" +
"\nClick on 'Stats'"
@ -398,9 +396,9 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
// ... all tiles around those in range of an average melee unit
// -> and now we look for a unit that could do the conquering because it's ours
// no matter whether civilian, air or ranged, tell user he needs melee
.any { it.getUnits().any { unit -> unit.civInfo == viewingCiv} }
.any { it.getUnits().any { unit -> unit.civInfo == viewingCiv } }
}
displayTutorial(Tutorial.AfterConquering) { viewingCiv.cities.any{it.hasJustBeenConquered} }
displayTutorial(Tutorial.AfterConquering) { viewingCiv.cities.any { it.hasJustBeenConquered } }
displayTutorial(Tutorial.InjuredUnits) { gameInfo.getCurrentPlayerCivilization().getCivUnits().any { it.health < 100 } }
@ -409,8 +407,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
private fun updateDiplomacyButton(civInfo: CivilizationInfo) {
diplomacyButtonHolder.clear()
if(!civInfo.isDefeated() && !civInfo.isSpectator() && civInfo.getKnownCivs()
.filterNot { it==viewingCiv || it.isBarbarian() }
if (!civInfo.isDefeated() && !civInfo.isSpectator() && civInfo.getKnownCivs()
.filterNot { it == viewingCiv || it.isBarbarian() }
.any()) {
displayTutorial(Tutorial.OtherCivEncountered)
val btn = "Diplomacy".toTextButton()
@ -439,7 +437,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
val buttonPic = Table()
buttonPic.background = ImageGetter.getRoundedEdgeTableBackground(colorFromRGB(7, 46, 43))
buttonPic.defaults().pad(20f)
val text = if(viewingCiv.tech.canResearchTech()) "{Pick a tech}!" else "Technologies"
val text = if (viewingCiv.tech.canResearchTech()) "{Pick a tech}!" else "Technologies"
buttonPic.add(text.toLabel(Color.WHITE, 30))
techButtonHolder.add(buttonPic)
}
@ -474,7 +472,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
nextTurnButton.label.setFontSize(30)
nextTurnButton.labelCell.pad(10f)
val nextTurnActionWrapped = { nextTurnAction() }
nextTurnButton.onClick (nextTurnActionWrapped)
nextTurnButton.onClick(nextTurnActionWrapped)
keyPressDispatcher[' '] = nextTurnActionWrapped
keyPressDispatcher['n'] = nextTurnActionWrapped
@ -486,13 +484,13 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
shouldUpdate = true
thread(name="NextTurn") { // on a separate thread so the user can explore their world while we're passing the turn
thread(name = "NextTurn") { // on a separate thread so the user can explore their world while we're passing the turn
val gameInfoClone = gameInfo.clone()
gameInfoClone.setTransients()
try {
gameInfoClone.nextTurn()
if(gameInfo.gameParameters.isOnlineMultiplayer) {
if (gameInfo.gameParameters.isOnlineMultiplayer) {
try {
OnlineMultiplayer().tryUploadGame(gameInfoClone)
} catch (ex: Exception) {
@ -520,7 +518,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
// do this on main thread - it's the only one that has a GL context to create images from
Gdx.app.postRunnable {
fun createNewWorldScreen(){
fun createNewWorldScreen() {
val newWorldScreen = WorldScreen(gameInfoClone.getPlayerToViewAs())
newWorldScreen.mapHolder.scrollX = mapHolder.scrollX
newWorldScreen.mapHolder.scrollY = mapHolder.scrollY
@ -542,7 +540,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
createNewWorldScreen()
}
if(shouldAutoSave) {
if (shouldAutoSave) {
val newWorldScreen = game.worldScreen
newWorldScreen.waitingForAutosave = true
newWorldScreen.shouldUpdate = true
@ -556,10 +554,10 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
}
}
private class NextTurnAction(val text:String, val color:Color, val action:()->Unit)
private class NextTurnAction(val text: String, val color: Color, val action: () -> Unit)
private fun updateNextTurnButton(isSomethingOpen: Boolean) {
val action:NextTurnAction = getNextTurnAction()
val action: NextTurnAction = getNextTurnAction()
nextTurnAction = action.action
nextTurnButton.setText(action.text.tr())
@ -568,6 +566,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
nextTurnButton.isEnabled = !isSomethingOpen && isPlayersTurn && !waitingForAutosave
nextTurnButton.setPosition(stage.width - nextTurnButton.width - 10f, topBar.y - nextTurnButton.height - 10f)
}
fun enableNextTurnButtonAfterOptions() {
nextTurnButton.isEnabled = isPlayersTurn && !waitingForAutosave
}
@ -598,7 +597,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
game.setScreen(TechPickerScreen(viewingCiv.tech.freeTechs != 0, viewingCiv))
}
viewingCiv.policies.shouldOpenPolicyPicker || (viewingCiv.policies.freePolicies > 0 && viewingCiv.policies.canAdoptPolicy()) ->
viewingCiv.policies.shouldOpenPolicyPicker || (viewingCiv.policies.freePolicies > 0 && viewingCiv.policies.canAdoptPolicy()) ->
NextTurnAction("Pick a policy", Color.VIOLET) {
game.setScreen(PolicyPickerScreen(this))
viewingCiv.policies.shouldOpenPolicyPicker = false
@ -634,10 +633,10 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
super.render(delta)
}
private fun showTutorialsOnNextTurn(){
private fun showTutorialsOnNextTurn() {
if (!game.settings.showTutorials) return
displayTutorial(Tutorial.SlowStart)
displayTutorial(Tutorial.CityExpansion){ viewingCiv.cities.any { it.expansion.tilesClaimed()>0 } }
displayTutorial(Tutorial.CityExpansion) { viewingCiv.cities.any { it.expansion.tilesClaimed() > 0 } }
displayTutorial(Tutorial.BarbarianEncountered) { viewingCiv.viewableTiles.any { it.getUnits().any { unit -> unit.civInfo.isBarbarian() } } }
displayTutorial(Tutorial.RoadsAndRailroads) { viewingCiv.cities.size > 2 }
displayTutorial(Tutorial.Happiness) { viewingCiv.getHappiness() < 5 }
@ -646,11 +645,11 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
displayTutorial(Tutorial.IdleUnits) { gameInfo.turns >= 50 && game.settings.checkForDueUnits }
displayTutorial(Tutorial.ContactMe) { gameInfo.turns >= 100 }
val resources = viewingCiv.detailedCivResources.asSequence().filter { it.origin == "All" } // Avoid full list copy
displayTutorial(Tutorial.LuxuryResource) { resources.any { it.resource.resourceType==ResourceType.Luxury } }
displayTutorial(Tutorial.StrategicResource) { resources.any { it.resource.resourceType==ResourceType.Strategic} }
displayTutorial(Tutorial.LuxuryResource) { resources.any { it.resource.resourceType == ResourceType.Luxury } }
displayTutorial(Tutorial.StrategicResource) { resources.any { it.resource.resourceType == ResourceType.Strategic } }
displayTutorial(Tutorial.EnemyCity) {
viewingCiv.getKnownCivs().asSequence().filter { viewingCiv.isAtWarWith(it) }
.flatMap { it.cities.asSequence() }.any { viewingCiv.exploredTiles.contains(it.location) }
.flatMap { it.cities.asSequence() }.any { viewingCiv.exploredTiles.contains(it.location) }
}
displayTutorial(Tutorial.ApolloProgram) { viewingCiv.hasUnique("Enables construction of Spaceship parts") }
displayTutorial(Tutorial.SiegeUnits) { viewingCiv.getCivUnits().any { it.type == UnitType.Siege } }
@ -693,17 +692,14 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
promptWindow.addGoodSizedLabel("Do you want to exit the game?".tr())
promptWindow.row()
promptWindow.addButton("Yes") { Gdx.app.exit() }
promptWindow.addButton("No") {
promptWindow.close()
}
promptWindow.addButton("No") { promptWindow.close() }
// show the dialog
promptWindow.open (true) // true = always on top
promptWindow.open(true) // true = always on top
}
companion object {
// this object must not be created multiple times
private var multiPlayerRefresher : Timer? = null
private var multiPlayerRefresher: Timer? = null
}
}
}