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
|
last = JVMTimeSource.INSTANCE.millis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println(starbound.treasurePools["motherpoptopTreasure"]!!.value.evaluate(Random(), 2.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
import com.google.gson.internal.bind.JsonTreeReader
|
import com.google.gson.internal.bind.JsonTreeReader
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
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)
|
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()) {
|
for ((k, v) in source.entrySet()) {
|
||||||
if (!destination.has(k)) {
|
if (!destination.has(k)) {
|
||||||
destination[k] = v.deepCopy()
|
destination[k] = v.deepCopy()
|
||||||
} else if (destination[k] is JsonObject && v is JsonObject) {
|
} else {
|
||||||
merge(v, destination[k] as JsonObject)
|
mergeJsonElements(v, destination[k])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return destination
|
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 структуры, из которой был спрототипирован [value]
|
||||||
*
|
*
|
||||||
* Более полная JSON структура (обработанная) доступа из метода [toJson]
|
* Более полная JSON структура (обработанная) доступа из метода [toJson]
|
||||||
*/
|
*/
|
||||||
fun copy(): JsonObject {
|
fun copy(): JsonElement {
|
||||||
return json.deepCopy()
|
return json.deepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,8 +112,8 @@ class RegistryObject<T : Any>(val value: T, private val json: JsonObject, val fi
|
|||||||
* Полнота определяется тем, что [value] может иметь свойства по умолчанию, которые не указаны
|
* Полнота определяется тем, что [value] может иметь свойства по умолчанию, которые не указаны
|
||||||
* в оригинальной JSON структуре. [copy] не вернёт данные свойства по умолчанию, а [toJson] вернёт.
|
* в оригинальной JSON структуре. [copy] не вернёт данные свойства по умолчанию, а [toJson] вернёт.
|
||||||
*/
|
*/
|
||||||
fun toJson(): JsonObject {
|
fun toJson(): JsonElement {
|
||||||
return merge(json, gson.toJsonTree(value) as JsonObject)
|
return mergeJsonElements(json, gson.toJsonTree(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
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 {
|
fun add(gson: Gson, file: IStarboundFile, pathStack: PathStack): Boolean {
|
||||||
return pathStack(file.computeDirectory()) {
|
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)
|
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"))
|
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"))
|
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)
|
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.IArmorItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
|
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.ItemPrototype
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemPrototype
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemPrototype
|
||||||
@ -146,6 +147,9 @@ class Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
val recipeRegistry = RecipeRegistry()
|
val recipeRegistry = RecipeRegistry()
|
||||||
|
|
||||||
|
private val _treasurePools = ObjectRegistry("treasure pools", TreasurePoolDefinition::name)
|
||||||
|
val treasurePools = _treasurePools.view
|
||||||
|
|
||||||
val gson: Gson = with(GsonBuilder()) {
|
val gson: Gson = with(GsonBuilder()) {
|
||||||
serializeNulls()
|
serializeNulls()
|
||||||
setDateFormat(DateFormat.LONG)
|
setDateFormat(DateFormat.LONG)
|
||||||
@ -221,22 +225,24 @@ class Starbound : ISBFileLocator {
|
|||||||
registerTypeAdapter(ItemStack.Adapter(this@Starbound))
|
registerTypeAdapter(ItemStack.Adapter(this@Starbound))
|
||||||
|
|
||||||
registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
|
registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
|
||||||
|
registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
|
||||||
|
|
||||||
registerTypeAdapterFactory(with(RegistryReferenceFactory()) {
|
registerTypeAdapterFactory(with(RegistryReferenceFactory()) {
|
||||||
add(tiles::get)
|
add(_tiles)
|
||||||
add(tileModifiers::get)
|
add(_tileModifiers)
|
||||||
add(liquid::get)
|
add(_liquid)
|
||||||
add(items::get)
|
add(_items)
|
||||||
add(species::get)
|
add(_species)
|
||||||
add(statusEffects::get)
|
add(_statusEffects)
|
||||||
add(particles::get)
|
add(_particles)
|
||||||
add(questTemplates::get)
|
add(_questTemplates)
|
||||||
add(techs::get)
|
add(_techs)
|
||||||
add(jsonFunctions::get)
|
add(_jsonFunctions)
|
||||||
add(json2Functions::get)
|
add(_json2Functions)
|
||||||
add(npcTypes::get)
|
add(_npcTypes)
|
||||||
add(projectiles::get)
|
add(_projectiles)
|
||||||
add(tenants::get)
|
add(_tenants)
|
||||||
|
add(_treasurePools)
|
||||||
})
|
})
|
||||||
|
|
||||||
registerTypeAdapter(LongRangeAdapter)
|
registerTypeAdapter(LongRangeAdapter)
|
||||||
@ -614,6 +620,31 @@ class Starbound : ISBFileLocator {
|
|||||||
1
|
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.pop()
|
||||||
|
|
||||||
state.load(polyfill, "@starbound.jar!/scripts/polyfill.lua")
|
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, { loadJsonFunctions(it, ext2files["functions"] ?: listOf()) }, "json functions")
|
||||||
loadStage(callback, { loadJson2Functions(it, ext2files["2functions"] ?: listOf()) }, "json 2functions")
|
loadStage(callback, { loadJson2Functions(it, ext2files["2functions"] ?: listOf()) }, "json 2functions")
|
||||||
loadStage(callback, { loadRecipes(it, ext2files["recipe"] ?: listOf()) }, "recipes")
|
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, _tiles, ext2files["material"] ?: listOf())
|
||||||
loadStage(callback, _tileModifiers, ext2files["matmod"] ?: listOf())
|
loadStage(callback, _tileModifiers, ext2files["matmod"] ?: listOf())
|
||||||
@ -888,16 +920,17 @@ class Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
private fun loadJsonFunctions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
|
private fun loadJsonFunctions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
|
||||||
for (listedFile in files) {
|
for (listedFile in files) {
|
||||||
try {
|
callback("Loading $listedFile")
|
||||||
callback("Loading $listedFile")
|
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||||
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)
|
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) {
|
if (terminateLoading) {
|
||||||
@ -908,16 +941,39 @@ class Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
private fun loadJson2Functions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
|
private fun loadJson2Functions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
|
||||||
for (listedFile in files) {
|
for (listedFile in files) {
|
||||||
try {
|
callback("Loading $listedFile")
|
||||||
callback("Loading $listedFile")
|
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||||
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)
|
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) {
|
if (terminateLoading) {
|
||||||
|
@ -3,22 +3,7 @@ package ru.dbotthepony.kstarbound.defs
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.internal.bind.JsonTreeReader
|
import com.google.gson.internal.bind.JsonTreeReader
|
||||||
import ru.dbotthepony.kstarbound.RegistryObject
|
import ru.dbotthepony.kstarbound.RegistryObject
|
||||||
|
import ru.dbotthepony.kstarbound.mergeJsonElements
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DynamicDefinition<Def : Any>(val original: RegistryObject<Def>) {
|
abstract class DynamicDefinition<Def : Any>(val original: RegistryObject<Def>) {
|
||||||
@Volatile
|
@Volatile
|
||||||
@ -36,7 +21,7 @@ abstract class DynamicDefinition<Def : Any>(val original: RegistryObject<Def>) {
|
|||||||
if (!isDirty) return field
|
if (!isDirty) return field
|
||||||
|
|
||||||
val copy = original.copy()
|
val copy = original.copy()
|
||||||
merge(copy, dynamicData)
|
mergeJsonElements(copy, dynamicData)
|
||||||
|
|
||||||
original.pathStack(original.file.computeDirectory()) {
|
original.pathStack(original.file.computeDirectory()) {
|
||||||
field = original.gson.fromJson(JsonTreeReader(copy), field::class.java)
|
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>? {
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == ItemReference::class.java) {
|
if (type.rawType == ItemReference::class.java) {
|
||||||
return object : TypeAdapter<ItemReference>() {
|
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>>
|
private val references = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, IItemDefinition::class.java)) as TypeAdapter<RegistryReference<IItemDefinition>>
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: ItemReference?) {
|
override fun write(out: JsonWriter, value: ItemReference?) {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
out.nullValue()
|
out.nullValue()
|
||||||
else
|
else
|
||||||
regular.write(out, value)
|
regularObject.write(out, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): ItemReference? {
|
override fun read(`in`: JsonReader): ItemReference? {
|
||||||
@ -47,8 +48,10 @@ data class ItemReference(
|
|||||||
|
|
||||||
if (`in`.peek() == JsonToken.STRING) {
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
return ItemReference(references.read(`in`))
|
return ItemReference(references.read(`in`))
|
||||||
|
} else if (`in`.peek() == JsonToken.BEGIN_ARRAY) {
|
||||||
|
return regularList.read(`in`)
|
||||||
} else {
|
} else {
|
||||||
return regular.read(`in`)
|
return regularObject.read(`in`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as TypeAdapter<T>
|
} as TypeAdapter<T>
|
||||||
|
@ -9,12 +9,15 @@ import com.google.gson.stream.JsonReader
|
|||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap
|
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.RegistryObject
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
|
|
||||||
class RegistryReferenceFactory : TypeAdapterFactory {
|
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
|
private var isLenient = false
|
||||||
|
|
||||||
fun lenient(): RegistryReferenceFactory {
|
fun lenient(): RegistryReferenceFactory {
|
||||||
@ -22,26 +25,30 @@ class RegistryReferenceFactory : TypeAdapterFactory {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> add(clazz: Class<T>, resolver: (String) -> RegistryObject<T>?): RegistryReferenceFactory {
|
fun <T : Any> add(clazz: Class<T>, resolver: (String) -> RegistryObject<T>?, name: String): RegistryReferenceFactory {
|
||||||
check(types.put(clazz, resolver as (String) -> RegistryObject<Nothing>?) == null) { "Already has resolver for class $clazz!" }
|
check(types.put(clazz, (resolver as (String) -> RegistryObject<Nothing>?) to name) == null) { "Already has resolver for class $clazz!" }
|
||||||
return this
|
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>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == RegistryReference::class.java) {
|
if (type.rawType == RegistryReference::class.java) {
|
||||||
val ptype = type.type as? ParameterizedType ?: return null
|
val ptype = type.type as? ParameterizedType ?: return null
|
||||||
val registryType = ptype.actualTypeArguments[0]
|
val registryType = ptype.actualTypeArguments[0]
|
||||||
val resolver = types[registryType] ?: return if (isLenient) null else throw NoSuchElementException("Can't deserialize registry reference with type $registryType!")
|
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
|
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>?) {
|
override fun write(out: JsonWriter, value: RegistryReference<T>?) {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
out.nullValue()
|
out.nullValue()
|
||||||
@ -50,19 +57,27 @@ class RegistryReferenceTypeAdapter<T : Any>(val resolver: (String) -> RegistryOb
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): RegistryReference<T>? {
|
override fun read(`in`: JsonReader): RegistryReference<T>? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (`in`.peek() == JsonToken.STRING) {
|
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}")
|
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>?> {
|
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 { resolver.invoke(name) }
|
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>? {
|
override fun get(): RegistryObject<T>? {
|
||||||
return lazy.value
|
return lazy.value
|
||||||
@ -78,4 +93,8 @@ data class RegistryReference<T : Any>(val name: String, val resolver: (String) -
|
|||||||
override fun invoke(): RegistryObject<T>? {
|
override fun invoke(): RegistryObject<T>? {
|
||||||
return lazy.value
|
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 params = type.type as? ParameterizedType ?: return null
|
||||||
val (left, right) = params.actualTypeArguments
|
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 leftAdapter = gson.getAdapter(TypeToken.get(left)) as TypeAdapter<Any?>
|
||||||
private val rightAdapter = gson.getAdapter(TypeToken.get(right)) 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)
|
if (value == null)
|
||||||
out.nullValue()
|
out.nullValue()
|
||||||
else
|
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)
|
if (`in`.peek() == JsonToken.NULL)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
Either.left(leftAdapter.read(`in`))
|
Either.left(leftAdapter.read(`in`) ?: throw NullPointerException("left was empty"))
|
||||||
} catch(leftError: Throwable) {
|
} catch(leftError: Throwable) {
|
||||||
try {
|
try {
|
||||||
Either.right(rightAdapter.read(`in`))
|
Either.right(rightAdapter.read(`in`) ?: throw NullPointerException("right was empty"))
|
||||||
} catch(rightError: Throwable) {
|
} catch(rightError: Throwable) {
|
||||||
val error = JsonSyntaxException("Can't read Either of values (left is $left, right is $right)")
|
val error = JsonSyntaxException("Can't read Either of values (left is $left, right is $right)")
|
||||||
error.addSuppressed(leftError)
|
error.addSuppressed(leftError)
|
||||||
|
@ -7,13 +7,13 @@ import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
|
|||||||
*
|
*
|
||||||
* 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 {
|
init {
|
||||||
require(left != null || right != null) { "Both inputs are null" }
|
require(left != null || right != null) { "Both inputs are null" }
|
||||||
require(!(left != null && right != null)) { "Both inputs are not 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)
|
if (this.left != null)
|
||||||
left.invoke(this.left)
|
left.invoke(this.left)
|
||||||
else
|
else
|
||||||
@ -29,13 +29,13 @@ data class Either<L, R>(val left: L?, val right: R?) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <L, R> left(value: L): Either<L, R> {
|
fun <L : Any, R : Any> left(value: L): Either<L, R> {
|
||||||
return Either(left = value ?: throw NullPointerException("Left is null"), right = null)
|
return Either(left = value, right = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <L, R> right(value: R): Either<L, R> {
|
fun <L : Any, R : Any> right(value: R): Either<L, R> {
|
||||||
return Either(left = null, right = value ?: throw NullPointerException("Right is null"))
|
return Either(left = null, right = value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user