root.isTreasurePool и root.createTreasure, и полноценная подгрузка treasure pool
This commit is contained in:
parent
aa4f73bc01
commit
2d87575bfc
@ -221,6 +221,8 @@ fun main() {
|
||||
last = JVMTimeSource.INSTANCE.millis
|
||||
}
|
||||
}
|
||||
|
||||
println(starbound.treasurePools["motherpoptopTreasure"]!!.value.evaluate(Random(), 2.0))
|
||||
}
|
||||
|
||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||
|
@ -1,7 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.internal.bind.JsonTreeReader
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
@ -24,25 +27,74 @@ inline fun <reified T : Any> ObjectRegistry(name: String, noinline key: ((T) ->
|
||||
return ObjectRegistry(T::class, name, key, intKey)
|
||||
}
|
||||
|
||||
private fun merge(source: JsonObject, destination: JsonObject): JsonObject {
|
||||
fun mergeJsonElements(source: JsonObject, destination: JsonObject): JsonObject {
|
||||
for ((k, v) in source.entrySet()) {
|
||||
if (!destination.has(k)) {
|
||||
destination[k] = v.deepCopy()
|
||||
} else if (destination[k] is JsonObject && v is JsonObject) {
|
||||
merge(v, destination[k] as JsonObject)
|
||||
} else {
|
||||
mergeJsonElements(v, destination[k])
|
||||
}
|
||||
}
|
||||
|
||||
return destination
|
||||
}
|
||||
|
||||
class RegistryObject<T : Any>(val value: T, private val json: JsonObject, val file: IStarboundFile, val gson: Gson, val pathStack: PathStack) {
|
||||
fun mergeJsonElements(source: JsonArray, destination: JsonArray): JsonArray {
|
||||
for ((i, v) in source.withIndex()) {
|
||||
if (i >= destination.size()) {
|
||||
destination.add(v.deepCopy())
|
||||
} else {
|
||||
destination[i] = mergeJsonElements(v, destination[i])
|
||||
}
|
||||
}
|
||||
|
||||
return destination
|
||||
}
|
||||
|
||||
fun mergeJsonElements(source: JsonElement, destination: JsonElement): JsonElement {
|
||||
if (destination is JsonPrimitive) {
|
||||
return destination
|
||||
}
|
||||
|
||||
if (destination is JsonObject && source is JsonObject) {
|
||||
return mergeJsonElements(source, destination)
|
||||
}
|
||||
|
||||
if (destination is JsonArray && source is JsonArray) {
|
||||
return mergeJsonElements(source, destination)
|
||||
}
|
||||
|
||||
return destination
|
||||
}
|
||||
|
||||
class RegistryObject<T : Any>(
|
||||
/**
|
||||
* Объект реестра
|
||||
*/
|
||||
val value: T,
|
||||
/**
|
||||
* Оригинальный JSON объекта без каких либо изменений
|
||||
*/
|
||||
private val json: JsonElement,
|
||||
/**
|
||||
* Файл, откуда данный объект был загружен
|
||||
*/
|
||||
val file: IStarboundFile,
|
||||
/**
|
||||
* [Gson], который загрузил данный объект из JSON
|
||||
*/
|
||||
val gson: Gson,
|
||||
/**
|
||||
* [PathStack] который используется для загрузки и чтения, из какого файла читается объект
|
||||
*/
|
||||
val pathStack: PathStack
|
||||
) {
|
||||
/**
|
||||
* Возвращает копию оригинальной JSON структуры, из которой был спрототипирован [value]
|
||||
*
|
||||
* Более полная JSON структура (обработанная) доступа из метода [toJson]
|
||||
*/
|
||||
fun copy(): JsonObject {
|
||||
fun copy(): JsonElement {
|
||||
return json.deepCopy()
|
||||
}
|
||||
|
||||
@ -60,8 +112,8 @@ class RegistryObject<T : Any>(val value: T, private val json: JsonObject, val fi
|
||||
* Полнота определяется тем, что [value] может иметь свойства по умолчанию, которые не указаны
|
||||
* в оригинальной JSON структуре. [copy] не вернёт данные свойства по умолчанию, а [toJson] вернёт.
|
||||
*/
|
||||
fun toJson(): JsonObject {
|
||||
return merge(json, gson.toJsonTree(value) as JsonObject)
|
||||
fun toJson(): JsonElement {
|
||||
return mergeJsonElements(json, gson.toJsonTree(value))
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@ -195,17 +247,17 @@ class ObjectRegistry<T : Any>(val clazz: KClass<T>, val name: String, val key: (
|
||||
|
||||
fun add(gson: Gson, file: IStarboundFile, pathStack: PathStack): Boolean {
|
||||
return pathStack(file.computeDirectory()) {
|
||||
val elem = gson.fromJson(file.reader(), JsonObject::class.java)
|
||||
val elem = gson.fromJson(file.reader(), JsonElement::class.java)
|
||||
val value = gson.fromJson<T>(JsonTreeReader(elem), clazz.java)
|
||||
add(RegistryObject(value, elem, file, gson, pathStack), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
|
||||
}
|
||||
}
|
||||
|
||||
fun add(value: T, json: JsonObject, file: IStarboundFile, gson: Gson, pathStack: PathStack): Boolean {
|
||||
fun add(value: T, json: JsonElement, file: IStarboundFile, gson: Gson, pathStack: PathStack): Boolean {
|
||||
return add(RegistryObject(value, json, file, gson, pathStack), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
|
||||
}
|
||||
|
||||
fun add(value: T, json: JsonObject, file: IStarboundFile, gson: Gson, pathStack: PathStack, key: String): Boolean {
|
||||
fun add(value: T, json: JsonElement, file: IStarboundFile, gson: Gson, pathStack: PathStack, key: String): Boolean {
|
||||
return add(RegistryObject(value, json, file, gson, pathStack), key)
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import ru.dbotthepony.kstarbound.defs.item.impl.HeadArmorItemPrototype
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.impl.ItemPrototype
|
||||
import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemPrototype
|
||||
import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemPrototype
|
||||
@ -146,6 +147,9 @@ class Starbound : ISBFileLocator {
|
||||
|
||||
val recipeRegistry = RecipeRegistry()
|
||||
|
||||
private val _treasurePools = ObjectRegistry("treasure pools", TreasurePoolDefinition::name)
|
||||
val treasurePools = _treasurePools.view
|
||||
|
||||
val gson: Gson = with(GsonBuilder()) {
|
||||
serializeNulls()
|
||||
setDateFormat(DateFormat.LONG)
|
||||
@ -221,22 +225,24 @@ class Starbound : ISBFileLocator {
|
||||
registerTypeAdapter(ItemStack.Adapter(this@Starbound))
|
||||
|
||||
registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
|
||||
registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
|
||||
|
||||
registerTypeAdapterFactory(with(RegistryReferenceFactory()) {
|
||||
add(tiles::get)
|
||||
add(tileModifiers::get)
|
||||
add(liquid::get)
|
||||
add(items::get)
|
||||
add(species::get)
|
||||
add(statusEffects::get)
|
||||
add(particles::get)
|
||||
add(questTemplates::get)
|
||||
add(techs::get)
|
||||
add(jsonFunctions::get)
|
||||
add(json2Functions::get)
|
||||
add(npcTypes::get)
|
||||
add(projectiles::get)
|
||||
add(tenants::get)
|
||||
add(_tiles)
|
||||
add(_tileModifiers)
|
||||
add(_liquid)
|
||||
add(_items)
|
||||
add(_species)
|
||||
add(_statusEffects)
|
||||
add(_particles)
|
||||
add(_questTemplates)
|
||||
add(_techs)
|
||||
add(_jsonFunctions)
|
||||
add(_json2Functions)
|
||||
add(_npcTypes)
|
||||
add(_projectiles)
|
||||
add(_tenants)
|
||||
add(_treasurePools)
|
||||
})
|
||||
|
||||
registerTypeAdapter(LongRangeAdapter)
|
||||
@ -614,6 +620,31 @@ class Starbound : ISBFileLocator {
|
||||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
state.setTableFunction("isTreasurePool", this) { args ->
|
||||
args.push(args.getString() in 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(treasurePools[name]?.value?.evaluate(rand, level)?.stream()?.map { it.toJson() }?.filterNotNull()?.collect(JsonArrayCollector) ?: throw NoSuchElementException("No such treasure pool $name"))
|
||||
1
|
||||
}
|
||||
|
||||
state.pop()
|
||||
|
||||
state.load(polyfill, "@starbound.jar!/scripts/polyfill.lua")
|
||||
@ -796,6 +827,7 @@ class Starbound : ISBFileLocator {
|
||||
loadStage(callback, { loadJsonFunctions(it, ext2files["functions"] ?: listOf()) }, "json functions")
|
||||
loadStage(callback, { loadJson2Functions(it, ext2files["2functions"] ?: listOf()) }, "json 2functions")
|
||||
loadStage(callback, { loadRecipes(it, ext2files["recipe"] ?: listOf()) }, "recipes")
|
||||
loadStage(callback, { loadTreasurePools(it, ext2files["treasurepools"] ?: listOf()) }, "treasure pools")
|
||||
|
||||
loadStage(callback, _tiles, ext2files["material"] ?: listOf())
|
||||
loadStage(callback, _tileModifiers, ext2files["matmod"] ?: listOf())
|
||||
@ -888,16 +920,17 @@ class Starbound : ISBFileLocator {
|
||||
|
||||
private fun loadJsonFunctions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
|
||||
for (listedFile in files) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||
callback("Loading $listedFile")
|
||||
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||
|
||||
for ((k, v) in json.entrySet()) {
|
||||
for ((k, v) in json.entrySet()) {
|
||||
try {
|
||||
callback("Loading $k from $listedFile")
|
||||
val fn = gson.fromJson<JsonFunction>(JsonTreeReader(v), JsonFunction::class.java)
|
||||
_jsonFunctions.add(fn, json, listedFile, gson, pathStack, k)
|
||||
_jsonFunctions.add(fn, v, listedFile, gson, pathStack, k)
|
||||
} catch (err: Throwable) {
|
||||
logger.error("Loading json function definition $k from file $listedFile", err)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
logger.error("Loading json function definition file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
@ -908,16 +941,39 @@ class Starbound : ISBFileLocator {
|
||||
|
||||
private fun loadJson2Functions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
|
||||
for (listedFile in files) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||
callback("Loading $listedFile")
|
||||
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||
|
||||
for ((k, v) in json.entrySet()) {
|
||||
for ((k, v) in json.entrySet()) {
|
||||
try {
|
||||
callback("Loading $k from $listedFile")
|
||||
val fn = gson.fromJson<Json2Function>(JsonTreeReader(v), Json2Function::class.java)
|
||||
_json2Functions.add(fn, json, listedFile, gson, pathStack, k)
|
||||
_json2Functions.add(fn, v, listedFile, gson, pathStack, k)
|
||||
} catch (err: Throwable) {
|
||||
logger.error("Loading json 2function definition $k from file $listedFile", err)
|
||||
}
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadTreasurePools(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
|
||||
for (listedFile in files) {
|
||||
callback("Loading $listedFile")
|
||||
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||
|
||||
for ((k, v) in json.entrySet()) {
|
||||
try {
|
||||
callback("Loading $k from $listedFile")
|
||||
val result = gson.fromJson<TreasurePoolDefinition>(JsonTreeReader(v), TreasurePoolDefinition::class.java)
|
||||
result.name = k
|
||||
_treasurePools.add(result, v, listedFile, gson, pathStack)
|
||||
} catch (err: Throwable) {
|
||||
logger.error("Loading treasure pool definition $k from file $listedFile", err)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
logger.error("Loading json 2function definition file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
|
@ -3,22 +3,7 @@ package ru.dbotthepony.kstarbound.defs
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.internal.bind.JsonTreeReader
|
||||
import ru.dbotthepony.kstarbound.RegistryObject
|
||||
|
||||
private fun merge(destination: JsonObject, source: JsonObject) {
|
||||
for ((k, v) in source.entrySet()) {
|
||||
if (v is JsonObject) {
|
||||
val original = destination[k]
|
||||
|
||||
if (original is JsonObject) {
|
||||
merge(original, v)
|
||||
} else {
|
||||
destination.add(k, v)
|
||||
}
|
||||
} else {
|
||||
destination.add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
import ru.dbotthepony.kstarbound.mergeJsonElements
|
||||
|
||||
abstract class DynamicDefinition<Def : Any>(val original: RegistryObject<Def>) {
|
||||
@Volatile
|
||||
@ -36,7 +21,7 @@ abstract class DynamicDefinition<Def : Any>(val original: RegistryObject<Def>) {
|
||||
if (!isDirty) return field
|
||||
|
||||
val copy = original.copy()
|
||||
merge(copy, dynamicData)
|
||||
mergeJsonElements(copy, dynamicData)
|
||||
|
||||
original.pathStack(original.file.computeDirectory()) {
|
||||
field = original.gson.fromJson(JsonTreeReader(copy), field::class.java)
|
||||
|
@ -31,14 +31,15 @@ data class ItemReference(
|
||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (type.rawType == ItemReference::class.java) {
|
||||
return object : TypeAdapter<ItemReference>() {
|
||||
private val regular = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, logMisses = true, asList = false), gson, stringInterner)
|
||||
private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, logMisses = true, asList = false), gson, stringInterner)
|
||||
private val regularList = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, logMisses = true, asList = true), gson, stringInterner)
|
||||
private val references = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, IItemDefinition::class.java)) as TypeAdapter<RegistryReference<IItemDefinition>>
|
||||
|
||||
override fun write(out: JsonWriter, value: ItemReference?) {
|
||||
if (value == null)
|
||||
out.nullValue()
|
||||
else
|
||||
regular.write(out, value)
|
||||
regularObject.write(out, value)
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): ItemReference? {
|
||||
@ -47,8 +48,10 @@ data class ItemReference(
|
||||
|
||||
if (`in`.peek() == JsonToken.STRING) {
|
||||
return ItemReference(references.read(`in`))
|
||||
} else if (`in`.peek() == JsonToken.BEGIN_ARRAY) {
|
||||
return regularList.read(`in`)
|
||||
} else {
|
||||
return regular.read(`in`)
|
||||
return regularObject.read(`in`)
|
||||
}
|
||||
}
|
||||
} as TypeAdapter<T>
|
||||
|
@ -9,12 +9,15 @@ import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.ObjectRegistry
|
||||
import ru.dbotthepony.kstarbound.RegistryObject
|
||||
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.util.function.Supplier
|
||||
|
||||
class RegistryReferenceFactory : TypeAdapterFactory {
|
||||
private val types = Reference2ObjectArrayMap<Class<*>, (String) -> RegistryObject<Nothing>?>()
|
||||
private val types = Reference2ObjectArrayMap<Class<*>, Pair<(String) -> RegistryObject<Nothing>?, String>>()
|
||||
private var isLenient = false
|
||||
|
||||
fun lenient(): RegistryReferenceFactory {
|
||||
@ -22,26 +25,30 @@ class RegistryReferenceFactory : TypeAdapterFactory {
|
||||
return this
|
||||
}
|
||||
|
||||
fun <T : Any> add(clazz: Class<T>, resolver: (String) -> RegistryObject<T>?): RegistryReferenceFactory {
|
||||
check(types.put(clazz, resolver as (String) -> RegistryObject<Nothing>?) == null) { "Already has resolver for class $clazz!" }
|
||||
fun <T : Any> add(clazz: Class<T>, resolver: (String) -> RegistryObject<T>?, name: String): RegistryReferenceFactory {
|
||||
check(types.put(clazz, (resolver as (String) -> RegistryObject<Nothing>?) to name) == null) { "Already has resolver for class $clazz!" }
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun <reified T: Any> add(noinline resolver: (String) -> RegistryObject<T>?) = add(T::class.java, resolver)
|
||||
fun <T : Any> add(registry: ObjectRegistry<T>): RegistryReferenceFactory {
|
||||
return add(registry.clazz.java, registry.view::get, registry.name)
|
||||
}
|
||||
|
||||
inline fun <reified T: Any> add(noinline resolver: (String) -> RegistryObject<T>?, name: String) = add(T::class.java, resolver, name)
|
||||
|
||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (type.rawType == RegistryReference::class.java) {
|
||||
val ptype = type.type as? ParameterizedType ?: return null
|
||||
val registryType = ptype.actualTypeArguments[0]
|
||||
val resolver = types[registryType] ?: return if (isLenient) null else throw NoSuchElementException("Can't deserialize registry reference with type $registryType!")
|
||||
return RegistryReferenceTypeAdapter(resolver, gson.getAdapter(String::class.java)) as TypeAdapter<T>
|
||||
return RegistryReferenceTypeAdapter(resolver.first, gson.getAdapter(String::class.java), resolver.second) as TypeAdapter<T>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class RegistryReferenceTypeAdapter<T : Any>(val resolver: (String) -> RegistryObject<T>?, val strings: TypeAdapter<String>) : TypeAdapter<RegistryReference<T>>() {
|
||||
class RegistryReferenceTypeAdapter<T : Any>(val resolver: (String) -> RegistryObject<T>?, val strings: TypeAdapter<String>, val name: String) : TypeAdapter<RegistryReference<T>>() {
|
||||
override fun write(out: JsonWriter, value: RegistryReference<T>?) {
|
||||
if (value == null)
|
||||
out.nullValue()
|
||||
@ -50,19 +57,27 @@ class RegistryReferenceTypeAdapter<T : Any>(val resolver: (String) -> RegistryOb
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): RegistryReference<T>? {
|
||||
if (`in`.peek() == JsonToken.NULL)
|
||||
if (`in`.consumeNull())
|
||||
return null
|
||||
|
||||
if (`in`.peek() == JsonToken.STRING) {
|
||||
return RegistryReference(strings.read(`in`)!!, resolver)
|
||||
return RegistryReference(strings.read(`in`)!!, resolver, name)
|
||||
}
|
||||
|
||||
throw JsonSyntaxException("Expecting string for registry reference, ${`in`.peek()} given, near ${`in`.path}")
|
||||
}
|
||||
}
|
||||
|
||||
data class RegistryReference<T : Any>(val name: String, val resolver: (String) -> RegistryObject<T>?) : Supplier<RegistryObject<T>?>, () -> RegistryObject<T>?, Lazy<RegistryObject<T>?> {
|
||||
private val lazy = lazy { resolver.invoke(name) }
|
||||
data class RegistryReference<T : Any>(val name: String, val resolver: (String) -> RegistryObject<T>?, val registryName: String) : Supplier<RegistryObject<T>?>, () -> RegistryObject<T>?, Lazy<RegistryObject<T>?> {
|
||||
private val lazy = lazy {
|
||||
val result = resolver.invoke(name)
|
||||
|
||||
if (result == null) {
|
||||
LOGGER.error("No such object '$name' in registry '$registryName'! Expect stuff being broken!")
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
override fun get(): RegistryObject<T>? {
|
||||
return lazy.value
|
||||
@ -78,4 +93,8 @@ data class RegistryReference<T : Any>(val name: String, val resolver: (String) -
|
||||
override fun invoke(): RegistryObject<T>? {
|
||||
return lazy.value
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,259 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.internal.bind.JsonTreeReader
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kstarbound.defs.ItemReference
|
||||
import ru.dbotthepony.kstarbound.defs.RegistryReference
|
||||
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||
import ru.dbotthepony.kstarbound.io.json.stream
|
||||
import ru.dbotthepony.kstarbound.util.Either
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
import ru.dbotthepony.kstarbound.util.WriteOnce
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
class TreasurePoolDefinition(pieces: List<Piece>) {
|
||||
var name: String by WriteOnce()
|
||||
|
||||
init {
|
||||
require(pieces.isNotEmpty()) { "Treasure pool is empty" }
|
||||
}
|
||||
|
||||
val pieces: ImmutableList<Piece> = pieces.stream().sorted { o1, o2 -> o1.level.compareTo(o2.level) }.collect(ImmutableList.toImmutableList())
|
||||
|
||||
fun evaluate(random: RandomGenerator, level: Double): List<ItemStack> {
|
||||
require(level >= 0.0) { "Invalid loot level: $level" }
|
||||
@Suppress("name_shadowing")
|
||||
var level = level
|
||||
|
||||
for (piece in pieces) {
|
||||
if (level <= piece.level) {
|
||||
return piece.evaluate(random, level)
|
||||
} else {
|
||||
level -= piece.level
|
||||
}
|
||||
}
|
||||
|
||||
if (pieces.last().level <= level) {
|
||||
return pieces.last().evaluate(random, level)
|
||||
} else {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
data class Piece(
|
||||
val level: Double,
|
||||
val pool: ImmutableList<PoolEntry> = ImmutableList.of(),
|
||||
val fill: ImmutableList<Either<ItemReference, RegistryReference<TreasurePoolDefinition>>> = ImmutableList.of(),
|
||||
val poolRounds: IPoolRounds = OneRound,
|
||||
// TODO: что оно делает?
|
||||
// оно точно не запрещает ему появляться несколько раз за одну генерацию treasure pool
|
||||
// а так же не "дублирует" содержимое если уровень генерации выше, чем указанный level
|
||||
val allowDuplication: Boolean = false
|
||||
) {
|
||||
val maxWeight = pool.stream().mapToDouble { it.weight }.sum()
|
||||
|
||||
fun evaluate(random: RandomGenerator, actualLevel: Double): List<ItemStack> {
|
||||
val rounds = poolRounds.evaluate(random)
|
||||
if (rounds <= 0) return emptyList()
|
||||
val result = ArrayList<ItemStack>()
|
||||
|
||||
for (round in 0 until rounds) {
|
||||
for (entry in fill) {
|
||||
entry.map(left = {
|
||||
val stack = it.makeStack()
|
||||
if (stack.isNotEmpty) result.add(stack)
|
||||
}, right = {
|
||||
it.value?.value?.evaluate(random, actualLevel)
|
||||
})
|
||||
}
|
||||
|
||||
if (pool.isNotEmpty()) {
|
||||
var chosen = random.nextDouble(maxWeight)
|
||||
|
||||
for (entry in pool) {
|
||||
if (chosen <= entry.weight) {
|
||||
entry.treasure.map(left = {
|
||||
val stack = it.makeStack()
|
||||
if (stack.isNotEmpty) result.add(stack)
|
||||
}, right = {
|
||||
it.value?.value?.evaluate(random, actualLevel)
|
||||
})
|
||||
|
||||
break
|
||||
} else {
|
||||
chosen -= entry.weight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
interface IPoolRounds {
|
||||
fun evaluate(random: RandomGenerator): Int
|
||||
}
|
||||
|
||||
object OneRound : IPoolRounds {
|
||||
override fun evaluate(random: RandomGenerator): Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
data class ConstantRound(val amount: Int) : IPoolRounds {
|
||||
init {
|
||||
check(amount >= 1) { "Invalid treasure pool rounds: $amount" }
|
||||
}
|
||||
|
||||
override fun evaluate(random: RandomGenerator): Int {
|
||||
return amount
|
||||
}
|
||||
}
|
||||
|
||||
data class PoolRounds(val edges: ImmutableList<RoundEdge>) : IPoolRounds {
|
||||
val maxWeight = edges.stream().mapToDouble { it.weight }.sum()
|
||||
|
||||
override fun evaluate(random: RandomGenerator): Int {
|
||||
val result = random.nextDouble(maxWeight)
|
||||
var lower = 0.0
|
||||
|
||||
for (edge in edges) {
|
||||
if (result in lower .. lower + edge.weight) {
|
||||
return edge.rounds
|
||||
} else {
|
||||
lower += edge.weight
|
||||
}
|
||||
}
|
||||
|
||||
return edges.last().rounds
|
||||
}
|
||||
}
|
||||
|
||||
data class RoundEdge(val weight: Double, val rounds: Int) {
|
||||
init {
|
||||
require(weight > 0.0) { "Invalid round weight: $weight" }
|
||||
require(rounds >= 0) { "Invalid rounds amount: $rounds" }
|
||||
}
|
||||
}
|
||||
|
||||
data class PoolEntry(
|
||||
val weight: Double,
|
||||
val treasure: Either<ItemReference, RegistryReference<TreasurePoolDefinition>>
|
||||
) {
|
||||
init {
|
||||
require(weight > 0.0) { "Invalid pool entry weight: $weight" }
|
||||
}
|
||||
}
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (type.rawType === TreasurePoolDefinition::class.java) {
|
||||
return object : TypeAdapter<TreasurePoolDefinition>() {
|
||||
private val itemAdapter = gson.getAdapter(ItemReference::class.java)
|
||||
private val poolAdapter = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, TreasurePoolDefinition::class.java)) as TypeAdapter<RegistryReference<TreasurePoolDefinition>>
|
||||
private val objReader = gson.getAdapter(JsonObject::class.java)
|
||||
|
||||
override fun write(out: JsonWriter, value: TreasurePoolDefinition?) {
|
||||
if (value == null) {
|
||||
out.nullValue()
|
||||
} else {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): TreasurePoolDefinition? {
|
||||
if (`in`.consumeNull()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val pieces = ArrayList<Piece>()
|
||||
|
||||
`in`.beginArray()
|
||||
|
||||
while (`in`.hasNext()) {
|
||||
`in`.beginArray()
|
||||
|
||||
val level = `in`.nextDouble()
|
||||
val things = objReader.read(`in`)
|
||||
|
||||
val pool = ImmutableList.Builder<PoolEntry>()
|
||||
val fill = ImmutableList.Builder<Either<ItemReference, RegistryReference<TreasurePoolDefinition>>>()
|
||||
var poolRounds: IPoolRounds = OneRound
|
||||
val allowDuplication = things["allowDuplication"]?.asBoolean ?: false
|
||||
|
||||
things["poolRounds"]?.let {
|
||||
if (it is JsonPrimitive) {
|
||||
poolRounds = ConstantRound(it.asInt)
|
||||
} else if (it is JsonArray) {
|
||||
poolRounds = PoolRounds(it.stream().map { it as JsonArray; RoundEdge(it[0].asDouble, it[1].asInt) }.collect(ImmutableList.toImmutableList()))
|
||||
} else {
|
||||
throw JsonSyntaxException("Expected either a number or an array for poolRounds, but got ${it::class.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
things["pool"]?.let {
|
||||
it as? JsonArray ?: throw JsonSyntaxException("pool must be an array")
|
||||
|
||||
for ((i, elem) in it.withIndex()) {
|
||||
elem as? JsonObject ?: throw JsonSyntaxException("Pool element at $i is not an object")
|
||||
val weight = (elem["weight"] as? JsonPrimitive)?.asDouble ?: throw JsonSyntaxException("Pool element at $i is missing weight")
|
||||
|
||||
if (elem.has("item")) {
|
||||
pool.add(PoolEntry(weight, Either.left(itemAdapter.read(JsonTreeReader(elem["item"]!!)))))
|
||||
} else if (elem.has("pool")) {
|
||||
pool.add(PoolEntry(weight, Either.right(poolAdapter.read(JsonTreeReader(elem["pool"]!!)))))
|
||||
} else {
|
||||
throw JsonSyntaxException("Pool element at $i is missing both 'item' and 'pool' entries")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
things["fill"]?.let {
|
||||
it as? JsonArray ?: throw JsonSyntaxException("fill must be an array")
|
||||
|
||||
for ((i, elem) in it.withIndex()) {
|
||||
elem as? JsonObject ?: throw JsonSyntaxException("Fill element at $i is not an object")
|
||||
|
||||
if (elem.has("item")) {
|
||||
fill.add(Either.left(itemAdapter.read(JsonTreeReader(elem["item"]!!))))
|
||||
} else if (elem.has("pool")) {
|
||||
fill.add(Either.right(poolAdapter.read(JsonTreeReader(elem["pool"]!!))))
|
||||
} else {
|
||||
throw JsonSyntaxException("Fill element at $i is missing both 'item' and 'pool' entries")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pieces.add(Piece(
|
||||
pool = pool.build(),
|
||||
fill = fill.build(),
|
||||
poolRounds = poolRounds,
|
||||
level = level,
|
||||
allowDuplication = allowDuplication
|
||||
))
|
||||
|
||||
`in`.endArray()
|
||||
}
|
||||
|
||||
`in`.endArray()
|
||||
|
||||
return TreasurePoolDefinition(pieces)
|
||||
}
|
||||
} as TypeAdapter<T>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -21,26 +21,26 @@ object EitherTypeAdapter : TypeAdapterFactory {
|
||||
val params = type.type as? ParameterizedType ?: return null
|
||||
val (left, right) = params.actualTypeArguments
|
||||
|
||||
return object : TypeAdapter<Either<Any?, Any?>>() {
|
||||
return object : TypeAdapter<Either<Any, Any>>() {
|
||||
private val leftAdapter = gson.getAdapter(TypeToken.get(left)) as TypeAdapter<Any?>
|
||||
private val rightAdapter = gson.getAdapter(TypeToken.get(right)) as TypeAdapter<Any?>
|
||||
|
||||
override fun write(out: JsonWriter, value: Either<Any?, Any?>?) {
|
||||
override fun write(out: JsonWriter, value: Either<Any, Any>?) {
|
||||
if (value == null)
|
||||
out.nullValue()
|
||||
else
|
||||
value.consume({ leftAdapter.write(out, it) }, { rightAdapter.write(out, it) })
|
||||
value.map({ leftAdapter.write(out, it) }, { rightAdapter.write(out, it) })
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): Either<Any?, Any?>? {
|
||||
override fun read(`in`: JsonReader): Either<Any, Any>? {
|
||||
if (`in`.peek() == JsonToken.NULL)
|
||||
return null
|
||||
|
||||
return try {
|
||||
Either.left(leftAdapter.read(`in`))
|
||||
Either.left(leftAdapter.read(`in`) ?: throw NullPointerException("left was empty"))
|
||||
} catch(leftError: Throwable) {
|
||||
try {
|
||||
Either.right(rightAdapter.read(`in`))
|
||||
Either.right(rightAdapter.read(`in`) ?: throw NullPointerException("right was empty"))
|
||||
} catch(rightError: Throwable) {
|
||||
val error = JsonSyntaxException("Can't read Either of values (left is $left, right is $right)")
|
||||
error.addSuppressed(leftError)
|
||||
|
@ -7,13 +7,13 @@ import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
|
||||
*
|
||||
* JSON адаптер реализуется через [EitherTypeAdapter]
|
||||
*/
|
||||
data class Either<L, R>(val left: L?, val right: R?) {
|
||||
data class Either<L : Any, R : Any>(val left: L?, val right: R?) {
|
||||
init {
|
||||
require(left != null || right != null) { "Both inputs are null" }
|
||||
require(!(left != null && right != null)) { "Both inputs are not null" }
|
||||
}
|
||||
|
||||
inline fun consume(left: (L) -> Unit, right: (R) -> Unit) {
|
||||
inline fun map(left: (L) -> Unit, right: (R) -> Unit) {
|
||||
if (this.left != null)
|
||||
left.invoke(this.left)
|
||||
else
|
||||
@ -29,13 +29,13 @@ data class Either<L, R>(val left: L?, val right: R?) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun <L, R> left(value: L): Either<L, R> {
|
||||
return Either(left = value ?: throw NullPointerException("Left is null"), right = null)
|
||||
fun <L : Any, R : Any> left(value: L): Either<L, R> {
|
||||
return Either(left = value, right = null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <L, R> right(value: R): Either<L, R> {
|
||||
return Either(left = null, right = value ?: throw NullPointerException("Right is null"))
|
||||
fun <L : Any, R : Any> right(value: R): Either<L, R> {
|
||||
return Either(left = null, right = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user