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() /** * Служит переменной для указания из какой папки происходит чтение asset'а в данном потоке */ var readingFolder: String? get() = _readingFolder.get() private set(value) { _readingFolder.set(value) } private val tiles = HashMap() private val tilesByMaterialID = Int2ObjectAVLTreeMap() private val tileModifiers = HashMap() private val tileModifiersByID = Int2ObjectAVLTreeMap() private val projectiles = HashMap() private val parallax = HashMap() private val functions = HashMap() val tileModifiersAccess: Map = Collections.unmodifiableMap(tileModifiers) val tileModifiersByIDAccess: Map = Collections.unmodifiableMap(tileModifiersByID) val tilesAccess: Map = Collections.unmodifiableMap(tiles) val tilesAccessID: Map = Collections.unmodifiableMap(tilesByMaterialID) val projectilesAccess: Map = Collections.unmodifiableMap(projectiles) val parallaxAccess: Map = Collections.unmodifiableMap(parallax) val functionsAccess: Map = 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() private val fileSystems = ArrayList() 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 { val listing = mutableListOf() for (fs in fileSystems) { listing.addAll(fs.listFiles(path)) } return listing } override fun listDirectories(path: String): Collection { val listing = mutableListOf() 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 } }