KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
DBotThePony e8eed40a73
object oriented file system skeleton
so there at least some preparation for java nio filesystem
2022-11-24 15:30:47 +07:00

413 lines
13 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.IStarboundFile
import ru.dbotthepony.kstarbound.api.NonExistingFile
import ru.dbotthepony.kstarbound.api.PhysicalFile
import ru.dbotthepony.kstarbound.api.explore
import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition
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.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 {
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 liquid = HashMap<String, LiquidDefinition>()
private val liquidByID = Int2ObjectAVLTreeMap<LiquidDefinition>()
private val projectiles = HashMap<String, ConfiguredProjectile>()
private val parallax = HashMap<String, ParallaxPrototype>()
private val functions = HashMap<String, JsonFunction>()
val liquidAccess: Map<String, LiquidDefinition> = Collections.unmodifiableMap(liquid)
val liquidByIDAccess: Map<Int, LiquidDefinition> = Collections.unmodifiableMap(liquidByID)
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)
.also(LiquidDefinition::registerGson)
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
.create()
var initializing = false
private set
var initialized = false
private set
@Volatile
var terminateLoading = false
private val archivePaths = ArrayList<File>()
private val fileSystems = ArrayList<IStarboundFile>()
fun addFilePath(path: File) {
fileSystems.add(PhysicalFile(path))
}
fun addPak(pak: StarboundPak) {
fileSystems.add(pak.root)
}
fun exists(path: String): Boolean {
@Suppress("name_shadowing")
var path = path
if (path[0] == '/') {
path = path.substring(1)
}
for (fs in fileSystems) {
if (fs.locate(path).exists) {
return true
}
}
return false
}
fun locate(path: String): IStarboundFile {
@Suppress("name_shadowing")
var path = path
if (path[0] == '/') {
path = path.substring(1)
}
for (fs in fileSystems) {
val file = fs.locate(path)
if (file.exists) {
return file
}
}
return NonExistingFile(path.split("/").last(), fullPath = path)
}
fun locate(vararg path: String): IStarboundFile {
for (p in path) {
val get = locate(p)
if (get.exists) {
return get
}
}
return NonExistingFile(path[0].split("/").last(), fullPath = path[0])
}
/**
* Добавляет pak к чтению при initializeGame
*/
fun addPakPath(pak: File) {
archivePaths.add(pak)
}
fun loadJson(path: String): JsonElement {
return JsonParser.parseReader(locate(path).reader())
}
fun readDirect(path: String) = locate(path).readDirect()
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")
loadStage(callback, this::loadLiquidDefinitions, "liquid definitions")
initializing = false
initialized = true
callback(true, false, "Finished loading in ${System.currentTimeMillis() - time}ms")
}, "Asset Loader").start()
}
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.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) {
try {
callback("Loading $listedFile")
readingFolder = listedFile.computeDirectory()
val tileDef = gson.fromJson(listedFile.reader(), 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)
}
if (terminateLoading) {
return
}
}
}
readingFolder = null
}
private fun loadProjectiles(callback: (String) -> Unit) {
for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".projectile") }) {
try {
callback("Loading $listedFile")
val def = gson.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory())
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)
}
if (terminateLoading) {
return
}
}
}
}
private fun loadFunctions(callback: (String) -> Unit) {
for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".functions") }) {
try {
callback("Loading $listedFile")
val readObject = JsonParser.parseReader(listedFile.reader()) 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)
}
if (terminateLoading) {
return
}
}
}
}
private fun loadParallax(callback: (String) -> Unit) {
for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".parallax") }) {
try {
callback("Loading $listedFile")
val def = gson.fromJson(listedFile.reader(), ParallaxPrototype::class.java)
parallax[listedFile.name.substringBefore('.')] = def
} catch(err: Throwable) {
LOGGER.error("Loading parallax file $listedFile", err)
}
if (terminateLoading) {
return
}
}
}
}
private fun loadMaterialModifiers(callback: (String) -> Unit) {
readingFolder = "/tiles/materials"
for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) {
try {
callback("Loading $listedFile")
readingFolder = listedFile.computeDirectory()
val tileDef = gson.fromJson(listedFile.reader(), 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)
}
if (terminateLoading) {
return
}
}
}
readingFolder = null
}
private fun loadLiquidDefinitions(callback: (String) -> Unit) {
for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".liquid") }) {
try {
callback("Loading $listedFile")
readingFolder = listedFile.computeDirectory()
val liquidDef = gson.fromJson(listedFile.reader(), LiquidDefinition::class.java)
check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" }
check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" }
} catch (err: Throwable) {
//throw TileDefLoadingException("Loading tile file $listedFile", err)
LOGGER.error("Loading liquid definition file $listedFile", err)
}
if (terminateLoading) {
return
}
}
}
readingFolder = null
}
}