new import screen

This commit is contained in:
minjaesong
2023-08-25 15:49:35 +09:00
parent eeee1ebdbc
commit 75fcb5be5b
8 changed files with 184 additions and 30 deletions

View File

@@ -1,5 +1,6 @@
{ {
"CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING": "This is a world currently playing.", "CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING": "This is a world currently playing.",
"CONTEXT_IMPORT_AVATAR_INSTRUCTION_1": "Copy the Avatar Code into the clipboard, then hit the Paste button below.", "CONTEXT_IMPORT_AVATAR_INSTRUCTION_1": "1. Place the Avatar file into the following directory:",
"CONTEXT_IMPORT_AVATAR_INSTRUCTION_2": "" "CONTEXT_IMPORT_AVATAR_INSTRUCTION_2": "",
"CONTEXT_IMPORT_AVATAR_INSTRUCTION_3": "2. Enter the name of the file below, then press Import"
} }

View File

@@ -1,5 +1,5 @@
{ {
"CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING": "현재 플레이 중인 월드입니다.", "CONTEXT_THIS_IS_A_WORLD_CURRENTLY_PLAYING": "현재 플레이 중인 월드입니다.",
"CONTEXT_IMPORT_AVATAR_INSTRUCTION_1": "아바타 코드를 클립보드에 복사한 다음, 아래의 붙여넣기 버튼을 눌러주세요.", "CONTEXT_IMPORT_AVATAR_INSTRUCTION_1": "1. 아바타 파일을 다음 폴더에 넣어주세요",
"CONTEXT_IMPORT_AVATAR_INSTRUCTION_2": "" "CONTEXT_IMPORT_AVATAR_INSTRUCTION_3": "2. 아바타 파일 이름을 아래에 입력하고 가져오기를 눌러주세요"
} }

View File

@@ -1185,6 +1185,8 @@ public class App implements ApplicationListener {
public static String configDir; public static String configDir;
/** defaultDir + "/LoadOrder.txt" */ /** defaultDir + "/LoadOrder.txt" */
public static String loadOrderDir; public static String loadOrderDir;
/** defaultDir + "/Imported" */
public static String importDir;
public static RunningEnvironment environment; public static RunningEnvironment environment;
@@ -1224,6 +1226,7 @@ public class App implements ApplicationListener {
loadOrderDir = defaultDir + "/LoadOrder.txt"; loadOrderDir = defaultDir + "/LoadOrder.txt";
recycledPlayersDir = defaultDir + "/Recycled/Players"; recycledPlayersDir = defaultDir + "/Recycled/Players";
recycledWorldsDir = defaultDir + "/Recycled/Worlds"; recycledWorldsDir = defaultDir + "/Recycled/Worlds";
importDir = defaultDir + "/Imports";
System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem)); System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem));
System.out.println(String.format("os.version = %s", OSVersion)); System.out.println(String.format("os.version = %s", OSVersion));
@@ -1239,6 +1242,7 @@ public class App implements ApplicationListener {
new File(worldsDir), new File(worldsDir),
new File(recycledPlayersDir), new File(recycledPlayersDir),
new File(recycledWorldsDir), new File(recycledWorldsDir),
new File(importDir)
}; };
for (File it : dirs) { for (File it : dirs) {

View File

@@ -8,6 +8,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.graphics.glutils.FrameBuffer
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.utils.OpenFile
import java.awt.Desktop import java.awt.Desktop
import java.io.File import java.io.File
@@ -90,7 +91,7 @@ class NoModuleDefaultTitlescreen(batch: FlippingSpriteBatch) : IngameInstance(ba
App.scr.hf - Gdx.input.y in pathButtonY - 12..pathButtonY + pathButtonH + 12) App.scr.hf - Gdx.input.y in pathButtonY - 12..pathButtonY + pathButtonH + 12)
if (mouseOnLink && Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) { if (mouseOnLink && Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) {
Desktop.getDesktop().open(pathFile) OpenFile(pathFile)
} }
fbatch.inUse { fbatch.inUse {

View File

@@ -6,15 +6,22 @@ import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.AppUpdateListOfSavegames
import net.torvald.terrarum.Second import net.torvald.terrarum.Second
import net.torvald.terrarum.ceilToInt import net.torvald.terrarum.ceilToInt
import net.torvald.terrarum.gamecontroller.* import net.torvald.terrarum.gamecontroller.*
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.serialise.Ascii85Codec import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.savegame.VDFileID.ROOT
import net.torvald.terrarum.ui.UIItem import net.torvald.terrarum.savegame.VDFileID.SAVEGAMEINFO
import net.torvald.terrarum.ui.UIItemTextButton import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.ui.*
import net.torvald.terrarum.utils.Clipboard import net.torvald.terrarum.utils.Clipboard
import net.torvald.terrarum.utils.JsonFetcher
import net.torvald.terrarum.utils.OpenFile
import java.awt.Desktop
import java.io.File
/** /**
* Created by minjaesong on 2023-08-24. * Created by minjaesong on 2023-08-24.
@@ -31,14 +38,23 @@ class UIImportAvatar(val remoCon: UIRemoCon) : Advanceable() {
private val rows = 30 private val rows = 30
private val goButtonWidth = 180 private val goButtonWidth = 180
private val descStartY = 24 * 4
private val lh = App.fontGame.lineHeight.toInt()
private val codeBox = UIItemCodeBox(this, (Toolkit.drawWidth - App.fontSmallNumbers.W * cols) / 2, drawY, cols, rows) // private val codeBox = UIItemCodeBox(this, (Toolkit.drawWidth - App.fontSmallNumbers.W * cols) / 2, drawY, cols, rows)
private val inputWidth = 340
private val filenameInput = UIItemTextLineInput(this,
(Toolkit.drawWidth - inputWidth) / 2, (App.scr.height - height) / 2 + descStartY + (5) * lh, inputWidth,
maxLen = InputLenCap(256, InputLenCap.CharLenUnit.UTF8_BYTES)
)
/*
private val clearButton = UIItemTextButton(this, private val clearButton = UIItemTextButton(this,
{ Lang["MENU_IO_CLEAR"] }, drawX + (width/2 - goButtonWidth) / 2, drawY + height - 24 - 34, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true) { Lang["MENU_IO_CLEAR"] }, drawX + (width/2 - goButtonWidth) / 2, drawY + height - 24 - 34, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
private val pasteButton = UIItemTextButton(this, private val pasteButton = UIItemTextButton(this,
{ Lang["MENU_LABEL_PASTE"] }, drawX + width/2 + (width/2 - goButtonWidth) / 2, drawY + height - 24 - 34, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true) { Lang["MENU_LABEL_PASTE"] }, drawX + width/2 + (width/2 - goButtonWidth) / 2, drawY + height - 24 - 34, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
*/
private val backButton = UIItemTextButton(this, private val backButton = UIItemTextButton(this,
{ Lang["MENU_LABEL_BACK"] }, drawX + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true) { Lang["MENU_LABEL_BACK"] }, drawX + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
@@ -46,32 +62,63 @@ class UIImportAvatar(val remoCon: UIRemoCon) : Advanceable() {
{ Lang["MENU_IO_IMPORT"] }, drawX + width/2 + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true) { Lang["MENU_IO_IMPORT"] }, drawX + width/2 + (width/2 - goButtonWidth) / 2, drawY + height - 24, goButtonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true)
init { init {
addUIitem(codeBox) // addUIitem(codeBox)
addUIitem(clearButton) // addUIitem(clearButton)
addUIitem(pasteButton) // addUIitem(pasteButton)
addUIitem(filenameInput)
addUIitem(backButton) addUIitem(backButton)
addUIitem(goButton) addUIitem(goButton)
clearButton.clickOnceListener = { _,_ -> /*clearButton.clickOnceListener = { _,_ ->
codeBox.clearTextBuffer() codeBox.clearTextBuffer()
} }
pasteButton.clickOnceListener = { _,_ -> pasteButton.clickOnceListener = { _,_ ->
codeBox.pasteFromClipboard() codeBox.pasteFromClipboard()
} }*/
backButton.clickOnceListener = { _,_ -> backButton.clickOnceListener = { _,_ ->
remoCon.openUI(UILoadSavegame(remoCon)) remoCon.openUI(UILoadSavegame(remoCon))
} }
goButton.clickOnceListener = { _,_ -> goButton.clickOnceListener = { _,_ ->
doImport() val returnCode = doImport()
if (returnCode == 0) remoCon.openUI(UILoadSavegame(remoCon))
} }
} }
// private var textX = 0
private var textY = 0
private var mouseOnLink = false
private var pathW = 0
override fun updateUI(delta: Float) { override fun updateUI(delta: Float) {
uiItems.forEach { it.update(delta) } uiItems.forEach { it.update(delta) }
pathW = App.fontGame.getWidth(App.importDir)
val textX = (Toolkit.drawWidth - pathW) / 2
textY = (App.scr.height - height) / 2 + descStartY + (1) * lh
mouseOnLink = (Gdx.input.x in textX - 48..textX + 48 + pathW &&
Gdx.input.y in textY - 12..textY + lh + 12)
if (mouseOnLink && Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) {
OpenFile(File(App.importDir))
}
} }
private val textboxIndices = (1..3)
override fun renderUI(batch: SpriteBatch, camera: Camera) { override fun renderUI(batch: SpriteBatch, camera: Camera) {
batch.color = Color.WHITE
val textboxWidth = textboxIndices.maxOf { App.fontGame.getWidth(Lang["CONTEXT_IMPORT_AVATAR_INSTRUCTION_$it"]) }
val textX = (Toolkit.drawWidth - textboxWidth) / 2
// draw texts
for (i in textboxIndices) {
App.fontGame.draw(batch, Lang["CONTEXT_IMPORT_AVATAR_INSTRUCTION_$i"], textX, (App.scr.height - height) / 2 + descStartY + (i - 1) * lh)
}
// draw path
batch.color = if (mouseOnLink) Toolkit.Theme.COL_SELECTED else Toolkit.Theme.COL_MOUSE_UP
App.fontGame.draw(batch, App.importDir, (Toolkit.drawWidth - pathW) / 2, textY)
uiItems.forEach { it.render(batch, camera) } uiItems.forEach { it.render(batch, camera) }
} }
@@ -81,13 +128,68 @@ class UIImportAvatar(val remoCon: UIRemoCon) : Advanceable() {
override fun advanceMode(button: UIItem) { override fun advanceMode(button: UIItem) {
} }
private fun doImport() { private fun doImport(): Int {
val rawStr = codeBox.textBuffer.toString() val file = File("${App.importDir}/${filenameInput.getText()}")
// sanity check
// check file's existence
if (!file.exists()) {
return 1
}
// try to mount the TEVd
try {
val dom = VDUtil.readDiskArchive(file)
val timeNow = App.getTIME_T()
// get the uuid
val oldPlayerInfoFile = dom.getEntry(SAVEGAMEINFO)!!
val playerInfo = JsonFetcher.readFromJsonString(ByteArray64Reader(VDUtil.getAsNormalFile(dom, SAVEGAMEINFO).bytes, Common.CHARSET))
val uuid = playerInfo.getString("uuid")
val newFile = File("${App.playersDir}/$uuid")
printdbg(this, "Avatar uuid: $uuid")
if (newFile.exists()) return 2
// update playerinfo so that:
// totalPlayTime to zero
// lastPlayedTime to now
// playerinfofile's lastModifiedTime to now
// root's lastModifiedTime to now
printdbg(this, "avatar old lastPlayTime: ${playerInfo.getLong("lastPlayTime")}")
printdbg(this, "avatar old totalPlayTime: ${playerInfo.getLong("totalPlayTime")}")
playerInfo.get("lastPlayTime").set(timeNow, null)
playerInfo.get("totalPlayTime").set(0, null)
printdbg(this, "avatar new lastPlayTime: ${playerInfo.getLong("lastPlayTime")}")
printdbg(this, "avatar new totalPlayTime: ${playerInfo.getLong("totalPlayTime")}")
val newJsonBytes = ByteArray64Writer(Common.CHARSET).let {
// println(playerInfo.toString())
it.write(playerInfo.toString())
it.close()
it.toByteArray64()
}
val newPlayerInfo = DiskEntry(SAVEGAMEINFO, ROOT, oldPlayerInfoFile.creationDate, timeNow, EntryFile(newJsonBytes))
VDUtil.addFile(dom, newPlayerInfo)
dom.getEntry(ROOT)!!.modificationDate = timeNow
// mark the file as Imported
dom.saveOrigin = VDSaveOrigin.IMPORTED
// write modified file to the Players dir
VDUtil.dumpToRealMachine(dom, newFile)
val ascii85codec = Ascii85Codec((33..117).map { it.toChar() }.joinToString("")) AppUpdateListOfSavegames()
val ascii85str = rawStr.substring(2 until rawStr.length - 2).replace("z", "!!!!!") }
catch (e: Throwable) {
// format error
e.printStackTrace()
return -1
}
return 0
} }
} }

View File

@@ -447,6 +447,13 @@ removefile:
fa.close() fa.close()
} }
fun setSaveOrigin(bits: Int) {
val fa = RandomAccessFile(diskFile, "rwd")
fa.seek(51L)
fa.writeByte(bits)
fa.close()
}
/** /**
* @return Save type (0b 0000 00ab) * @return Save type (0b 0000 00ab)
* b: unset - full save; set - quicksave (only applicable to worlds -- quicksave just means the disk is in dirty state) * b: unset - full save; set - quicksave (only applicable to worlds -- quicksave just means the disk is in dirty state)
@@ -467,6 +474,15 @@ removefile:
return fa.read().also { fa.close() } return fa.read().also { fa.close() }
} }
/**
* @return 16 if the savegame was imported, 0 if the savegame was generated in-game
*/
fun getSaveOrigin(): Int {
val fa = RandomAccessFile(diskFile, "rwd")
fa.seek(51L)
return fa.read().also { fa.close() }
}
override fun getDiskName(charset: Charset): String { override fun getDiskName(charset: Charset): String {

View File

@@ -76,8 +76,11 @@ Version 254 is a customised version of TEVD tailored to be used as a savegame fo
0: Undefined (or very old version of the game) 0: Undefined (or very old version of the game)
1: Player Data 1: Player Data
2: World Data 2: World Data
Int8[13] Extra info bytes reserved for future usage Int8 Savefile Origin Flags
/* END extraInfoBytes */ 0: Created in-game
16: Imported
Int8[12] Extra info bytes reserved for future usage
-- END extraInfoBytes --
UInt8[236] Rest of the long disk name (268 bytes total) UInt8[236] Rest of the long disk name (268 bytes total)
(Header size: 300 bytes) (Header size: 300 bytes)
@@ -150,6 +153,9 @@ class VirtualDisk(
var saveKind: Int var saveKind: Int
set(value) { extraInfoBytes[2] = value.toByte() } set(value) { extraInfoBytes[2] = value.toByte() }
get() = extraInfoBytes[2].toUint() get() = extraInfoBytes[2].toUint()
var saveOrigin: Int
set(value) { extraInfoBytes[3] = value.toByte() }
get() = extraInfoBytes[3].toUint()
override fun getDiskName(charset: Charset) = diskName.toCanonicalString(charset) override fun getDiskName(charset: Charset) = diskName.toCanonicalString(charset)
val root: DiskEntry val root: DiskEntry
get() = entries[0]!! get() = entries[0]!!
@@ -249,6 +255,11 @@ object VDSaveKind {
const val WORLD_DATA = 2 const val WORLD_DATA = 2
} }
object VDSaveOrigin {
const val INGAME = 0
const val IMPORTED = 16
}
object VDFileID { object VDFileID {
const val ROOT = 0L const val ROOT = 0L
const val SAVEGAMEINFO = -1L const val SAVEGAMEINFO = -1L

View File

@@ -1,24 +1,43 @@
package net.torvald.terrarum.utils package net.torvald.terrarum.utils
import net.torvald.terrarum.App
import java.awt.Desktop
import java.awt.Toolkit import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection import java.awt.datatransfer.StringSelection
import java.awt.datatransfer.UnsupportedFlavorException import java.awt.datatransfer.UnsupportedFlavorException
import java.io.File
/** /**
* Created by minjaesong on 2016-07-31. * Created by minjaesong on 2016-07-31.
*/ */
object Clipboard { object Clipboard {
fun fetch(): String = try { private val IS_MACOS = App.operationSystem == "OSX"
Toolkit.getDefaultToolkit().systemClipboard.getData(DataFlavor.stringFlavor) as String
} fun fetch(): String =
catch (e: UnsupportedFlavorException) { if (IS_MACOS) "Clipboard is disabled on macOS" else
"" try {
} Toolkit.getDefaultToolkit().systemClipboard.getData(DataFlavor.stringFlavor) as String
}
catch (e: UnsupportedFlavorException) {
""
}
fun copy(s: String) { fun copy(s: String) {
if (IS_MACOS) return
val selection = StringSelection(s) val selection = StringSelection(s)
val clipboard = Toolkit.getDefaultToolkit().systemClipboard val clipboard = Toolkit.getDefaultToolkit().systemClipboard
clipboard.setContents(selection, selection) clipboard.setContents(selection, selection)
} }
}
/**
* Created by minjaesong on 2023-08-25.
*/
object OpenFile {
private val IS_MACOS = App.operationSystem == "OSX"
operator fun invoke(file: File) {
if (IS_MACOS) return // at this point macOS might as well be a bane of existence for "some" devs Apple fanboys think they are not worthy of existence
Desktop.getDesktop().open(file)
}
} }