diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index e3879492..dfb60953 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -40,6 +40,7 @@ fun main() { } val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world")) + //val db = BTreeDB(File("world.world")) /*if (true) { val a = System.currentTimeMillis() @@ -89,6 +90,7 @@ fun main() { //Starbound.addFilePath(File("./unpacked_assets/")) Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) + //Starbound.addPakPath(File("packed.pak")) Starbound.initializeGame { finished, replaceStatus, status -> client.putDebugLog(status, replaceStatus) @@ -163,14 +165,19 @@ fun main() { } println("$find $set $parse") + + //client.world!!.parallax = Starbound.parallaxAccess["garden"] } //ent.position += Vector2d(y = 14.0, x = -10.0) - ent.position = Vector2d(128.0 + 16.0, 672.0 + 48.0) + ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0) + client.camera.pos.x = 616f + client.camera.pos.y = 721f client.onDrawGUI { client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f) client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f) + client.gl.font.render("${client.camera.pos}", y = 140f, scale = 0.25f) } client.onPreDrawWorld { @@ -178,8 +185,6 @@ fun main() { //client.camera.pos.y = ent.pos.y.toFloat() } - client.camera.pos.y = 10f - client.gl.box2dRenderer.drawShapes = false client.gl.box2dRenderer.drawPairs = false client.gl.box2dRenderer.drawAABB = false @@ -187,19 +192,33 @@ fun main() { ent.spawn() + client.input.addScrollCallback { _, x, y -> + if (y > 0.0) { + client.settings.scale *= y.toFloat() * 2f + } else if (y < 0.0) { + client.settings.scale /= -y.toFloat() * 2f + } + } + while (client.renderFrame()) { Starbound.pollCallbacks() //ent.think(client.frameRenderTime) - client.camera.pos.x = ent.position.x.toFloat() - client.camera.pos.y = ent.position.y.toFloat() + //client.camera.pos.x = ent.position.x.toFloat() + //client.camera.pos.y = ent.position.y.toFloat() + + client.camera.pos.x += if (client.input.KEY_LEFT_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.x += if (client.input.KEY_RIGHT_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + + client.camera.pos.y += if (client.input.KEY_UP_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.y += if (client.input.KEY_DOWN_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f //println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1) //if (ent.onGround) //ent.velocity += client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1 - if (client.input.KEY_LEFT_DOWN) { + /*if (client.input.KEY_LEFT_DOWN) { ent.movement.moveDirection = Move.MOVE_LEFT } else if (client.input.KEY_RIGHT_DOWN) { ent.movement.moveDirection = Move.MOVE_RIGHT @@ -211,13 +230,13 @@ fun main() { ent.movement.requestJump() } else if (client.input.KEY_SPACE_RELEASED) { ent.movement.recallJump() - } + }*/ if (client.input.KEY_ESCAPE_PRESSED) { glfwSetWindowShouldClose(client.window, true) } - ent.wantsToDuck = client.input.KEY_DOWN_DOWN + //ent.wantsToDuck = client.input.KEY_DOWN_DOWN //if (chunkA != null && glfwGetTime() < 10.0) { // val tile = Starbound.getTileDefinition("alienrock") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt index e265f4da..9b69d509 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt @@ -52,7 +52,7 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World?): Double } +typealias ScrollCallback = (invalidate: () -> Unit, x: Double, y: Double) -> Unit + @Suppress("unused") class UserInput : IUserInput { fun keyPressed(code: Int) { @@ -129,15 +131,77 @@ class UserInput : IUserInput { mappedKey.updateState(action) } + private var lastScrollUpdate = 0 + + private val scrollCallbacks = ArrayList() + + fun addScrollCallback(callback: ScrollCallback): Boolean { + if (!scrollCallbacks.contains(callback)) { + scrollCallbacks.add(callback) + return true + } + + return false + } + + private var inScrollCallback = false + + fun removeScrollCallback(callback: ScrollCallback): Boolean { + if (inScrollCallback) { + throw ConcurrentModificationException("Can't remove scroll callback while iterating them, use invalidate function inside scroll callback itself!") + } + + return scrollCallbacks.remove(callback) + } + + var lastScrollX: Double = 0.0 + private set + + var lastScrollY: Double = 0.0 + private set + + fun callScroll(x: Double, y: Double) { + lastScrollX = x + lastScrollY = y + lastScrollUpdate = frame + + val iterator = scrollCallbacks.iterator() + var i = 0 + + while (iterator.hasNext()) { + i++ + val ithis = i + val value = iterator.next() + + value.invoke({ + if (i != ithis) { + throw IllegalStateException("What the hell did you just do") + } + + iterator.remove() + }, x, y) + } + } + fun installCallback(window: Long) { glfwSetKeyCallback(window) { _, key, scancode, action, mods -> callChange(key, action) } + + glfwSetScrollCallback(window) { _, x, y -> + callScroll(x, y) + } } private var lastTick = 0.0 + var frame = 0 + private set + fun think() { + val scrollCooldown = lastScrollUpdate != frame + frame++ + val thisTime = glfwGetTime() val delta = thisTime - lastTick lastTick = thisTime @@ -145,6 +209,11 @@ class UserInput : IUserInput { for (key in userKeys) { key.think(delta, thisTime) } + + if (scrollCooldown) { + lastScrollX = 0.0 + lastScrollY = 0.0 + } } private val keyMap = mapOf( diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/BTreeDB.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/BTreeDB.kt index 88b9208b..e06a4d5c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/BTreeDB.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/BTreeDB.kt @@ -1,12 +1,8 @@ package ru.dbotthepony.kstarbound.io -import com.google.gson.JsonElement import it.unimi.dsi.fastutil.ints.IntArraySet -import java.io.ByteArrayInputStream -import java.io.DataInputStream -import java.io.File -import java.io.InputStream -import java.io.RandomAccessFile +import java.io.* +import java.util.* private fun readHeader(reader: RandomAccessFile, required: Char) { val read = reader.read() @@ -63,7 +59,7 @@ class BTreeDB(val path: File) { readHeader(reader, '5') } - val blockSize = reader.readInt().toLong() + val blockSize = reader.readInt() val dbNameRaw = ByteArray(16).also { reader.read(it) } val indexKeySize = reader.readInt() val useNodeTwo = reader.readBoolean() @@ -123,7 +119,7 @@ class BTreeDB(val path: File) { // иначе, ищем следующий блок // пропускаем оставшиеся данные, переходим на границу текущего блока-лепестка - val delta = (blockSize - 4 - offset).toInt() + val delta = (blockSize - 4 - offset) reader.skipBytes(delta) // ищем следующий блок с нашими данными @@ -217,76 +213,30 @@ class BTreeDB(val path: File) { } // мы пришли в лепесток, теперь прямолинейно ищем в linked list - var offset = 6 - val keyCount = reader.readInt() + val leafStream = DataInputStream(BufferedInputStream(LeafInputStream(2))) + val keyCount = leafStream.readInt() for (keyIndex in 0 until keyCount) { // читаем ключ - reader.read(keyLoader) - offset += indexKeySize + leafStream.read(keyLoader) // читаем размер данных - var (dataLength, readBytes) = reader.readVarIntInfo() - offset += readBytes + val dataLength = leafStream.readVarInt() // это наш блок if (keyLoader.contentEquals(key)) { val binary = ByteArray(dataLength) - var binaryOffset = 0 - // читаем данные - while (true) { - // если конец данных внутри текущего блока, останавливаемся - if (offset + dataLength <= blockSize - 4) { - reader.readFully(binary, binaryOffset, dataLength) - offset += dataLength - binaryOffset += dataLength - break - } - - // иначе, ищем следующий блок - - // пропускаем оставшиеся данные, переходим на границу текущего блока-лепестка - val delta = (blockSize - 4 - offset).toInt() - reader.readFully(binary, binaryOffset, delta) - binaryOffset += delta - - // ищем следующий блок с нашими данными - val nextBlockIndex = reader.readInt() - seekBlock(nextBlockIndex.toLong()) - - // удостоверяемся что мы попали в лепесток - check(readBlockType() == TreeBlockType.LEAF) { "Did not hit leaf block" } - offset = 2 - dataLength -= delta + if (dataLength == 0) { + // нет данных (?) + return binary } + leafStream.readFully(binary) + return binary } else { - // это не наш блок, пропускаем его - while (true) { - // если конец данных внутри текущего блока, останавливаемся - if (offset + dataLength <= blockSize - 4) { - reader.skipBytes(dataLength) - offset += dataLength - break - } - - // иначе, ищем следующий блок - - // пропускаем оставшиеся данные, переходим на границу текущего блока-лепестка - val delta = (blockSize - 4 - offset).toInt() - reader.skipBytes(delta) - - // ищем следующий блок с нашими данными - val nextBlockIndex = reader.readInt() - seekBlock(nextBlockIndex.toLong()) - - // удостоверяемся что мы попали в лепесток - check(readBlockType() == TreeBlockType.LEAF) { "Did not hit leaf block" } - offset = 2 - dataLength -= delta - } + leafStream.skipBytes(dataLength) } } @@ -295,6 +245,70 @@ class BTreeDB(val path: File) { fun seekBlock(id: Long) { require(id >= 0) { "Negative id $id" } + require(id * blockSize + blocksOffsetStart < reader.length()) { "Tried to seek block with $id, but it is outside of file's bounds (file size ${reader.length()} bytes, seeking ${id * blockSize + blocksOffsetStart})! (does not exist)" } reader.seek(id * blockSize + blocksOffsetStart) } + + private inner class LeafInputStream(private var offset: Int) : InputStream() { + private var canRead = true + + override fun read(): Int { + if (offset + 4 >= blockSize) { + if (!seekNextBlock()) { + return -1 + } + } + + offset++ + return reader.read() + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + Objects.checkFromIndexSize(off, len, b.size) + var totalRead = 0 + var index = off + + while (canRead && totalRead < len) { + if (offset + 4 >= blockSize) { + if (!seekNextBlock()) { + return totalRead + } + } + + val readAtMost = (blockSize - 4 - offset).coerceAtMost(len - totalRead) + val readBytes = reader.read(b, index, readAtMost) + + if (readBytes <= 0) { + canRead = false + break + } + + totalRead += readBytes + index += readBytes + offset += readBytes + } + + return totalRead + } + + private fun seekNextBlock(): Boolean { + if (!canRead) { + return false + } + + val nextBlockIndex = reader.readInt() + + if (nextBlockIndex < 0L) { + canRead = false + return false + } + + seekBlock(nextBlockIndex.toLong()) + + check(readBlockType() == TreeBlockType.LEAF) { "Did not hit leaf block" } + offset = 2 + + return true + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/Ext.kt index dba92a09..b0000b88 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/Ext.kt @@ -70,6 +70,28 @@ fun RandomAccessFile.readVarIntInfo(): VarIntReadResult { return VarIntReadResult(result, i) } +/** + * Читает Variable Length Integer как Int + */ +fun InputStream.readVarIntInfo(): VarIntReadResult { + var result = 0 + var read = read() + var i = 1 + + while (true) { + result = (result shl 7) or (read and 0x7F) + + if (read and 0x80 == 0) { + break + } + + read = read() + i++ + } + + return VarIntReadResult(result, i) +} + /** * Читает Variable Length Integer как Long */ diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt index d56d8840..6a12c565 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt @@ -163,7 +163,10 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) // сразу за метаданными идёт количество файлов внутри данного pak в формате Big Endian variable int val indexNodeCount = reader.readVarLong() - private val indexNodes = HashMap() + private val _indexNodes = HashMap() + val indexNodes: Map = Collections.unmodifiableMap(_indexNodes) + private val _indexNodesLowercase = HashMap() + val indexNodesLowercase: Map = Collections.unmodifiableMap(_indexNodesLowercase) val root = StarboundPakDirectory("/") init { @@ -187,7 +190,10 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) throw IndexOutOfBoundsException("Garbage length at index $i: ${read.length}") } - indexNodes[read.name] = read + _indexNodes[read.name] = read + // Starbound игнорирует регистр букв когда ищет пути, даже внутри pak архивов + _indexNodesLowercase[read.name.lowercase()] = read + root.resolve(read.directoryHiearchy).writeFile(read) val last = read.name.substringAfterLast('/').substringAfterLast('.', "") @@ -213,11 +219,11 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) } override fun pathExists(path: String): Boolean { - return indexNodes.containsKey(path) + return _indexNodesLowercase.containsKey(path) } override fun readOrNull(path: String): ByteBuffer? { - val node = indexNodes[path] ?: return null + val node = _indexNodesLowercase[path] ?: return null return node.read() }