package net.torvald.terrarum.modulebasegame.ui import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input.Keys import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.TextureRegion import net.torvald.terrarum.App import net.torvald.terrarum.CommonResourcePool import net.torvald.terrarum.TerrarumAppConfiguration import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.modulebasegame.serialise.LoadSavegame import net.torvald.terrarum.savegame.ByteArray64Reader import net.torvald.terrarum.savegame.DiskSkimmer import net.torvald.terrarum.savegame.VDFileID import net.torvald.terrarum.savegame.VDFileID.SAVEGAMEINFO import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.tryDispose import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UIItemTextButton import net.torvald.terrarum.ui.UIItemTextLineInput import net.torvald.terrarum.utils.JsonFetcher import net.torvald.terrarum.utils.forEachSiblings import net.torvald.unicode.EMDASH import java.time.Instant import java.time.format.DateTimeFormatter import java.util.* /** * Created by minjaesong on 2023-07-05. */ class UILoadManage(val full: UILoadSavegame) : UICanvas() { init { handler.allowESCtoClose = false } override var width: Int = Toolkit.drawWidth override var height: Int = App.scr.height private val buttonHeight = full.buttonHeight private val buttonGap = full.buttonGap private val buttonWidth = full.buttonWidth private val drawX = full.drawX private val hx = Toolkit.hdrawWidth private val buttonX1third = hx - (buttonWidth * 1.5).toInt() - buttonGap private val buttonXcentre = hx - (buttonWidth / 2) private val buttonX3third = hx + (buttonWidth / 2) + buttonGap private val buttonXleft = drawX + (240 - buttonWidth) / 2 private val buttonXright = drawX + 240 + (240 - buttonWidth) / 2 private val buttonRowY = full.buttonRowY - buttonHeight private val buttonRowY2 = buttonRowY - buttonHeight - buttonGap private var altDown = false private val mainGoButton = UIItemTextButton(this, { if (altDown && savegameIsNotNew) Lang["MENU_LABEL_PREV_SAVES"] else Lang["MENU_IO_LOAD_GAME"] }, buttonX1third, buttonRowY2, buttonWidth * 3 + buttonGap * 2, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> App.printdbg(this, "Load playerUUID: ${UILoadGovernor.playerUUID}, worldUUID: ${UILoadGovernor.worldUUID}") /*if (full.loadables.moreRecentAutosaveAvailable()) { full.bringAutosaveSelectorUp() full.changePanelTo(2) } else */ if (altDown && savegameIsNotNew) { mode = MODE_PREV_SAVES loadPrevGameInfo() } else { if (full.loadables.saveAvaliable()) { if (full.loadables.newerSaveIsDamaged) { UILoadGovernor.previousSaveWasLoaded = true } // full.takeAutosaveSelectorDown() full.loadManageSelectedGame = full.loadables.getLoadableSave()!! mode = MODE_LOAD full.interruptListGenerator() } } } } private val mainNoGoButton = UIItemTextButton(this, { Lang["ERROR_SAVE_CORRUPTED"].replace(".","") }, buttonX1third, buttonRowY2, buttonWidth * 3 + buttonGap * 2, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.isEnabled = false } private val mainNoGoButtonSaveFromNewerVersion = UIItemTextButton(this, { if (altDown && savegameIsNotNew) Lang["MENU_LABEL_PREV_SAVES"] else Lang["ERROR_SAVE_IS_FROM_NEWER_VERSION"].replace(".","") }, buttonX1third, buttonRowY2, buttonWidth * 3 + buttonGap * 2, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.isEnabled = false it.clickOnceListener = { _,_ -> App.printdbg(this, "Load playerUUID: ${UILoadGovernor.playerUUID}, worldUUID: ${UILoadGovernor.worldUUID}") if (altDown && savegameIsNotNew) { mode = MODE_PREV_SAVES loadPrevGameInfo() } } it.updateListener = { _ -> it.isEnabled = altDown } } private val mainImportedPlayerCreateNewWorldButton = UIItemTextButton(this, { Lang["CONTEXT_WORLD_NEW"].replace(".","") }, buttonX1third, buttonRowY2, buttonWidth * 3 + buttonGap * 2, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> val playerDisk = full.loadables.getImportedPlayer()!! full.remoCon.openUI(UINewWorld(full.remoCon, playerDisk)) } } private val mainRenameButton = UIItemTextButton(this, { if (altDown && savegameIsNotNew) Lang["MENU_MODULES"] else Lang["MENU_LABEL_RENAME"] }, buttonX1third, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> if (altDown && savegameIsNotNew) { mode = MODE_SHOW_LOAD_ORDER } else { mode = MODE_RENAME } } } private val mainBackButton = UIItemTextButton(this, { Lang["MENU_LABEL_BACK"] }, buttonXcentre, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> full.resetScroll() full.changePanelTo(0) full.playerButtonSelected?.forceUnhighlight = false } } private val mainDeleteButton = UIItemTextButton(this, { Lang["CONTEXT_CHARACTER_DELETE"] }, buttonX3third, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true, inactiveCol = Toolkit.Theme.COL_RED, activeCol = Toolkit.Theme.COL_REDD).also { it.clickOnceListener = { _,_ -> mode = MODE_DELETE } } private val confirmDeleteButton = UIItemTextButton(this, { Lang["MENU_LABEL_DELETE"] }, buttonXleft, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true, inactiveCol = Toolkit.Theme.COL_RED, activeCol = Toolkit.Theme.COL_REDD).also { it.clickOnceListener = { _,_ -> val pu = full.playerButtonSelected!!.playerUUID val wu = full.playerButtonSelected!!.worldUUID App.savegamePlayers[pu]?.moveToRecycle(App.recycledPlayersDir)?.let { App.sortedPlayers.remove(pu) App.savegamePlayers.remove(pu) App.savegamePlayersName.remove(pu) } // don't delete the world please full.remoCon.openUI(UILoadSavegame(full.remoCon)) } } private val confirmCancelButton = UIItemTextButton(this, { Lang["MENU_LABEL_CANCEL"] }, buttonXright, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> mode = MODE_INIT } } private val renameInput = UIItemTextLineInput(this, buttonXleft, App.scr.halfh, 240 + buttonWidth) private val renameCancelButton = UIItemTextButton(this, { Lang["MENU_LABEL_CANCEL"] }, buttonXleft, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> mode = MODE_INIT } } private val renameRenameButton = UIItemTextButton(this, { Lang["MENU_LABEL_RENAME"] }, buttonXright, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> val newName = renameInput.getText().trim() if (newName.isNotBlank()) { full.playerButtonSelected!!.playerUUID.let { uuid -> App.savegamePlayersName[uuid] = newName App.savegamePlayers[uuid]!!.renamePlayer(newName) full.playerButtonSelected!!.playerName = newName } } mode = MODE_INIT } } private val modulesBackButton = UIItemTextButton(this, { Lang["MENU_LABEL_BACK"] }, buttonXcentre, buttonRowY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true).also { it.clickOnceListener = { _,_ -> mode = MODE_INIT } } private var mode = 0 private var mainButtons0 = listOf(mainGoButton, mainBackButton, mainRenameButton, mainDeleteButton) private var mainButtonsNoGoSaveCorrupted = listOf(mainNoGoButton, mainBackButton, mainRenameButton, mainDeleteButton) private var mainButtonsNoGoSaveFromNewer = listOf(mainNoGoButtonSaveFromNewerVersion, mainBackButton, mainRenameButton, mainDeleteButton) private var mainButtons2 = listOf(mainImportedPlayerCreateNewWorldButton, mainBackButton, mainRenameButton, mainDeleteButton) private var delButtons = listOf(confirmCancelButton, confirmDeleteButton) private var renameButtons = listOf(renameRenameButton, renameCancelButton) private val savegameHasError: Boolean get() = (!full.loadables.saveAvaliable() && !full.loadables.isImported) private val savegameIsNotNew: Boolean get() = full.loadables.saveAvaliable() private val mainButtons: List get() = if (full.playerButtonSelected?.isNewerVersion == true) mainButtonsNoGoSaveFromNewer else if (full.loadables.saveAvaliable()) mainButtons0 else if (full.loadables.isImported) mainButtons2 else mainButtonsNoGoSaveCorrupted private val MODE_INIT = 0 private val MODE_DELETE = 16 // are you sure? private val MODE_RENAME = 32 // show rename dialogue private val MODE_PREV_SAVES = 48 private val MODE_SHOW_LOAD_ORDER = 64 private val MODE_LOAD = 256 // is needed to make the static loading screen init { } private var screencap: TextureRegion? = null private val screencapW = SAVE_THUMBNAIL_MAIN_WIDTH private val screencapH = SAVE_THUMBNAIL_MAIN_HEIGHT override fun doOpening(delta: Float) { full.playerButtonSelected?.forceUnhighlight = true full.playerButtonSelected?.let { button -> screencap?.texture?.tryDispose() button.savegameThumbnailPixmap?.let { Texture(it).also { it.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear) screencap = TextureRegion(it) } } } } override fun doClosing(delta: Float) { full.playerButtonSelected?.forceUnhighlight = false } private val modulesBoxBaseY = full.titleTopGradEnd + 48 private val modulesTextboxW = 280 private val boxTextMargin = 3 private val listGap = 10 private val playerModTextboxX = Toolkit.drawWidth / 2 - (2 * boxTextMargin + listGap) - modulesTextboxW private val worldModTextboxX = Toolkit.drawWidth / 2 + (2 * boxTextMargin + listGap) private lateinit var sortedPlayerWorldList: List> private lateinit var loadOrderPlayer: List private lateinit var loadOrderWorld: List private var playerName = "" private var px64 = 0 private var wx64 = 0 private var px48 = 0 private var wx48 = 0 private var totalTBH48 = 0 private var totalTBH64 = 0 override fun show() { super.show() } private fun loadPrevGameInfo() { val players = App.savegamePlayers[full.playerButtonSelected!!.playerUUID]!!.files val worlds = App.savegameWorlds[full.playerButtonSelected!!.worldUUID]!!.files val playerSavesInfo = players.map { it.getSavegameMeta() }.sortedByDescending { it.lastPlayTime } val worldSavesInfo = worlds.map { it.getSavegameMeta() }.sortedByDescending { it.lastPlayTime } sortedPlayerWorldList = getChronologicalPair(playerSavesInfo, worldSavesInfo) px48 = playerModTextboxX + (modulesTextboxW - tbw48) / 2 wx48 = worldModTextboxX + (modulesTextboxW - tbw48) / 2 totalTBH48 = sortedPlayerWorldList.size * 32 } internal fun loadSavegameInfo() { playerName = App.savegamePlayersName[full.playerButtonSelected!!.playerUUID] ?: "Player" loadOrderPlayer = App.savegamePlayers[full.playerButtonSelected!!.playerUUID]!!.files[selectedRevision].getFile( VDFileID.LOADORDER )?.getContent()?.toByteArray()?.toString(Common.CHARSET)?.split('\n')?.let { it.mapIndexed { index, s -> "${(index+1).toString().padStart(it.size.fastLen())}. $s" } } ?: listOf("$EMDASH") loadOrderWorld = App.savegameWorlds[full.playerButtonSelected!!.worldUUID]?.files?.get(selectedRevision)?.getFile( // it's null for freshly-imported avatars VDFileID.LOADORDER )?.getContent()?.toByteArray()?.toString(Common.CHARSET)?.split('\n')?.let { it.mapIndexed { index, s -> "${(index+1).toString().padStart(it.size.fastLen())}. $s" } } ?: listOf("$EMDASH") val playerTBW64 = loadOrderPlayer.maxOfOrNull { App.fontGame.getWidth(it) } ?: 0 val worldTBW64 = loadOrderWorld.maxOfOrNull { App.fontGame.getWidth(it) } ?: 0 px64 = playerModTextboxX + (modulesTextboxW - playerTBW64) / 2 wx64 = worldModTextboxX + (modulesTextboxW - worldTBW64) / 2 totalTBH64 = maxOf(loadOrderPlayer.size, loadOrderWorld.size) * App.fontGame.lineHeight.toInt() } override fun updateImpl(delta: Float) { altDown = Gdx.input.isKeyPressed(Keys.ALT_LEFT) || Gdx.input.isKeyPressed(Keys.ALT_RIGHT) when (mode) { MODE_INIT -> { mainButtons.forEach { it.update(delta) } } MODE_DELETE -> { delButtons.forEach { it.update(delta) } } MODE_RENAME -> { renameButtons.forEach { it.update(delta) } renameInput.update(delta) } MODE_SHOW_LOAD_ORDER, MODE_PREV_SAVES -> { modulesBackButton.update(delta) } } } override fun inputStrobed(e: TerrarumKeyboardEvent) { when (mode) { MODE_RENAME -> { renameInput.inputStrobed(e) } } } private var loadFiredFrameCounter = 0 private var selectedRevision = 0 // 0: most recent, 1: second most recent, etc. private val icons = CommonResourcePool.getAsTextureRegionPack("inventory_category") override fun renderImpl(frameDelta: Float, batch: SpriteBatch, camera: OrthographicCamera) { if (mode != MODE_SHOW_LOAD_ORDER) { val buttonYdelta = (full.titleTopGradEnd) - full.playerButtonSelected!!.posY full.playerButtonSelected!!.render(frameDelta, batch, camera, 0, buttonYdelta) } when (mode) { MODE_INIT -> { mainButtons.forEach { it.render(frameDelta, batch, camera) } // draw thumbnails of the most recent game // val tex = screencap ?: CommonResourcePool.getAsTextureRegion("terrarum-defaultsavegamethumb") if (screencap != null) { val tx = (Toolkit.drawWidth - screencapW) / 2 val tys = full.titleTopGradEnd + SAVE_CELL_HEIGHT + buttonGap val tye = buttonRowY2 - buttonGap val ty = tys + (tye - tys - SAVE_THUMBNAIL_MAIN_HEIGHT) / 2 batch.color = Toolkit.Theme.COL_INACTIVE Toolkit.drawBoxBorder(batch, tx - 1, ty - 1, screencapW + 2, screencapH + 2) batch.color = UIInventoryFull.CELL_COL Toolkit.fillArea(batch, tx, ty, screencapW, screencapH) batch.color = Color.WHITE batch.draw(screencap, tx.toFloat(), ty.toFloat(), screencapW.toFloat(), screencapH.toFloat()) } } MODE_DELETE -> { Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_LABEL_SAVE_WILL_BE_DELETED"], Toolkit.drawWidth, 0, full.titleTopGradEnd + full.cellInterval + SAVE_CELL_HEIGHT + 36) Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_LABEL_ARE_YOU_SURE"], Toolkit.drawWidth, 0, full.titleTopGradEnd + full.cellInterval + SAVE_CELL_HEIGHT + 36 + 24) delButtons.forEach { it.render(frameDelta, batch, camera) } } MODE_RENAME -> { renameInput.render(frameDelta, batch, camera) renameButtons.forEach { it.render(frameDelta, batch, camera) } } MODE_LOAD -> { loadFiredFrameCounter += 1 StaticLoadScreenSubstitute(batch) if (loadFiredFrameCounter == 2) LoadSavegame(full.loadManageSelectedGame) } MODE_SHOW_LOAD_ORDER -> { Toolkit.drawTextCentered(batch, App.fontUITitle, Lang["MENU_MODULES"], Toolkit.drawWidth, 0, full.titleTopGradEnd) batch.color = Toolkit.Theme.COL_CELL_FILL Toolkit.fillArea(batch, playerModTextboxX, modulesBoxBaseY - 4, modulesTextboxW, 36 + totalTBH64) Toolkit.fillArea(batch, worldModTextboxX, modulesBoxBaseY - 4, modulesTextboxW, 36 + totalTBH64) batch.color = Toolkit.Theme.COL_INACTIVE Toolkit.drawBoxBorder(batch, playerModTextboxX - 1, modulesBoxBaseY - 4 - 1, modulesTextboxW + 2, 36 + totalTBH64 + 2) Toolkit.drawBoxBorder(batch, worldModTextboxX - 1, modulesBoxBaseY - 4 - 1, modulesTextboxW + 2, 36 + totalTBH64 + 2) batch.color = Toolkit.Theme.COL_INVENTORY_CELL_BORDER for (i in 0 until maxOf(loadOrderPlayer.size, loadOrderWorld.size)) { Toolkit.drawStraightLine(batch, playerModTextboxX + boxTextMargin, modulesBoxBaseY + 32 + App.fontGame.lineHeight.toInt() * i, playerModTextboxX + modulesTextboxW - boxTextMargin, 1, false) Toolkit.drawStraightLine(batch, worldModTextboxX + boxTextMargin, modulesBoxBaseY + 32 + App.fontGame.lineHeight.toInt() * i, worldModTextboxX + modulesTextboxW - boxTextMargin, 1, false) } batch.color = Color.WHITE Toolkit.drawTextCentered(batch, App.fontGame, playerName, modulesTextboxW, playerModTextboxX, modulesBoxBaseY + 1) Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_LABEL_WORLD"], modulesTextboxW, worldModTextboxX, modulesBoxBaseY + 1) loadOrderPlayer.forEachIndexed { index, s -> App.fontGame.draw(batch, s, px64, modulesBoxBaseY + 32 + App.fontGame.lineHeight.toInt() * index) } loadOrderWorld.forEachIndexed { index, s -> App.fontGame.draw(batch, s, wx64, modulesBoxBaseY + 32 + App.fontGame.lineHeight.toInt() * index) } modulesBackButton.render(frameDelta, batch, camera) } MODE_PREV_SAVES -> { val modulesBoxBaseY2 = full.titleTopGradEnd + SAVE_CELL_HEIGHT + listGap + 4 batch.color = Toolkit.Theme.COL_CELL_FILL Toolkit.fillArea(batch, playerModTextboxX, modulesBoxBaseY2 - 4, modulesTextboxW, 36 + totalTBH48) Toolkit.fillArea(batch, worldModTextboxX, modulesBoxBaseY2 - 4, modulesTextboxW, 36 + totalTBH48) batch.color = Toolkit.Theme.COL_INACTIVE Toolkit.drawBoxBorder(batch, playerModTextboxX - 1, modulesBoxBaseY2 - 4 - 1, modulesTextboxW + 2, 36 + totalTBH48 + 2) Toolkit.drawBoxBorder(batch, worldModTextboxX - 1, modulesBoxBaseY2 - 4 - 1, modulesTextboxW + 2, 36 + totalTBH48 + 2) batch.color = Toolkit.Theme.COL_INVENTORY_CELL_BORDER for (i in 0 until sortedPlayerWorldList.size) { Toolkit.drawStraightLine(batch, playerModTextboxX + boxTextMargin, modulesBoxBaseY2 + 32 + 32 * i, playerModTextboxX + modulesTextboxW - boxTextMargin, 1, false) Toolkit.drawStraightLine(batch, worldModTextboxX + boxTextMargin, modulesBoxBaseY2 + 32 + 32 * i, worldModTextboxX + modulesTextboxW - boxTextMargin, 1, false) } batch.color = Color.WHITE Toolkit.drawTextCentered(batch, App.fontGame, playerName, modulesTextboxW, playerModTextboxX, modulesBoxBaseY2 + 1) Toolkit.drawTextCentered(batch, App.fontGame, Lang["MENU_LABEL_WORLD"], modulesTextboxW, worldModTextboxX, modulesBoxBaseY2 + 1) sortedPlayerWorldList.forEachIndexed { index, (pmeta, wmeta) -> if (pmeta != null) { if (pmeta.isAuto) batch.draw(icons.get(24,1), playerModTextboxX + 4f, modulesBoxBaseY2 + 38f + 32 * index) App.fontGame.draw(batch, "$pmeta", px48, modulesBoxBaseY2 + 36 + 32 * index) } if (wmeta != null) { if (wmeta.isAuto) batch.draw(icons.get(24,1), worldModTextboxX + 4f, modulesBoxBaseY2 + 38f + 32 * index) App.fontGame.draw(batch, "$wmeta", wx48, modulesBoxBaseY2 + 36 + 32 * index) } } modulesBackButton.render(frameDelta, batch, camera) } } } val tbw48 = App.fontGame.getWidth("8888-88-88 88:88:88 (8.8.88)") private data class SavegameMeta( val lastPlayTime: Long, val genver: String, val isAuto: Boolean, ) { private val lastPlayTimeS = Instant.ofEpochSecond(lastPlayTime) .atZone(TimeZone.getDefault().toZoneId()) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) override fun toString() = "$lastPlayTimeS\u3000($genver)" } private fun DiskSkimmer.getSavegameMeta(): SavegameMeta { if (!this.initialised) this.rebuild() this.getFile(SAVEGAMEINFO)!!.bytes.let { var lastPlayTime = 0L var genver = "" val isAuto = (this.getSaveMode() and 0b10 != 0) JsonFetcher.readFromJsonString(ByteArray64Reader(it, Common.CHARSET)).forEachSiblings { name, value -> if (name == "lastPlayTime") lastPlayTime = value.asLong() if (name == "genver") genver = value.asLong().let { "${it.ushr(48)}.${it.ushr(24).and(0xFFFFFF)}.${it.and(0xFFFFFF)}" } } val snap = this.getSaveSnapshotVersion()?.toString() val versionString = if (snap != null) "$snap" else TerrarumAppConfiguration.convertVersionNumberToReadableShort(genver) return SavegameMeta( lastPlayTime, versionString, isAuto ) } } private fun getChronologicalPair(ps: List, ws: List): List> { val li = ArrayList>() var pc = 0 var wc = 0 var breakStatus = -1 // 0: ps ran out, 1: ws ran out while (true) { if (ps.size == pc) { breakStatus = 0 break } else if (ws.size == wc) { breakStatus = 1 break } if (ps[pc].lastPlayTime == ws[wc].lastPlayTime) { li.add(ps[pc] to ws[wc]) pc++; wc++ } else if (ps[pc].lastPlayTime > ws[wc].lastPlayTime) { li.add(ps[pc] to null) pc++ } else { li.add(null to ws[wc]) wc++ } } val remainder = if (breakStatus == 0) ws else ps var rc = if (breakStatus == 0) wc else pc while (rc < remainder.size) { if (breakStatus == 0) li.add(null to ws[rc]) else li.add(ps[pc] to null) rc++ } return li } override fun dispose() { screencap?.texture?.tryDispose() } override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { when (mode) { MODE_INIT -> { mainButtons.forEach { it.touchDown(screenX, screenY, pointer, button) } } MODE_DELETE -> { delButtons.forEach { it.touchDown(screenX, screenY, pointer, button) } } MODE_RENAME -> { renameInput.touchDown(screenX, screenY, pointer, button) renameButtons.forEach { it.touchDown(screenX, screenY, pointer, button) } } MODE_SHOW_LOAD_ORDER, MODE_PREV_SAVES -> { modulesBackButton.touchDown(screenX, screenY, pointer, button) } } return true } override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { when (mode) { MODE_INIT -> { mainButtons.forEach { it.touchUp(screenX, screenY, pointer, button) } } MODE_DELETE -> { delButtons.forEach { it.touchUp(screenX, screenY, pointer, button) } } MODE_RENAME -> { renameInput.touchUp(screenX, screenY, pointer, button) renameButtons.forEach { it.touchUp(screenX, screenY, pointer, button) } } MODE_SHOW_LOAD_ORDER, MODE_PREV_SAVES -> { modulesBackButton.touchUp(screenX, screenY, pointer, button) } } return true } private fun Int.fastLen(): Int { return if (this < 0) 1 + this.unaryMinus().fastLen() else if (this < 10) 1 else if (this < 100) 2 else if (this < 1000) 3 else if (this < 10000) 4 else if (this < 100000) 5 else if (this < 1000000) 6 else if (this < 10000000) 7 else if (this < 100000000) 8 else if (this < 1000000000) 9 else 10 } }