939 lines
29 KiB
Kotlin
939 lines
29 KiB
Kotlin
package ru.dbotthepony.kstarbound
|
|
|
|
import com.github.benmanes.caffeine.cache.Interner
|
|
import com.google.gson.*
|
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
|
import org.apache.logging.log4j.LogManager
|
|
import ru.dbotthepony.kstarbound.api.ISBFileLocator
|
|
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
|
import ru.dbotthepony.kstarbound.api.NonExistingFile
|
|
import ru.dbotthepony.kstarbound.api.PhysicalFile
|
|
import ru.dbotthepony.kstarbound.defs.*
|
|
import ru.dbotthepony.kstarbound.defs.image.Image
|
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
|
import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition
|
|
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
|
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
|
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
|
import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList
|
|
import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition
|
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
|
import ru.dbotthepony.kstarbound.util.JsonArrayCollector
|
|
import ru.dbotthepony.kstarbound.io.*
|
|
import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.ColorTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.FastutilTypeAdapterFactory
|
|
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.InternedStringAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.KOptionalTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.LongRangeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.NothingAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.OneOfTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.Vector4dTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory
|
|
import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory
|
|
import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory
|
|
import ru.dbotthepony.kstarbound.io.json.factory.PairAdapterFactory
|
|
import ru.dbotthepony.kstarbound.lua.LuaState
|
|
import ru.dbotthepony.kstarbound.lua.loadInternalScript
|
|
import ru.dbotthepony.kstarbound.math.*
|
|
import ru.dbotthepony.kstarbound.util.ITimeSource
|
|
import ru.dbotthepony.kstarbound.util.ItemStack
|
|
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
|
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
|
import ru.dbotthepony.kstarbound.util.WriteOnce
|
|
import ru.dbotthepony.kstarbound.util.filterNotNull
|
|
import ru.dbotthepony.kstarbound.util.set
|
|
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
|
import java.io.*
|
|
import java.lang.ref.Cleaner
|
|
import java.text.DateFormat
|
|
import java.util.concurrent.ForkJoinPool
|
|
import java.util.concurrent.ForkJoinTask
|
|
import java.util.function.BiConsumer
|
|
import java.util.function.BinaryOperator
|
|
import java.util.function.Function
|
|
import java.util.function.Supplier
|
|
import java.util.stream.Collector
|
|
import kotlin.NoSuchElementException
|
|
import kotlin.collections.ArrayList
|
|
import kotlin.random.Random
|
|
|
|
object Starbound : ISBFileLocator {
|
|
const val TICK_TIME_ADVANCE = 0.01666666666666664
|
|
const val TICK_TIME_ADVANCE_NANOS = 16_666_666L
|
|
|
|
val CLEANER: Cleaner = Cleaner.create {
|
|
val t = Thread(it, "Starbound Global Cleaner Thread")
|
|
t.isDaemon = true
|
|
t.priority = 2
|
|
t
|
|
}
|
|
|
|
// currently Caffeine one saves only 4 megabytes of RAM on pretty big modpack
|
|
// Hrm.
|
|
// val strings: Interner<String> = Interner.newWeakInterner()
|
|
// val strings: Interner<String> = Interner { it }
|
|
val STRINGS: Interner<String> = HashTableInterner(5)
|
|
|
|
private val polyfill by lazy { loadInternalScript("polyfill") }
|
|
|
|
private val LOGGER = LogManager.getLogger()
|
|
|
|
val gson: Gson = with(GsonBuilder()) {
|
|
serializeNulls()
|
|
setDateFormat(DateFormat.LONG)
|
|
setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
|
setPrettyPrinting()
|
|
|
|
registerTypeAdapter(InternedStringAdapter(STRINGS))
|
|
|
|
InternedJsonElementAdapter(STRINGS).also {
|
|
registerTypeAdapter(it)
|
|
registerTypeAdapter(it.arrays)
|
|
registerTypeAdapter(it.objects)
|
|
}
|
|
|
|
registerTypeAdapter(Nothing::class.java, NothingAdapter)
|
|
|
|
// Обработчик @JsonImplementation
|
|
registerTypeAdapterFactory(JsonImplementationTypeFactory)
|
|
|
|
// ImmutableList, ImmutableSet, ImmutableMap
|
|
registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(STRINGS))
|
|
|
|
// fastutil collections
|
|
registerTypeAdapterFactory(FastutilTypeAdapterFactory(STRINGS))
|
|
|
|
// ArrayList
|
|
registerTypeAdapterFactory(ArrayListAdapterFactory)
|
|
|
|
// все enum'ы без особых настроек
|
|
registerTypeAdapterFactory(EnumAdapter.Companion)
|
|
|
|
// @JsonBuilder
|
|
registerTypeAdapterFactory(BuilderAdapter.Factory(STRINGS))
|
|
|
|
// @JsonFactory
|
|
registerTypeAdapterFactory(FactoryAdapter.Factory(STRINGS))
|
|
|
|
// Either<>
|
|
registerTypeAdapterFactory(EitherTypeAdapter)
|
|
// OneOf<>
|
|
registerTypeAdapterFactory(OneOfTypeAdapter)
|
|
// KOptional<>
|
|
registerTypeAdapterFactory(KOptionalTypeAdapter)
|
|
|
|
// Pair<>
|
|
registerTypeAdapterFactory(PairAdapterFactory)
|
|
registerTypeAdapterFactory(SBPattern.Companion)
|
|
|
|
registerTypeAdapterFactory(JsonReference.Companion)
|
|
|
|
registerTypeAdapter(ColorReplacements.Companion)
|
|
registerTypeAdapterFactory(BlueprintLearnList.Companion)
|
|
|
|
registerTypeAdapter(ColorTypeAdapter.nullSafe())
|
|
|
|
registerTypeAdapter(Drawable::Adapter)
|
|
registerTypeAdapter(ObjectOrientation::Adapter)
|
|
registerTypeAdapter(ObjectDefinition::Adapter)
|
|
registerTypeAdapter(StatModifier::Adapter)
|
|
|
|
// математические классы
|
|
registerTypeAdapter(AABBTypeAdapter)
|
|
registerTypeAdapter(AABBiTypeAdapter)
|
|
registerTypeAdapter(Vector2dTypeAdapter)
|
|
registerTypeAdapter(Vector2fTypeAdapter)
|
|
registerTypeAdapter(Vector2iTypeAdapter)
|
|
registerTypeAdapter(Vector4iTypeAdapter)
|
|
registerTypeAdapter(Vector4dTypeAdapter)
|
|
registerTypeAdapter(PolyTypeAdapter)
|
|
registerTypeAdapter(LineF::Adapter)
|
|
|
|
// Функции
|
|
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
|
|
registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER)
|
|
registerTypeAdapter(JsonFunction.Companion)
|
|
registerTypeAdapterFactory(Json2Function.Companion)
|
|
|
|
// Общее
|
|
registerTypeAdapterFactory(ThingDescription.Factory(STRINGS))
|
|
|
|
registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL))
|
|
|
|
registerTypeAdapter(InventoryIcon.Companion)
|
|
|
|
registerTypeAdapterFactory(IArmorItemDefinition.Frames.Factory)
|
|
registerTypeAdapterFactory(AssetPath.Companion)
|
|
registerTypeAdapter(SpriteReference.Companion)
|
|
|
|
registerTypeAdapterFactory(AssetReference.Companion)
|
|
|
|
registerTypeAdapter(ItemStack.Adapter(this@Starbound))
|
|
|
|
registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
|
|
registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
|
|
|
|
registerTypeAdapter(Image.Companion)
|
|
|
|
registerTypeAdapterFactory(with(RegistryReferenceFactory()) {
|
|
add(Registries.tiles)
|
|
add(Registries.tileModifiers)
|
|
add(Registries.liquid)
|
|
add(Registries.items)
|
|
add(Registries.species)
|
|
add(Registries.statusEffects)
|
|
add(Registries.particles)
|
|
add(Registries.questTemplates)
|
|
add(Registries.techs)
|
|
add(Registries.jsonFunctions)
|
|
add(Registries.json2Functions)
|
|
add(Registries.npcTypes)
|
|
add(Registries.projectiles)
|
|
add(Registries.tenants)
|
|
add(Registries.treasurePools)
|
|
add(Registries.monsterSkills)
|
|
add(Registries.monsterTypes)
|
|
add(Registries.worldObjects)
|
|
})
|
|
|
|
registerTypeAdapter(LongRangeAdapter)
|
|
|
|
create()
|
|
}
|
|
|
|
init {
|
|
val f = NonExistingFile("/metamaterials.config")
|
|
|
|
for (material in BuiltinMetaMaterials.MATERIALS) {
|
|
Registries.tiles.add(material, JsonNull.INSTANCE, f)
|
|
}
|
|
}
|
|
|
|
fun item(name: String): ItemStack {
|
|
return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY)
|
|
}
|
|
|
|
fun item(name: String, count: Long): ItemStack {
|
|
if (count <= 0L)
|
|
return ItemStack.EMPTY
|
|
|
|
return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY, count = count)
|
|
}
|
|
|
|
fun item(name: String, count: Long, parameters: JsonObject): ItemStack {
|
|
if (count <= 0L)
|
|
return ItemStack.EMPTY
|
|
|
|
return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY, count = count, parameters = parameters)
|
|
}
|
|
|
|
fun item(descriptor: JsonObject): ItemStack {
|
|
return item(
|
|
(descriptor["name"] as? JsonPrimitive)?.asString ?: return ItemStack.EMPTY,
|
|
descriptor["count"]?.asLong ?: return ItemStack.EMPTY,
|
|
(descriptor["parameters"] as? JsonObject)?.deepCopy() ?: JsonObject()
|
|
)
|
|
}
|
|
|
|
fun item(descriptor: JsonElement?): ItemStack {
|
|
if (descriptor is JsonPrimitive) {
|
|
return item(descriptor.asString)
|
|
} else if (descriptor is JsonObject) {
|
|
return item(descriptor)
|
|
} else {
|
|
return ItemStack.EMPTY
|
|
}
|
|
}
|
|
|
|
var initializing = false
|
|
private set
|
|
var initialized = false
|
|
private set
|
|
|
|
@Volatile
|
|
var terminateLoading = false
|
|
|
|
fun loadJsonAsset(path: String): JsonElement? {
|
|
val filename: String
|
|
val jsonPath: String?
|
|
|
|
if (path.contains(':')) {
|
|
filename = path.substringBefore(':')
|
|
jsonPath = path.substringAfter(':')
|
|
} else {
|
|
filename = path
|
|
jsonPath = null
|
|
}
|
|
|
|
val file = locate(filename)
|
|
|
|
if (!file.isFile) {
|
|
return null
|
|
}
|
|
|
|
return traverseJsonPath(jsonPath, gson.fromJson(file.reader(), JsonElement::class.java))
|
|
}
|
|
|
|
private fun luaRequire(it: LuaState, args: LuaState.ArgStack) {
|
|
val name = args.getString()
|
|
val file = locate(name)
|
|
|
|
if (!file.exists) {
|
|
throw FileNotFoundException("File $name does not exist")
|
|
}
|
|
|
|
if (!file.isFile) {
|
|
throw FileNotFoundException("File $name is a directory")
|
|
}
|
|
|
|
val read = file.readToString()
|
|
it.load(read, chunkName = "@" + file.computeFullPath())
|
|
it.call()
|
|
}
|
|
|
|
// wrapping for Lua refs
|
|
private fun getImage(path: String) = Image.get(path)
|
|
|
|
/**
|
|
* **ПРЕДУПРЕЖДЕНИЕ:**
|
|
* [time] не должен иметь ссылок (прямые или непрямые) на [state], иначе произойдёт утечка памяти!
|
|
*/
|
|
fun pushLuaAPI(state: LuaState, time: ITimeSource = JVMTimeSource.INSTANCE) {
|
|
state.pushWeak(this) { args ->
|
|
luaRequire(args.lua, args)
|
|
0
|
|
}
|
|
|
|
state.storeGlobal("require")
|
|
|
|
state.pushTable()
|
|
state.storeGlobal("root")
|
|
state.loadGlobal("root")
|
|
|
|
state.setTableFunction("assetJson", this) {args ->
|
|
args.lua.push(loadJsonAsset(args.getString()))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("makeCurrentVersionedJson", this) {args ->
|
|
TODO("makeCurrentVersionedJson")
|
|
}
|
|
|
|
state.setTableFunction("loadVersionedJson", this) {args ->
|
|
TODO("loadVersionedJson")
|
|
}
|
|
|
|
state.setTableFunction("evalFunction", this) {args ->
|
|
val name = args.getString()
|
|
val fn = Registries.jsonFunctions[name] ?: throw NoSuchElementException("No such function $name")
|
|
args.push(fn.value.evaluate(args.getDouble()))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("evalFunction2", this) {args ->
|
|
val name = args.getString()
|
|
val fn = Registries.json2Functions[name] ?: throw NoSuchElementException("No such 2function $name")
|
|
args.push(fn.value.evaluate(args.getDouble(), args.getDouble()))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("imageSize", this) {args ->
|
|
val name = args.getString()
|
|
args.lua.push(getImage(name)?.size ?: throw FileNotFoundException("No such file $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("imageSpaces", this) { args ->
|
|
// List<Vec2I> root.imageSpaces(String imagePath, Vec2F worldPosition, float spaceScan, bool flip)
|
|
val name = args.getString()
|
|
val values = getImage(name)?.worldSpaces(args.getVector2i(), args.getDouble(), args.getBool()) ?: throw FileNotFoundException("No such file $name")
|
|
|
|
args.lua.pushTable(arraySize = values.size)
|
|
val table = args.lua.stackTop
|
|
|
|
for ((i, value) in values.withIndex()) {
|
|
args.lua.push(i + 1)
|
|
args.lua.push(value)
|
|
args.lua.setTableValue(table)
|
|
}
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("nonEmptyRegion", this) { args ->
|
|
val name = args.getString()
|
|
args.lua.push(getImage(name)?.nonEmptyRegion ?: throw FileNotFoundException("No such file $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("npcConfig", this) { args ->
|
|
// Json root.npcConfig(String npcType)
|
|
val name = args.getString()
|
|
args.push(Registries.npcTypes[name] ?: throw NoSuchElementException("No such NPC type $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("npcVariant", this) { args ->
|
|
// Json root.npcVariant(String species, String npcType, float level, [unsigned seed], [Json parameters])
|
|
TODO("npcVariant")
|
|
}
|
|
|
|
state.setTableFunction("projectileGravityMultiplier", this) { args ->
|
|
// float root.projectileGravityMultiplier(String projectileName)
|
|
TODO("projectileGravityMultiplier")
|
|
}
|
|
|
|
state.setTableFunction("projectileConfig", this) { args ->
|
|
// Json root.projectileConfig(String projectileName)
|
|
val name = args.getString()
|
|
args.lua.push(Registries.projectiles[name]?.json ?: throw kotlin.NoSuchElementException("No such Projectile type $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("recipesForItem", this) { args ->
|
|
args.lua.push(JsonArray().also { a ->
|
|
RecipeRegistry.output2recipes[args.getString()]?.stream()?.map { it.toJson() }?.forEach {
|
|
a.add(it)
|
|
}
|
|
})
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("itemType", this) { args ->
|
|
val name = args.getString()
|
|
args.lua.push(Registries.items[name]?.value?.itemType ?: throw NoSuchElementException("No such item $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("itemTags", this) { args ->
|
|
val name = args.getString()
|
|
args.lua.pushStrings(Registries.items[name]?.value?.itemTags ?: throw NoSuchElementException("No such item $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("itemHasTag", this) { args ->
|
|
val name = args.getString()
|
|
val tag = args.getString()
|
|
args.push((Registries.items[name]?.value?.itemTags ?: throw NoSuchElementException("No such item $name")).contains(tag))
|
|
1
|
|
}
|
|
|
|
// TODO: генерация
|
|
state.setTableFunction("itemConfig", this) { args ->
|
|
// Json root.itemConfig(ItemDescriptor descriptor, [float level], [unsigned seed])
|
|
val item = item(args.getValue())
|
|
val level = if (args.hasSomethingAt()) args.getDouble() else null
|
|
val seed = if (args.hasSomethingAt()) args.getLong() else null
|
|
|
|
if (item.isEmpty) {
|
|
args.push()
|
|
} else {
|
|
args.push(JsonObject().also {
|
|
it["directory"] = item.item!!.file.computeDirectory()
|
|
it["config"] = item.item!!.json
|
|
it["parameters"] = item.parameters
|
|
})
|
|
}
|
|
|
|
1
|
|
}
|
|
|
|
// TODO: генерация
|
|
state.setTableFunction("createItem", this) { args ->
|
|
// ItemDescriptor root.createItem(ItemDescriptor descriptor, [float level], [unsigned seed])
|
|
val item = item(args.getValue())
|
|
val level = if (args.hasSomethingAt()) args.getDouble() else null
|
|
val seed = if (args.hasSomethingAt()) args.getLong() else null
|
|
|
|
if (item.isEmpty) {
|
|
args.push()
|
|
return@setTableFunction 1
|
|
}
|
|
|
|
if (item.maxStackSize < item.size) {
|
|
item.size = item.maxStackSize
|
|
}
|
|
|
|
args.push(gson.toJsonTree(item))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("tenantConfig", this) { args ->
|
|
// Json root.tenantConfig(String tenantName)
|
|
val name = args.getString()
|
|
Registries.tenants[name]?.push(args) ?: throw NoSuchElementException("No such tenant $name")
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("getMatchingTenants", this) { args ->
|
|
// Json root.tenantConfig(String tenantName)
|
|
val tags = args.getTable()
|
|
val actualTags = Object2IntOpenHashMap<String>()
|
|
|
|
for ((k, v) in tags.entrySet()) {
|
|
if (v is JsonPrimitive && v.isNumber) {
|
|
actualTags[k] = v.asInt
|
|
}
|
|
}
|
|
|
|
args.push(Registries.tenants.values
|
|
.stream()
|
|
.filter { it.value.test(actualTags) }
|
|
.sorted { a, b -> b.value.compareTo(a.value) }
|
|
.map { it.toJson() }
|
|
.collect(JsonArrayCollector))
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("liquidStatusEffects", this) { args ->
|
|
val liquid: LiquidDefinition
|
|
|
|
if (args.isStringAt()) {
|
|
val name = args.getString()
|
|
liquid = Registries.liquid[name]?.value ?: throw NoSuchElementException("No such liquid with name $name")
|
|
} else {
|
|
val id = args.getInt()
|
|
liquid = Registries.liquid[id]?.value ?: throw NoSuchElementException("No such liquid with ID $id")
|
|
}
|
|
|
|
args.lua.pushStrings(liquid.statusEffects.stream().map { it.value?.value?.name }.filterNotNull().toList())
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("generateName", this) { args ->
|
|
val assetName = args.getString()
|
|
val seed = if (args.hasSomethingAt()) args.getLong() else time.nanos
|
|
val names = loadJsonAsset(assetName) ?: throw NoSuchElementException("No such JSON asset $assetName")
|
|
|
|
if (names !is JsonArray) {
|
|
var possibleName: String? = null
|
|
|
|
if (names is JsonObject) {
|
|
for ((k, v) in names.entrySet()) {
|
|
if (v is JsonArray && !v.isEmpty) {
|
|
if (possibleName == null || (names[possibleName] as JsonArray).size() < v.size())
|
|
possibleName = k
|
|
}
|
|
}
|
|
}
|
|
|
|
if (possibleName != null) {
|
|
if (assetName.contains(':')) {
|
|
throw IllegalArgumentException("JSON asset $assetName is not an array, did you mean $assetName.$possibleName?")
|
|
} else {
|
|
throw IllegalArgumentException("JSON asset $assetName is not an array, did you mean $assetName:$possibleName?")
|
|
}
|
|
} else {
|
|
throw IllegalArgumentException("JSON asset $assetName is not an array")
|
|
}
|
|
}
|
|
|
|
if (names.isEmpty) {
|
|
throw IllegalStateException("JSON array $assetName is empty")
|
|
}
|
|
|
|
args.push(names[Random(seed).nextInt(0, names.size())])
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("questConfig", this) { args ->
|
|
val name = args.getString()
|
|
args.push(Registries.questTemplates[name] ?: throw NoSuchElementException("No such quest template $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("npcPortrait", this) { args ->
|
|
// JsonArray root.npcPortrait(String portraitMode, String species, String npcType, float level, [unsigned seed], [Json parameters])
|
|
// Generates an NPC with the specified type, level, seed and parameters and returns a portrait in the given portraitMode as a list of drawables.
|
|
TODO("npcPortrait")
|
|
}
|
|
|
|
state.setTableFunction("monsterPortrait", this) { args ->
|
|
// JsonArray root.monsterPortrait(String typeName, [Json parameters])
|
|
// Generates a monster of the given type with the given parameters and returns its portrait as a list of drawables.
|
|
TODO("monsterPortrait")
|
|
}
|
|
|
|
state.setTableFunction("isTreasurePool", this) { args ->
|
|
args.push(args.getString() in Registries.treasurePools)
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("createTreasure", this) { args ->
|
|
val name = args.getString()
|
|
val level = args.getDouble()
|
|
val rand = if (args.hasSomethingAt()) java.util.Random(args.getLong()) else java.util.Random()
|
|
args.push(Registries.treasurePools[name]?.value?.evaluate(rand, level)?.stream()?.map { it.toJson() }?.filterNotNull()?.collect(JsonArrayCollector) ?: throw NoSuchElementException("No such treasure pool $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("materialMiningSound", this) { args ->
|
|
val name = args.getString()
|
|
val mod = if (args.hasSomethingAt()) args.getString() else null
|
|
val mat = Registries.tiles[name] ?: throw NoSuchElementException("No such material $name")
|
|
|
|
if (mod == null) {
|
|
args.push(mat.value.miningSounds.firstOrNull())
|
|
} else {
|
|
val getMod = Registries.tileModifiers[mod] ?: throw NoSuchElementException("No such material modifier $mod")
|
|
args.push(getMod.value.miningSounds.firstOrNull() ?: mat.value.miningSounds.firstOrNull())
|
|
}
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("materialFootstepSound", this) { args ->
|
|
val name = args.getString()
|
|
val mod = if (args.hasSomethingAt()) args.getString() else null
|
|
val mat = Registries.tiles[name] ?: throw NoSuchElementException("No such material $name")
|
|
|
|
if (mod == null) {
|
|
args.push(mat.value.footstepSound)
|
|
} else {
|
|
val getMod = Registries.tileModifiers[mod] ?: throw NoSuchElementException("No such material modifier $mod")
|
|
args.push(getMod.value.footstepSound ?: mat.value.footstepSound)
|
|
}
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("materialHealth", this) { args ->
|
|
val name = args.getString()
|
|
val mod = if (args.hasSomethingAt()) args.getString() else null
|
|
val mat = Registries.tiles[name] ?: throw NoSuchElementException("No such material $name")
|
|
|
|
if (mod == null) {
|
|
args.push(mat.value.health)
|
|
} else {
|
|
val getMod = Registries.tileModifiers[mod] ?: throw NoSuchElementException("No such material modifier $mod")
|
|
args.push(getMod.value.health + mat.value.health)
|
|
}
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("materialConfig", this) { args ->
|
|
val name = args.getString()
|
|
args.pushFull(Registries.tiles[name])
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("modConfig", this) { args ->
|
|
val name = args.getString()
|
|
args.pushFull(Registries.tileModifiers[name])
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("liquidConfig", this) { args ->
|
|
if (args.isNumberAt()) {
|
|
val id = args.getLong().toInt()
|
|
args.pushFull(Registries.liquid[id])
|
|
} else {
|
|
val name = args.getString()
|
|
args.pushFull(Registries.liquid[name])
|
|
}
|
|
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("liquidName", this) { args ->
|
|
val id = args.getLong().toInt()
|
|
args.push(Registries.liquid[id]?.value?.name ?: throw NoSuchElementException("No such liquid with ID $id"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("liquidId", this) { args ->
|
|
val name = args.getString()
|
|
args.push(Registries.liquid[name]?.value?.name ?: throw NoSuchElementException("No such liquid $name"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("monsterSkillParameter", this) { args ->
|
|
val name = args.getString()
|
|
val param = args.getString()
|
|
// parity: если скила не существует, то оригинальный движок просто возвращает nil
|
|
args.push(Registries.monsterSkills[name]?.value?.config?.get(param))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("monsterParameters", this) { args ->
|
|
val name = args.getString()
|
|
val monster = Registries.monsterTypes[name] ?: throw NoSuchElementException("No such monster type $name")
|
|
args.push(monster.traverseJsonPath("baseParameters"))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("monsterMovementSettings", this) { args ->
|
|
val name = args.getString()
|
|
val monster = Registries.monsterTypes[name] ?: throw NoSuchElementException("No such monster type $name")
|
|
args.push(gson.toJsonTree(monster.value.baseParameters.movementSettings))
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("createBiome", this) { args ->
|
|
TODO("createBiome")
|
|
}
|
|
|
|
state.setTableFunction("hasTech", this) { args ->
|
|
args.push(args.getString() in Registries.techs)
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("techType", this) { args ->
|
|
val name = args.getString()
|
|
val tech = Registries.techs[name] ?: throw NoSuchElementException("No such tech $name")
|
|
args.push(tech.value.type)
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("techConfig", this) { args ->
|
|
val name = args.getString()
|
|
val tech = Registries.techs[name] ?: throw NoSuchElementException("No such tech $name")
|
|
tech.push(args)
|
|
1
|
|
}
|
|
|
|
state.setTableFunction("treeStemDirectory", this) { args ->
|
|
// String root.treeStemDirectory(String stemName)
|
|
TODO("treeStemDirectory")
|
|
}
|
|
|
|
state.setTableFunction("treeFoliageDirectory", this) { args ->
|
|
// String root.treeFoliageDirectory(String foliageName)
|
|
TODO("treeFoliageDirectory")
|
|
}
|
|
|
|
state.setTableFunction("collection", this) { args ->
|
|
// Collection root.collection(String collectionName)
|
|
TODO("collection")
|
|
}
|
|
|
|
state.setTableFunction("collectables", this) { args ->
|
|
// List<Collectable> root.collectables(String collectionName)
|
|
TODO("collectables")
|
|
}
|
|
|
|
state.setTableFunction("elementalResistance", this) { args ->
|
|
// String root.elementalResistance(String elementalType)
|
|
TODO("elementalResistance")
|
|
}
|
|
|
|
state.setTableFunction("dungeonMetadata", this) { args ->
|
|
// Json root.dungeonMetadata(String dungeonName)
|
|
TODO("dungeonMetadata")
|
|
}
|
|
|
|
state.setTableFunction("behavior", this) { args ->
|
|
// BehaviorState root.behavior(`LuaTable` context, Json config, `JsonObject` parameters)
|
|
TODO("behavior")
|
|
}
|
|
|
|
state.pop()
|
|
|
|
state.load(polyfill, "@starbound.jar!/scripts/polyfill.lua")
|
|
state.call()
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
override 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
|
|
}
|
|
|
|
override 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)
|
|
}
|
|
|
|
private val initCallbacks = ArrayList<() -> Unit>()
|
|
|
|
var playerDefinition: PlayerDefinition by WriteOnce()
|
|
private set
|
|
|
|
private fun doInitialize(log: ILoadingLog, parallel: Boolean) {
|
|
var time = System.currentTimeMillis()
|
|
|
|
if (archivePaths.isNotEmpty()) {
|
|
log.line("Searching for pak archives...".also(LOGGER::info))
|
|
|
|
for (path in archivePaths) {
|
|
val line = log.line("Reading index of ${path}...".also(LOGGER::info))
|
|
|
|
addPak(StarboundPak(path) { _, status ->
|
|
line.text = ("${path.parent}/${path.name}: $status")
|
|
})
|
|
}
|
|
}
|
|
|
|
log.line("Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(LOGGER::info))
|
|
time = System.currentTimeMillis()
|
|
log.line("Building file index...".also(LOGGER::info))
|
|
|
|
val ext2files = fileSystems.parallelStream()
|
|
.flatMap { it.explore() }
|
|
.filter { it.isFile }
|
|
.collect(object :
|
|
Collector<IStarboundFile, Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>, Map<String, List<IStarboundFile>>>
|
|
{
|
|
override fun supplier(): Supplier<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>> {
|
|
return Supplier { Object2ObjectOpenHashMap() }
|
|
}
|
|
|
|
override fun accumulator(): BiConsumer<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>, IStarboundFile> {
|
|
return BiConsumer { t, u ->
|
|
t.computeIfAbsent(u.name.substringAfterLast('.'), Object2ObjectFunction { ArrayList() }).add(u)
|
|
}
|
|
}
|
|
|
|
override fun combiner(): BinaryOperator<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>> {
|
|
return BinaryOperator { t, u ->
|
|
for ((k, v) in u)
|
|
t.computeIfAbsent(k, Object2ObjectFunction { ArrayList() }).addAll(v)
|
|
|
|
t
|
|
}
|
|
}
|
|
|
|
override fun finisher(): Function<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>, Map<String, List<IStarboundFile>>> {
|
|
return Function { it }
|
|
}
|
|
|
|
override fun characteristics(): Set<Collector.Characteristics> {
|
|
return setOf(Collector.Characteristics.IDENTITY_FINISH)
|
|
}
|
|
})
|
|
|
|
log.line("Finished building file index in ${System.currentTimeMillis() - time}ms".also(LOGGER::info))
|
|
|
|
val tasks = ArrayList<ForkJoinTask<*>>()
|
|
val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1)
|
|
|
|
tasks.addAll(Registries.load(log, ext2files, pool))
|
|
tasks.addAll(RecipeRegistry.load(log, ext2files, pool))
|
|
tasks.addAll(GlobalDefaults.load(log, pool))
|
|
|
|
AssetPathStack.block("/") {
|
|
//playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java)
|
|
}
|
|
|
|
tasks.forEach { it.join() }
|
|
|
|
if (!parallel)
|
|
pool.shutdown()
|
|
|
|
initializing = false
|
|
initialized = true
|
|
log.line("Finished loading in ${System.currentTimeMillis() - time}ms")
|
|
}
|
|
|
|
fun initializeGame(log: ILoadingLog, parallel: Boolean = true) {
|
|
if (initializing) {
|
|
throw IllegalStateException("Already initializing!")
|
|
}
|
|
|
|
if (initialized) {
|
|
throw IllegalStateException("Already initialized!")
|
|
}
|
|
|
|
initializing = true
|
|
Thread({ doInitialize(log, parallel) }, "Asset Loader").also {
|
|
it.isDaemon = true
|
|
it.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()
|
|
}
|
|
}
|
|
}
|
|
|