root.isTreasurePool и root.createTreasure, и полноценная подгрузка treasure pool

This commit is contained in:
DBotThePony 2023-03-31 11:05:48 +07:00
parent aa4f73bc01
commit 2d87575bfc
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 456 additions and 80 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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) {

View File

@ -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)

View File

@ -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>

View File

@ -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()
}
}

View File

@ -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
}
}
}

View File

@ -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)

View File

@ -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)
}
}
}