1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /**
 28  * @constant
 29  * @type Number
 30  */
 31 cc.TMX_LAYER_ATTRIB_NONE = 1 << 0;
 32 /**
 33  * @constant
 34  * @type Number
 35  */
 36 cc.TMX_LAYER_ATTRIB_BASE64 = 1 << 1;
 37 /**
 38  * @constant
 39  * @type Number
 40  */
 41 cc.TMX_LAYER_ATTRIB_GZIP = 1 << 2;
 42 /**
 43  * @constant
 44  * @type Number
 45  */
 46 cc.TMX_LAYER_ATTRIB_ZLIB = 1 << 3;
 47 
 48 /**
 49  * @constant
 50  * @type Number
 51  */
 52 cc.TMX_PROPERTY_NONE = 0;
 53 
 54 /**
 55  * @constant
 56  * @type Number
 57  */
 58 cc.TMX_PROPERTY_MAP = 1;
 59 
 60 /**
 61  * @constant
 62  * @type Number
 63  */
 64 cc.TMX_PROPERTY_LAYER = 2;
 65 
 66 /**
 67  * @constant
 68  * @type Number
 69  */
 70 cc.TMX_PROPERTY_OBJECTGROUP = 3;
 71 
 72 /**
 73  * @constant
 74  * @type Number
 75  */
 76 cc.TMX_PROPERTY_OBJECT = 4;
 77 
 78 /**
 79  * @constant
 80  * @type Number
 81  */
 82 cc.TMX_PROPERTY_TILE = 5;
 83 
 84 /**
 85  * @constant
 86  * @type Number
 87  */
 88 cc.TMX_TILE_HORIZONTAL_FLAG = 0x80000000;
 89 
 90 
 91 /**
 92  * @constant
 93  * @type Number
 94  */
 95 cc.TMX_TILE_VERTICAL_FLAG = 0x40000000;
 96 
 97 /**
 98  * @constant
 99  * @type Number
100  */
101 cc.TMX_TILE_DIAGONAL_FLAG = 0x20000000;
102 
103 /**
104  * @constant
105  * @type Number
106  */
107 cc.TMX_TILE_ALL_FLAGS = (cc.TMX_TILE_HORIZONTAL_FLAG | cc.TMX_TILE_VERTICAL_FLAG | cc.TMX_TILE_DIAGONAL_FLAG) >>> 0;
108 
109 /**
110  * @constant
111  * @type Number
112  */
113 cc.TMX_TILE_ALL_FLAGS_MASK = (~(cc.TMX_TILE_ALL_FLAGS)) >>> 0;
114 
115 // Bits on the far end of the 32-bit global tile ID (GID's) are used for tile flags
116 
117 /**
118  * <p>cc.TMXLayerInfo contains the information about the layers like: <br />
119  * - Layer name<br />
120  * - Layer size <br />
121  * - Layer opacity at creation time (it can be modified at runtime)  <br />
122  * - Whether the layer is visible (if it's not visible, then the CocosNode won't be created) <br />
123  *  <br />
124  * This information is obtained from the TMX file.</p>
125  * @class
126  * @extends cc.Class
127  */
128 cc.TMXLayerInfo = cc.Class.extend(/** @lends cc.TMXLayerInfo# */{
129     _properties:null,
130     name:"",
131     _layerSize:null,
132     _tiles:[],
133     visible:null,
134     _opacity:null,
135     ownTiles:true,
136     _minGID:100000,
137     _maxGID:0,
138     offset:cc.PointZero(),
139     ctor:function () {
140         this._properties = [];
141     },
142     /**
143      * @return {Array}
144      */
145     getProperties:function () {
146         return this._properties;
147     },
148 
149     /**
150      * @param {object} Var
151      */
152     setProperties:function (Var) {
153         this._properties.push(Var);
154     }
155 });
156 
157 /**
158  * <p>cc.TMXTilesetInfo contains the information about the tilesets like: <br />
159  * - Tileset name<br />
160  * - Tileset spacing<br />
161  * - Tileset margin<br />
162  * - size of the tiles<br />
163  * - Image used for the tiles<br />
164  * - Image size<br />
165  *
166  * This information is obtained from the TMX file. </p>
167  * @class
168  * @extends cc.Class
169  */
170 cc.TMXTilesetInfo = cc.Class.extend(/** @lends cc.TMXTilesetInfo# */{
171 
172     /**
173      * Tileset name
174      */
175     name:"",
176 
177     /**
178      * First grid
179      */
180     firstGid:0,
181     _tileSize:cc.SizeZero(),
182 
183     /**
184      * Spacing
185      */
186     spacing:0,
187 
188     /**
189      *  Margin
190      */
191     margin:0,
192 
193     /**
194      * Filename containing the tiles (should be sprite sheet / texture atlas)
195      */
196     sourceImage:"",
197 
198     /**
199      * Size in pixels of the image
200      */
201     imageSize:cc.SizeZero(),
202 
203     /**
204      * @param {Number} gid
205      * @return {cc.Rect}
206      */
207     rectForGID:function (gid) {
208         var rect = cc.RectZero();
209         rect.size = this._tileSize;
210         gid &= cc.TMX_TILE_ALL_FLAGS_MASK;
211         gid = gid - parseInt(this.firstGid, 10);
212         var max_x = parseInt((this.imageSize.width - this.margin * 2 + this.spacing) / (this._tileSize.width + this.spacing), 10);
213         rect.origin.x = parseInt((gid % max_x) * (this._tileSize.width + this.spacing) + this.margin, 10);
214         rect.origin.y = parseInt(parseInt(gid / max_x, 10) * (this._tileSize.height + this.spacing) + this.margin, 10);
215         return rect;
216     }
217 });
218 
219 /**
220  * <p>cc.TMXMapInfo contains the information about the map like: <br/>
221  *- Map orientation (hexagonal, isometric or orthogonal)<br/>
222  *- Tile size<br/>
223  *- Map size</p>
224  *
225  * <p>And it also contains: <br/>
226  * - Layers (an array of TMXLayerInfo objects)<br/>
227  * - Tilesets (an array of TMXTilesetInfo objects) <br/>
228  * - ObjectGroups (an array of TMXObjectGroupInfo objects) </p>
229  *
230  * <p>This information is obtained from the TMX file. </p>
231  * @class
232  * @extends cc.SAXParser
233  */
234 cc.TMXMapInfo = cc.SAXParser.extend(/** @lends cc.TMXMapInfo# */{
235     // map orientation
236     _orientation:null,
237     _mapSize:cc.SizeZero(),
238     _tileSize:cc.SizeZero(),
239     _layers:null,
240     _tileSets:null,
241     _objectGroups:null,
242     _parentElement:null,
243     _parentGID:null,
244     _layerAttribs:0,
245     _storingCharacters:false,
246     _properties:[],
247     // tmx filename
248     _TMXFileName:null,
249     //current string
250     _currentString:null,
251     // tile properties
252     _tileProperties:null,
253     _resources:null,
254     ctor:function () {
255         this._tileSets = [];
256         this._tileProperties = [];
257         this._properties = [];
258     },
259     /**
260      * @return {Number}
261      */
262     getOrientation:function () {
263         return this._orientation;
264     },
265 
266     /**
267      * @param {Number} Var
268      */
269     setOrientation:function (Var) {
270         this._orientation = Var;
271     },
272 
273     /**
274      * Map width & height
275      * @return {cc.Size}
276      */
277     getMapSize:function () {
278         return this._mapSize;
279     },
280 
281     /**
282      * @param {cc.Size} Var
283      */
284     setMapSize:function (Var) {
285         this._mapSize = Var;
286     },
287 
288     /**
289      * Tiles width & height
290      * @return {cc.Size}
291      */
292     getTileSize:function () {
293         return this._tileSize;
294     },
295 
296     /**
297      * @param {cc.Size} Var
298      */
299     setTileSize:function (Var) {
300         this._tileSize = Var;
301     },
302 
303     /**
304      * Layers
305      * @return {Array}
306      */
307     getLayers:function () {
308         return this._layers;
309     },
310 
311     /**
312      * @param {cc.TMXLayerInfo} Var
313      */
314     setLayers:function (Var) {
315         this._layers.push(Var);
316     },
317 
318     /**
319      * tilesets
320      * @return {Array}
321      */
322     getTilesets:function () {
323         return this._tileSets;
324     },
325 
326     /**
327      * @param {cc.TMXTilesetInfo} Var
328      */
329     setTilesets:function (Var) {
330         this._tileSets.push(Var);
331     },
332 
333     /**
334      * ObjectGroups
335      * @return {Array}
336      */
337     getObjectGroups:function () {
338         return this._objectGroups;
339     },
340 
341     /**
342      * @param {cc.TMXObjectGroup} Var
343      */
344     setObjectGroups:function (Var) {
345         this._objectGroups.push(Var);
346     },
347 
348     /**
349      * parent element
350      * @return {Number}
351      */
352     getParentElement:function () {
353         return this._parentElement;
354     },
355 
356     /**
357      * @param {Number} Var
358      */
359     setParentElement:function (Var) {
360         this._parentElement = Var;
361     },
362 
363     /**
364      * parent GID
365      * @return {Number}
366      */
367     getParentGID:function () {
368         return this._parentGID;
369     },
370 
371     /**
372      * @param {Number} Var
373      */
374     setParentGID:function (Var) {
375         this._parentGID = Var;
376     },
377 
378     /**
379      *  layer attribute
380      * @return {Number}
381      */
382     getLayerAttribs:function () {
383         return this._layerAttribs;
384     },
385 
386     /**
387      * @param {Number} Var
388      */
389     setLayerAttribs:function (Var) {
390         this._layerAttribs = Var;
391     },
392 
393     /**
394      * is string characters?
395      * @return {Boolean}
396      */
397     getStoringCharacters:function () {
398         return this._storingCharacters;
399     },
400 
401     /**
402      * @param {Boolean} Var
403      */
404     setStoringCharacters:function (Var) {
405         this._storingCharacters = Var;
406     },
407 
408     /**
409      * Properties
410      * @return {Array}
411      */
412     getProperties:function () {
413         return this._properties;
414     },
415 
416     /**
417      * @param {object} Var
418      */
419     setProperties:function (Var) {
420         this._properties.push(Var);
421     },
422 
423     /**
424      * Initializes a TMX format with a  tmx file
425      * @param {String} tmxFile
426      * @return {String}
427      */
428     initWithTMXFile:function (tmxFile, resourcePath) {
429         this._internalInit(tmxFile, resourcePath);
430         return this.parseXMLFile(this._TMXFileName);
431     },
432 
433     /** Initalises parsing of an XML file, either a tmx (Map) file or tsx (Tileset) file
434      * @param {String} tmxFile
435      * @return {Element}
436      */
437     parseXMLFile:function (tmxFile) {
438         var mapXML = cc.SAXParser.getInstance().tmxParse(tmxFile);
439         var i, j;
440 
441         // PARSE <map>
442         var map = mapXML.documentElement;
443 
444         var version = map.getAttribute('version');
445         var orientationStr = map.getAttribute('orientation');
446 
447         if (map.nodeName == "map") {
448             if (version != "1.0" && version !== null)
449                 cc.log("cocos2d: TMXFormat: Unsupported TMX version:" + version);
450 
451             if (orientationStr == "orthogonal")
452                 this.setOrientation(cc.TMX_ORIENTATION_ORTHO);
453             else if (orientationStr == "isometric")
454                 this.setOrientation(cc.TMX_ORIENTATION_ISO);
455             else if (orientationStr == "hexagonal")
456                 this.setOrientation(cc.TMX_ORIENTATION_HEX);
457             else if (orientationStr !== null)
458                 cc.log("cocos2d: TMXFomat: Unsupported orientation:" + this.getOrientation());
459 
460             var mapSize = cc.size(0, 0);
461             mapSize.width = parseFloat(map.getAttribute('width'));
462             mapSize.height = parseFloat(map.getAttribute('height'));
463             this.setMapSize(mapSize);
464 
465             mapSize = cc.size(0, 0);
466             mapSize.width = parseFloat(map.getAttribute('tilewidth'));
467             mapSize.height = parseFloat(map.getAttribute('tileheight'));
468             this.setTileSize(mapSize);
469 
470             // The parent element is the map
471             var propertyArr = map.querySelectorAll("map > properties >  property");
472             if (propertyArr) {
473                 for (i = 0; i < propertyArr.length; i++) {
474                     var aProperty = {};
475                     aProperty[propertyArr[i].getAttribute('name')] = propertyArr[i].getAttribute('value');
476                     this.setProperties(aProperty);
477                 }
478             }
479         }
480 
481         // PARSE <tileset>
482         var tilesets = map.getElementsByTagName('tileset');
483         if (map.nodeName !== "map") {
484             tilesets = [];
485             tilesets.push(map);
486         }
487 
488         for (i = 0; i < tilesets.length; i++) {
489             var selTileset = tilesets[i];
490             // If this is an external tileset then start parsing that
491             var externalTilesetFilename = selTileset.getAttribute('source');
492             if (externalTilesetFilename) {
493                 this.parseXMLFile(cc.FileUtils.getInstance().fullPathFromRelativeFile(externalTilesetFilename, tmxFile));
494             } else {
495                 var tileset = new cc.TMXTilesetInfo();
496                 tileset.name = selTileset.getAttribute('name') || "";
497                 tileset.firstGid = parseInt(selTileset.getAttribute('firstgid')) || 1;
498                 tileset.spacing = parseInt(selTileset.getAttribute('spacing')) || 0;
499                 tileset.margin = parseInt(selTileset.getAttribute('margin')) || 0;
500 
501                 var tilesetSize = cc.size(0, 0);
502                 tilesetSize.width = parseFloat(selTileset.getAttribute('tilewidth'));
503                 tilesetSize.height = parseFloat(selTileset.getAttribute('tileheight'));
504                 tileset._tileSize = tilesetSize;
505 
506                 var image = selTileset.getElementsByTagName('image')[0];
507                 var imgSource = image.getAttribute('source');
508                 if (imgSource) {
509                     if (this._resources)
510                         imgSource = this._resources + "/" + imgSource;
511                     else
512                         imgSource = cc.FileUtils.getInstance().fullPathFromRelativeFile(imgSource, tmxFile);
513                 }
514                 tileset.sourceImage = imgSource;
515                 this.setTilesets(tileset);
516             }
517         }
518 
519         // PARSE  <tile>
520         var tiles = map.querySelectorAll('tile');
521         if (tiles) {
522             for (i = 0; i < tiles.length; i++) {
523                 var info = this._tileSets[0];
524                 var t = tiles[i];
525                 this.setParentGID(parseInt(info.firstGid) + parseInt(t.getAttribute('id') || 0));
526                 var tp = t.querySelectorAll("properties > property");
527                 if (tp) {
528                     var dict = {};
529                     for (var j = 0; j < tp.length; j++) {
530                         var name = tp[j].getAttribute('name');
531                         var value = tp[j].getAttribute('value');
532                         dict[name] = value;
533                     }
534                     this._tileProperties[this.getParentGID()] = dict;
535                 }
536             }
537         }
538 
539         // PARSE  <layer>
540         var layers = map.getElementsByTagName('layer');
541         if (layers) {
542             for (i = 0; i < layers.length; i++) {
543                 var selLayer = layers[i];
544                 var data = selLayer.getElementsByTagName('data')[0];
545 
546                 var layer = new cc.TMXLayerInfo();
547                 layer.name = selLayer.getAttribute('name');
548 
549                 var layerSize = cc.size(0, 0);
550                 layerSize.width = parseFloat(selLayer.getAttribute('width'));
551                 layerSize.height = parseFloat(selLayer.getAttribute('height'));
552                 layer._layerSize = layerSize;
553 
554                 var visible = selLayer.getAttribute('visible');
555                 layer.visible = !(visible == "0");
556 
557                 var opacity = selLayer.getAttribute('opacity') || 1;
558 
559                 if (opacity)
560                     layer._opacity = parseInt(255 * parseFloat(opacity));
561                 else
562                     layer._opacity = 255;
563                 layer.offset = cc.p(parseFloat(selLayer.getAttribute('x')) || 0, parseFloat(selLayer.getAttribute('y')) || 0);
564 
565                 var nodeValue = '';
566                 for (j = 0; j < data.childNodes.length; j++) {
567                     nodeValue += data.childNodes[j].nodeValue
568                 }
569                 nodeValue = nodeValue.trim();
570 
571                 // Unpack the tilemap data
572                 var compression = data.getAttribute('compression');
573                 var encoding = data.getAttribute('encoding');
574                 cc.Assert(compression == null || compression === "gzip" || compression === "zlib", "TMX: unsupported compression method");
575                 switch (compression) {
576                     case 'gzip':
577                         layer._tiles = cc.unzipBase64AsArray(nodeValue, 4);
578                         break;
579                     case 'zlib':
580                         var inflator = new Zlib.Inflate(cc.Codec.Base64.decodeAsArray(nodeValue, 1));
581                         layer._tiles = cc.uint8ArrayToUint32Array(inflator.decompress());
582                         break;
583                     case null:
584                     case '':
585                         // Uncompressed
586                         if (encoding == "base64")
587                             layer._tiles = cc.Codec.Base64.decodeAsArray(nodeValue, 4);
588                         else if (encoding === "csv") {
589                             layer._tiles = [];
590                             var csvTiles = nodeValue.split(',');
591                             for (var csvIdx = 0; csvIdx < csvTiles.length; csvIdx++)
592                                 layer._tiles.push(parseInt(csvTiles[csvIdx]));
593                         } else {
594                             //XML format
595                             var selDataTiles = data.getElementsByTagName("tile");
596                             layer._tiles = [];
597                             for(var xmlIdx = 0; xmlIdx < selDataTiles.length; xmlIdx++)
598                                 layer._tiles.push(parseInt(selDataTiles[xmlIdx].getAttribute("gid")));
599                         }
600                         break;
601                     default:
602                         cc.Assert(this.getLayerAttribs() != cc.TMX_LAYER_ATTRIB_NONE, "TMX tile map: Only base64 and/or gzip/zlib maps are supported");
603                 }
604 
605                 // The parent element is the last layer
606                 var layerProps = selLayer.querySelectorAll("properties > property");
607                 if (layerProps) {
608                     for (j = 0; j < layerProps.length; j++) {
609                         var layerProp = {};
610                         layerProp[layerProps[j].getAttribute('name')] = layerProps[j].getAttribute('value');
611                         layer.setProperties(layerProp);
612                     }
613                 }
614                 this.setLayers(layer);
615             }
616         }
617 
618         // PARSE <objectgroup>
619         var objectGroups = map.getElementsByTagName('objectgroup');
620         if (objectGroups) {
621             for (i = 0; i < objectGroups.length; i++) {
622                 var selGroup = objectGroups[i];
623                 var objectGroup = new cc.TMXObjectGroup();
624                 objectGroup.setGroupName(selGroup.getAttribute('name'));
625                 objectGroup.setPositionOffset(cc.p(parseFloat(selGroup.getAttribute('x')) * this.getTileSize().width || 0,
626                     parseFloat(selGroup.getAttribute('y')) * this.getTileSize().height || 0));
627 
628                 var groupProps = selGroup.querySelectorAll("objectgroup > properties > property");
629                 if (groupProps) {
630                     for (j = 0; j < groupProps.length; j++) {
631                         var groupProp = {};
632                         groupProp[groupProps[j].getAttribute('name')] = groupProps[j].getAttribute('value');
633                         // Add the property to the layer
634                         objectGroup.setProperties(groupProp);
635                     }
636                 }
637 
638                 var objects = selGroup.querySelectorAll('object');
639                 if (objects) {
640                     for (j = 0; j < objects.length; j++) {
641                         var selObj = objects[j];
642                         // The value for "type" was blank or not a valid class name
643                         // Create an instance of TMXObjectInfo to store the object and its properties
644                         var objectProp = {};
645 
646                         // Set the name of the object to the value for "name"
647                         objectProp["name"] = selObj.getAttribute('name') || "";
648 
649                         // Assign all the attributes as key/name pairs in the properties dictionary
650                         objectProp["type"] = selObj.getAttribute('type') || "";
651 
652                         objectProp["x"] = parseInt(selObj.getAttribute('x') || 0) + objectGroup.getPositionOffset().x;
653                         var y = parseInt(selObj.getAttribute('y') || 0) + objectGroup.getPositionOffset().y;
654 
655                         objectProp["width"] = parseInt(selObj.getAttribute('width')) || 0;
656                         objectProp["height"] = parseInt(selObj.getAttribute('height')) || 0;
657 
658                         // Correct y position. (Tiled uses Flipped, cocos2d uses Standard)
659                         objectProp["y"] = parseInt(this.getMapSize().height * this.getTileSize().height) - y - objectProp["height"];
660 
661                         var docObjProps = selObj.querySelectorAll("properties > property");
662                         if (docObjProps) {
663                             for (var k = 0; k < docObjProps.length; k++)
664                                 objectProp[docObjProps[k].getAttribute('name')] = docObjProps[k].getAttribute('value');
665                         }
666 
667                         // Add the object to the objectGroup
668                         objectGroup.setObjects(objectProp);
669                     }
670                 }
671 
672                 this.setObjectGroups(objectGroup);
673             }
674         }
675         return map;
676     },
677 
678     /**
679      * @return {object}
680      */
681     getTileProperties:function () {
682         return this._tileProperties;
683     },
684 
685     /**
686      * @param {object} tileProperties
687      */
688     setTileProperties:function (tileProperties) {
689         this._tileProperties.push(tileProperties);
690     },
691 
692     /**
693      * @return {String}
694      */
695     getCurrentString:function () {
696         return this._currentString;
697     },
698 
699     /**
700      * @param {String} currentString
701      */
702     setCurrentString:function (currentString) {
703         this._currentString = currentString;
704     },
705 
706     /**
707      * @return {String}
708      */
709     getTMXFileName:function () {
710         return this._TMXFileName;
711     },
712 
713     /**
714      * @param {String} fileName
715      */
716     setTMXFileName:function (fileName) {
717         this._TMXFileName = fileName;
718     },
719 
720     _internalInit:function (tmxFileName, resourcePath) {
721         this._tileSets = [];
722         this._layers = [];
723 
724         this._TMXFileName = cc.FileUtils.getInstance().fullPathFromRelativePath(tmxFileName);
725 
726         if (resourcePath) {
727             this._resources = resourcePath;
728         }
729 
730         this._objectGroups = [];
731 
732         this._properties = [];
733         this._tileProperties = [];
734 
735         // tmp vars
736         this._currentString = "";
737         this._storingCharacters = false;
738         this._layerAttribs = cc.TMX_LAYER_ATTRIB_NONE;
739         this._parentElement = cc.TMX_PROPERTY_NONE;
740     }
741 });
742 
743 /**
744  * Creates a TMX Format with a tmx file
745  * @param {String} tmxFile
746  * @param {String} resourcePath
747  * @return {cc.TMXMapInfo}
748  */
749 cc.TMXMapInfo.create = function (tmxFile, resourcePath) {
750     var ret = new cc.TMXMapInfo();
751     if (ret.initWithTMXFile(tmxFile, resourcePath)) {
752         return ret;
753     }
754     return null;
755 };
756