package net.torvald.terrarum.serialise import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.GL30 import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.utils.ScreenUtils import net.torvald.gdx.graphics.PixmapIO2 import net.torvald.terrarum.* import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension import net.torvald.terrarum.modulebasegame.weather.WeatherMixer import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream import org.apache.commons.codec.digest.DigestUtils import java.io.ByteArrayOutputStream import java.util.zip.Deflater import java.util.zip.DeflaterOutputStream object WriteWorldInfo { val META_MAGIC = "TESV".toByteArray(Charsets.UTF_8) val NULL = 0.toByte() val VERSION = 1 val HASHED_FILES_COUNT = 3 /** * TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file. * * @return List of ByteArray64, worldinfo0..worldinfo3; `null` on failure */ internal operator fun invoke(ingame: IngameInstance): List? { //val path = "${AppLoader.defaultSaveDir}/tmp_worldinfo" val infileList = arrayOf( ModMgr.getGdxFilesFromEveryMod("blocks/blocks.csv"), ModMgr.getGdxFilesFromEveryMod("items/items.csv"), ModMgr.getGdxFilesFromEveryMod("materials/materials.csv") ) val outFiles = ArrayList() // for worldinfo1-3 only val worldInfoHash = ArrayList() // hash of worldinfo1-3 // try to write worldinfo1-3 for (filenum in 1..HASHED_FILES_COUNT) { val outputStream = ByteArray64GrowableOutputStream() val infile = infileList[filenum - 1] infile.forEach { outputStream.write("## from file: ${it.nameWithoutExtension()} ##############################\n".toByteArray()) val readBytes = it.readBytes() outputStream.write(readBytes) outputStream.write("\n".toByteArray()) } outputStream.flush() outputStream.close() outFiles.add(outputStream.toByteArray64()) worldInfoHash.add(DigestUtils.sha256(ByteArray64InputStream(outputStream.toByteArray64()))) } // compose save meta (actual writing part) val metaOut = ByteArray64GrowableOutputStream() metaOut.write(META_MAGIC) metaOut.write(VERSION) metaOut.write(HASHED_FILES_COUNT) // world name val world = ingame.world val worldNameBytes = world.worldName.toByteArray(Charsets.UTF_8) //metaOut.write(worldNameBytes) worldNameBytes.forEach { if (it != 0.toByte()) metaOut.write(it.toInt()) } metaOut.write(NULL.toInt()) // terrain seed metaOut.write(world.generatorSeed.toLittle()) // randomiser seed metaOut.write(RoguelikeRandomiser.RNG.state0.toLittle()) metaOut.write(RoguelikeRandomiser.RNG.state1.toLittle()) // weather seed metaOut.write(WeatherMixer.RNG.state0.toLittle()) metaOut.write(WeatherMixer.RNG.state1.toLittle()) // reference ID of the player metaOut.write(Terrarum.PLAYER_REF_ID.toLittle()) // ingame time_t metaOut.write((world as GameWorldExtension).worldTime.TIME_T.toLittle()) // creation time (real world time) metaOut.write(world.creationTime.toULittle48()) // time at save (real world time) val timeNow = System.currentTimeMillis() / 1000L metaOut.write(timeNow.toULittle48()) // get playtime and save it val timeToAdd = (timeNow - world.loadTime).toInt() metaOut.write((world.totalPlayTime + timeToAdd).toLittle()) world.lastPlayTime = timeNow world.totalPlayTime += timeToAdd // SHA256SUM of worldinfo1-3 worldInfoHash.forEach { metaOut.write(it) } // thumbnail val texreg = ingame.actorGamer.sprite?.textureRegion if (texreg != null) { val batch = SpriteBatch() val camera = OrthographicCamera(texreg.tileW.toFloat(), texreg.tileH.toFloat()) val fbo = FrameBuffer(Pixmap.Format.RGBA8888, texreg.tileW, texreg.tileH, false) fbo.inAction(camera, batch) { batch.inUse { batch.draw(texreg.get(0, 0), 0f, 0f) } } // bind and unbind the fbo so that I can get the damned Pixmap using ScreenUtils // NullPointerException if not appconfig.useGL30 Gdx.gl30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, fbo.framebufferHandle) Gdx.gl30.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0) val outpixmap = ScreenUtils.getFrameBufferPixmap(0, 0, fbo.width, fbo.height) Gdx.gl30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0) Gdx.gl30.glReadBuffer(GL30.GL_BACK) val tgaSize = PixmapIO2.HEADER_FOOTER_SIZE + outpixmap.width * outpixmap.height * 4 val byteArrayOS = ByteArrayOutputStream(tgaSize) PixmapIO2._writeTGA(byteArrayOS, outpixmap, true, true) byteArrayOS.flush() byteArrayOS.close() //PixmapIO2.writeTGA(Gdx.files.absolute(AppLoader.defaultDir+"/tmp_writeworldinfo+outpixmap.tga"), outpixmap, true) outpixmap.dispose() batch.dispose() fbo.dispose() // write uncompressed size metaOut.write(tgaSize.toULittleShort()) // write compressed tga val deflater = DeflaterOutputStream(metaOut, Deflater(Deflater.BEST_COMPRESSION, true), false) deflater.write(byteArrayOS.toByteArray()) deflater.flush(); deflater.finish() // write footer metaOut.write(-1); metaOut.write(-2) } // more data goes here // metaOut.flush() metaOut.close() return listOf(metaOut.toByteArray64()) + outFiles.toList() } }