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) { var name: String by WriteOnce() init { require(pieces.isNotEmpty()) { "Treasure pool is empty" } } val pieces: ImmutableList = pieces.stream().sorted { o1, o2 -> o1.level.compareTo(o2.level) }.collect(ImmutableList.toImmutableList()) fun evaluate(random: RandomGenerator, level: Double): List { require(level >= 0.0) { "Invalid loot level: $level" } for (piece in pieces) { if (level <= piece.level) { return piece.evaluate(random, level) } } if (pieces.last().level <= level) { return pieces.last().evaluate(random, level) } else { return emptyList() } } data class Piece( val level: Double, val pool: ImmutableList = ImmutableList.of(), val fill: ImmutableList>> = 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 { val rounds = poolRounds.evaluate(random) if (rounds <= 0) return emptyList() val result = ArrayList() for (round in 0 until rounds) { for (entry in fill) { entry.run(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.run(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) : 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> ) { init { require(weight > 0.0) { "Invalid pool entry weight: $weight" } } } companion object : TypeAdapterFactory { override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType === TreasurePoolDefinition::class.java) { return object : TypeAdapter() { private val itemAdapter = gson.getAdapter(ItemReference::class.java) private val poolAdapter = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, TreasurePoolDefinition::class.java)) as TypeAdapter> 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() `in`.beginArray() while (`in`.hasNext()) { `in`.beginArray() val level = `in`.nextDouble() val things = objReader.read(`in`) val pool = ImmutableList.Builder() val fill = ImmutableList.Builder>>() 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 } return null } } }