256 lines
7.9 KiB
Kotlin
256 lines
7.9 KiB
Kotlin
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" }
|
||
|
||
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<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.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<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
|
||
}
|
||
}
|
||
}
|