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() private val projectiles = HashMap() 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() 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>() 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 { 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) { 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) } } } } } }