From 331e89b4df7f9cbd98a371f320341df11407842d Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 29 May 2023 17:51:15 +0900 Subject: [PATCH] world portal listing gui --- .../mods/basegame/gui/worldportal_catbar.tga | 4 +- assets/mods/basegame/locales/en/game.json | 3 +- src/net/torvald/terrarum/gameactors/AVKey.kt | 2 +- src/net/torvald/terrarum/langpack/Lang.kt | 3 + .../modulebasegame/ui/UIWorldPortal.kt | 17 +- .../modulebasegame/ui/UIWorldPortalListing.kt | 196 +++++++++++++++--- 6 files changed, 186 insertions(+), 39 deletions(-) diff --git a/assets/mods/basegame/gui/worldportal_catbar.tga b/assets/mods/basegame/gui/worldportal_catbar.tga index 2e3ad36f8..a236634e2 100644 --- a/assets/mods/basegame/gui/worldportal_catbar.tga +++ b/assets/mods/basegame/gui/worldportal_catbar.tga @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9483029bd08a1bc0cde7691b0d4d758b54178f68ef083c5fb9834675b6a64fe3 -size 14418 +oid sha256:3160054bcfbacc8caf012155361ccbe47b6ca3dc33e0e15874965815140029bf +size 21618 diff --git a/assets/mods/basegame/locales/en/game.json b/assets/mods/basegame/locales/en/game.json index 1e9b6c61c..972b8db51 100644 --- a/assets/mods/basegame/locales/en/game.json +++ b/assets/mods/basegame/locales/en/game.json @@ -18,5 +18,6 @@ "GAME_CRAFTING": "Crafting", "GAME_CRAFTABLE_ITEMS": "Craftable Items", "CONTEXT_WORLD_SEARCH": "World Search", - "CONTEXT_WORLD_LIST": "Worlds List" + "CONTEXT_WORLD_LIST": "Worlds List", + "MENU_LABEL_RENAME": "Rename" } \ No newline at end of file diff --git a/src/net/torvald/terrarum/gameactors/AVKey.kt b/src/net/torvald/terrarum/gameactors/AVKey.kt index a59489fae..0ecae3575 100644 --- a/src/net/torvald/terrarum/gameactors/AVKey.kt +++ b/src/net/torvald/terrarum/gameactors/AVKey.kt @@ -148,5 +148,5 @@ object AVKey { * * example value: `"SIxM+kGlrjZgLx5Zeqz7,;:UIZ5Q=2WT35SgKpOp.,vvf'fNW3G() - private val thumbw = 360 - private val thumbh = 240 + private val thumbw = 378 + private val textAreaW = thumbw - 32 + private val thumbh = 252 private val hx = Toolkit.drawWidth.div(2) private val y = INVENTORY_CELLS_OFFSET_Y() + 1 private val listCount = getCellCountVertically(UIItemWorldCellsSimple.height, gridGap) private val listHeight = UIItemWorldCellsSimple.height + (listCount - 1) * (UIItemWorldCellsSimple.height + gridGap) - private val memoryGaugeWidth = 360 + private val memoryGaugeWidth = textAreaW private val deleteButtonWidth = (memoryGaugeWidth - gridGap) / 2 private val buttonDeleteWorld = UIItemTextButton(this, "MENU_LABEL_DELETE_WORLD", @@ -68,16 +73,24 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { alignment = UIItemTextButton.Companion.Alignment.CENTRE ) + private val worldList = ArrayList() data class WorldInfo( val uuid: UUID, val diskSkimmer: DiskSkimmer, val dimensionInChunks: Int, - val seed: Long - ) + val seed: Long, + val lastPlayedString: String, + val totalPlayedString: String, + val screenshot: TextureRegion?, + ) { + fun dispose() { + screenshot?.texture?.dispose() + } + } init { CommonResourcePool.addToLoadingList("terrarum-basegame-worldportalicons") { - TextureRegionPack(ModMgr.getGdxFile("basegame", "gui/worldportal_catbar.tga"), 20, 20) + TextureRegionPack(ModMgr.getGdxFile("basegame", "gui/worldportal_catbar.tga"), 30, 20) } CommonResourcePool.loadAll() @@ -92,27 +105,51 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { private lateinit var worldCells: Array + private var selected: UIItemWorldCellsSimple? = null + private var selectedIndex: Int? = null + override fun show() { worldList.clear() - worldList.addAll((INGAME.actorGamer.actorValue.getAsString(AVKey.WORLD_PORTAL_DICT) ?: "").split(",").filter { it.isNotBlank() }.map { + (INGAME.actorGamer.actorValue.getAsString(AVKey.WORLD_PORTAL_DICT) ?: "").split(",").filter { it.isNotBlank() }.map { it.ascii85toUUID().let { it to App.savegameWorlds[it] } - }.filter { it.second != null }.map { (uuid, disk) -> + }.filter { it.second != null }.mapIndexed { index, (uuid, disk) -> + var chunksCount = 0 var seed = 0L - worldList.forEach { - var w = 0 - var h = 0 - JsonFetcher.readFromJsonString(ByteArray64Reader((disk!!.requestFile(-1)!!.contents.getContent() as EntryFile).bytes, Common.CHARSET)).let { - JsonFetcher.forEachSiblings(it) { name, value -> - if (name == "width") w = value.asInt() - if (name == "height") h = value.asInt() - if (name == "generatorSeed") seed = value.asLong() - } + var lastPlayed = 0L + var totalPlayed = 0L + var w = 0 + var h = 0 + var thumb: TextureRegion? = null + + JsonFetcher.readFromJsonString(ByteArray64Reader(disk!!.requestFile(-1)!!.contents.getContent() as ByteArray64, Common.CHARSET)).let { + JsonFetcher.forEachSiblings(it) { name, value -> + if (name == "width") w = value.asInt() + if (name == "height") h = value.asInt() + if (name == "generatorSeed") seed = value.asLong() + if (name == "lastPlayTime") lastPlayed = value.asLong() + if (name == "totalPlayTime") totalPlayed = value.asLong() } - chunksCount = (w / CHUNK_W) * (h / CHUNK_H) } - WorldInfo(uuid, disk!!, chunksCount, seed) - } as List) + chunksCount = (w / CHUNK_W) * (h / CHUNK_H) + + disk.requestFile(-2)?.let { + val zippedTga = (it.contents as EntryFile).bytes + val gzin = GZIPInputStream(ByteArray64InputStream(zippedTga)) + val tgaFileContents = gzin.readAllBytes(); gzin.close() + + val thumbPixmap = Pixmap(tgaFileContents, 0, tgaFileContents.size) + val thumbTex = Texture(thumbPixmap) + thumbTex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear) + thumb = TextureRegion(thumbTex) + } + + WorldInfo(uuid, disk, chunksCount, seed, lastPlayed.toTimestamp(), totalPlayed.toDurationStamp(), thumb) + }.let { + worldList.addAll(it) + } + + chunksUsed = worldList.sumOf { it.dimensionInChunks } @@ -123,11 +160,42 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { y + (gridGap + UIItemWorldCellsSimple.height) * it, worldList.getOrNull(it), worldList.getOrNull(it)?.diskSkimmer?.getDiskName(Common.CHARSET) - ) + ).also { button -> + button.clickOnceListener = { _, _, _ -> + selected = button + selectedIndex = it + updateUIbyButtonSelection() + } + } } uiItems.forEach { it.show() } worldCells.forEach { it.show() } + selected = null + + updateUIbyButtonSelection() + } + + private fun Long.toTimestamp() = Instant.ofEpochSecond(this) + .atZone(TimeZone.getDefault().toZoneId()) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + private fun Long.toDurationStamp(): String { + val s = this % 60 + val m = (this / 60) % 60 + val h = (this / 3600) % 24 + val d = this / 86400 + return if (d == 0L) + "${h.toString().padStart(2,'0')}h${m.toString().padStart(2,'0')}m${s.toString().padStart(2,'0')}s" + else + "${d}d${h.toString().padStart(2,'0')}h${m.toString().padStart(2,'0')}m${s.toString().padStart(2,'0')}s" + } + private val nullTimestamp = "0000-00-00 --:--:--" + private val nullDurationStamp = "--h--m--s" + private fun Int.chunkCountToWorldSize() = when(this) { + in 0 until 2000 -> "CONTEXT_DESCRIPTION_SMALL" + in 2000 until 4500 -> "MENU_SETTING_MEDIUM" + in 4500 until 10000 -> "CONTEXT_DESCRIPTION_BIG" + else -> "CONTEXT_DESCRIPTION_HUGE" } override fun updateUI(delta: Float) { @@ -135,11 +203,35 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { worldCells.forEach { it.update(delta) } } + private val iconGap = 12f + private val iconSize = 30f + private val textualListHeight = 30f + private val iconSizeGap = iconSize + iconGap + private fun updateUIbyButtonSelection() { + worldTexts = listOf( + // last played + icons.get(2, 1) to (selected?.worldInfo?.lastPlayedString ?: nullTimestamp), + // total played + icons.get(0, 2) to (selected?.worldInfo?.totalPlayedString ?: nullDurationStamp), + // world size + icons.get(1, 2) to (Lang.getOrNull(selected?.worldInfo?.dimensionInChunks?.chunkCountToWorldSize()) ?: "$EMDASH"), + ) + + selectedWorldThumb = selected?.worldInfo?.screenshot + + worldCells.forEach { + it.highlighted = (selected == it && selected?.worldInfo != null) + } + } + + private lateinit var worldTexts: List> + private var selectedWorldThumb: TextureRegion? = null + + val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons") override fun renderUI(batch: SpriteBatch, camera: Camera) { val memoryGaugeXpos = hx - memoryGaugeWidth - gridGap/2 val memoryGaugeYpos = y + listHeight - buttonHeight - gridGap - buttonHeight - val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons") // draw background // // screencap panel @@ -157,13 +249,30 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { val barCol = UIItemInventoryCellCommonRes.getHealthMeterColour(chunksMax - chunksUsed, 0, chunksMax) val barBack = barCol mul UIItemInventoryCellCommonRes.meterBackDarkening batch.color = Color.WHITE - batch.draw(icons.get(2, 2), memoryGaugeXpos - 32f, memoryGaugeYpos + 2f) + batch.draw(icons.get(2, 2), memoryGaugeXpos - iconSizeGap, memoryGaugeYpos + 2f) Toolkit.drawBoxBorder(batch, memoryGaugeXpos - 1, memoryGaugeYpos - 1, memoryGaugeWidth + 2, buttonHeight + 2) batch.color = barBack Toolkit.fillArea(batch, memoryGaugeXpos, memoryGaugeYpos, memoryGaugeWidth, buttonHeight) batch.color = barCol Toolkit.fillArea(batch, memoryGaugeXpos, memoryGaugeYpos, (memoryGaugeWidth * (chunksUsed / chunksMax.toFloat())).ceilInt(), buttonHeight) + batch.color = Color.WHITE + if (selected?.worldInfo != null) { + // some texts + worldTexts.forEachIndexed { index, (icon, str) -> + batch.draw(icon, memoryGaugeXpos - iconSizeGap, y + thumbh + 16f + textualListHeight * index) + App.fontGame.draw(batch, str, memoryGaugeXpos.toFloat(), y + thumbh + 16f + textualListHeight * index) + } + // size indicator on the memory gauge + Toolkit.fillArea(batch, memoryGaugeXpos, memoryGaugeYpos, (memoryGaugeWidth * (selected?.worldInfo!!.dimensionInChunks / chunksMax.toFloat())).ceilInt(), buttonHeight) + + // thumbnail + selected?.worldInfo?.screenshot?.let { + batch.draw(it, (hx - thumbw - gridGap/2).toFloat(), y.toFloat(), thumbw.toFloat(), thumbh.toFloat()) + } + } + + uiItems.forEach { it.render(batch, camera) } @@ -171,7 +280,7 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { // control hints batch.color = Color.WHITE - App.fontGame.draw(batch, full.portalListingControlHelp, (hx - thumbw - gridGap/2).toInt(), (full.yEnd - 20).toInt()) + App.fontGame.draw(batch, full.portalListingControlHelp, (hx - textAreaW - gridGap/2).toInt(), (full.yEnd - 20).toInt()) } override fun hide() { @@ -179,11 +288,33 @@ class UIWorldPortalListing(val full: UIWorldPortal) : UICanvas() { worldCells.forEach { it.hide() } worldCells.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} } + worldList.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} } } override fun dispose() { uiItems.forEach { it.dispose() } worldCells.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} } + worldList.forEach { try { it.dispose() } catch (_: GdxRuntimeException) {} } + } + + override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { + if (this.isVisible && mouseInScreen(screenX, screenY)) { + uiItems.forEach { it.touchDown(screenX, screenY, pointer, button) } + worldCells.forEach { it.touchDown(screenX, screenY, pointer, button) } + handler.subUIs.forEach { it.touchDown(screenX, screenY, pointer, button) } + return true + } + else return false + } + + override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { + if (this.isVisible) { + uiItems.forEach { it.touchUp(screenX, screenY, pointer, button) } + worldCells.forEach { it.touchUp(screenX, screenY, pointer, button) } + handler.subUIs.forEach { it.touchUp(screenX, screenY, pointer, button) } + return true + } + else return false } } @@ -197,15 +328,16 @@ class UIItemWorldCellsSimple( ) : UIItem(parent, initialX, initialY) { companion object { - const val width = 360 + const val width = 378 const val height = 46 } - override val width: Int = 360 + override val width: Int = 378 override val height: Int = 46 private val icons = CommonResourcePool.getAsTextureRegionPack("terrarum-basegame-worldportalicons") + var highlighted = false override fun show() { super.show() @@ -228,7 +360,7 @@ class UIItemWorldCellsSimple( Toolkit.fillArea(batch, posX, posY, width, height) // draw border - val bcol = if (mouseUp && mousePushed) Toolkit.Theme.COL_SELECTED + val bcol = if (highlighted || mouseUp && mousePushed) Toolkit.Theme.COL_SELECTED else if (mouseUp) Toolkit.Theme.COL_MOUSE_UP else Toolkit.Theme.COL_LIST_DEFAULT batch.color = bcol Toolkit.drawBoxBorder(batch, posX - 1, posY - 1, width + 2, height + 2) @@ -236,7 +368,7 @@ class UIItemWorldCellsSimple( batch.draw(icons.get(0, 1), posX + 4f, posY + 1f) App.fontGame.draw(batch, worldName ?: "$EMDASH", posX + 32, posY + 1) batch.draw(icons.get(1, 1), posX + 4f, posY + 25f) - App.fontGame.draw(batch, if (worldInfo?.seed == null) "$EMDASH" else "${(if (worldInfo.seed > 0) " + " else "")}${worldInfo.seed}" , posX + 32, posY + 25) + App.fontGame.draw(batch, if (worldInfo?.seed == null) "$EMDASH" else "${(if (worldInfo.seed > 0) "+" else "")}${worldInfo.seed}" , posX + 32, posY + 25) // text separator batch.color = bcol.cpy().sub(0f,0f,0f,0.65f) Toolkit.fillArea(batch, posX + 2, posY + 23, width - 4, 1)