313 lines
12 KiB
Kotlin
313 lines
12 KiB
Kotlin
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<Drawable>,
|
|
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<Vector2i>,
|
|
val boundingBox: AABBi,
|
|
val metaBoundBox: AABB?,
|
|
val anchors: ImmutableSet<Anchor>,
|
|
val anchorAny: Boolean,
|
|
val directionAffinity: Direction?,
|
|
val materialSpaces: ImmutableList<Pair<Vector2i, Registry.Ref<TileDefinition>>>,
|
|
val interactiveSpaces: ImmutableSet<Vector2i>,
|
|
val lightPosition: Vector2i,
|
|
val beamAngle: Double,
|
|
val statusEffectArea: Vector2d?,
|
|
val touchDamage: JsonElement,
|
|
val particleEmitters: ArrayList<ParticleEmissionEntry>,
|
|
) {
|
|
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<JsonObject>()
|
|
|
|
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<ParticleEmissionEntry>()
|
|
private val spaces = gson.setAdapter<Vector2i>()
|
|
private val materialSpaces = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, Vector2i::class.java, String::class.java).type)) as TypeAdapter<ImmutableList<Pair<Vector2i, String>>>
|
|
|
|
suspend fun read(obj: JsonObject, folder: String): ObjectOrientation {
|
|
val drawables = ArrayList<Drawable>()
|
|
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<Vector2i>()
|
|
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<Anchor>()
|
|
|
|
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<Pair<Vector2i, String>>
|
|
|
|
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<Pair<Vector2i, String>>()
|
|
|
|
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<ParticleEmissionEntry>()
|
|
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
}
|