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

354 lines
11 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound
import com.google.gson.*
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
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.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
import ru.dbotthepony.kstarbound.defs.tile.RenderTemplate
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
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 _readingFolder = ThreadLocal<String>()
/**
* Служит переменной для указания из какой папки происходит чтение asset'а в данном потоке
*/
var readingFolder: String?
get() = _readingFolder.get()
private set(value) { _readingFolder.set(value) }
private val tiles = HashMap<String, TileDefinition>()
private val tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>()
private val tileModifiers = HashMap<String, MaterialModifier>()
private val tileModifiersByID = Int2ObjectAVLTreeMap<MaterialModifier>()
private val projectiles = HashMap<String, ConfiguredProjectile>()
private val parallax = HashMap<String, ParallaxPrototype>()
private val functions = HashMap<String, JsonFunction>()
val tileModifiersAccess: Map<String, MaterialModifier> = Collections.unmodifiableMap(tileModifiers)
val tileModifiersByIDAccess: Map<Int, MaterialModifier> = Collections.unmodifiableMap(tileModifiersByID)
val tilesAccess: Map<String, TileDefinition> = Collections.unmodifiableMap(tiles)
val tilesAccessID: Map<Int, TileDefinition> = Collections.unmodifiableMap(tilesByMaterialID)
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)
.also(MaterialModifier::registerGson)
.also(RenderParameters::registerGson)
.also(RenderTemplate::registerGson)
.also(TileDefinition::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")
loadStage(callback, this::loadMaterialModifiers, "material modifier 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) {
readingFolder = "/tiles/materials"
for (fs in fileSystems) {
for (listedFile in fs.listAllFilesWithExtension("material")) {
try {
callback("Loading $listedFile")
readingFolder = getPathFolder(listedFile)
val tileDef = gson.fromJson(getReader(listedFile), TileDefinition::class.java)
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
tilesByMaterialID[tileDef.materialId] = tileDef
tiles[tileDef.materialName] = tileDef
} catch (err: Throwable) {
//throw TileDefLoadingException("Loading tile file $listedFile", err)
LOGGER.error("Loading tile file $listedFile", err)
}
}
}
readingFolder = null
}
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).assemble(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)
}
}
}
}
}
private fun loadMaterialModifiers(callback: (String) -> Unit) {
readingFolder = "/tiles/materials"
for (fs in fileSystems) {
for (listedFile in fs.listAllFilesWithExtension("matmod")) {
try {
callback("Loading $listedFile")
readingFolder = getPathFolder(listedFile)
val tileDef = gson.fromJson(getReader(listedFile), MaterialModifier::class.java)
check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" }
check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" }
tileModifiersByID[tileDef.modId] = tileDef
tileModifiers[tileDef.modName] = tileDef
} catch (err: Throwable) {
//throw TileDefLoadingException("Loading tile file $listedFile", err)
LOGGER.error("Loading tile modifier file $listedFile", err)
}
}
}
readingFolder = null
}
}