@canvasengine/tiled 2.0.0-beta.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/classes/Gid.d.ts +13 -0
  2. package/dist/classes/Layer.d.ts +26 -0
  3. package/dist/classes/Map.d.ts +232 -0
  4. package/dist/classes/Object.d.ts +8 -0
  5. package/dist/classes/Properties.d.ts +11 -0
  6. package/dist/classes/Tile.d.ts +19 -0
  7. package/dist/classes/Tileset.d.ts +13 -0
  8. package/dist/generate/tileset.d.ts +11 -0
  9. package/dist/generate/wangtile.d.ts +21 -0
  10. package/dist/index.d.ts +16 -0
  11. package/dist/index.js +6205 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/parser/open-file.d.ts +24 -0
  14. package/dist/parser/parser.d.ts +27 -0
  15. package/dist/types/Layer.d.ts +116 -0
  16. package/dist/types/Map.d.ts +72 -0
  17. package/dist/types/Objects.d.ts +57 -0
  18. package/dist/types/Text.d.ts +47 -0
  19. package/dist/types/Tile.d.ts +17 -0
  20. package/dist/types/Tileset.d.ts +92 -0
  21. package/dist/types/Types.d.ts +147 -0
  22. package/dist/types/WorldMaps.d.ts +12 -0
  23. package/dist/utils.d.ts +10 -0
  24. package/package.json +31 -0
  25. package/readme.md +1 -0
  26. package/src/classes/Gid.ts +46 -0
  27. package/src/classes/Layer.ts +135 -0
  28. package/src/classes/Map.ts +443 -0
  29. package/src/classes/Object.ts +16 -0
  30. package/src/classes/Properties.ts +30 -0
  31. package/src/classes/Tile.ts +22 -0
  32. package/src/classes/Tileset.ts +34 -0
  33. package/src/generate/tileset.ts +35 -0
  34. package/src/generate/wangtile.ts +166 -0
  35. package/src/index.ts +16 -0
  36. package/src/parser/open-file.ts +155 -0
  37. package/src/parser/parser.ts +309 -0
  38. package/src/types/Layer.ts +127 -0
  39. package/src/types/Map.ts +83 -0
  40. package/src/types/Objects.ts +64 -0
  41. package/src/types/Text.ts +47 -0
  42. package/src/types/Tile.ts +19 -0
  43. package/src/types/Tileset.ts +99 -0
  44. package/src/types/Types.ts +157 -0
  45. package/src/types/WorldMaps.ts +13 -0
  46. package/src/utils.ts +22 -0
  47. package/tests/class.spec.ts +88 -0
  48. package/tests/data.ts +5440 -0
  49. package/tests/parser.spec.ts +112 -0
  50. package/tests/tile-properties.spec.ts +99 -0
  51. package/tests/tiledmap-multi-layers.spec.ts +99 -0
  52. package/tests/tiledmap.spec.ts +223 -0
  53. package/tsconfig.json +28 -0
  54. package/vite.config.ts +21 -0
@@ -0,0 +1,309 @@
1
+ import { xml2js } from 'xml-js'
2
+ import { TiledMap } from '../types/Map'
3
+ import { TilesetTile } from '../types/Tile'
4
+ import { TiledTileset } from '../types/Tileset'
5
+ import { Buffer } from 'buffer'
6
+ import { getBaseName, joinPath } from '../utils'
7
+
8
+ export class TiledParser {
9
+ private layers: Map<number, any> = new Map()
10
+
11
+ constructor(private xml: string, private filePath: string = '', private basePath: string = '') {
12
+ }
13
+
14
+ static propToNumber = (obj, props: string[]) => {
15
+ for (let key of props) {
16
+ if (obj[key] !== undefined) {
17
+ obj[key] = +obj[key]
18
+ }
19
+ }
20
+ return obj
21
+ }
22
+
23
+ static propToBool = (obj, props: string[]) => {
24
+ for (let key of props) {
25
+ if (obj[key] !== undefined) {
26
+ obj[key] = obj[key] == 'true' || obj[key] == '1'
27
+ }
28
+ }
29
+ return obj
30
+ }
31
+
32
+ static toArray<T>(prop): T[] {
33
+ if (!prop) return []
34
+ if (!Array.isArray(prop)) return [prop]
35
+ return prop
36
+ }
37
+
38
+ getImagePath(image: string) {
39
+ if (this.filePath.startsWith('http')) return new URL(image, this.filePath).href
40
+ return joinPath(this.basePath, image)
41
+ }
42
+
43
+ /**
44
+ * Check if the object is a tileset source reference
45
+ * Tileset sources should not have their paths transformed with getImagePath
46
+ */
47
+ private isTilesetSource(obj: any): boolean {
48
+ // Check if object has tileset-specific properties
49
+ return obj.firstgid !== undefined ||
50
+ obj.tilewidth !== undefined ||
51
+ obj.tileheight !== undefined ||
52
+ obj.tilecount !== undefined ||
53
+ obj.columns !== undefined
54
+ }
55
+
56
+ transform = (obj) => {
57
+ if (!obj) return
58
+ const attr = obj.attributes || obj._attributes
59
+ if (!attr) return obj
60
+ let newObj = {
61
+ ...obj,
62
+ ...attr,
63
+ ...TiledParser.propToNumber(attr, [
64
+ 'version',
65
+ 'width',
66
+ 'height',
67
+ 'tilewidth',
68
+ 'tileheight',
69
+ 'nextlayerid',
70
+ 'nextobjectid',
71
+ 'hexsidelength',
72
+ 'opacity',
73
+ 'x',
74
+ 'y',
75
+ 'offsetx',
76
+ 'offsety',
77
+ 'startx',
78
+ 'starty',
79
+ 'id',
80
+ 'firstgid',
81
+ 'imageheight',
82
+ 'imagewidth',
83
+ 'margin',
84
+ 'columns',
85
+ 'rows',
86
+ 'tilecount',
87
+ 'rotation',
88
+ 'gid',
89
+ 'tileid',
90
+ 'duration',
91
+ 'parallaxx',
92
+ 'parallaxy',
93
+ 'repeatx',
94
+ 'repeaty',
95
+ 'pixelsize'
96
+ ]),
97
+ ...TiledParser.propToBool(attr, [
98
+ 'visible',
99
+ 'infinite',
100
+ 'locked',
101
+ 'bold',
102
+ 'italic',
103
+ 'kerning',
104
+ 'strikeout',
105
+ 'underline',
106
+ 'wrap'
107
+ ])
108
+ }
109
+ if (newObj.properties) {
110
+ const properties: any = TiledParser.toArray(newObj.properties.property)
111
+ const propObj = {}
112
+ for (let prop of properties) {
113
+ const attr = prop._attributes
114
+ if (!attr) continue
115
+ let val
116
+ switch (attr.type) {
117
+ case 'file':
118
+ val = this.getImagePath(attr.value)
119
+ break
120
+ case 'object':
121
+ case 'float':
122
+ case 'int':
123
+ val = +attr.value
124
+ break
125
+ case 'bool':
126
+ val = attr.value == 'true' ? true : false
127
+ break
128
+ case 'class':
129
+ val = {
130
+ ...(this.transform(prop)?.properties ?? {}),
131
+ _classname: attr.propertytype
132
+ }
133
+ break
134
+ default:
135
+ val = attr.value
136
+ }
137
+ propObj[attr.name] = val
138
+ }
139
+ newObj.properties = propObj
140
+ }
141
+ if (newObj.polygon) {
142
+ newObj.polygon = this.transform(newObj.polygon)
143
+ }
144
+ if (newObj.polyline) {
145
+ newObj.polyline = this.transform(newObj.polyline)
146
+ }
147
+ if (newObj.points) {
148
+ newObj = newObj.points.split(' ').map(point => {
149
+ const pos = point.split(',')
150
+ return { x: +pos[0], y: +pos[1] }
151
+ })
152
+ }
153
+ if (newObj.point) {
154
+ newObj.point = true
155
+ }
156
+ if (newObj.ellipse) {
157
+ newObj.ellipse = true
158
+ }
159
+ if (newObj.text) {
160
+ newObj.text = {
161
+ text: newObj.text._text,
162
+ ...this.transform(newObj.text)
163
+ }
164
+ delete newObj.text._text
165
+ }
166
+ if (newObj.image) {
167
+ newObj.image = this.transform(newObj.image)
168
+ }
169
+ if (newObj.source) {
170
+ // For tileset source, keep the original path without transformation
171
+ // The path resolution will be handled in the TiledParserFile class
172
+ if (!this.isTilesetSource(newObj)) {
173
+ newObj.source = this.getImagePath(newObj.source)
174
+ }
175
+ }
176
+ const objectgroup = newObj.object || newObj.objectgroup?.object
177
+ if (objectgroup) {
178
+ newObj.objects = TiledParser.toArray(objectgroup).map((object: any) => {
179
+ return this.transform(object)
180
+ })
181
+ }
182
+ delete newObj._attributes
183
+ delete newObj.attributes
184
+ delete newObj.object
185
+ delete newObj.objectgroup
186
+ return newObj
187
+ }
188
+
189
+ static unpackTileBytes(buffer: Buffer, size: number): number[] | never {
190
+ const expectedCount = size * 4
191
+ if (buffer.length !== expectedCount) {
192
+ throw new Error("Expected " + expectedCount +
193
+ " bytes of tile data; received " + buffer.length)
194
+ }
195
+ let tileIndex = 0
196
+ const array: number[] = []
197
+ for (let i = 0; i < expectedCount; i += 4) {
198
+ array[tileIndex] = buffer.readUInt32LE(i)
199
+ tileIndex++
200
+ }
201
+ return array
202
+ }
203
+
204
+ static decode(obj: { encoding: string, data: string }, size: number) {
205
+ const { encoding, data } = obj
206
+ if (encoding == 'base64') {
207
+ return TiledParser.unpackTileBytes(Buffer.from(data.trim(), 'base64'), size)
208
+ }
209
+ else if (encoding == 'csv') {
210
+ return data.trim().split(',').map(x => +x)
211
+ }
212
+ return data
213
+ }
214
+
215
+ parseMap(): TiledMap {
216
+ const json: any = xml2js(this.xml, { compact: true })
217
+ const jsonNoCompact: any = xml2js(this.xml)
218
+ //const layer = json.map.layer
219
+ const tileset = json.map.tileset
220
+ const group = json.map.group
221
+
222
+ const recursiveObjectGroup = (obj) => {
223
+ const { objectgroup, group, layer, imagelayer } = obj
224
+ const setLayer = (type) => {
225
+ if (!type) return
226
+ TiledParser.toArray(type).forEach((val: any) => {
227
+ if (this.layers.has(+val._attributes.id)) {
228
+ throw new Error(`Tiled Parser Error: Layer with id ${val._attributes.id} already exists`)
229
+ }
230
+ this.layers.set(+val._attributes.id, val)
231
+ })
232
+ }
233
+ setLayer(objectgroup)
234
+ setLayer(layer)
235
+ setLayer(group)
236
+ setLayer(imagelayer)
237
+ if (group) {
238
+ recursiveObjectGroup(group)
239
+ }
240
+ }
241
+
242
+ recursiveObjectGroup(json.map)
243
+
244
+ const recursiveLayer = (elements, array: any = []) => {
245
+ if (!elements) return array
246
+ for (let element of elements) {
247
+ const { name } = element
248
+ if (!['layer', 'group', 'imagelayer', 'objectgroup'].includes(name)) continue
249
+ const data = element.elements?.find(el => el.name == 'data')
250
+ element.layer = this.layers.get(+element.attributes.id)
251
+ const obj = {
252
+ ...(this.transform(data) ?? {}),
253
+ ...this.transform(element),
254
+ ...this.transform(element.layer),
255
+ layers: recursiveLayer(element.elements),
256
+ data: data ? data.elements[0].text : undefined,
257
+ type: name == 'layer' ? 'tilelayer' : name
258
+ }
259
+ delete obj.elements
260
+ delete obj.layer
261
+ if (obj.data) obj.data = TiledParser.decode(obj, obj.width * obj.height)
262
+ array.push(obj)
263
+ }
264
+ return array
265
+ }
266
+
267
+ const layers = recursiveLayer(jsonNoCompact.elements[0].elements)
268
+
269
+ const tilesets = TiledParser.toArray<TiledTileset>(tileset).map(tileset => {
270
+ const obj = this.transform(tileset)
271
+ return obj
272
+ })
273
+
274
+ const ret = {
275
+ ...this.transform(json.map),
276
+ layers,
277
+ tilesets
278
+ }
279
+
280
+ delete ret.layer
281
+ delete ret.tileset
282
+ delete ret.group
283
+ delete ret.imagelayer
284
+
285
+ return ret
286
+ }
287
+
288
+ parseTileset(): TiledTileset {
289
+ const json: any = xml2js(this.xml, { compact: true })
290
+ const { tileset } = json
291
+
292
+ const ret = {
293
+ ...this.transform(tileset),
294
+ image: this.transform(tileset.image),
295
+ tiles: TiledParser.toArray<TilesetTile>(tileset.tile).map((tile: any) => {
296
+ const ret = this.transform(tile)
297
+ if (tile.animation) {
298
+ ret.animations = TiledParser.toArray(tile.animation.frame).map(this.transform)
299
+ }
300
+ delete ret.animation
301
+ return ret
302
+ })
303
+ }
304
+
305
+ delete ret.tile
306
+
307
+ return ret
308
+ }
309
+ }
@@ -0,0 +1,127 @@
1
+ import { TiledObject } from "./Objects";
2
+ import { TiledChunk, TiledCompression, TiledEncoding, TiledImage, TiledProperty } from "./Types";
3
+
4
+ export enum TiledLayerType {
5
+ Tile = 'tilelayer',
6
+ ObjectGroup = 'objectgroup',
7
+ Image = 'imagelayer',
8
+ Group = 'group'
9
+ }
10
+
11
+ export interface TiledLayer {
12
+ /**
13
+ * Incremental ID - unique across all layers
14
+ */
15
+ id: number;
16
+ /**
17
+ * Image used by this layer. imagelayer only.
18
+ */
19
+ image: TiledImage;
20
+ /**
21
+ * Array of unsigned int (GIDs) or base64-encoded data. tilelayer only.
22
+ */
23
+ data: number[] | string;
24
+ /**
25
+ * Array of chunks (optional). tilelayer only.
26
+ */
27
+ chunks: TiledChunk[];
28
+ /**
29
+ * Column count. Same as map width for fixed-size maps.
30
+ */
31
+ width: number;
32
+ /**
33
+ * Row count. Same as map height for fixed-size maps.
34
+ */
35
+ height: number;
36
+ /**
37
+ * Name assigned to this layer
38
+ */
39
+ name: string;
40
+ /**
41
+ * From [0, 1]
42
+ */
43
+ opacity: number;
44
+ properties: {
45
+ [key: string]: any
46
+ }
47
+ /**
48
+ * csv (default) or base64. tilelayer only.
49
+ */
50
+ encoding: TiledEncoding;
51
+ /**
52
+ * zlib, gzip, zstd (since Tiled 1.3) or empty (default). tilelayer only.
53
+ */
54
+ compression?: TiledCompression;
55
+
56
+ /**
57
+ * Type of layer (tilelayer, objectgroup)
58
+ */
59
+ type: TiledLayerType;
60
+
61
+ /**
62
+ * @since 1.9
63
+ */
64
+ class: string
65
+
66
+ /**
67
+ * Whether layer is shown or hidden in editor
68
+ */
69
+ visible: boolean;
70
+
71
+ /**
72
+ * Horizontal layer offset in tiles. Always 0.
73
+ */
74
+ x: number;
75
+ /**
76
+ * Vertical layer offset in tiles. Always 0.
77
+ */
78
+ y: number;
79
+
80
+ /**
81
+ * Layer order in the original Tiled source
82
+ */
83
+ order: number;
84
+ /**
85
+ * Horizontal layer offset in pixels (default: 0)
86
+ */
87
+ offsetx: number;
88
+ /**
89
+ * Vertical layer offset in pixels (default: 0)
90
+ */
91
+ offsety: number;
92
+ /**
93
+ * X coordinate where layer content starts (for infinite maps)
94
+ */
95
+ startx: number;
96
+ /**
97
+ * Y coordinate where layer content starts (for infinite maps)
98
+ */
99
+ starty: number;
100
+
101
+ /**
102
+ * Hex-formatted color (#RRGGBB or #AARRGGBB) that is multiplied with any graphics drawn by this layer or any child layers (optional).
103
+ */
104
+ tintcolor: string;
105
+ /**
106
+ * Hex-formatted color (#RRGGBB) (optional). imagelayer only.
107
+ */
108
+ transparentcolor: string;
109
+
110
+ /**
111
+ * topdown (default) or index. objectgroup only.
112
+ */
113
+ draworder: 'topdown' | 'index' | 'objectgroup';
114
+ /**
115
+ * Array of objects. objectgroup only.
116
+ */
117
+ objects: TiledObject[];
118
+
119
+ layers: TiledLayer[]
120
+
121
+ parallaxx: number
122
+ parallaxy: number
123
+ repeatx: number
124
+ repeaty: number
125
+ locked: boolean
126
+ color: string
127
+ }
@@ -0,0 +1,83 @@
1
+ import { TiledLayer } from "./Layer";
2
+ import { TiledTileset } from "./Tileset";
3
+ import { TiledProperty } from "./Types";
4
+
5
+ export interface TiledMap {
6
+ type: 'map';
7
+
8
+ version: number;
9
+
10
+ width: number;
11
+ /**
12
+ * Number of tile rows
13
+ */
14
+ height: number;
15
+ /**
16
+ * Length of the side of a hex tile in pixels (hexagonal maps only)
17
+ */
18
+ hexsidelength: number;
19
+ /**
20
+ * Map grid height
21
+ */
22
+ tileheight: number;
23
+ /**
24
+ * Map grid width
25
+ */
26
+ tilewidth: number;
27
+
28
+ /**
29
+ * Hex-formatted color (#RRGGBB or #AARRGGBB) (optional)
30
+ */
31
+ backgroundcolor: string;
32
+ /**
33
+ * The compression level to use for tile layer data (defaults to -1, which means to use the algorithm default)
34
+ */
35
+ compressionlevel: number;
36
+ /**
37
+ * Whether the map has infinite dimensions
38
+ */
39
+ infinite: boolean;
40
+ /**
41
+ * Auto-increments for each layer
42
+ */
43
+ nextlayerid: number;
44
+ /**
45
+ * Auto-increments for each placed object
46
+ */
47
+ nextobjectid: number;
48
+
49
+ /**
50
+ * Map orientation (orthogonal, isometric, staggered or hexagonal)
51
+ */
52
+ orientation: 'orthogonal' | 'isometric' | 'staggered' | 'hexagonal';
53
+
54
+ layers: TiledLayer[];
55
+ properties: {
56
+ [key: string]: any
57
+ }
58
+ tilesets: TiledTileset[];
59
+
60
+ /**
61
+ * Render order: right-down (the default), right-up, left-down or left-up (currently only supported for orthogonal maps)
62
+ */
63
+ renderorder: 'right-down' | 'right-up' | 'left-down' | 'left-up';
64
+
65
+ /**
66
+ * x or y (staggered / hexagonal maps only)
67
+ */
68
+ staggeraxis: 'x' | 'y';
69
+ /**
70
+ * odd or even (staggered / hexagonal maps only)
71
+ */
72
+ staggerindex: 'odd' | 'even';
73
+
74
+ /**
75
+ * The Tiled version used to save the file
76
+ */
77
+ tiledversion: string;
78
+
79
+ /**
80
+ * @since 1.9
81
+ */
82
+ class: string
83
+ }
@@ -0,0 +1,64 @@
1
+ import { TiledText } from "./Text";
2
+ import { TiledPoint, TiledProperty } from "./Types";
3
+
4
+ export interface TiledObject {
5
+ id: number;
6
+
7
+ /**
8
+ * Tile object id
9
+ */
10
+ gid: number;
11
+ /**
12
+ * Used to mark an object as a point
13
+ */
14
+ point: boolean;
15
+ height: number;
16
+ name: string;
17
+ properties: {
18
+ [key: string]: any
19
+ }
20
+ /**
21
+ * Angle in degrees clockwise
22
+ */
23
+ rotation: number;
24
+ type: string;
25
+ /**
26
+ * @since 1.9
27
+ */
28
+ class: string
29
+ visible: boolean;
30
+ width: number;
31
+ /**
32
+ * X coordinate in pixels
33
+ */
34
+ x: number;
35
+ /**
36
+ * Y coordinate in pixels
37
+ */
38
+ y: number;
39
+
40
+ /**
41
+ * Reference to a template file, in case object is a template instance
42
+ */
43
+ template: string;
44
+
45
+ /**
46
+ * Only used for text objects
47
+ */
48
+ text: TiledText;
49
+
50
+ /**
51
+ * Whether or not object is an ellipse
52
+ */
53
+ ellipse: boolean;
54
+
55
+ /**
56
+ * Polygon points
57
+ */
58
+ polygon: TiledPoint[];
59
+
60
+ /**
61
+ * Polyline points
62
+ */
63
+ polyline: TiledPoint[];
64
+ }
@@ -0,0 +1,47 @@
1
+ export interface TiledText {
2
+ text: string;
3
+ /**
4
+ * Whether to use a bold font (default: false)
5
+ */
6
+ bold: boolean;
7
+ /**
8
+ * Hex-formatted color (#RRGGBB or #AARRGGBB) (default: #000000)
9
+ */
10
+ color: string;
11
+ /**
12
+ * Font family (default: sans-serif)
13
+ */
14
+ fontfamily: string;
15
+ /**
16
+ * Horizontal alignment (center, right, justify or left (default))
17
+ */
18
+ halign: 'center' | 'right' | 'justify' | 'left';
19
+ /**
20
+ * Whether to use an italic font (default: false)
21
+ */
22
+ italic: boolean;
23
+ /**
24
+ * Whether to use kerning when placing characters (default: true)
25
+ */
26
+ kerning: boolean;
27
+ /**
28
+ * Pixel size of font (default: 16)
29
+ */
30
+ pixelsize: number;
31
+ /**
32
+ * Whether to strike out the text (default: false)
33
+ */
34
+ strikeout: boolean;
35
+ /**
36
+ * Whether to underline the text (default: false)
37
+ */
38
+ underline: boolean;
39
+ /**
40
+ * Vertical alignment (center, bottom or top (default))
41
+ */
42
+ valign: 'center' | 'bottom' | 'top';
43
+ /**
44
+ * Whether the text is wrapped within the object bounds (default: false)
45
+ */
46
+ wrap: boolean;
47
+ }
@@ -0,0 +1,19 @@
1
+ import { TiledLayer } from "./Layer";
2
+ import { TiledObject } from "./Objects";
3
+ import { TiledFrame, TiledProperty } from "./Types";
4
+
5
+ export interface TilesetTile {
6
+ gid: number
7
+ id: number;
8
+ type: string;
9
+ image: string;
10
+ imageheight: number;
11
+ imagewidth: number;
12
+ animations: TiledFrame[];
13
+ properties: {
14
+ [key: string]: any
15
+ }
16
+ terrain: number[];
17
+ objects: TiledObject[];
18
+ probability: number;
19
+ }