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:
Mikhail Reznichenko
2020-07-06 19:15:13 +07:00
committed by GitHub
parent ca597a9db2
commit 0e69e4ebad
18 changed files with 1434 additions and 668 deletions

View File

@@ -3,4 +3,4 @@ kotlin.code.style=official
# version
kotlinVersion=1.3.72
korgeVersion=1.12.2.2
korgeVersion=1.13.8.3

View File

@@ -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)

View File

@@ -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+")

View File

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

View File

@@ -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.*

View File

@@ -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.*

View File

@@ -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) {

View File

@@ -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")

View File

@@ -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() },

View File

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

View File

@@ -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),

View 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+")

View File

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

View File

@@ -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.*

View File

@@ -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.*

View File

@@ -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()

View File

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

View File

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