package ru.dbotthepony.kstarbound.defs.dungeon import com.google.common.collect.ImmutableSet import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.annotations.JsonAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kstarbound.defs.tile.isEmptyLiquid import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile import ru.dbotthepony.kstarbound.defs.tile.isObjectTile import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.stream import ru.dbotthepony.kstarbound.server.world.ServerWorld @JsonAdapter(DungeonRule.Adapter::class) abstract class DungeonRule { enum class Type(override val jsonName: String, val factory: (JsonArray) -> DungeonRule) : IStringSerializable { NOOP("", { Noop }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { return null } }, MUST_CONTAIN_LIQUID("worldGenMustContainLiquid", { MustContainLiquid }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { if ("worldGenMustContainLiquid" in json) return MustContainLiquid return null } }, MUST_NOT_CONTAIN_LIQUID("worldGenMustNotContainLiquid", { MustNotContainLiquid }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { if ("worldGenMustNotContainLiquid" in json) return MustNotContainLiquid return null } }, HAVE_SOLID_FOREGROUND("worldGenMustContainSolidForeground", { HaveSolidForeground }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { if (json["layer"].asString == "front" && "worldGenMustContainSolid" in json) return HaveSolidForeground return null } }, HAVE_EMPTY_FOREGROUND("worldGenMustContainAirForeground", { HaveEmptyForeground }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { if (json["layer"].asString == "front" && "worldGenMustContainAir" in json) return HaveEmptyForeground return null } }, HAVE_SOLID_BACKGROUND("worldGenMustContainSolidBackground", { HaveSolidBackground }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { if (json["layer"].asString == "back" && "worldGenMustContainSolid" in json) return HaveSolidBackground return null } }, HAVE_EMPTY_BACKGROUND("worldGenMustContainAirBackground", { HaveEmptyBackground }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { if (json["layer"].asString == "back" && "worldGenMustContainAir" in json) return HaveEmptyBackground return null } }, ALLOW_OVERDRAWING("allowOverdrawing", { AllowOverdrawing }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { if ("allowOverdrawing" in json) return AllowOverdrawing return null } }, IGNORE_PART_MAXIMUM("ignorePartMaximumRule", { IgnorePartMaximum }) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { return null } }, MAX_SPAWN_COUNT("maxSpawnCount", ::MaxSpawnCount) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { return null } }, DO_NOT_CONNECT_TO_PART("doNotConnectToPart", ::DoNotConnectToPart) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { return null } }, DO_NOT_COMBINE_WITH("doNotCombineWith", ::DoNotCombineWith) { override fun readTiled(json: JsonObject, flipX: Boolean, flipY: Boolean): DungeonRule? { return null } }; abstract fun readTiled(json: JsonObject, flipX: Boolean = false, flipY: Boolean = false): DungeonRule? } open val requiresSolid: Boolean get() = false open val requiresLiquid: Boolean get() = false open val requiresOpen: Boolean get() = false open val allowOverdrawing: Boolean get() = false open val ignorePartMaximum: Boolean get() = false open fun doesNotConnectToPart(name: String): Boolean { return false } open fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean { return true } open fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean { return true } open fun checkPartCombinationsAllowed(placements: Map): Boolean { return true } open fun allowSpawnCount(currentCount: Int): Boolean { return true } object Noop : DungeonRule() object MustContainLiquid : DungeonRule() { override val requiresLiquid: Boolean get() = true override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean { val cell = world.parent.template.cellInfo(x, y) return cell.oceanLiquid.isNotEmptyLiquid && cell.oceanLiquidLevel > y } override fun toString(): String { return "Must contain liquid" } } object MustNotContainLiquid : DungeonRule() { override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean { val cell = world.parent.template.cellInfo(x, y) return cell.oceanLiquid.isEmptyLiquid || cell.oceanLiquidLevel <= y } override fun toString(): String { return "Must not contain liquid" } } object HaveSolidForeground : DungeonRule() { override val requiresSolid: Boolean get() = true override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean { if (world.markSurfaceLevel != null) return y < world.markSurfaceLevel val cell = world.parent.getCell(x, y) if (cell.foreground.material.isObjectTile && world.isClearingTileEntityAt(x, y)) return false return cell.foreground.material.isNotEmptyTile && !world.isClearingTileEntityAt(x, y) } override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean { val cell = world.getCell(x, y) return cell.foreground.material.isNotEmptyTile } override fun toString(): String { return "Solid foreground" } } object HaveEmptyForeground : DungeonRule() { override val requiresOpen: Boolean get() = true override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean { if (world.markSurfaceLevel != null) return y >= world.markSurfaceLevel val cell = world.parent.getCell(x, y) return cell.foreground.material.isEmptyTile || cell.foreground.material.isObjectTile && world.isClearingTileEntityAt(x, y) } override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean { val cell = world.getCell(x, y) return cell.foreground.material.isEmptyTile } override fun toString(): String { return "Empty foreground" } } object HaveSolidBackground : DungeonRule() { override val requiresSolid: Boolean get() = true override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean { if (world.markSurfaceLevel != null) return y < world.markSurfaceLevel val cell = world.parent.getCell(x, y) if (cell.background.material.isObjectTile && world.isClearingTileEntityAt(x, y)) return false return cell.background.material.isNotEmptyTile } override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean { val cell = world.getCell(x, y) return cell.background.material.isNotEmptyTile } override fun toString(): String { return "Solid background" } } object HaveEmptyBackground : DungeonRule() { override val requiresOpen: Boolean get() = true override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean { if (world.markSurfaceLevel != null) return y >= world.markSurfaceLevel val cell = world.parent.getCell(x, y) return cell.background.material.isEmptyTile || cell.background.material.isObjectTile && world.isClearingTileEntityAt(x, y) } override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean { val cell = world.getCell(x, y) return cell.background.material.isEmptyTile } override fun toString(): String { return "Empty background" } } object AllowOverdrawing : DungeonRule() { override val allowOverdrawing: Boolean get() = true override fun toString(): String { return "Allow overdrawing" } } object IgnorePartMaximum : DungeonRule() { override val ignorePartMaximum: Boolean get() = true override fun toString(): String { return "Ignore part maximum" } } data class MaxSpawnCount(val count: Int) : DungeonRule() { constructor(json: JsonArray) : this(json[1].asJsonArray[0].asInt) override fun allowSpawnCount(currentCount: Int): Boolean { return currentCount < count } override fun toString(): String { return "Max spawn count = $count" } } data class DoNotConnectToPart(val parts: ImmutableSet) : DungeonRule() { constructor(json: JsonArray) : this(json[1].asJsonArray.stream().map { it.asString }.collect(ImmutableSet.toImmutableSet())) override fun doesNotConnectToPart(name: String): Boolean { return name in parts } override fun toString(): String { return "Do not connect to $parts" } } data class DoNotCombineWith(val parts: ImmutableSet) : DungeonRule() { constructor(json: JsonArray) : this(json[1].asJsonArray.stream().map { it.asString }.collect(ImmutableSet.toImmutableSet())) override fun checkPartCombinationsAllowed(placements: Map): Boolean { return placements.keys.none { it in parts } } override fun toString(): String { return "Do not combine with $parts" } } class Adapter(gson: Gson) : TypeAdapter() { private val arrays = gson.getAdapter(JsonArray::class.java) private val types = gson.getAdapter(Type::class.java) override fun write(out: JsonWriter, value: DungeonRule) { throw UnsupportedOperationException("Dungeon Rules can't be serialized at this moment") } override fun read(`in`: JsonReader): DungeonRule { val read = arrays.read(`in`) if (read.isEmpty) { throw JsonSyntaxException("Empty rule") } val type = types.fromJsonTree(read[0]) return type.factory(read) } } companion object { private val LOGGER = LogManager.getLogger() } }