KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/TreasurePoolDefinition.kt

256 lines
7.9 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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