package ru.dbotthepony.kstarbound.defs.`object` import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonNull 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.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import kotlinx.coroutines.future.await import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.gson.clear import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.math.AABBi import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.render.RenderLayer import ru.dbotthepony.kstarbound.defs.Drawable import ru.dbotthepony.kstarbound.defs.JsonReference import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kstarbound.json.listAdapter import ru.dbotthepony.kstarbound.json.setAdapter import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.World import java.util.concurrent.CompletableFuture import kotlin.math.PI data class ObjectOrientation( val json: JsonObject, val flipImages: Boolean = false, val drawables: ImmutableList, val renderLayer: RenderLayer.Point, val imagePosition: Vector2f, val frames: Int, val animationCycle: Double, // world tiles this object occupy while in this orientation val occupySpaces: ImmutableSet, val boundingBox: AABBi, val metaBoundBox: AABB?, val anchors: ImmutableSet, val anchorAny: Boolean, val directionAffinity: Direction?, val materialSpaces: ImmutableList>>, val interactiveSpaces: ImmutableSet, val lightPosition: Vector2i, val beamAngle: Double, val statusEffectArea: Vector2d?, val touchDamage: JsonElement, val particleEmitters: ArrayList, ) { fun placementValid(world: World<*, *>, position: Vector2i, ignoreProtectedDungeons: Boolean = false): Boolean { if (occupySpaces.isEmpty()) return true return occupySpaces.all { val cell = world.chunkMap.getCell(it + position) //if (!cell.foreground.material.isEmptyTile) println("not empty tile: ${it + position}, space $it, pos $position") //if (cell.dungeonId in world.protectedDungeonIDs) println("position is protected: ${it + position}") cell.foreground.material.isEmptyTile && (ignoreProtectedDungeons || cell.dungeonId !in world.protectedDungeonIDs) } } fun anchorsValid(world: World<*, *>, position: Vector2i, considerNullAsValid: Boolean = false): Boolean { if (anchors.isEmpty()) return true var anyValid = false for (anchor in anchors) { val isValid = anchor.isValidPlacement(world, position, considerNullAsValid) if (isValid) anyValid = true else if (!anchorAny) { // println("anchor $anchor reported false for $position ${world.chunkMap.getCell(position + anchor.position)}") return false } } return anyValid } companion object { private val LOGGER = LogManager.getLogger() fun preprocess(json: JsonArray): JsonArray { val actual = ArrayList() for (elem in json) { val obj = elem.asJsonObject if ("dualImage" in obj) { var copy = obj.deepCopy() copy["image"] = obj["dualImage"]!! copy["flipImages"] = true copy["direction"] = "left" actual.add(copy) copy = obj.deepCopy() copy["image"] = obj["dualImage"]!! copy["flipImages"] = false copy["direction"] = "right" actual.add(copy) } else if ("leftImage" in obj) { require("rightImage" in obj) { "Provided leftImage, but there is no rightImage!" } var copy = obj.deepCopy() copy["image"] = obj["leftImage"]!! copy["direction"] = "left" actual.add(copy) copy = obj.deepCopy() copy["image"] = obj["rightImage"]!! copy["direction"] = "right" actual.add(copy) } else { actual.add(obj) } } json.clear() actual.forEach { json.add(it) } return json } } class Adapter(gson: Gson) { private val vectors = gson.getAdapter(Vector2f::class.java) private val vectorsi = gson.getAdapter(Vector2i::class.java) private val vectorsd = gson.getAdapter(Vector2d::class.java) private val drawables = gson.getAdapter(Drawable::class.java) private val aabbs = gson.getAdapter(AABB::class.java) private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java) private val emitters = gson.listAdapter() private val spaces = gson.setAdapter() private val materialSpaces = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, Vector2i::class.java, String::class.java).type)) as TypeAdapter>> suspend fun read(obj: JsonObject, folder: String): ObjectOrientation { val drawables = ArrayList() val flipImages = obj.get("flipImages", false) val renderLayer = RenderLayer.parse(obj.get("renderLayer", "Object")) AssetPathStack(folder) { if ("imageLayers" in obj) { for (value in obj["imageLayers"].asJsonArray) { var result = this.drawables.fromJsonTree(value) if (flipImages) { result = result.flop() } drawables.add(result) } } else { var result = this.drawables.fromJsonTree(obj) if (flipImages) { result = result.flop() } drawables.add(result) } } val imagePosition = (obj["imagePosition"]?.let { vectors.fromJsonTree(it) } ?: Vector2f.ZERO) / PIXELS_IN_STARBOUND_UNITf val imagePositionI = (obj["imagePosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO) val frames = obj.get("frames", 1) val animationCycle = obj.get("animationCycle", 1.0) var occupySpaces = obj["spaces"]?.let { spaces.fromJsonTree(it) } ?: ImmutableSet.of(Vector2i.ZERO) if ("spaceScan" in obj) { occupySpaces = ImmutableSet.of() for (drawable in drawables) { if (drawable is Drawable.Image) { val bound = drawable.path.with { "default" } val sprite = bound.sprite if (sprite != null) { val new = ImmutableSet.Builder() new.addAll(occupySpaces) new.addAll(sprite.worldSpacesAsync(imagePositionI, obj["spaceScan"].asDouble, flipImages).await()) occupySpaces = new.build() } else { LOGGER.error("Unable to space scan image, not a valid sprite reference: $bound") } } } } val minX: Int val minY: Int val maxX: Int val maxY: Int if (occupySpaces.isNotEmpty()) { minX = occupySpaces.minOf { it.x } minY = occupySpaces.minOf { it.y } maxX = occupySpaces.maxOf { it.x } maxY = occupySpaces.maxOf { it.y } } else { minX = 0 minY = 0 maxX = 0 maxY = 0 } val boundingBox = AABBi(Vector2i(minX, minY), Vector2i(maxX, maxY)) val metaBoundBox = obj["metaBoundBox"]?.let { aabbs.fromJsonTree(it) } val requireTilledAnchors = obj.get("requireTilledAnchors", false) val requireSoilAnchors = obj.get("requireSoilAnchors", false) val anchorMaterial = obj["anchorMaterial"]?.asString?.let { Registries.tiles.ref(it) } val anchors = ImmutableSet.Builder() for (v in obj.get("anchors", JsonArray())) { when (v.asString.lowercase()) { "left" -> occupySpaces.stream().filter { it.x == minX }.forEach { anchors.add(Anchor(false, it + Vector2i.NEGATIVE_X, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) } "right" -> occupySpaces.stream().filter { it.x == maxX }.forEach { anchors.add(Anchor(false, it + Vector2i.POSITIVE_X, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) } "top" -> occupySpaces.stream().filter { it.y == maxY }.forEach { anchors.add(Anchor(false, it + Vector2i.POSITIVE_Y, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) } "bottom" -> occupySpaces.stream().filter { it.y == minY }.forEach { anchors.add(Anchor(false, it + Vector2i.NEGATIVE_Y, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) } "background" -> occupySpaces.forEach { anchors.add(Anchor(true, it + Vector2i.NEGATIVE_Y, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) } else -> throw JsonSyntaxException("Unknown anchor type $v") } } for (v in obj.get("bgAnchors", JsonArray())) anchors.add(Anchor(true, vectorsi.fromJsonTree(v), requireTilledAnchors, requireSoilAnchors, anchorMaterial)) for (v in obj.get("fgAnchors", JsonArray())) anchors.add(Anchor(false, vectorsi.fromJsonTree(v), requireTilledAnchors, requireSoilAnchors, anchorMaterial)) val anchorAny = obj["anchorAny"]?.asBoolean ?: false val directionAffinity = obj["direction"]?.asString?.uppercase()?.let { Direction.valueOf(it) } val materialSpaces: ImmutableList> if ("materialSpaces" in obj) { materialSpaces = this.materialSpaces.fromJsonTree(obj["materialSpaces"]) } else { val collisionSpaces = obj["collisionSpaces"]?.let { this.spaces.fromJsonTree(it) } ?: occupySpaces val builder = ImmutableList.Builder>() when (val collisionType = obj.get("collision", "none").lowercase()) { "solid" -> collisionSpaces.forEach { builder.add(it to BuiltinMetaMaterials.OBJECT_SOLID.key) } "platform" -> collisionSpaces.forEach { if (it.y == boundingBox.maxs.y) builder.add(it to BuiltinMetaMaterials.OBJECT_PLATFORM.key) } "none" -> {} else -> throw JsonSyntaxException("Unknown collision type $collisionType") } materialSpaces = builder.build() } val interactiveSpaces = obj["interactiveSpaces"]?.let { this.spaces.fromJsonTree(it) } ?: occupySpaces val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) } val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.last()).await() val emitters = ArrayList() if ("particleEmitter" in obj) { emitters.add(this.emitter.fromJsonTree(obj["particleEmitter"])) } if ("particleEmitters" in obj) { emitters.addAll(this.emitters.fromJsonTree(obj["particleEmitters"])) } return ObjectOrientation( obj, flipImages = flipImages, drawables = ImmutableList.copyOf(drawables), renderLayer = renderLayer, imagePosition = imagePosition, frames = frames, animationCycle = animationCycle, occupySpaces = occupySpaces, boundingBox = boundingBox, metaBoundBox = metaBoundBox, anchors = anchors.build(), anchorAny = anchorAny, directionAffinity = directionAffinity, materialSpaces = materialSpaces.stream().map { it.first to Registries.tiles.ref(it.second) }.collect(ImmutableList.toImmutableList()), interactiveSpaces = interactiveSpaces, lightPosition = lightPosition, beamAngle = beamAngle, statusEffectArea = statusEffectArea, touchDamage = touchDamage, particleEmitters = emitters, ) } } }