KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt

254 lines
7.2 KiB
Kotlin

package ru.dbotthepony.kstarbound
import com.google.gson.*
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.api.IVFS
import ru.dbotthepony.kstarbound.api.PhysicalFS
import ru.dbotthepony.kstarbound.api.getPathFolder
import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.projectile.ConfigurableProjectile
import ru.dbotthepony.kstarbound.defs.projectile.ConfiguredProjectile
import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics
import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.util.CustomEnumTypeAdapter
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.util2d.AABBi
import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.io.*
import java.nio.ByteBuffer
import java.text.DateFormat
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
const val METRES_IN_STARBOUND_UNIT = 0.25
const val METRES_IN_STARBOUND_UNITf = 0.25f
const val PIXELS_IN_STARBOUND_UNIT = 8.0
const val PIXELS_IN_STARBOUND_UNITf = 8.0f
class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
class ProjectileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
object Starbound : IVFS {
private val LOGGER = LogManager.getLogger()
private val tiles = HashMap<String, TileDefinition>()
private val projectiles = HashMap<String, ConfiguredProjectile>()
val tilesAccess = Collections.unmodifiableMap(tiles)
val projectilesAccess = Collections.unmodifiableMap(projectiles)
val gson = GsonBuilder()
.enableComplexMapKeySerialization()
.serializeNulls()
.setDateFormat(DateFormat.LONG)
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
.setPrettyPrinting()
.registerTypeAdapter(Color::class.java, ColorTypeAdapter.nullSafe())
.registerTypeAdapter(ProjectilePhysics::class.java, CustomEnumTypeAdapter(ProjectilePhysics.values()).nullSafe())
.registerTypeAdapter(AABB::class.java, AABBTypeAdapter)
.registerTypeAdapter(AABBi::class.java, AABBiTypeAdapter)
.registerTypeAdapter(Vector2d::class.java, Vector2dTypeAdapter)
.registerTypeAdapter(Vector2f::class.java, Vector2fTypeAdapter)
.registerTypeAdapter(Vector2i::class.java, Vector2iTypeAdapter)
.registerTypeAdapter(Poly::class.java, PolyTypeAdapter)
.registerTypeAdapter(ConfigurableProjectile::class.java, ConfigurableProjectile.ADAPTER)
.create()
var initializing = false
private set
var initialized = false
private set
var terminateLoading = false
private val archivePaths = ArrayList<File>()
private val fileSystems = ArrayList<IVFS>()
fun addFilePath(path: File) {
fileSystems.add(PhysicalFS(path))
}
/**
* Добавляет уже прочитанный pak
*/
fun addPak(pak: StarboundPak) {
fileSystems.add(pak)
}
/**
* Добавляет pak к чтению при initializeGame
*/
fun addPakPath(pak: File) {
archivePaths.add(pak)
}
fun loadJson(path: String): JsonElement {
return JsonParser.parseReader(getReader(path))
}
fun getTileDefinition(name: String) = tiles[name]
private val initCallbacks = ArrayList<() -> Unit>()
fun initializeGame(callback: (finished: Boolean, replaceStatus: Boolean, status: String) -> Unit) {
if (initializing) {
throw IllegalStateException("Already initializing!")
}
if (initialized) {
throw IllegalStateException("Already initialized!")
}
initializing = true
Thread({
val time = System.currentTimeMillis()
if (archivePaths.isNotEmpty()) {
callback(false, false, "Reading pak archives...")
for (path in archivePaths) {
callback(false, false, "Reading ${path.name}...")
addPak(StarboundPak(path) { _, status ->
callback(false, true, "${path.name}: $status")
})
}
}
run {
val localTime = System.currentTimeMillis()
callback(false, false, "Loading materials...")
loadTileMaterials {
if (terminateLoading) {
throw InterruptedException("Game is terminating")
}
callback(false, true, it)
}
callback(false, true, "Loaded materials in ${System.currentTimeMillis() - localTime}ms")
}
run {
val localTime = System.currentTimeMillis()
callback(false, false, "Loading projectiles...")
loadProjectiles {
if (terminateLoading) {
throw InterruptedException("Game is terminating")
}
callback(false, true, it)
}
callback(false, true, "Loaded Projectiles in ${System.currentTimeMillis() - localTime}ms")
}
initializing = false
initialized = true
callback(true, false, "Finished loading in ${System.currentTimeMillis() - time}ms")
}, "Asset Loader").start()
}
override fun pathExists(path: String): Boolean {
for (fs in fileSystems) {
if (fs.pathExists(path)) {
return true
}
}
return false
}
override fun readOrNull(path: String): ByteBuffer? {
for (fs in fileSystems) {
if (fs.pathExists(path)) {
return fs.read(path)
}
}
return null
}
override fun listFiles(path: String): List<String> {
val listing = mutableListOf<String>()
for (fs in fileSystems) {
listing.addAll(fs.listFiles(path))
}
return listing
}
override fun listDirectories(path: String): Collection<String> {
val listing = mutableListOf<String>()
for (fs in fileSystems) {
listing.addAll(fs.listDirectories(path))
}
return listing
}
fun onInitialize(callback: () -> Unit) {
if (initialized) {
callback()
} else {
initCallbacks.add(callback)
}
}
fun pollCallbacks() {
if (initialized && initCallbacks.isNotEmpty()) {
for (callback in initCallbacks) {
callback()
}
initCallbacks.clear()
}
}
private fun loadTileMaterials(callback: (String) -> Unit) {
for (fs in fileSystems) {
for (listedFile in fs.listAllFiles("tiles/materials")) {
if (listedFile.endsWith(".material")) {
try {
callback("Loading $listedFile")
val tileDef = TileDefinitionBuilder.fromJson(JsonParser.parseReader(getReader(listedFile)) as JsonObject).build("/tiles/materials")
check(tiles[tileDef.materialName] == null) { "Already has material with ID ${tileDef.materialName} loaded!" }
tiles[tileDef.materialName] = tileDef
} catch (err: Throwable) {
throw TileDefLoadingException("Loading tile file $listedFile", err)
}
}
}
}
}
private fun loadProjectiles(callback: (String) -> Unit) {
for (fs in fileSystems) {
for (listedFile in fs.listAllFiles("projectiles")) {
if (listedFile.endsWith(".projectile")) {
try {
callback("Loading $listedFile")
val def = gson.fromJson(getReader(listedFile), ConfigurableProjectile::class.java).configure(getPathFolder(listedFile))
check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" }
projectiles[def.projectileName] = def
} catch(err: Throwable) {
//throw ProjectileDefLoadingException("Loading projectile file $listedFile", err)
LOGGER.error("Loading projectile file $listedFile", err)
}
}
}
}
}
}