mirror of
https://github.com/jlengrand/korge-intellij-plugin.git
synced 2026-03-10 00:21:21 +00:00
Add support for reading/writing TMX file data (#25)
* Add fixes from korge, update file naming (tile -> tiled) * Support reading/writing almost all attributes of a tilemap and its children
This commit is contained in:
committed by
GitHub
parent
ca597a9db2
commit
0e69e4ebad
@@ -3,4 +3,4 @@ kotlin.code.style=official
|
||||
|
||||
# version
|
||||
kotlinVersion=1.3.72
|
||||
korgeVersion=1.12.2.2
|
||||
korgeVersion=1.13.8.3
|
||||
@@ -1,223 +0,0 @@
|
||||
// @TODO: @WARNING: Duplicated from KorGE to be able to modify it. Please, copy again to KorGE once this is stable
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
|
||||
import com.soywiz.klogger.*
|
||||
import com.soywiz.korge.view.tiles.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korio.serialization.xml.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import kotlin.reflect.*
|
||||
|
||||
class TiledMapData(
|
||||
var width: Int = 0,
|
||||
var height: Int = 0,
|
||||
var tilewidth: Int = 0,
|
||||
var tileheight: Int = 0,
|
||||
val allLayers: MutableList<TiledMap.Layer> = arrayListOf(),
|
||||
val tilesets: MutableList<TileSetData> = arrayListOf()
|
||||
) {
|
||||
val maxGid get() = tilesets.map { it.firstgid + it.tilecount }.max() ?: 0
|
||||
val pixelWidth: Int get() = width * tilewidth
|
||||
val pixelHeight: Int get() = height * tileheight
|
||||
inline val tileLayers get() = allLayers.tiles
|
||||
inline val imageLayers get() = allLayers.images
|
||||
inline val objectLayers get() = allLayers.objects
|
||||
fun getObjectByName(name: String) = objectLayers.mapNotNull { it.getByName(name) }.firstOrNull()
|
||||
fun clone() = TiledMapData(
|
||||
width, height, tilewidth, tileheight,
|
||||
allLayers.map { it.clone() }.toMutableList(),
|
||||
tilesets.map { it.clone() }.toMutableList()
|
||||
)
|
||||
}
|
||||
|
||||
fun TiledMap.Layer.Objects.Object.getPos(map: TiledMapData): IPoint =
|
||||
IPoint(bounds.x / map.tilewidth, bounds.y / map.tileheight)
|
||||
|
||||
fun TiledMapData?.getObjectPosByName(name: String): IPoint? {
|
||||
val obj = this?.getObjectByName(name) ?: return null
|
||||
return obj.getPos(this)
|
||||
}
|
||||
|
||||
data class TerrainData(
|
||||
val name: String,
|
||||
val tile: Int
|
||||
)
|
||||
|
||||
data class AnimationFrameData(
|
||||
val tileid: Int, val duration: Int
|
||||
)
|
||||
|
||||
data class TerrainInfo(val info: List<Int?>) {
|
||||
operator fun get(x: Int, y: Int): Int? = if (x in 0..1 && y in 0..1) info[y * 2 + x] else null
|
||||
}
|
||||
|
||||
data class TileData(
|
||||
val id: Int,
|
||||
val terrain: List<Int?>? = null,
|
||||
val probability: Double = 1.0,
|
||||
val frames: List<AnimationFrameData>? = null
|
||||
) {
|
||||
val terrainInfo = TerrainInfo(terrain ?: listOf(null, null ,null, null))
|
||||
}
|
||||
|
||||
data class TileSetData constructor(
|
||||
val name: String = "unknown",
|
||||
val firstgid: Int = 1,
|
||||
val tilewidth: Int,
|
||||
val tileheight: Int,
|
||||
val tilecount: Int,
|
||||
val columns: Int,
|
||||
val image: Xml?,
|
||||
val imageSource: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val tilesetSource: String? = null,
|
||||
val terrains: List<TerrainData> = listOf(),
|
||||
val tiles: List<TileData> = listOf()
|
||||
) {
|
||||
fun clone() = copy()
|
||||
}
|
||||
|
||||
//e: java.lang.UnsupportedOperationException: Class literal annotation arguments are not yet supported: Factory
|
||||
//@AsyncFactoryClass(TiledMapFactory::class)
|
||||
class TiledMap constructor(
|
||||
var data: TiledMapData,
|
||||
var tilesets: MutableList<TiledTileset>
|
||||
) {
|
||||
val width get() = data.width
|
||||
val height get() = data.height
|
||||
val tilewidth get() = data.tilewidth
|
||||
val tileheight get() = data.tileheight
|
||||
val pixelWidth: Int get() = data.pixelWidth
|
||||
val pixelHeight: Int get() = data.pixelHeight
|
||||
val allLayers get() = data.allLayers
|
||||
val tileLayers get() = data.tileLayers
|
||||
val imageLayers get() = data.imageLayers
|
||||
val objectLayers get() = data.objectLayers
|
||||
val nextGid get() = tilesets.map { it.firstgid + it.tileset.textures.size }.max() ?: 1
|
||||
|
||||
fun clone() = TiledMap(data.clone(), tilesets.map { it.clone() }.toMutableList())
|
||||
|
||||
data class TiledTileset(
|
||||
val tileset: TileSet,
|
||||
val data: TileSetData = TileSetData(
|
||||
name = "unknown",
|
||||
firstgid = 1,
|
||||
tilewidth = tileset.width,
|
||||
tileheight = tileset.height,
|
||||
tilecount = tileset.textures.size,
|
||||
columns = tileset.base.width / tileset.width,
|
||||
image = null,
|
||||
imageSource = "",
|
||||
width = tileset.base.width,
|
||||
height = tileset.base.height,
|
||||
tilesetSource = null,
|
||||
terrains = listOf(),
|
||||
tiles = tileset.textures.mapIndexed { index, bmpSlice -> TileData(index) }
|
||||
),
|
||||
val firstgid: Int = 1
|
||||
) {
|
||||
fun clone(): TiledTileset = TiledTileset(tileset.clone(), data.clone(), firstgid)
|
||||
}
|
||||
|
||||
sealed class Layer {
|
||||
var id: Int = 1
|
||||
var name: String = ""
|
||||
var visible: Boolean = true
|
||||
var locked: Boolean = false
|
||||
var draworder: String = ""
|
||||
var color: RGBA = Colors.WHITE
|
||||
var opacity = 1.0
|
||||
var offsetx: Double = 0.0
|
||||
var offsety: Double = 0.0
|
||||
val properties = LinkedHashMap<String, Any>()
|
||||
companion object {
|
||||
val BASE_PROPS = listOf(
|
||||
Layer::id,
|
||||
Layer::name, Layer::visible, Layer::locked, Layer::draworder,
|
||||
Layer::color, Layer::opacity, Layer::offsetx, Layer::offsety
|
||||
)
|
||||
}
|
||||
open fun copyFrom(other: Layer) {
|
||||
for (prop in BASE_PROPS) {
|
||||
val p = prop as KMutableProperty1<Layer, Any>
|
||||
p.set(this, p.get(other))
|
||||
}
|
||||
this.properties.clear()
|
||||
this.properties.putAll(other.properties)
|
||||
}
|
||||
abstract fun clone(): Layer
|
||||
|
||||
class Tiles(
|
||||
var map: Bitmap32 = Bitmap32(0, 0),
|
||||
var encoding: String = "csv",
|
||||
var compression: String = ""
|
||||
) : Layer() {
|
||||
val data get() = map
|
||||
val width: Int get() = map.width
|
||||
val height: Int get() = map.height
|
||||
val area: Int get() = width * height
|
||||
operator fun set(x: Int, y: Int, value: Int) = run { data.setInt(x, y, value) }
|
||||
operator fun get(x: Int, y: Int): Int = data.getInt(x, y)
|
||||
override fun clone(): Tiles = Tiles(map.clone(), encoding, compression).also { it.copyFrom(this) }
|
||||
}
|
||||
|
||||
data class ObjectInfo(
|
||||
val id: Int, val name: String, val type: String,
|
||||
val bounds: IRectangleInt,
|
||||
val objprops: Map<String, Any>
|
||||
)
|
||||
|
||||
class Objects(
|
||||
val objects: MutableList<Object> = arrayListOf<Object>()
|
||||
) : Layer() {
|
||||
interface Object {
|
||||
val info: ObjectInfo
|
||||
}
|
||||
|
||||
interface Poly : Object {
|
||||
val points: List<IPoint>
|
||||
}
|
||||
|
||||
data class Rect(override val info: ObjectInfo) : Object
|
||||
data class Ellipse(override val info: ObjectInfo) : Object
|
||||
data class Polyline(override val info: ObjectInfo, override val points: List<IPoint>) : Poly
|
||||
data class Polygon(override val info: ObjectInfo, override val points: List<IPoint>) : Poly
|
||||
|
||||
override fun clone(): Objects = Objects(objects.toMutableList()).also { it.copyFrom(this) }
|
||||
|
||||
fun getById(id: Int): Object? = objects.firstOrNull { it.id == id }
|
||||
fun getByName(name: String): Object? = objects.firstOrNull { it.name == name }
|
||||
}
|
||||
|
||||
class Image(
|
||||
var width: Int = 0,
|
||||
var height: Int = 0,
|
||||
var source: String = "",
|
||||
var image: Bitmap = Bitmap32(0, 0)
|
||||
) : Layer() {
|
||||
override fun clone(): Image = Image(width, height, source, image.clone()).also { it.copyFrom(this) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TileSet.clone(): TileSet = TileSet(this.textures, this.width, this.height, this.base)
|
||||
|
||||
fun Bitmap.clone(): Bitmap = when (this) {
|
||||
is Bitmap32 -> this.clone()
|
||||
else -> TODO()
|
||||
}
|
||||
|
||||
val TiledMap.Layer.Objects.Object.id get() = this.info.id
|
||||
val TiledMap.Layer.Objects.Object.name get() = this.info.name
|
||||
val TiledMap.Layer.Objects.Object.bounds get() = this.info.bounds
|
||||
val TiledMap.Layer.Objects.Object.objprops get() = this.info.objprops
|
||||
|
||||
inline val Iterable<TiledMap.Layer>.tiles get() = this.filterIsInstance<TiledMap.Layer.Tiles>()
|
||||
inline val Iterable<TiledMap.Layer>.images get() = this.filterIsInstance<TiledMap.Layer.Image>()
|
||||
inline val Iterable<TiledMap.Layer>.objects get() = this.filterIsInstance<TiledMap.Layer.Objects>()
|
||||
|
||||
val tilemapLog = Logger("tilemap")
|
||||
|
||||
class TiledFile(val name: String)
|
||||
@@ -1,311 +0,0 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
|
||||
import com.soywiz.kds.iterators.*
|
||||
import com.soywiz.kmem.*
|
||||
import com.soywiz.korge.view.tiles.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korim.format.*
|
||||
import com.soywiz.korio.compression.*
|
||||
import com.soywiz.korio.compression.deflate.*
|
||||
import com.soywiz.korio.file.*
|
||||
import com.soywiz.korio.lang.*
|
||||
import com.soywiz.korio.serialization.xml.*
|
||||
import com.soywiz.korio.util.encoding.*
|
||||
import com.soywiz.korma.geom.*
|
||||
|
||||
suspend fun VfsFile.readTiledMap(
|
||||
hasTransparentColor: Boolean = false,
|
||||
transparentColor: RGBA = Colors.FUCHSIA,
|
||||
createBorder: Int = 1
|
||||
): TiledMap {
|
||||
val folder = this.parent.jail()
|
||||
val data = readTiledMapData()
|
||||
|
||||
//val combinedTileset = kotlin.arrayOfNulls<Texture>(data.maxGid + 1)
|
||||
|
||||
data.imageLayers.fastForEach { layer ->
|
||||
layer.image = try {
|
||||
folder[layer.source].readBitmapOptimized()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
Bitmap32(layer.width, layer.height)
|
||||
}
|
||||
}
|
||||
|
||||
val tiledTilesets = arrayListOf<TiledMap.TiledTileset>()
|
||||
|
||||
data.tilesets.fastForEach { tileset ->
|
||||
tiledTilesets += tileset.toTiledSet(folder, hasTransparentColor, transparentColor, createBorder)
|
||||
}
|
||||
|
||||
return TiledMap(data, tiledTilesets)
|
||||
}
|
||||
|
||||
private fun Xml.parseProperties(): Map<String, Any> {
|
||||
val out = LinkedHashMap<String, Any>()
|
||||
for (property in this.children("property")) {
|
||||
val pname = property.str("name")
|
||||
val rawValue = if (property.hasAttribute("value")) property.str("value") else property.text
|
||||
val type = property.str("type", "text")
|
||||
val pvalue: Any = when (type) {
|
||||
"bool" -> rawValue == "true"
|
||||
"color" -> Colors[rawValue]
|
||||
"text" -> rawValue
|
||||
"int" -> rawValue.toIntOrNull() ?: 0
|
||||
"float" -> rawValue.toDoubleOrNull() ?: 0.0
|
||||
"file" -> TiledFile(pname)
|
||||
else -> rawValue
|
||||
}
|
||||
out[pname] = pvalue
|
||||
//println("$pname: $pvalue")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fun parseTileSetData(element: Xml, firstgid: Int, tilesetSource: String? = null): TileSetData {
|
||||
//<?xml version="1.0" encoding="UTF-8"?>
|
||||
//<tileset version="1.2" tiledversion="1.3.1" name="Overworld" tilewidth="16" tileheight="16" tilecount="1440" columns="40">
|
||||
// <image source="Overworld.png" width="640" height="576"/>
|
||||
//</tileset>
|
||||
val image = element.child("image")
|
||||
|
||||
return TileSetData(
|
||||
name = element.str("name"),
|
||||
firstgid = firstgid,
|
||||
tilewidth = element.int("tilewidth"),
|
||||
tileheight = element.int("tileheight"),
|
||||
tilecount = element.int("tilecount", -1),
|
||||
columns = element.int("columns", -1),
|
||||
image = image,
|
||||
tilesetSource = tilesetSource,
|
||||
imageSource = image?.str("source") ?: "",
|
||||
width = image?.int("width", 0) ?: 0,
|
||||
height = image?.int("height", 0) ?: 0,
|
||||
terrains = element.children("terraintypes").children("terrain").map { TerrainData(name = it.str("name"), tile = it.int("tile")) },
|
||||
tiles = element.children("tile").map {
|
||||
TileData(
|
||||
id = it.int("id"),
|
||||
terrain = it.str("terrain").takeIf { it.isNotEmpty() }?.split(',')?.map { it.toIntOrNull() },
|
||||
probability = it.double("probability", 1.0),
|
||||
frames = it.child("animation")?.children("frame")?.map {
|
||||
AnimationFrameData(it.int("tileid"), it.int("duration"))
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun VfsFile.readTileSetData(firstgid: Int = 1): TileSetData {
|
||||
return parseTileSetData(this.readXml(), firstgid, this.baseName)
|
||||
}
|
||||
|
||||
suspend fun TileSetData.toTiledSet(
|
||||
folder: VfsFile,
|
||||
hasTransparentColor: Boolean = false,
|
||||
transparentColor: RGBA = Colors.FUCHSIA,
|
||||
createBorder: Int = 1
|
||||
): TiledMap.TiledTileset {
|
||||
val tileset = this
|
||||
var bmp = try {
|
||||
folder[tileset.imageSource].readBitmapOptimized()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
Bitmap32(tileset.width, tileset.height)
|
||||
}
|
||||
|
||||
// @TODO: Preprocess this, so in JS we don't have to do anything!
|
||||
if (hasTransparentColor) {
|
||||
bmp = bmp.toBMP32()
|
||||
for (n in 0 until bmp.area) {
|
||||
if (bmp.data[n] == transparentColor) bmp.data[n] = Colors.TRANSPARENT_BLACK
|
||||
}
|
||||
}
|
||||
|
||||
val ptileset = if (createBorder > 0) {
|
||||
bmp = bmp.toBMP32()
|
||||
|
||||
val slices =
|
||||
TileSet.extractBitmaps(bmp, tileset.tilewidth, tileset.tileheight, tileset.columns, tileset.tilecount)
|
||||
|
||||
TileSet.fromBitmaps(
|
||||
tileset.tilewidth, tileset.tileheight,
|
||||
slices,
|
||||
border = createBorder,
|
||||
mipmaps = false
|
||||
)
|
||||
} else {
|
||||
TileSet(bmp.slice(), tileset.tilewidth, tileset.tileheight, tileset.columns, tileset.tilecount)
|
||||
}
|
||||
|
||||
val tiledTileset = TiledMap.TiledTileset(
|
||||
tileset = ptileset,
|
||||
data = tileset,
|
||||
firstgid = tileset.firstgid
|
||||
)
|
||||
|
||||
return tiledTileset
|
||||
}
|
||||
|
||||
suspend fun VfsFile.readTiledMapData(): TiledMapData {
|
||||
val log = tilemapLog
|
||||
val file = this
|
||||
val folder = this.parent.jail()
|
||||
val tiledMap = TiledMapData()
|
||||
val mapXml = file.readXml()
|
||||
|
||||
if (mapXml.nameLC != "map") error("Not a TiledMap XML TMX file starting with <map>")
|
||||
|
||||
tiledMap.width = mapXml.getInt("width") ?: 0
|
||||
tiledMap.height = mapXml.getInt("height") ?: 0
|
||||
tiledMap.tilewidth = mapXml.getInt("tilewidth") ?: 32
|
||||
tiledMap.tileheight = mapXml.getInt("tileheight") ?: 32
|
||||
|
||||
tilemapLog.trace { "tilemap: width=${tiledMap.width}, height=${tiledMap.height}, tilewidth=${tiledMap.tilewidth}, tileheight=${tiledMap.tileheight}" }
|
||||
tilemapLog.trace { "tilemap: $tiledMap" }
|
||||
|
||||
val elements = mapXml.allChildrenNoComments
|
||||
|
||||
tilemapLog.trace { "tilemap: elements=${elements.size}" }
|
||||
tilemapLog.trace { "tilemap: elements=$elements" }
|
||||
|
||||
var maxGid = 1
|
||||
//var lastBaseTexture = views.transparentTexture.base
|
||||
|
||||
elements.fastForEach { element ->
|
||||
val elementName = element.nameLC
|
||||
@Suppress("IntroduceWhenSubject") // @TODO: BUG IN KOTLIN-JS with multicase in suspend functions
|
||||
when {
|
||||
elementName == "tileset" -> {
|
||||
tilemapLog.trace { "tileset" }
|
||||
val firstgid = element.int("firstgid", +1)
|
||||
// TSX file / embedded element
|
||||
val sourcePath = element.getString("source")
|
||||
val element = if (sourcePath != null) folder[sourcePath].readXml() else element
|
||||
tiledMap.tilesets += parseTileSetData(element, firstgid, sourcePath)
|
||||
//lastBaseTexture = tex.base
|
||||
}
|
||||
elementName == "layer" || elementName == "objectgroup" || elementName == "imagelayer" -> {
|
||||
tilemapLog.trace { "layer:$elementName" }
|
||||
val layer = when (element.nameLC) {
|
||||
"layer" -> TiledMap.Layer.Tiles()
|
||||
"objectgroup" -> TiledMap.Layer.Objects()
|
||||
"imagelayer" -> TiledMap.Layer.Image()
|
||||
else -> invalidOp
|
||||
}
|
||||
tiledMap.allLayers += layer
|
||||
layer.name = element.str("name")
|
||||
layer.visible = element.int("visible", 1) != 0
|
||||
layer.draworder = element.str("draworder", "")
|
||||
layer.color = Colors[element.str("color", "#ffffff")]
|
||||
layer.opacity = element.double("opacity", 1.0)
|
||||
layer.offsetx = element.double("offsetx", 0.0)
|
||||
layer.offsety = element.double("offsety", 0.0)
|
||||
|
||||
val properties = element.child("properties")?.parseProperties()
|
||||
if (properties != null) {
|
||||
layer.properties.putAll(properties)
|
||||
}
|
||||
|
||||
when (layer) {
|
||||
is TiledMap.Layer.Tiles -> {
|
||||
val width = element.int("width")
|
||||
val height = element.int("height")
|
||||
val count = width * height
|
||||
val data = element.child("data")
|
||||
val encoding = data?.str("encoding", "") ?: ""
|
||||
val compression = data?.str("compression", "") ?: ""
|
||||
@Suppress("IntroduceWhenSubject") // @TODO: BUG IN KOTLIN-JS with multicase in suspend functions
|
||||
val tilesArray: IntArray = when {
|
||||
encoding == "" || encoding == "xml" -> {
|
||||
val items = data?.children("tile")?.map { it.uint("gid") } ?: listOf()
|
||||
items.toIntArray()
|
||||
}
|
||||
encoding == "csv" -> {
|
||||
val content = data?.text ?: ""
|
||||
val items = content.replace(spaces, "").split(',').map { it.toUInt().toInt() }
|
||||
items.toIntArray()
|
||||
}
|
||||
encoding == "base64" -> {
|
||||
val base64Content = (data?.text ?: "").trim()
|
||||
val rawContent = base64Content.fromBase64()
|
||||
|
||||
val content = when (compression) {
|
||||
"" -> rawContent
|
||||
"gzip" -> rawContent.uncompress(GZIP)
|
||||
"zlib" -> rawContent.uncompress(ZLib)
|
||||
else -> invalidOp("Unknown compression '$compression'")
|
||||
}
|
||||
content.readIntArrayLE(0, count)
|
||||
}
|
||||
else -> invalidOp("Unhandled encoding '$encoding'")
|
||||
}
|
||||
if (tilesArray.size != count) invalidOp("tilesArray.size != count (${tilesArray.size} != ${count})")
|
||||
layer.map = Bitmap32(width, height, RgbaArray(tilesArray))
|
||||
layer.encoding = encoding
|
||||
layer.compression = compression
|
||||
}
|
||||
is TiledMap.Layer.Image -> {
|
||||
for (image in element.children("image")) {
|
||||
layer.source = image.str("source")
|
||||
layer.width = image.int("width")
|
||||
layer.height = image.int("height")
|
||||
}
|
||||
}
|
||||
is TiledMap.Layer.Objects -> {
|
||||
for (obj in element.children("object")) {
|
||||
val id = obj.int("id")
|
||||
val name = obj.str("name")
|
||||
val type = obj.str("type")
|
||||
val bounds = obj.run { IRectangleInt(int("x"), int("y"), int("width"), int("height")) }
|
||||
var rkind = RKind.RECT
|
||||
var points = listOf<IPoint>()
|
||||
var objprops: Map<String, Any> = LinkedHashMap()
|
||||
|
||||
for (kind in obj.allNodeChildren) {
|
||||
val kindType = kind.nameLC
|
||||
@Suppress("IntroduceWhenSubject") // @TODO: BUG IN KOTLIN-JS with multicase in suspend functions
|
||||
when {
|
||||
kindType == "ellipse" -> {
|
||||
rkind = RKind.ELLIPSE
|
||||
}
|
||||
kindType == "polyline" || kindType == "polygon" -> {
|
||||
val pointsStr = kind.str("points")
|
||||
points = pointsStr.split(spaces).map {
|
||||
val parts = it.split(',').map { it.trim().toDoubleOrNull() ?: 0.0 }
|
||||
IPoint(parts[0], parts[1])
|
||||
}
|
||||
|
||||
rkind = (if (kindType == "polyline") RKind.POLYLINE else RKind.POLYGON)
|
||||
}
|
||||
kindType == "properties" -> {
|
||||
objprops = kind.parseProperties()
|
||||
}
|
||||
else -> invalidOp("Invalid object kind '$kindType'")
|
||||
}
|
||||
}
|
||||
|
||||
val info = TiledMap.Layer.ObjectInfo(id, name, type, bounds, objprops)
|
||||
layer.objects += when (rkind) {
|
||||
RKind.RECT -> TiledMap.Layer.Objects.Rect(info)
|
||||
RKind.ELLIPSE -> TiledMap.Layer.Objects.Ellipse(info)
|
||||
RKind.POLYLINE -> TiledMap.Layer.Objects.Polyline(info, points)
|
||||
RKind.POLYGON -> TiledMap.Layer.Objects.Polygon(info, points)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tiledMap
|
||||
}
|
||||
|
||||
private fun Xml.uint(name: String, defaultValue: Int = 0): Int = this.attributesLC[name]?.toUIntOrNull()?.toInt() ?: defaultValue
|
||||
|
||||
private enum class RKind {
|
||||
RECT, ELLIPSE, POLYLINE, POLYGON
|
||||
}
|
||||
|
||||
private val spaces = Regex("\\s+")
|
||||
@@ -1,96 +0,0 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
|
||||
import com.soywiz.korio.file.*
|
||||
import com.soywiz.korio.serialization.xml.*
|
||||
|
||||
|
||||
suspend fun VfsFile.writeTiledMap(map: TiledMap) {
|
||||
writeString(map.toXML().toString())
|
||||
}
|
||||
|
||||
fun TiledMap.toXML(): Xml {
|
||||
val map = this
|
||||
return buildXml("map",
|
||||
"version" to 1.2,
|
||||
"tiledversion" to "1.3.1",
|
||||
"orientation" to "orthogonal",
|
||||
"renderorder" to "right-down",
|
||||
"compressionlevel" to -1,
|
||||
"width" to map.width,
|
||||
"height" to map.height,
|
||||
"tilewidth" to map.tilewidth,
|
||||
"tileheight" to map.tileheight,
|
||||
"infinite" to 0,
|
||||
"nextlayerid" to map.allLayers.size + 1,
|
||||
"nextobjectid" to map.objectLayers.size + 1
|
||||
) {
|
||||
for (tileset in map.tilesets) {
|
||||
if (tileset.data.tilesetSource != null) {
|
||||
node("tileset", "firstgid" to tileset.data.firstgid, "source" to tileset.data.tilesetSource)
|
||||
} else {
|
||||
node(tileset.data.toXML())
|
||||
}
|
||||
}
|
||||
for (layer in map.allLayers) {
|
||||
when (layer) {
|
||||
is TiledMap.Layer.Tiles -> {
|
||||
node("layer", "id" to layer.id, "name" to layer.name, "width" to layer.width, "height" to layer.height) {
|
||||
node("data", "encoding" to "csv") {
|
||||
text(buildString(layer.area * 4) {
|
||||
append("\n")
|
||||
for (y in 0 until layer.height) {
|
||||
for (x in 0 until layer.width) {
|
||||
append(layer.data[x, y].value)
|
||||
if (y != layer.height - 1 || x != layer.width - 1) append(',')
|
||||
}
|
||||
append("\n")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> TODO("Unsupported layer $layer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TileSetData.toXML(): Xml {
|
||||
//<tileset version="1.2" tiledversion="1.3.1" name="Overworld" tilewidth="16" tileheight="16" tilecount="1440" columns="40">
|
||||
// <image source="Overworld.png" width="640" height="576"/>
|
||||
//</tileset>
|
||||
return buildXml("tileset",
|
||||
"firstgid" to firstgid,
|
||||
"name" to name,
|
||||
"tilewidth" to tilewidth,
|
||||
"tileheight" to tileheight,
|
||||
"tilecount" to tilecount,
|
||||
"columns" to columns
|
||||
) {
|
||||
node("image", "source" to imageSource, "width" to width, "height" to height)
|
||||
if (terrains.isNotEmpty()) {
|
||||
node("terraintypes") {
|
||||
//<terrain name="Ground1" tile="0"/>
|
||||
for (terrain in terrains) {
|
||||
node("terrain", "name" to terrain.name, "tile" to terrain.tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (tile in tiles) {
|
||||
node("tile",
|
||||
"id" to tile.id,
|
||||
"terrain" to tile.terrain?.joinToString(",") { it?.toString() ?: "" },
|
||||
"probability" to tile.probability.takeIf { it != 1.0 }
|
||||
) {
|
||||
val frames = tile.frames
|
||||
if (frames != null) {
|
||||
node("animation") {
|
||||
for (frame in frames) {
|
||||
node("frame", "tileid" to frame.tileid, "duration" to frame.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.intellij.util.ui.*
|
||||
import com.soywiz.korge.intellij.util.*
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.soywiz.korge.intellij.editor.*
|
||||
import com.soywiz.korge.intellij.editor.tile.dialog.*
|
||||
import com.soywiz.korge.intellij.editor.tile.editor.*
|
||||
import com.soywiz.korge.intellij.util.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.dialog.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.editor.*
|
||||
import com.soywiz.korge.intellij.util.ObservableProperty
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korio.async.*
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
object TerrainFiller {
|
||||
fun updateTile(layer: TiledMap.Layer.Tiles, x: Int, y: Int, tileset: TiledMap.TiledTileset) {
|
||||
@@ -0,0 +1,378 @@
|
||||
// @TODO: @WARNING: Duplicated from KorGE to be able to modify it. Please, copy again to KorGE once this is stable
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.soywiz.klogger.*
|
||||
import com.soywiz.korge.view.tiles.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import java.util.*
|
||||
import kotlin.collections.LinkedHashMap
|
||||
|
||||
class TiledMapData(
|
||||
var orientation: TiledMap.Orientation = TiledMap.Orientation.ORTHOGONAL,
|
||||
//TODO: support render order
|
||||
var renderOrder: TiledMap.RenderOrder = TiledMap.RenderOrder.RIGHT_DOWN,
|
||||
var compressionLevel: Int = -1,
|
||||
var width: Int = 0,
|
||||
var height: Int = 0,
|
||||
var tilewidth: Int = 0,
|
||||
var tileheight: Int = 0,
|
||||
var hexSideLength: Int? = null,
|
||||
var staggerAxis: TiledMap.StaggerAxis? = null,
|
||||
var staggerIndex: TiledMap.StaggerIndex? = null,
|
||||
var backgroundColor: RGBA? = null,
|
||||
var nextLayerId: Int = 1,
|
||||
var nextObjectId: Int = 1,
|
||||
var infinite: Boolean = false,
|
||||
val properties: MutableMap<String, TiledMap.Property> = mutableMapOf(),
|
||||
val allLayers: MutableList<TiledMap.Layer> = arrayListOf(),
|
||||
val tilesets: MutableList<TileSetData> = arrayListOf(),
|
||||
var editorSettings: TiledMap.EditorSettings? = null
|
||||
) {
|
||||
val pixelWidth: Int get() = width * tilewidth
|
||||
val pixelHeight: Int get() = height * tileheight
|
||||
inline val tileLayers get() = allLayers.tiles
|
||||
inline val imageLayers get() = allLayers.images
|
||||
inline val objectLayers get() = allLayers.objects
|
||||
|
||||
val maxGid get() = tilesets.map { it.firstgid + it.tileCount }.max() ?: 0
|
||||
|
||||
fun getObjectByName(name: String) = objectLayers.mapNotNull { it.getByName(name) }.firstOrNull()
|
||||
|
||||
fun clone() = TiledMapData(
|
||||
orientation, renderOrder, compressionLevel,
|
||||
width, height, tilewidth, tileheight,
|
||||
hexSideLength, staggerAxis, staggerIndex,
|
||||
backgroundColor, nextLayerId, nextObjectId, infinite,
|
||||
LinkedHashMap(properties),
|
||||
allLayers.map { it.clone() }.toMutableList(),
|
||||
tilesets.map { it.clone() }.toMutableList()
|
||||
)
|
||||
}
|
||||
|
||||
fun TiledMap.Object.getPos(map: TiledMapData) = Point(bounds.x / map.tilewidth, bounds.y / map.tileheight)
|
||||
|
||||
fun TiledMapData.getObjectPosByName(name: String) = getObjectByName(name)?.getPos(this)
|
||||
|
||||
data class TerrainData(
|
||||
val name: String,
|
||||
val tile: Int,
|
||||
val properties: Map<String, TiledMap.Property> = mapOf()
|
||||
)
|
||||
|
||||
data class AnimationFrameData(
|
||||
val tileId: Int, val duration: Int
|
||||
)
|
||||
|
||||
data class TerrainInfo(val info: List<Int?>) {
|
||||
operator fun get(x: Int, y: Int): Int? = if (x in 0..1 && y in 0..1) info[y * 2 + x] else null
|
||||
}
|
||||
|
||||
class WangSet(
|
||||
val name: String,
|
||||
val tileId: Int,
|
||||
val properties: Map<String, TiledMap.Property> = mapOf(),
|
||||
val cornerColors: List<WangColor> = listOf(),
|
||||
val edgeColors: List<WangColor> = listOf(),
|
||||
val wangtiles: List<WangTile> = listOf()
|
||||
) {
|
||||
class WangColor(
|
||||
val name: String,
|
||||
val color: RGBA,
|
||||
val tileId: Int,
|
||||
val probability: Double = 0.0
|
||||
)
|
||||
|
||||
class WangTile(
|
||||
val tileId: Int,
|
||||
val wangId: Int,
|
||||
val hflip: Boolean = false,
|
||||
val vflip: Boolean = false,
|
||||
val dflip: Boolean = false
|
||||
)
|
||||
}
|
||||
|
||||
data class TileData(
|
||||
val id: Int,
|
||||
val type: Int = -1,
|
||||
val terrain: List<Int?>? = null,
|
||||
val probability: Double = 0.0,
|
||||
val image: TiledMap.Image? = null,
|
||||
val properties: Map<String, TiledMap.Property> = mapOf(),
|
||||
val objectGroup: TiledMap.Layer.Objects? = null,
|
||||
val frames: List<AnimationFrameData>? = null
|
||||
) {
|
||||
val terrainInfo = TerrainInfo(terrain ?: listOf(null, null, null, null))
|
||||
}
|
||||
|
||||
data class TileSetData(
|
||||
val name: String,
|
||||
val firstgid: Int,
|
||||
val tileWidth: Int,
|
||||
val tileHeight: Int,
|
||||
val tileCount: Int,
|
||||
val spacing: Int,
|
||||
val margin: Int,
|
||||
val columns: Int,
|
||||
val image: TiledMap.Image?,
|
||||
val tileOffsetX: Int = 0,
|
||||
val tileOffsetY: Int = 0,
|
||||
val grid: TiledMap.Grid? = null,
|
||||
val tilesetSource: String? = null,
|
||||
val objectAlignment: TiledMap.ObjectAlignment = TiledMap.ObjectAlignment.UNSPECIFIED,
|
||||
val terrains: List<TerrainData> = listOf(),
|
||||
val wangsets: List<WangSet> = listOf(),
|
||||
val tiles: List<TileData> = listOf(),
|
||||
val properties: Map<String, TiledMap.Property> = mapOf()
|
||||
) {
|
||||
val width: Int get() = image?.width ?: 0
|
||||
val height: Int get() = image?.height ?: 0
|
||||
fun clone() = copy()
|
||||
}
|
||||
|
||||
//e: java.lang.UnsupportedOperationException: Class literal annotation arguments are not yet supported: Factory
|
||||
//@AsyncFactoryClass(TiledMapFactory::class)
|
||||
class TiledMap constructor(
|
||||
var data: TiledMapData,
|
||||
var tilesets: MutableList<TiledTileset>
|
||||
) {
|
||||
val width get() = data.width
|
||||
val height get() = data.height
|
||||
val tilewidth get() = data.tilewidth
|
||||
val tileheight get() = data.tileheight
|
||||
val pixelWidth: Int get() = data.pixelWidth
|
||||
val pixelHeight: Int get() = data.pixelHeight
|
||||
val allLayers get() = data.allLayers
|
||||
val tileLayers get() = data.tileLayers
|
||||
val imageLayers get() = data.imageLayers
|
||||
val objectLayers get() = data.objectLayers
|
||||
val nextGid get() = tilesets.map { it.firstgid + it.tileset.textures.size }.max() ?: 1
|
||||
|
||||
fun clone() = TiledMap(data.clone(), tilesets.map { it.clone() }.toMutableList())
|
||||
|
||||
enum class Orientation(val value: String) {
|
||||
ORTHOGONAL("orthogonal"),
|
||||
ISOMETRIC("isometric"),
|
||||
STAGGERED("staggered"),
|
||||
HEXAGONAL("hexagonal")
|
||||
}
|
||||
|
||||
enum class RenderOrder(val value: String) {
|
||||
RIGHT_DOWN("right-down"),
|
||||
RIGHT_UP("right-up"),
|
||||
LEFT_DOWN("left-down"),
|
||||
LEFT_UP("left-up")
|
||||
}
|
||||
|
||||
enum class StaggerAxis(val value: String) {
|
||||
X("x"), Y("y")
|
||||
}
|
||||
|
||||
enum class StaggerIndex(val value: String) {
|
||||
EVEN("even"), ODD("odd")
|
||||
}
|
||||
|
||||
enum class ObjectAlignment(val value: String) {
|
||||
UNSPECIFIED("unspecified"),
|
||||
TOP_LEFT("topleft"),
|
||||
TOP("top"),
|
||||
TOP_RIGHT("topright"),
|
||||
LEFT("left"),
|
||||
CENTER("center"),
|
||||
RIGHT("right"),
|
||||
BOTTOM_LEFT("bottomleft"),
|
||||
BOTTOM("bottom"),
|
||||
BOTTOM_RIGHT("bottomright")
|
||||
}
|
||||
|
||||
class Grid(
|
||||
val cellWidth: Int,
|
||||
val cellHeight: Int,
|
||||
val orientation: Orientation = Orientation.ORTHOGONAL
|
||||
) {
|
||||
enum class Orientation(val value: String) {
|
||||
ORTHOGONAL("orthogonal"),
|
||||
ISOMETRIC("isometric")
|
||||
}
|
||||
}
|
||||
|
||||
data class Object(
|
||||
val id: Int,
|
||||
var gid: Int?,
|
||||
var name: String,
|
||||
var type: String,
|
||||
var bounds: Rectangle,
|
||||
var rotation: Double, // in degrees
|
||||
var visible: Boolean,
|
||||
var objectType: Type = Type.Rectangle,
|
||||
val properties: MutableMap<String, Property> = mutableMapOf()
|
||||
) {
|
||||
enum class DrawOrder(val value: String) {
|
||||
INDEX("index"), TOP_DOWN("topdown")
|
||||
}
|
||||
|
||||
sealed class Type {
|
||||
object Rectangle : Type()
|
||||
object Ellipse : Type()
|
||||
object PPoint : Type()
|
||||
class Polygon(val points: List<Point>) : Type()
|
||||
class Polyline(val points: List<Point>) : Type()
|
||||
class Text(
|
||||
val fontFamily: String,
|
||||
val pixelSize: Int,
|
||||
val wordWrap: Boolean,
|
||||
val color: RGBA,
|
||||
val bold: Boolean,
|
||||
val italic: Boolean,
|
||||
val underline: Boolean,
|
||||
val strikeout: Boolean,
|
||||
val kerning: Boolean,
|
||||
val hAlign: TextHAlignment,
|
||||
val vAlign: TextVAlignment
|
||||
) : Type()
|
||||
}
|
||||
}
|
||||
|
||||
enum class TextHAlignment(val value: String) {
|
||||
LEFT("left"), CENTER("center"), RIGHT("right"), JUSTIFY("justify")
|
||||
}
|
||||
|
||||
enum class TextVAlignment(val value: String) {
|
||||
TOP("top"), CENTER("center"), BOTTOM("bottom")
|
||||
}
|
||||
|
||||
sealed class Image(val width: Int, val height: Int, val transparent: RGBA? = null) {
|
||||
class Embedded(
|
||||
val format: String,
|
||||
val image: Bitmap32,
|
||||
val encoding: Encoding,
|
||||
val compression: Compression,
|
||||
transparent: RGBA? = null
|
||||
) : Image(image.width, image.height, transparent)
|
||||
|
||||
class External(
|
||||
val source: String,
|
||||
width: Int,
|
||||
height: Int,
|
||||
transparent: RGBA? = null
|
||||
) : Image(width, height, transparent)
|
||||
}
|
||||
|
||||
enum class Encoding(val value: String?) {
|
||||
BASE64("base64"), CSV("csv"), XML(null)
|
||||
}
|
||||
|
||||
enum class Compression(val value: String?) {
|
||||
NO(null), GZIP("gzip"), ZLIB("zlib"), ZSTD("zstd")
|
||||
}
|
||||
|
||||
sealed class Property {
|
||||
class StringT(var value: String) : Property()
|
||||
class IntT(var value: Int) : Property()
|
||||
class FloatT(var value: Double) : Property()
|
||||
class BoolT(var value: Boolean) : Property()
|
||||
class ColorT(var value: RGBA) : Property()
|
||||
class FileT(var path: String) : Property()
|
||||
class ObjectT(var id: Int) : Property()
|
||||
}
|
||||
|
||||
data class TiledTileset(
|
||||
val tileset: TileSet,
|
||||
val data: TileSetData = TileSetData(
|
||||
name = "unknown",
|
||||
firstgid = 1,
|
||||
tileWidth = tileset.width,
|
||||
tileHeight = tileset.height,
|
||||
tileCount = tileset.textures.size,
|
||||
spacing = 0,
|
||||
margin = 0,
|
||||
columns = tileset.base.width / tileset.width,
|
||||
image = TODO(), //null
|
||||
//width = tileset.base.width,
|
||||
//height = tileset.base.height,
|
||||
terrains = listOf(),
|
||||
tiles = tileset.textures.mapIndexed { index, bmpSlice -> TileData(index) }
|
||||
),
|
||||
val firstgid: Int = 1
|
||||
) {
|
||||
fun clone(): TiledTileset = TiledTileset(tileset.clone(), data.clone(), firstgid)
|
||||
}
|
||||
|
||||
sealed class Layer {
|
||||
var id: Int = 1
|
||||
var name: String = ""
|
||||
var visible: Boolean = true
|
||||
var locked: Boolean = false
|
||||
var opacity = 1.0
|
||||
var tintColor: RGBA? = null
|
||||
var offsetx: Double = 0.0
|
||||
var offsety: Double = 0.0
|
||||
val properties: MutableMap<String, Property> = mutableMapOf()
|
||||
|
||||
open fun copyFrom(other: Layer) {
|
||||
this.id = other.id
|
||||
this.name = other.name
|
||||
this.visible = other.visible
|
||||
this.locked = other.locked
|
||||
this.opacity = other.opacity
|
||||
this.tintColor = other.tintColor
|
||||
this.offsetx = other.offsetx
|
||||
this.offsety = other.offsety
|
||||
this.properties.clear()
|
||||
this.properties.putAll(other.properties)
|
||||
}
|
||||
|
||||
abstract fun clone(): Layer
|
||||
|
||||
class Tiles(
|
||||
var map: Bitmap32 = Bitmap32(0, 0),
|
||||
var encoding: Encoding = Encoding.XML,
|
||||
var compression: Compression = Compression.NO
|
||||
) : Layer() {
|
||||
val width: Int get() = map.width
|
||||
val height: Int get() = map.height
|
||||
val area: Int get() = width * height
|
||||
operator fun set(x: Int, y: Int, value: Int) = run { map.setInt(x, y, value) }
|
||||
operator fun get(x: Int, y: Int): Int = map.getInt(x, y)
|
||||
override fun clone(): Tiles = Tiles(map.clone(), encoding, compression).also { it.copyFrom(this) }
|
||||
}
|
||||
|
||||
class Objects(
|
||||
var color: RGBA = Colors.WHITE,
|
||||
var drawOrder: Object.DrawOrder = Object.DrawOrder.TOP_DOWN,
|
||||
val objects: MutableList<Object> = arrayListOf()
|
||||
) : Layer() {
|
||||
val objectsById by lazy { objects.associateBy { it.id } }
|
||||
val objectsByName by lazy { objects.associateBy { it.name } }
|
||||
|
||||
fun getById(id: Int): Object? = objectsById[id]
|
||||
fun getByName(name: String): Object? = objectsByName[name]
|
||||
|
||||
override fun clone() = Objects(color, drawOrder, ArrayList(objects)).also { it.copyFrom(this) }
|
||||
}
|
||||
|
||||
class Image(var image: TiledMap.Image? = null) : Layer() {
|
||||
override fun clone(): Image = Image(image).also { it.copyFrom(this) }
|
||||
}
|
||||
|
||||
class Group(
|
||||
val layers: MutableList<Layer> = arrayListOf()
|
||||
) : Layer() {
|
||||
override fun clone(): Group = Group(ArrayList(layers)).also { it.copyFrom(this) }
|
||||
}
|
||||
}
|
||||
|
||||
class EditorSettings(
|
||||
val chunkWidth: Int = 16,
|
||||
val chunkHeight: Int = 16
|
||||
)
|
||||
}
|
||||
|
||||
private fun TileSet.clone(): TileSet = TileSet(this.textures, this.width, this.height, this.base)
|
||||
|
||||
inline val Iterable<TiledMap.Layer>.tiles get() = this.filterIsInstance<TiledMap.Layer.Tiles>()
|
||||
inline val Iterable<TiledMap.Layer>.images get() = this.filterIsInstance<TiledMap.Layer.Image>()
|
||||
inline val Iterable<TiledMap.Layer>.objects get() = this.filterIsInstance<TiledMap.Layer.Objects>()
|
||||
|
||||
val tilemapLog = Logger("tilemap")
|
||||
@@ -1,21 +1,20 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.intellij.ui.components.*
|
||||
import com.soywiz.kmem.*
|
||||
import com.soywiz.korge.intellij.editor.*
|
||||
import com.soywiz.korge.intellij.editor.tile.dialog.*
|
||||
import com.soywiz.korge.intellij.editor.tile.editor.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.dialog.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.editor.*
|
||||
import com.soywiz.korge.intellij.ui.*
|
||||
import com.soywiz.korge.intellij.util.*
|
||||
import com.soywiz.korge.intellij.util.ObservableProperty
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korio.async.*
|
||||
import com.soywiz.korio.file.*
|
||||
import com.soywiz.korio.file.std.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.awt.*
|
||||
import java.awt.event.*
|
||||
import javax.swing.*
|
||||
|
||||
fun Styled<out Container>.createTileMapEditor(
|
||||
tilemap: TiledMap = runBlocking { localCurrentDirVfs["samples/gfx/sample.tmx"].readTiledMap() },
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.soywiz.korge.intellij.editor.HistoryManager
|
||||
import com.soywiz.korge.intellij.editor.tile.dialog.ProjectContext
|
||||
import com.soywiz.korge.intellij.editor.tiled.dialog.ProjectContext
|
||||
import com.soywiz.korge.intellij.ui.styled
|
||||
import com.soywiz.korio.file.VfsFile
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.JPanel
|
||||
|
||||
class TileMapEditorPanel(
|
||||
class TiledMapEditorPanel(
|
||||
val tmxFile: VfsFile,
|
||||
val history: HistoryManager = HistoryManager(),
|
||||
val registerHistoryShortcuts: Boolean = true,
|
||||
@@ -20,7 +20,7 @@ class TileMapEditorPanel(
|
||||
styled.createTileMapEditor(tmx, history, registerHistoryShortcuts, projectCtx)
|
||||
history.onSave {
|
||||
runBlocking {
|
||||
val xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + tmx.toXML().toOuterXmlIndented().toString()
|
||||
val xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + tmx.toXml().toOuterXmlIndented().toString()
|
||||
onSaveXml(xmlString)
|
||||
//tmxFile.writeString(xmlString)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.soywiz.korge.intellij.editor.tile
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.intellij.diff.util.*
|
||||
import com.intellij.openapi.command.*
|
||||
@@ -9,12 +9,12 @@ import com.intellij.openapi.util.*
|
||||
import com.intellij.openapi.vfs.*
|
||||
import com.soywiz.korge.intellij.*
|
||||
import com.soywiz.korge.intellij.editor.*
|
||||
import com.soywiz.korge.intellij.editor.tile.dialog.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.dialog.*
|
||||
import com.soywiz.korge.intellij.util.*
|
||||
import java.beans.*
|
||||
import javax.swing.*
|
||||
|
||||
class TileMapEditorProvider : FileEditorProvider, DumbAware {
|
||||
class TiledMapEditorProvider : FileEditorProvider, DumbAware {
|
||||
override fun getEditorTypeId(): String = this::class.java.name
|
||||
override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.PLACE_BEFORE_DEFAULT_EDITOR
|
||||
|
||||
@@ -37,7 +37,7 @@ class TileMapEditorProvider : FileEditorProvider, DumbAware {
|
||||
val refs = arrayOf(ref)
|
||||
|
||||
val fileEditor = object : FileEditorBase(), DumbAware {
|
||||
val panel = TileMapEditorPanel(
|
||||
val panel = TiledMapEditorPanel(
|
||||
tmxFile, history,
|
||||
registerHistoryShortcuts = false,
|
||||
projectCtx = ProjectContext(project, file),
|
||||
@@ -0,0 +1,580 @@
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.soywiz.kds.iterators.*
|
||||
import com.soywiz.kmem.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.TiledMap.*
|
||||
import com.soywiz.korge.view.tiles.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korim.format.*
|
||||
import com.soywiz.korio.compression.*
|
||||
import com.soywiz.korio.compression.deflate.*
|
||||
import com.soywiz.korio.file.*
|
||||
import com.soywiz.korio.lang.*
|
||||
import com.soywiz.korio.serialization.xml.*
|
||||
import com.soywiz.korio.util.encoding.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import kotlin.collections.set
|
||||
|
||||
suspend fun VfsFile.readTiledMap(
|
||||
hasTransparentColor: Boolean = false,
|
||||
transparentColor: RGBA = Colors.FUCHSIA,
|
||||
createBorder: Int = 1
|
||||
): TiledMap {
|
||||
val folder = this.parent.jail()
|
||||
val data = readTiledMapData()
|
||||
|
||||
val tiledTilesets = arrayListOf<TiledTileset>()
|
||||
|
||||
data.tilesets.fastForEach { tileset ->
|
||||
tiledTilesets += tileset.toTiledSet(folder, hasTransparentColor, transparentColor, createBorder)
|
||||
}
|
||||
|
||||
return TiledMap(data, tiledTilesets)
|
||||
}
|
||||
|
||||
suspend fun VfsFile.readTileSetData(firstgid: Int = 1): TileSetData {
|
||||
return parseTileSetData(this.readXml(), firstgid, this.baseName)
|
||||
}
|
||||
|
||||
suspend fun TileSetData.toTiledSet(
|
||||
folder: VfsFile,
|
||||
hasTransparentColor: Boolean = false,
|
||||
transparentColor: RGBA = Colors.FUCHSIA,
|
||||
createBorder: Int = 1
|
||||
): TiledTileset {
|
||||
val tileset = this
|
||||
var bmp = try {
|
||||
when (tileset.image) {
|
||||
is Image.Embedded -> TODO()
|
||||
is Image.External -> folder[tileset.image.source].readBitmapOptimized()
|
||||
null -> Bitmap32(0, 0)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
Bitmap32(tileset.width, tileset.height)
|
||||
}
|
||||
|
||||
// @TODO: Preprocess this, so in JS we don't have to do anything!
|
||||
if (hasTransparentColor) {
|
||||
bmp = bmp.toBMP32()
|
||||
for (n in 0 until bmp.area) {
|
||||
if (bmp.data[n] == transparentColor) bmp.data[n] = Colors.TRANSPARENT_BLACK
|
||||
}
|
||||
}
|
||||
|
||||
val ptileset = if (createBorder > 0) {
|
||||
bmp = bmp.toBMP32()
|
||||
|
||||
if (tileset.spacing >= createBorder) {
|
||||
// There is already separation between tiles, use it as it is
|
||||
val slices = TileSet.extractBmpSlices(
|
||||
bmp,
|
||||
tileset.tileWidth,
|
||||
tileset.tileHeight,
|
||||
tileset.columns,
|
||||
tileset.tileCount,
|
||||
tileset.spacing,
|
||||
tileset.margin
|
||||
)
|
||||
TileSet(slices, tileset.tileWidth, tileset.tileHeight, bmp)
|
||||
} else {
|
||||
// No separation between tiles: create a new Bitmap adding that separation
|
||||
val bitmaps = TileSet.extractBitmaps(
|
||||
bmp,
|
||||
tileset.tileWidth,
|
||||
tileset.tileHeight,
|
||||
tileset.columns,
|
||||
tileset.tileCount,
|
||||
tileset.spacing,
|
||||
tileset.margin
|
||||
)
|
||||
TileSet.fromBitmaps(tileset.tileWidth, tileset.tileHeight, bitmaps, border = createBorder, mipmaps = false)
|
||||
}
|
||||
} else {
|
||||
TileSet(bmp.slice(), tileset.tileWidth, tileset.tileHeight, tileset.columns, tileset.tileCount)
|
||||
}
|
||||
|
||||
val tiledTileset = TiledTileset(
|
||||
tileset = ptileset,
|
||||
data = tileset,
|
||||
firstgid = tileset.firstgid
|
||||
)
|
||||
|
||||
return tiledTileset
|
||||
}
|
||||
|
||||
suspend fun VfsFile.readTiledMapData(): TiledMapData {
|
||||
val file = this
|
||||
val folder = this.parent.jail()
|
||||
val tiledMap = TiledMapData()
|
||||
val mapXml = file.readXml()
|
||||
|
||||
if (mapXml.nameLC != "map") error("Not a TiledMap XML TMX file starting with <map>")
|
||||
|
||||
//TODO: Support different orientations
|
||||
val orientation = mapXml.getString("orientation")
|
||||
tiledMap.orientation = when (orientation) {
|
||||
"orthogonal" -> TiledMap.Orientation.ORTHOGONAL
|
||||
else -> unsupported("Orientation \"$orientation\" is not supported")
|
||||
}
|
||||
val renderOrder = mapXml.getString("renderorder")
|
||||
tiledMap.renderOrder = when (renderOrder) {
|
||||
"right-down" -> RenderOrder.RIGHT_DOWN
|
||||
"right-up" -> RenderOrder.RIGHT_UP
|
||||
"left-down" -> RenderOrder.LEFT_DOWN
|
||||
"left-up" -> RenderOrder.LEFT_UP
|
||||
else -> RenderOrder.RIGHT_DOWN
|
||||
}
|
||||
tiledMap.compressionLevel = mapXml.getInt("compressionlevel") ?: -1
|
||||
tiledMap.width = mapXml.getInt("width") ?: 0
|
||||
tiledMap.height = mapXml.getInt("height") ?: 0
|
||||
tiledMap.tilewidth = mapXml.getInt("tilewidth") ?: 32
|
||||
tiledMap.tileheight = mapXml.getInt("tileheight") ?: 32
|
||||
tiledMap.hexSideLength = mapXml.getInt("hexsidelength")
|
||||
val staggerAxis = mapXml.getString("staggeraxis")
|
||||
tiledMap.staggerAxis = when (staggerAxis) {
|
||||
"x" -> StaggerAxis.X
|
||||
"y" -> StaggerAxis.Y
|
||||
else -> null
|
||||
}
|
||||
val staggerIndex = mapXml.getString("staggerindex")
|
||||
tiledMap.staggerIndex = when (staggerIndex) {
|
||||
"even" -> StaggerIndex.EVEN
|
||||
"odd" -> StaggerIndex.ODD
|
||||
else -> null
|
||||
}
|
||||
tiledMap.backgroundColor = mapXml.getString("backgroundcolor")?.let { colorFromARGB(it, Colors.TRANSPARENT_BLACK) }
|
||||
val nextLayerId = mapXml.getInt("nextlayerid")
|
||||
val nextObjectId = mapXml.getInt("nextobjectid")
|
||||
tiledMap.infinite = mapXml.getInt("infinite") == 1
|
||||
|
||||
mapXml.child("properties")?.parseProperties()?.let {
|
||||
tiledMap.properties.putAll(it)
|
||||
}
|
||||
|
||||
tilemapLog.trace { "tilemap: width=${tiledMap.width}, height=${tiledMap.height}, tilewidth=${tiledMap.tilewidth}, tileheight=${tiledMap.tileheight}" }
|
||||
tilemapLog.trace { "tilemap: $tiledMap" }
|
||||
|
||||
val elements = mapXml.allChildrenNoComments
|
||||
|
||||
tilemapLog.trace { "tilemap: elements=${elements.size}" }
|
||||
tilemapLog.trace { "tilemap: elements=$elements" }
|
||||
|
||||
elements.fastForEach { element ->
|
||||
when (element.nameLC) {
|
||||
"tileset" -> {
|
||||
tilemapLog.trace { "tileset" }
|
||||
val firstgid = element.int("firstgid", 1)
|
||||
val sourcePath = element.getString("source")
|
||||
val tileset = if (sourcePath != null) folder[sourcePath].readXml() else element
|
||||
tiledMap.tilesets += parseTileSetData(tileset, firstgid, sourcePath)
|
||||
}
|
||||
"layer" -> {
|
||||
val layer = element.parseTileLayer(tiledMap.infinite)
|
||||
tiledMap.allLayers += layer
|
||||
}
|
||||
"objectgroup" -> {
|
||||
val layer = element.parseObjectLayer()
|
||||
tiledMap.allLayers += layer
|
||||
}
|
||||
"imagelayer" -> {
|
||||
val layer = element.parseImageLayer()
|
||||
tiledMap.allLayers += layer
|
||||
}
|
||||
"group" -> {
|
||||
val layer = element.parseGroupLayer(tiledMap.infinite)
|
||||
tiledMap.allLayers += layer
|
||||
}
|
||||
"editorsettings" -> {
|
||||
val chunkSize = element.child("chunksize")
|
||||
tiledMap.editorSettings = EditorSettings(
|
||||
chunkWidth = chunkSize?.int("width", 16) ?: 16,
|
||||
chunkHeight = chunkSize?.int("height", 16) ?: 16
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tiledMap.nextLayerId = nextLayerId ?: run {
|
||||
var maxLayerId = 0
|
||||
for (layer in tiledMap.allLayers) {
|
||||
if (layer.id > maxLayerId) maxLayerId = layer.id
|
||||
}
|
||||
maxLayerId + 1
|
||||
}
|
||||
tiledMap.nextObjectId = nextObjectId ?: run {
|
||||
var maxObjectId = 0
|
||||
for (objects in tiledMap.objectLayers) {
|
||||
for (obj in objects.objects) {
|
||||
if (obj.id > maxObjectId) maxObjectId = obj.id
|
||||
}
|
||||
}
|
||||
maxObjectId + 1
|
||||
}
|
||||
|
||||
return tiledMap
|
||||
}
|
||||
|
||||
fun parseTileSetData(tileset: Xml, firstgid: Int, tilesetSource: String? = null): TileSetData {
|
||||
val alignment = tileset.str("objectalignment", "unspecified")
|
||||
val objectAlignment = ObjectAlignment.values().find { it.value == alignment } ?: ObjectAlignment.UNSPECIFIED
|
||||
val tileOffset = tileset.child("tileoffset")
|
||||
|
||||
return TileSetData(
|
||||
name = tileset.str("name"),
|
||||
firstgid = firstgid,
|
||||
tileWidth = tileset.int("tilewidth"),
|
||||
tileHeight = tileset.int("tileheight"),
|
||||
tileCount = tileset.int("tilecount", 0),
|
||||
spacing = tileset.int("spacing", 0),
|
||||
margin = tileset.int("margin", 0),
|
||||
columns = tileset.int("columns", 0),
|
||||
image = tileset.child("image")?.parseImage(),
|
||||
tileOffsetX = tileOffset?.int("x") ?: 0,
|
||||
tileOffsetY = tileOffset?.int("y") ?: 0,
|
||||
grid = tileset.child("grid")?.parseGrid(),
|
||||
tilesetSource = tilesetSource,
|
||||
objectAlignment = objectAlignment,
|
||||
terrains = tileset.children("terraintypes").children("terrain").map { it.parseTerrain() },
|
||||
wangsets = tileset.children("wangsets").children("wangset").map { it.parseWangSet() },
|
||||
properties = tileset.child("properties")?.parseProperties() ?: mapOf(),
|
||||
tiles = tileset.children("tile").map { it.parseTile() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun Xml.parseTile(): TileData {
|
||||
val tile = this
|
||||
fun Xml.parseFrame(): AnimationFrameData {
|
||||
return AnimationFrameData(this.int("tileid"), this.int("duration"))
|
||||
}
|
||||
return TileData(
|
||||
id = tile.int("id"),
|
||||
type = tile.int("type", -1),
|
||||
terrain = tile.str("terrain").takeIf { it.isNotEmpty() }?.split(',')?.map { it.toIntOrNull() },
|
||||
probability = tile.double("probability"),
|
||||
image = tile.child("image")?.parseImage(),
|
||||
properties = tile.child("properties")?.parseProperties() ?: mapOf(),
|
||||
objectGroup = tile.child("objectgroup")?.parseObjectLayer(),
|
||||
frames = tile.child("animation")?.children("frame")?.map { it.parseFrame() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun Xml.parseTerrain(): TerrainData {
|
||||
return TerrainData(
|
||||
name = str("name"),
|
||||
tile = int("tile"),
|
||||
properties = parseProperties()
|
||||
)
|
||||
}
|
||||
|
||||
private fun Xml.parseWangSet(): WangSet {
|
||||
fun Xml.parseWangColor(): WangSet.WangColor {
|
||||
val wangcolor = this
|
||||
return WangSet.WangColor(
|
||||
name = wangcolor.str("name"),
|
||||
color = Colors[wangcolor.str("color")],
|
||||
tileId = wangcolor.int("tile"),
|
||||
probability = wangcolor.double("probability")
|
||||
)
|
||||
}
|
||||
|
||||
fun Xml.parseWangTile(): WangSet.WangTile {
|
||||
val wangtile = this
|
||||
val hflip = wangtile.str("hflip")
|
||||
val vflip = wangtile.str("vflip")
|
||||
val dflip = wangtile.str("dflip")
|
||||
return WangSet.WangTile(
|
||||
tileId = wangtile.int("tileid"),
|
||||
wangId = wangtile.int("wangid"),
|
||||
hflip = hflip == "1" || hflip == "true",
|
||||
vflip = vflip == "1" || vflip == "true",
|
||||
dflip = dflip == "1" || dflip == "true"
|
||||
)
|
||||
}
|
||||
|
||||
val wangset = this
|
||||
return WangSet(
|
||||
name = wangset.str("name"),
|
||||
tileId = wangset.int("tile"),
|
||||
properties = wangset.parseProperties(),
|
||||
cornerColors = wangset.children("wangcornercolor").map { it.parseWangColor() },
|
||||
edgeColors = wangset.children("wangedgecolor").map { it.parseWangColor() },
|
||||
wangtiles = wangset.children("wangtile").map { it.parseWangTile() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun Xml.parseGrid(): Grid {
|
||||
val grid = this
|
||||
val orientation = grid.str("orientation")
|
||||
return Grid(
|
||||
cellWidth = grid.int("width"),
|
||||
cellHeight = grid.int("height"),
|
||||
orientation = Grid.Orientation.values().find { it.value == orientation } ?: Grid.Orientation.ORTHOGONAL
|
||||
)
|
||||
}
|
||||
|
||||
private fun Xml.parseCommonLayerData(layer: Layer) {
|
||||
val element = this
|
||||
layer.id = element.int("id")
|
||||
layer.name = element.str("name")
|
||||
layer.opacity = element.double("opacity", 1.0)
|
||||
layer.visible = element.int("visible", 1) == 1
|
||||
layer.locked = element.int("locked", 0) == 1
|
||||
layer.tintColor = element.strNull("tintcolor")?.let { colorFromARGB(it, Colors.WHITE) }
|
||||
layer.offsetx = element.double("offsetx")
|
||||
layer.offsety = element.double("offsety")
|
||||
|
||||
element.child("properties")?.parseProperties()?.let {
|
||||
layer.properties.putAll(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Xml.parseTileLayer(infinite: Boolean): Layer.Tiles {
|
||||
val layer = Layer.Tiles()
|
||||
parseCommonLayerData(layer)
|
||||
|
||||
val element = this
|
||||
val width = element.int("width")
|
||||
val height = element.int("height")
|
||||
val data = element.child("data")
|
||||
|
||||
val map: Bitmap32
|
||||
val encoding: Encoding
|
||||
val compression: Compression
|
||||
|
||||
if (data == null) {
|
||||
map = Bitmap32(width, height)
|
||||
encoding = Encoding.CSV
|
||||
compression = Compression.NO
|
||||
} else {
|
||||
val enc = data.strNull("encoding")
|
||||
val com = data.strNull("compression")
|
||||
encoding = Encoding.values().find { it.value == enc } ?: Encoding.XML
|
||||
compression = Compression.values().find { it.value == com } ?: Compression.NO
|
||||
val count = width * height
|
||||
|
||||
fun Xml.encodeGids(): IntArray = when (encoding) {
|
||||
Encoding.XML -> {
|
||||
children("tile").map { it.uint("gid").toInt() }.toIntArray()
|
||||
}
|
||||
Encoding.CSV -> {
|
||||
text.replace(spaces, "").split(',').map { it.toUInt().toInt() }.toIntArray()
|
||||
}
|
||||
Encoding.BASE64 -> {
|
||||
val rawContent = text.trim().fromBase64()
|
||||
val content = when (compression) {
|
||||
Compression.NO -> rawContent
|
||||
Compression.GZIP -> rawContent.uncompress(GZIP)
|
||||
Compression.ZLIB -> rawContent.uncompress(ZLib)
|
||||
//TODO: support "zstd" compression
|
||||
//Data.Compression.ZSTD -> rawContent.uncompress(ZSTD)
|
||||
else -> invalidOp("Unknown compression '$compression'")
|
||||
}
|
||||
//TODO: read UIntArray
|
||||
content.readIntArrayLE(0, count)
|
||||
}
|
||||
}
|
||||
|
||||
val tiles: IntArray
|
||||
if (infinite) {
|
||||
tiles = IntArray(count)
|
||||
data.children("chunk").forEach { chunk ->
|
||||
val offsetX = chunk.int("x")
|
||||
val offsetY = chunk.int("y")
|
||||
val cwidth = chunk.int("width")
|
||||
val cheight = chunk.int("height")
|
||||
chunk.encodeGids().forEachIndexed { i, gid ->
|
||||
val x = offsetX + i % cwidth
|
||||
val y = offsetY + i / cwidth
|
||||
tiles[x + y * (offsetX + cwidth)] = gid
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tiles = data.encodeGids()
|
||||
}
|
||||
map = Bitmap32(width, height, RgbaArray(tiles))
|
||||
}
|
||||
|
||||
layer.map = map
|
||||
layer.encoding = encoding
|
||||
layer.compression = compression
|
||||
|
||||
return layer
|
||||
}
|
||||
|
||||
private fun Xml.parseObjectLayer(): Layer.Objects {
|
||||
val layer = Layer.Objects()
|
||||
parseCommonLayerData(layer)
|
||||
|
||||
val element = this
|
||||
val order = element.str("draworder", "topdown")
|
||||
layer.color = colorFromARGB(element.str("color"), Colors["#a0a0a4"])
|
||||
layer.drawOrder = Object.DrawOrder.values().find { it.value == order } ?: Object.DrawOrder.TOP_DOWN
|
||||
|
||||
for (obj in element.children("object")) {
|
||||
val objInstance = Object(
|
||||
id = obj.int("id"),
|
||||
gid = obj.intNull("gid"),
|
||||
name = obj.str("name"),
|
||||
type = obj.str("type"),
|
||||
bounds = obj.run { Rectangle(double("x"), double("y"), double("width"), double("height")) },
|
||||
rotation = obj.double("rotation"),
|
||||
visible = obj.int("visible", 1) != 0
|
||||
//TODO: support object templates
|
||||
//templatePath = obj.strNull("template")
|
||||
)
|
||||
obj.child("properties")?.parseProperties()?.let {
|
||||
objInstance.properties.putAll(it)
|
||||
}
|
||||
|
||||
fun Xml.readPoints() = str("points").split(spaces).map { xy ->
|
||||
val parts = xy.split(',').map { it.trim().toDoubleOrNull() ?: 0.0 }
|
||||
Point(parts[0], parts[1])
|
||||
}
|
||||
|
||||
val ellipse = obj.child("ellipse")
|
||||
val point = obj.child("point")
|
||||
val polygon = obj.child("polygon")
|
||||
val polyline = obj.child("polyline")
|
||||
val text = obj.child("text")
|
||||
val objectType: Object.Type = when {
|
||||
ellipse != null -> Object.Type.Ellipse
|
||||
point != null -> Object.Type.PPoint
|
||||
polygon != null -> Object.Type.Polygon(polygon.readPoints())
|
||||
polyline != null -> Object.Type.Polyline(polyline.readPoints())
|
||||
text != null -> Object.Type.Text(
|
||||
fontFamily = text.str("fontfamily", "sans-serif"),
|
||||
pixelSize = text.int("pixelsize", 16),
|
||||
wordWrap = text.int("wrap", 0) == 1,
|
||||
color = colorFromARGB(text.str("color"), Colors.BLACK),
|
||||
bold = text.int("bold") == 1,
|
||||
italic = text.int("italic") == 1,
|
||||
underline = text.int("underline") == 1,
|
||||
strikeout = text.int("strikeout") == 1,
|
||||
kerning = text.int("kerning", 1) == 1,
|
||||
hAlign = text.str("halign", "left").let { align ->
|
||||
TextHAlignment.values().find { it.value == align } ?: TextHAlignment.LEFT
|
||||
},
|
||||
vAlign = text.str("valign", "top").let { align ->
|
||||
TextVAlignment.values().find { it.value == align } ?: TextVAlignment.TOP
|
||||
}
|
||||
)
|
||||
else -> Object.Type.Rectangle
|
||||
}
|
||||
|
||||
objInstance.objectType = objectType
|
||||
layer.objects.add(objInstance)
|
||||
}
|
||||
|
||||
return layer
|
||||
}
|
||||
|
||||
private fun Xml.parseImageLayer(): Layer.Image {
|
||||
val layer = Layer.Image()
|
||||
parseCommonLayerData(layer)
|
||||
layer.image = child("image")?.parseImage()
|
||||
return layer
|
||||
}
|
||||
|
||||
private fun Xml.parseGroupLayer(infinite: Boolean): Layer.Group {
|
||||
val layer = Layer.Group()
|
||||
parseCommonLayerData(layer)
|
||||
|
||||
allChildrenNoComments.fastForEach { element ->
|
||||
when (element.nameLC) {
|
||||
"layer" -> {
|
||||
val tileLayer = element.parseTileLayer(infinite)
|
||||
layer.layers += tileLayer
|
||||
}
|
||||
"objectgroup" -> {
|
||||
val objectLayer = element.parseObjectLayer()
|
||||
layer.layers += objectLayer
|
||||
}
|
||||
"imagelayer" -> {
|
||||
val imageLayer = element.parseImageLayer()
|
||||
layer.layers += imageLayer
|
||||
}
|
||||
"group" -> {
|
||||
val groupLayer = element.parseGroupLayer(infinite)
|
||||
layer.layers += groupLayer
|
||||
}
|
||||
}
|
||||
}
|
||||
return layer
|
||||
}
|
||||
|
||||
private fun Xml.parseImage(): Image? {
|
||||
val image = this
|
||||
val width = image.int("width")
|
||||
val height = image.int("height")
|
||||
val trans = image.str("trans")
|
||||
val transparent = when {
|
||||
trans.isEmpty() -> null
|
||||
trans.startsWith("#") -> Colors[trans]
|
||||
else -> Colors["#$trans"]
|
||||
}
|
||||
val source = image.str("source")
|
||||
return if (source.isNotEmpty()) {
|
||||
Image.External(
|
||||
source = source,
|
||||
width = width,
|
||||
height = height,
|
||||
transparent = transparent
|
||||
)
|
||||
} else {
|
||||
val data = image.child("data") ?: return null
|
||||
val enc = data.strNull("encoding")
|
||||
val com = data.strNull("compression")
|
||||
val encoding = Encoding.values().find { it.value == enc } ?: Encoding.XML
|
||||
val compression = Compression.values().find { it.value == com } ?: Compression.NO
|
||||
//TODO: read embedded image (png, jpg, etc.) and convert to bitmap
|
||||
val bitmap = Bitmap32(width, height)
|
||||
Image.Embedded(
|
||||
format = image.str("format"),
|
||||
image = bitmap,
|
||||
encoding = encoding,
|
||||
compression = compression,
|
||||
transparent = transparent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Xml.parseProperties(): Map<String, Property> {
|
||||
val out = LinkedHashMap<String, Property>()
|
||||
for (property in this.children("property")) {
|
||||
val pname = property.str("name")
|
||||
val rawValue = property.str("value")
|
||||
val type = property.str("type", "string")
|
||||
val pvalue = when (type) {
|
||||
"string" -> Property.StringT(rawValue)
|
||||
"int" -> Property.IntT(rawValue.toIntOrNull() ?: 0)
|
||||
"float" -> Property.FloatT(rawValue.toDoubleOrNull() ?: 0.0)
|
||||
"bool" -> Property.BoolT(rawValue == "true")
|
||||
"color" -> Property.ColorT(colorFromARGB(rawValue, Colors.TRANSPARENT_BLACK))
|
||||
"file" -> Property.FileT(if (rawValue.isEmpty()) "." else rawValue)
|
||||
"object" -> Property.ObjectT(rawValue.toIntOrNull() ?: 0)
|
||||
else -> Property.StringT(rawValue)
|
||||
}
|
||||
out[pname] = pvalue
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
//TODO: move to korio
|
||||
private fun Xml.uint(name: String, defaultValue: UInt = 0u): UInt =
|
||||
this.attributesLC[name]?.toUIntOrNull() ?: defaultValue
|
||||
|
||||
//TODO: move to korim
|
||||
fun colorFromARGB(color: String, default: RGBA): RGBA {
|
||||
if (!color.startsWith('#') || color.length != 9 && color.length != 7) return default
|
||||
val hex = color.substring(1)
|
||||
val start = if (color.length == 7) 0 else 2
|
||||
val a = if (color.length == 9) hex.substr(0, 2).toInt(16) else 0xFF
|
||||
val r = hex.substr(start + 0, 2).toInt(16)
|
||||
val g = hex.substr(start + 2, 2).toInt(16)
|
||||
val b = hex.substr(start + 4, 2).toInt(16)
|
||||
return RGBA(r, g, b, a)
|
||||
}
|
||||
|
||||
private val spaces = Regex("\\s+")
|
||||
@@ -0,0 +1,440 @@
|
||||
package com.soywiz.korge.intellij.editor.tiled
|
||||
|
||||
import com.soywiz.kmem.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.TiledMap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korio.file.*
|
||||
import com.soywiz.korio.serialization.xml.*
|
||||
import com.soywiz.korio.util.*
|
||||
import com.soywiz.korma.geom.*
|
||||
|
||||
suspend fun VfsFile.writeTiledMap(map: TiledMap) {
|
||||
writeString(map.toXml().toString())
|
||||
}
|
||||
|
||||
fun TiledMap.toXml(): Xml {
|
||||
val map = this
|
||||
val mapData = map.data
|
||||
return buildXml(
|
||||
"map",
|
||||
"version" to 1.2,
|
||||
"tiledversion" to "1.3.1",
|
||||
"orientation" to mapData.orientation.value,
|
||||
"renderorder" to mapData.renderOrder.value,
|
||||
"compressionlevel" to mapData.compressionLevel,
|
||||
"width" to mapData.width,
|
||||
"height" to mapData.height,
|
||||
"tilewidth" to mapData.tilewidth,
|
||||
"tileheight" to mapData.tileheight,
|
||||
"hexsidelength" to mapData.hexSideLength,
|
||||
"staggeraxis" to mapData.staggerAxis,
|
||||
"staggerindex" to mapData.staggerIndex,
|
||||
"backgroundcolor" to mapData.backgroundColor?.toStringARGB(),
|
||||
"infinite" to mapData.infinite.toInt(),
|
||||
"nextlayerid" to mapData.nextLayerId,
|
||||
"nextobjectid" to mapData.nextObjectId
|
||||
) {
|
||||
propertiesToXml(mapData.properties)
|
||||
for (tileset in map.tilesets) {
|
||||
val tilesetData = tileset.data
|
||||
if (tilesetData.tilesetSource != null) {
|
||||
node("tileset", "firstgid" to tilesetData.firstgid, "source" to tilesetData.tilesetSource)
|
||||
} else {
|
||||
node(tilesetData.toXml())
|
||||
}
|
||||
}
|
||||
for (layer in map.allLayers) {
|
||||
when (layer) {
|
||||
is Layer.Tiles -> tileLayerToXml(
|
||||
layer,
|
||||
mapData.infinite,
|
||||
mapData.editorSettings?.chunkWidth ?: 16,
|
||||
mapData.editorSettings?.chunkHeight ?: 16
|
||||
)
|
||||
is Layer.Objects -> objectLayerToXml(layer)
|
||||
is Layer.Image -> imageLayerToXml(layer)
|
||||
is Layer.Group -> groupLayerToXml(
|
||||
layer,
|
||||
mapData.infinite,
|
||||
mapData.editorSettings?.chunkWidth ?: 16,
|
||||
mapData.editorSettings?.chunkHeight ?: 16
|
||||
)
|
||||
}
|
||||
}
|
||||
val editorSettings = mapData.editorSettings
|
||||
if (editorSettings != null && (editorSettings.chunkWidth != 16 || editorSettings.chunkHeight != 16)) {
|
||||
node("editorsettings") {
|
||||
node(
|
||||
"chunksize",
|
||||
"width" to editorSettings.chunkWidth,
|
||||
"height" to editorSettings.chunkHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TileSetData.toXml(): Xml {
|
||||
return buildXml(
|
||||
"tileset",
|
||||
"firstgid" to firstgid,
|
||||
"name" to name,
|
||||
"tilewidth" to tileWidth,
|
||||
"tileheight" to tileHeight,
|
||||
"spacing" to spacing.takeIf { it > 0 },
|
||||
"margin" to margin.takeIf { it > 0 },
|
||||
"tilecount" to tileCount,
|
||||
"columns" to columns,
|
||||
"objectalignment" to objectAlignment.takeIf { it != ObjectAlignment.UNSPECIFIED }?.value
|
||||
) {
|
||||
imageToXml(image)
|
||||
if (tileOffsetX != 0 || tileOffsetY != 0) {
|
||||
node("tileoffset", "x" to tileOffsetX, "y" to tileOffsetY)
|
||||
}
|
||||
grid?.let { grid ->
|
||||
node(
|
||||
"grid",
|
||||
"orientation" to grid.orientation.value,
|
||||
"width" to grid.cellWidth,
|
||||
"height" to grid.cellHeight
|
||||
)
|
||||
}
|
||||
propertiesToXml(properties)
|
||||
if (terrains.isNotEmpty()) {
|
||||
node("terraintypes") {
|
||||
for (terrain in terrains) {
|
||||
node("terrain", "name" to terrain.name, "tile" to terrain.tile) {
|
||||
propertiesToXml(terrain.properties)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tiles.isNotEmpty()) {
|
||||
for (tile in tiles) {
|
||||
node(tile.toXml())
|
||||
}
|
||||
}
|
||||
if (wangsets.isNotEmpty()) {
|
||||
node("wangsets") {
|
||||
for (wangset in wangsets) node(wangset.toXml())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WangSet.toXml(): Xml {
|
||||
return buildXml("wangset", "name" to name, "tile" to tileId) {
|
||||
propertiesToXml(properties)
|
||||
if (cornerColors.isNotEmpty()) {
|
||||
for (color in cornerColors) {
|
||||
node(
|
||||
"wangcornercolor",
|
||||
"name" to color.name,
|
||||
"color" to color.color,
|
||||
"tile" to color.tileId,
|
||||
"probability" to color.probability.takeIf { it != 0.0 }?.niceStr
|
||||
)
|
||||
}
|
||||
}
|
||||
if (edgeColors.isNotEmpty()) {
|
||||
for (color in edgeColors) {
|
||||
node(
|
||||
"wangedgecolor",
|
||||
"name" to color.name,
|
||||
"color" to color.color.toStringARGB(),
|
||||
"tile" to color.tileId,
|
||||
"probability" to color.probability.takeIf { it != 0.0 }?.niceStr
|
||||
)
|
||||
}
|
||||
}
|
||||
if (wangtiles.isNotEmpty()) {
|
||||
for (wangtile in wangtiles) {
|
||||
node(
|
||||
"wangtile",
|
||||
"tileid" to wangtile.tileId,
|
||||
"wangid" to wangtile.wangId.toUInt().toString(16).toUpperCase(),
|
||||
"hflip" to wangtile.hflip.takeIf { it },
|
||||
"vflip" to wangtile.vflip.takeIf { it },
|
||||
"dflip" to wangtile.dflip.takeIf { it }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TileData.toXml(): Xml {
|
||||
return buildXml("tile",
|
||||
"id" to id,
|
||||
"type" to type.takeIf { it != -1 },
|
||||
"terrain" to terrain?.joinToString(",") { it?.toString() ?: "" },
|
||||
"probability" to probability.takeIf { it != 0.0 }?.niceStr
|
||||
) {
|
||||
propertiesToXml(properties)
|
||||
imageToXml(image)
|
||||
objectLayerToXml(objectGroup)
|
||||
if (frames != null && frames.isNotEmpty()) {
|
||||
node("animation") {
|
||||
for (frame in frames) {
|
||||
node("frame", "tileid" to frame.tileId, "duration" to frame.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Chunk(val x: Int, val y: Int, val ids: IntArray)
|
||||
|
||||
private fun XmlBuilder.tileLayerToXml(
|
||||
layer: Layer.Tiles,
|
||||
infinite: Boolean,
|
||||
chunkWidth: Int,
|
||||
chunkHeight: Int
|
||||
) {
|
||||
node(
|
||||
"layer",
|
||||
"id" to layer.id,
|
||||
"name" to layer.name.takeIf { it.isNotEmpty() },
|
||||
"width" to layer.width,
|
||||
"height" to layer.height,
|
||||
"opacity" to layer.opacity.takeIf { it != 1.0 },
|
||||
"visible" to layer.visible.toInt().takeIf { it != 1 },
|
||||
"locked" to layer.locked.toInt().takeIf { it != 0 },
|
||||
"tintcolor" to layer.tintColor,
|
||||
"offsetx" to layer.offsetx.takeIf { it != 0.0 },
|
||||
"offsety" to layer.offsety.takeIf { it != 0.0 }
|
||||
) {
|
||||
propertiesToXml(layer.properties)
|
||||
node("data", "encoding" to layer.encoding.value, "compression" to layer.compression.value) {
|
||||
if (infinite) {
|
||||
val chunks = divideIntoChunks(layer.map.data.ints, chunkWidth, chunkHeight, layer.width)
|
||||
chunks.forEach { chunk ->
|
||||
node(
|
||||
"chunk",
|
||||
"x" to chunk.x,
|
||||
"y" to chunk.y,
|
||||
"width" to chunkWidth,
|
||||
"height" to chunkHeight
|
||||
) {
|
||||
when (layer.encoding) {
|
||||
Encoding.XML -> {
|
||||
chunk.ids.forEach { gid ->
|
||||
node("tile", "gid" to gid.toUInt().takeIf { it != 0u })
|
||||
}
|
||||
}
|
||||
Encoding.CSV -> {
|
||||
text(buildString(chunkWidth * chunkHeight * 4) {
|
||||
append("\n")
|
||||
for (y in 0 until chunkHeight) {
|
||||
for (x in 0 until chunkWidth) {
|
||||
append(chunk.ids[x + y * chunkWidth].toUInt())
|
||||
if (y != chunkHeight - 1 || x != chunkWidth - 1) append(',')
|
||||
}
|
||||
append("\n")
|
||||
}
|
||||
})
|
||||
}
|
||||
Encoding.BASE64 -> {
|
||||
//TODO: convert int array of gids into compressed string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (layer.encoding) {
|
||||
Encoding.XML -> {
|
||||
layer.map.data.ints.forEach { gid ->
|
||||
node("tile", "gid" to gid.toUInt().takeIf { it != 0u })
|
||||
}
|
||||
}
|
||||
Encoding.CSV -> {
|
||||
text(buildString(layer.area * 4) {
|
||||
append("\n")
|
||||
for (y in 0 until layer.height) {
|
||||
for (x in 0 until layer.width) {
|
||||
append(layer.map[x, y].value.toUInt())
|
||||
if (y != layer.height - 1 || x != layer.width - 1) append(',')
|
||||
}
|
||||
append("\n")
|
||||
}
|
||||
})
|
||||
}
|
||||
Encoding.BASE64 -> {
|
||||
//TODO: convert int array of gids into compressed string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun divideIntoChunks(array: IntArray, width: Int, height: Int, totalWidth: Int): Array<Chunk> {
|
||||
val columns = totalWidth / width
|
||||
val rows = array.size / columns
|
||||
return Array(rows * columns) { i ->
|
||||
val cx = i % rows
|
||||
val cy = i / rows
|
||||
Chunk(cx * width, cy * height, IntArray(width * height) { j ->
|
||||
val tx = j % width
|
||||
val ty = j / width
|
||||
array[(cx * width + tx) + (cy * height + ty) * totalWidth]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.objectLayerToXml(layer: Layer.Objects?) {
|
||||
if (layer == null) return
|
||||
node(
|
||||
"objectgroup",
|
||||
"draworder" to layer.drawOrder.value,
|
||||
"id" to layer.id,
|
||||
"name" to layer.name.takeIf { it.isNotEmpty() },
|
||||
"color" to layer.color.toStringARGB().takeIf { it != "#a0a0a4" },
|
||||
"opacity" to layer.opacity.takeIf { it != 1.0 },
|
||||
"visible" to layer.visible.toInt().takeIf { it != 1 },
|
||||
"locked" to layer.locked.toInt().takeIf { it != 0 },
|
||||
"tintcolor" to layer.tintColor,
|
||||
"offsetx" to layer.offsetx.takeIf { it != 0.0 },
|
||||
"offsety" to layer.offsety.takeIf { it != 0.0 }
|
||||
) {
|
||||
propertiesToXml(layer.properties)
|
||||
layer.objects.forEach { obj ->
|
||||
node(
|
||||
"object",
|
||||
"id" to obj.id,
|
||||
"gid" to obj.gid,
|
||||
"name" to obj.name.takeIf { it.isNotEmpty() },
|
||||
"type" to obj.type.takeIf { it.isNotEmpty() },
|
||||
"x" to obj.bounds.x.takeIf { it != 0.0 },
|
||||
"y" to obj.bounds.y.takeIf { it != 0.0 },
|
||||
"width" to obj.bounds.width.takeIf { it != 0.0 },
|
||||
"height" to obj.bounds.height.takeIf { it != 0.0 },
|
||||
"rotation" to obj.rotation.takeIf { it != 0.0 },
|
||||
"visible" to obj.visible.toInt().takeIf { it != 1 }
|
||||
//TODO: support object template
|
||||
//"template" to obj.template
|
||||
) {
|
||||
propertiesToXml(obj.properties)
|
||||
|
||||
fun List<Point>.toXml() = joinToString(" ") { p -> "${p.x.niceStr},${p.y.niceStr}" }
|
||||
|
||||
when (val type = obj.objectType) {
|
||||
is Object.Type.Rectangle -> Unit
|
||||
is Object.Type.Ellipse -> node("ellipse")
|
||||
is Object.Type.PPoint -> node("point")
|
||||
is Object.Type.Polygon -> node("polygon", "points" to type.points.toXml())
|
||||
is Object.Type.Polyline -> node("polyline", "points" to type.points.toXml())
|
||||
is Object.Type.Text -> node(
|
||||
"text",
|
||||
"fontfamily" to type.fontFamily,
|
||||
"pixelsize" to type.pixelSize.takeIf { it != 16 },
|
||||
"wrap" to type.wordWrap.toInt().takeIf { it != 0 },
|
||||
"color" to type.color.toStringARGB(),
|
||||
"bold" to type.bold.toInt().takeIf { it != 0 },
|
||||
"italic" to type.italic.toInt().takeIf { it != 0 },
|
||||
"underline" to type.underline.toInt().takeIf { it != 0 },
|
||||
"strikeout" to type.strikeout.toInt().takeIf { it != 0 },
|
||||
"kerning" to type.kerning.toInt().takeIf { it != 1 },
|
||||
"halign" to type.hAlign.value,
|
||||
"valign" to type.vAlign.value
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.imageLayerToXml(layer: Layer.Image) {
|
||||
node(
|
||||
"imagelayer",
|
||||
"id" to layer.id,
|
||||
"name" to layer.name.takeIf { it.isNotEmpty() },
|
||||
"opacity" to layer.opacity.takeIf { it != 1.0 },
|
||||
"visible" to layer.visible.toInt().takeIf { it != 1 },
|
||||
"locked" to layer.locked.toInt().takeIf { it != 0 },
|
||||
"tintcolor" to layer.tintColor,
|
||||
"offsetx" to layer.offsetx.takeIf { it != 0.0 },
|
||||
"offsety" to layer.offsety.takeIf { it != 0.0 }
|
||||
) {
|
||||
propertiesToXml(layer.properties)
|
||||
imageToXml(layer.image)
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.groupLayerToXml(layer: Layer.Group, infinite: Boolean, chunkWidth: Int, chunkHeight: Int) {
|
||||
node(
|
||||
"group",
|
||||
"id" to layer.id,
|
||||
"name" to layer.name.takeIf { it.isNotEmpty() },
|
||||
"opacity" to layer.opacity.takeIf { it != 1.0 },
|
||||
"visible" to layer.visible.toInt().takeIf { it != 1 },
|
||||
"locked" to layer.locked.toInt().takeIf { it != 0 },
|
||||
"tintcolor" to layer.tintColor,
|
||||
"offsetx" to layer.offsetx.takeIf { it != 0.0 },
|
||||
"offsety" to layer.offsety.takeIf { it != 0.0 }
|
||||
) {
|
||||
propertiesToXml(layer.properties)
|
||||
layer.layers.forEach {
|
||||
when (it) {
|
||||
is Layer.Tiles -> tileLayerToXml(it, infinite, chunkWidth, chunkHeight)
|
||||
is Layer.Objects -> objectLayerToXml(it)
|
||||
is Layer.Image -> imageLayerToXml(it)
|
||||
is Layer.Group -> groupLayerToXml(it, infinite, chunkWidth, chunkHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.imageToXml(image: Image?) {
|
||||
if (image == null) return
|
||||
node(
|
||||
"image",
|
||||
when (image) {
|
||||
is Image.Embedded -> "format" to image.format
|
||||
is Image.External -> "source" to image.source
|
||||
},
|
||||
"width" to image.width,
|
||||
"height" to image.height,
|
||||
"transparent" to image.transparent
|
||||
) {
|
||||
if (image is Image.Embedded) {
|
||||
node(
|
||||
"data",
|
||||
"encoding" to image.encoding.value,
|
||||
"compression" to image.compression.value
|
||||
) {
|
||||
//TODO: encode and compress image
|
||||
text(image.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun XmlBuilder.propertiesToXml(properties: Map<String, Property>) {
|
||||
if (properties.isEmpty()) return
|
||||
|
||||
fun property(name: String, type: String, value: Any) =
|
||||
node("property", "name" to name, "type" to type, "value" to value)
|
||||
|
||||
node("properties") {
|
||||
properties.forEach { (name, prop) ->
|
||||
when (prop) {
|
||||
is Property.StringT -> property(name, "string", prop.value)
|
||||
is Property.IntT -> property(name, "int", prop.value)
|
||||
is Property.FloatT -> property(name, "float", prop.value)
|
||||
is Property.BoolT -> property(name, "bool", prop.value.toString())
|
||||
is Property.ColorT -> property(name, "color", prop.value.toStringARGB())
|
||||
is Property.FileT -> property(name, "file", prop.path)
|
||||
is Property.ObjectT -> property(name, "object", prop.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: move to korim
|
||||
private fun RGBA.toStringARGB(): String {
|
||||
if (a == 0xFF) {
|
||||
return "#%02x%02x%02x".format(r, g, b)
|
||||
} else {
|
||||
return "#%02x%02x%02x%02x".format(a, r, g, b)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.soywiz.korge.intellij.editor.tile.dialog
|
||||
package com.soywiz.korge.intellij.editor.tiled.dialog
|
||||
|
||||
import com.intellij.openapi.fileChooser.*
|
||||
import com.intellij.openapi.project.*
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.soywiz.korge.intellij.editor.tile.dialog
|
||||
package com.soywiz.korge.intellij.editor.tiled.dialog
|
||||
|
||||
import com.intellij.ui.*
|
||||
import com.soywiz.korge.intellij.ui.*
|
||||
import com.soywiz.korge.intellij.util.*
|
||||
import com.soywiz.korge.intellij.util.ObservableProperty
|
||||
import com.soywiz.korio.async.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import java.awt.event.*
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.soywiz.korge.intellij.editor.tile.editor
|
||||
package com.soywiz.korge.intellij.editor.tiled.editor
|
||||
|
||||
import com.intellij.ui.components.*
|
||||
import com.soywiz.kmem.*
|
||||
import com.soywiz.korge.intellij.*
|
||||
import com.soywiz.korge.intellij.editor.tile.*
|
||||
import com.soywiz.korge.intellij.editor.tile.dialog.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.*
|
||||
import com.soywiz.korge.intellij.editor.tiled.dialog.*
|
||||
import com.soywiz.korge.intellij.ui.*
|
||||
import com.soywiz.korge.intellij.util.*
|
||||
import com.soywiz.korge.view.tiles.*
|
||||
@@ -151,9 +151,8 @@ fun Styled<out JTabbedPane>.tilesetTab(
|
||||
data class PickedSelection(val data: Bitmap32)
|
||||
|
||||
private fun TiledMap.TiledTileset.pickerTilemap(): TiledMap {
|
||||
val tileset = this.tileset
|
||||
val mapWidth = this.data.columns.takeIf { it >= 0 } ?: (this.tileset.width / this.data.tilewidth)
|
||||
val mapHeight = ceil(this.data.tilecount.toDouble() / this.data.columns.toDouble()).toInt()
|
||||
val mapWidth = data.columns.takeIf { it >= 0 } ?: (tileset.width / data.tileWidth)
|
||||
val mapHeight = ceil(data.tileCount.toDouble() / data.columns.toDouble()).toInt()
|
||||
|
||||
return TiledMap(TiledMapData(
|
||||
width = mapWidth, height = mapHeight,
|
||||
@@ -169,14 +168,14 @@ private suspend fun tiledsetFromBitmap(file: VfsFile, tileWidth: Int, tileHeight
|
||||
return TileSetData(
|
||||
name = file.baseName.substringBeforeLast("."),
|
||||
firstgid = firstgid,
|
||||
tilewidth = tileset.width,
|
||||
tileheight = tileset.height,
|
||||
tilecount = tileset.textures.size,
|
||||
tileWidth = tileset.width,
|
||||
tileHeight = tileset.height,
|
||||
tileCount = tileset.textures.size,
|
||||
//TODO: provide these values as params
|
||||
spacing = 0,
|
||||
margin = 0,
|
||||
columns = tileset.base.width / tileset.width,
|
||||
image = null,
|
||||
imageSource = file.baseName,
|
||||
width = tileset.base.width,
|
||||
height = tileset.base.height,
|
||||
image = TiledMap.Image.External(file.baseName, bmp.width, bmp.height),
|
||||
tilesetSource = null,
|
||||
terrains = listOf(),
|
||||
tiles = listOf()
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.soywiz.korge.intellij.ui
|
||||
|
||||
import com.soywiz.korge.intellij.editor.tile.createTileMapEditor
|
||||
import com.soywiz.korge.intellij.editor.tiled.createTileMapEditor
|
||||
import java.awt.Dimension
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.UIManager
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<fileIconProvider implementation="com.soywiz.korge.intellij.KorgeFileIconProvider"/>
|
||||
<fileEditorProvider implementation="com.soywiz.korge.intellij.editor.KorgeBothFileEditorProvider"/>
|
||||
<fileEditorProvider implementation="com.soywiz.korge.intellij.editor.KorgeHiddenFileEditorProvider"/>
|
||||
<fileEditorProvider implementation="com.soywiz.korge.intellij.editor.tile.TileMapEditorProvider"/>
|
||||
<fileEditorProvider implementation="com.soywiz.korge.intellij.editor.tiled.TiledMapEditorProvider"/>
|
||||
<fileEditorProvider implementation="com.soywiz.korge.intellij.editor.KorgeImageEditorProvider"/>
|
||||
<!--
|
||||
<fileTypeDetector implementation="com.soywiz.korge.intellij.KorgeFileTypeDetector"></fileTypeDetector>
|
||||
|
||||
Reference in New Issue
Block a user