297 lines
8.6 KiB
Kotlin
297 lines
8.6 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.getPathFilename
|
|
import ru.dbotthepony.kstarbound.api.getPathFolder
|
|
import ru.dbotthepony.kstarbound.defs.*
|
|
import ru.dbotthepony.kstarbound.defs.projectile.*
|
|
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
|
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
|
|
import ru.dbotthepony.kstarbound.io.*
|
|
import ru.dbotthepony.kstarbound.math.*
|
|
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.5
|
|
const val METRES_IN_STARBOUND_UNITf = 0.5f
|
|
|
|
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>()
|
|
private val parallax = HashMap<String, ParallaxPrototype>()
|
|
private val functions = HashMap<String, JsonFunction>()
|
|
|
|
val tilesAccess: Map<String, TileDefinition> = Collections.unmodifiableMap(tiles)
|
|
val projectilesAccess: Map<String, ConfiguredProjectile> = Collections.unmodifiableMap(projectiles)
|
|
val parallaxAccess: Map<String, ParallaxPrototype> = Collections.unmodifiableMap(parallax)
|
|
val functionsAccess: Map<String, JsonFunction> = Collections.unmodifiableMap(functions)
|
|
|
|
val gson: Gson = GsonBuilder()
|
|
.enableComplexMapKeySerialization()
|
|
.serializeNulls()
|
|
.setDateFormat(DateFormat.LONG)
|
|
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
|
.setPrettyPrinting()
|
|
.registerTypeAdapter(Color::class.java, ColorTypeAdapter.nullSafe())
|
|
|
|
// math
|
|
.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)
|
|
|
|
.also(ConfigurableProjectile::registerGson)
|
|
.also(SkyParameters::registerGson)
|
|
.also(DungeonWorldDef::registerGson)
|
|
.also(ParallaxPrototype::registerGson)
|
|
.also(JsonFunction::registerGson)
|
|
|
|
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
|
|
|
|
.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>()
|
|
|
|
private fun loadStage(
|
|
callback: (Boolean, Boolean, String) -> Unit,
|
|
loader: ((String) -> Unit) -> Unit,
|
|
name: String,
|
|
) {
|
|
val time = System.currentTimeMillis()
|
|
callback(false, false, "Loading $name...")
|
|
|
|
loader {
|
|
if (terminateLoading) {
|
|
throw InterruptedException("Game is terminating")
|
|
}
|
|
|
|
callback(false, true, it)
|
|
}
|
|
|
|
callback(false, true, "Loaded $name in ${System.currentTimeMillis() - time}ms")
|
|
}
|
|
|
|
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")
|
|
})
|
|
}
|
|
}
|
|
|
|
loadStage(callback, this::loadFunctions, "functions")
|
|
loadStage(callback, this::loadTileMaterials, "materials")
|
|
loadStage(callback, this::loadProjectiles, "projectiles")
|
|
loadStage(callback, this::loadParallax, "parallax definitions")
|
|
|
|
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)
|
|
LOGGER.error("Loading tile file $listedFile", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun loadProjectiles(callback: (String) -> Unit) {
|
|
for (fs in fileSystems) {
|
|
for (listedFile in fs.listAllFilesWithExtension("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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun loadFunctions(callback: (String) -> Unit) {
|
|
for (fs in fileSystems) {
|
|
for (listedFile in fs.listAllFilesWithExtension("functions")) {
|
|
try {
|
|
callback("Loading $listedFile")
|
|
|
|
val readObject = loadJson(listedFile) as JsonObject
|
|
|
|
for (key in readObject.keySet()) {
|
|
val def = gson.fromJson(readObject[key], JsonFunction::class.java)
|
|
functions[key] = def
|
|
}
|
|
} catch(err: Throwable) {
|
|
LOGGER.error("Loading function file $listedFile", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun loadParallax(callback: (String) -> Unit) {
|
|
for (fs in fileSystems) {
|
|
for (listedFile in fs.listAllFiles("parallax")) {
|
|
if (listedFile.endsWith(".parallax")) {
|
|
try {
|
|
callback("Loading $listedFile")
|
|
|
|
val def = gson.fromJson(getReader(listedFile), ParallaxPrototype::class.java)
|
|
parallax[getPathFilename(listedFile).substringBefore('.')] = def
|
|
} catch(err: Throwable) {
|
|
LOGGER.error("Loading parallax file $listedFile", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|