1 /**
  2  * The Render Engine
  3  * Level
  4  *
  5  * @fileoverview A class for working with loaded levels.
  6  *
  7  * @author: Brett Fattori (brettf@renderengine.com)
  8  * @author: $Author: bfattori $
  9  * @version: $Revision: 1555 $
 10  *
 11  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 12  *
 13  * Permission is hereby granted, free of charge, to any person obtaining a copy
 14  * of this software and associated documentation files (the "Software"), to deal
 15  * in the Software without restriction, including without limitation the rights
 16  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 17  * copies of the Software, and to permit persons to whom the Software is
 18  * furnished to do so, subject to the following conditions:
 19  *
 20  * The above copyright notice and this permission notice shall be included in
 21  * all copies or substantial portions of the Software.
 22  *
 23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 29  * THE SOFTWARE.
 30  *
 31  */
 32 
 33 // The class this file defines and its required classes
 34 R.Engine.define({
 35     "class":"R.resources.types.Level",
 36     "requires":[
 37         "R.engine.BaseObject",
 38         "R.resources.loaders.SpriteLoader",
 39         "R.resources.loaders.TileLoader",
 40         "R.resources.types.Sound",
 41         "R.resources.types.TileMap"
 42     ]
 43 });
 44 
 45 /**
 46  * @class Creates an instance of a Level object.
 47  *
 48  * @constructor
 49  * @param name {String} The name of the object
 50  * @param levelResource {Object} The level resource loaded by the LevelLoader
 51  * @extends R.engine.BaseObject
 52  */
 53 R.resources.types.Level = function () {
 54     return R.engine.BaseObject.extend(/** @scope R.resources.types.Level.prototype */{
 55 
 56         editing:false,
 57         actors:null,
 58         fixtures:null,
 59         tilemaps:null,
 60         backgroundMusic:null,
 61         width:0,
 62         height:0,
 63         renderContext:null,
 64         notAdded:null,
 65         version:0,
 66         resourceLoaders:null,
 67 
 68         /** @private */
 69         constructor:function (name, width, height) {
 70             this.base(name);
 71             this.actors = R.struct.Container.create("Actors");
 72             this.fixtures = R.struct.Container.create("Fixtures");
 73             this.tilemaps = R.struct.HashContainer.create("Tilemaps");
 74             this.backgroundMusic = null;
 75             this.width = width;
 76             this.height = height;
 77             this.renderContext = null;
 78             this.notAdded = [];
 79             this.version = 0;
 80             this.resourceLoaders = {
 81                 sprite:[R.resources.loaders.SpriteLoader.create("LevelSpriteLoader")],
 82                 tile:[R.resources.loaders.TileLoader.create("LevelTileLoader")],
 83                 sound:[]
 84             };
 85 
 86             // Add the three tile maps
 87             this.tilemaps.add("background", R.resources.types.TileMap.create("background", width, height));
 88             this.tilemaps.add("playfield", R.resources.types.TileMap.create("playfield", width, height));
 89             this.tilemaps.add("foreground", R.resources.types.TileMap.create("foreground", width, height));
 90 
 91             // Set the z-index for each tile map
 92             this.getTileMap("background").setZIndex(0);
 93             this.getTileMap("playfield").setZIndex(1);
 94             this.getTileMap("foreground").setZIndex(2);
 95         },
 96 
 97         destroy:function () {
 98             if (this.renderContext) {
 99                 // Remove actors and tile maps from the render context
100                 var itr;
101                 for (itr = this.actors.iterator(); itr.hasNext();) {
102                     this.renderContext.remove(itr.next());
103                 }
104 
105                 for (itr = this.tilemaps.iterator(); itr.hasNext();) {
106                     this.renderContext.remove(itr.next());
107                 }
108             }
109 
110             // Clean up and destroy the actors, fixtures, and tile maps
111             this.actors.cleanUp();
112             this.actors.destroy();
113             this.fixtures.cleanUp();
114             this.fixtures.destroy();
115             this.tilemaps.cleanUp();
116             this.tilemaps.destroy();
117 
118             // If there's background music, get rid of that too
119             if (this.backgroundMusic) {
120                 this.backgroundMusic.destroy();
121             }
122 
123             this.base();
124         },
125 
126         /**
127          * Release the level back into the pool for reuse
128          */
129         release:function () {
130             this.base();
131             this.actors = null;
132             this.fixtures = null;
133             this.tilemaps = null;
134             this.backgroundMusic = null;
135             this.renderContext = null;
136             this.width = 0;
137             this.height = 0;
138             this.version = 0;
139         },
140 
141         /**
142          * Add a new resource loader to the set of resource loaders that the
143          * level has access to.  By default, there is already a sprite and
144          * tile loader when a level object is created.
145          *
146          * @param resourceLoader {R.resources.loaders.AbstractResourceLoader} The resource loader to add
147          */
148         addResourceLoader:function (resourceLoader) {
149             var loaderCache;
150             if (resourceLoader instanceof R.resources.loaders.TileLoader) {
151                 loaderCache = this.resourceLoaders.tile;
152             } else if (resourceLoader instanceof R.resources.loaders.SpriteLoader) {
153                 loaderCache = this.resourceLoaders.sprite;
154             } else {
155                 loaderCache = this.resourceLoaders.sound;
156             }
157             loaderCache.push(resourceLoader);
158         },
159 
160         /**
161          * Set a version number for the level.
162          * @param version {Number} A version number
163          */
164         setVersion:function (version) {
165             this.version = version;
166         },
167 
168         /**
169          * Get the version number associated with the level
170          * @return {Number}
171          */
172         getVersion:function () {
173             return this.version;
174         },
175 
176         /**
177          * Associate the level with its render context so the tile maps can
178          * be rendered properly.
179          * @param renderContext {R.rendercontexts.AbstractRenderContext}
180          */
181         setRenderContext:function (renderContext) {
182             if (!this.renderContext) {
183                 this.renderContext = renderContext;
184                 renderContext.add(this.getTileMap("background"));
185                 renderContext.add(this.getTileMap("playfield"));
186                 renderContext.add(this.getTileMap("foreground"));
187 
188                 while (this.notAdded.length > 0) {
189                     // Add objects which aren't a part of the render context yet
190                     this.renderContext.add(this.notAdded.shift());
191                 }
192             }
193         },
194 
195         /**
196          * Get the width of the level, in tiles.
197          * @return {Number} The width of the level in tiles
198          */
199         getWidth:function () {
200             return this.width;
201         },
202 
203         /**
204          * Get the height of the level, in tiles.
205          * @return {Number} The height of the level in tiles
206          */
207         getHeight:function () {
208             return this.height;
209         },
210 
211         addActor:function (actor) {
212             this.actors.add(actor);
213             actor.setTileMap(this.getTileMap("playfield"));
214 
215             if (this.renderContext) {
216                 this.renderContext.add(actor);
217             } else {
218                 this.notAdded.push(actor);
219             }
220         },
221 
222         removeActor:function (actor) {
223             this.actors.remove(actor);
224         },
225 
226         getActors:function () {
227             return this.actors;
228         },
229 
230         addFixture:function (fixture) {
231             this.fixtures.add(fixture);
232         },
233 
234         removeFixture:function (fixture) {
235             this.fixtures.remove(fixture);
236         },
237 
238         getFixtures:function (type) {
239             if (!type) {
240                 return this.fixtures;
241             } else {
242                 return this.fixtures.filter(function (fixture) {
243                     return (fixture.getType() == type);
244                 });
245             }
246         },
247 
248         getTileMap:function (name) {
249             return this.tilemaps.get(name);
250         },
251 
252         setBackgroundMusic:function (sound) {
253             this.backgroundMusic = sound;
254         },
255 
256         /**
257          * Set the editing mode of the level, used by the LevelEditor
258          * @private
259          */
260         setEditing:function (state) {
261             this.editing = state;
262         }
263 
264     }, /** @scope R.resources.types.Level.prototype */ {
265         /**
266          * Gets the class name of this object.
267          * @return {String} The string "R.resources.types.Level"
268          */
269         getClassName:function () {
270             return "R.resources.types.Level";
271         },
272 
273         /**
274          * Generate an object which represents the level as a complete
275          * entity, including resource locations.
276          * @param level {R.resources.types.Level}
277          * @return {Object}
278          */
279         serialize:function (level) {
280             function getCanonicalName(obj) {
281                 return obj.getSpriteResource().resourceName + ":" + obj.getName();
282             }
283 
284             var lvl = {
285                 name:level.getName(),
286                 version:level.getVersion(),
287                 width:level.getWidth(),
288                 height:level.getHeight(),
289                 resourceURLs:{
290                     sprites:[],
291                     tiles:[],
292                     sounds:[]
293                 },
294                 actors:[],
295                 fixtures:[],
296                 tilemaps:{
297                     background:null,
298                     playfield:null,
299                     foreground:null
300                 }
301             };
302 
303             // Get all of the resource URLs
304             var resourceName, resourceURL, obj, t, itr;
305 
306             // SPRITES & ACTORS
307             for (itr = level.getActors().iterator(); itr.hasNext();) {
308                 obj = itr.next();
309                 resourceName = obj.getSprite().getSpriteResource().resourceName;
310                 resourceURL = obj.getSprite().getSpriteLoader().getPathUrl(resourceName);
311                 if (R.engine.Support.filter(lvl.resourceURLs.sprites,function (e) {
312                     return (e && e[resourceName]);
313                 }).length == 0) {
314                     var o = {};
315                     o[resourceName] = resourceURL;
316                     lvl.resourceURLs.sprites.push(o);
317                 }
318 
319                 // Do the actors at the same time
320                 lvl.actors.push(R.objects.SpriteActor.serialize(obj));
321             }
322             itr.destroy();
323 
324             // FIXTURES
325             for (itr = level.getFixtures().iterator(); itr.hasNext();) {
326                 lvl.fixtures.push(R.objects.Object2D.serialize(itr.next()));
327             }
328 
329             // TILES & TILEMAPS
330             $.each(["background", "playfield", "foreground"], function (i, e) {
331                 var tile;
332                 obj = level.getTileMap(e);
333                 for (t = 0; t < obj.getTileMap().length; t++) {
334                     tile = obj.getTileMap()[t];
335                     if (tile) {
336                         resourceName = tile.getTileResource().resourceName;
337                         resourceURL = tile.getTileLoader().getPathUrl(resourceName);
338                         if (R.engine.Support.filter(lvl.resourceURLs.tiles,function (e) {
339                             return (e && e[resourceName]);
340                         }).length == 0) {
341                             var o = {};
342                             o[resourceName] = resourceURL;
343                             lvl.resourceURLs.tiles.push(o);
344                         }
345                     }
346                 }
347 
348                 // Do the tile map at the same time
349                 lvl.tilemaps[e] = R.resources.types.TileMap.serialize(obj);
350             });
351 
352             // SOUNDS
353             // ...
354 
355             return lvl;
356         },
357 
358         deserialize:function (obj) {
359             // Create the level
360             var level = R.resources.types.Level.create(obj.name, obj.width, obj.height);
361 
362             // Pull together all of the resources
363             var loader;
364             for (var resType in obj.resourceURLs) {
365                 if (resType === "sprites") {
366                     loader = level.resourceLoaders.sprite[0];
367                 } else if (resType === "tiles") {
368                     loader = level.resourceLoaders.tile[0];
369                 } else {
370                     loader = level.resourceLoaders.sound[0];
371                 }
372                 for (var res in obj.resourceURLs[resType]) {
373                     for (var rName in obj.resourceURLs[resType][res]) {
374                         loader.load(rName, R.Engine.getGame().getFilePath(obj.resourceURLs[resType][res][rName]));
375                     }
376                 }
377             }
378 
379             // Now that we've started the resource loading, we need to wait until
380             // all resources are loaded before we can finish loading the level
381             R.lang.Timeout.create("lvlResourceWait", 250, function () {
382                 if (level.resourceLoaders.sprite[0].isReady() &&
383                     level.resourceLoaders.tile[0].isReady()) {
384                     this.destroy();
385                     R.resources.types.Level.finishDeserialize(level, obj);
386                 } else {
387                     this.restart();
388                 }
389             });
390 
391             return level;
392         },
393 
394         /**
395          * @private
396          */
397         finishDeserialize:function (level, obj) {
398             // Deserialize the tile maps
399             for (var tilemap in obj.tilemaps) {
400                 R.resources.types.TileMap.deserialize(obj.tilemaps[tilemap], level.resourceLoaders.tile,
401                     level.getTileMap(tilemap));
402             }
403 
404             // Deserialize the actors
405             for (var actor in obj.actors) {
406                 var spriteActor = R.objects.SpriteActor.deserialize(obj.actors[actor], level.resourceLoaders.sprite);
407                 level.addActor(spriteActor);
408             }
409 
410             // Deserialize the fixtures
411 
412 
413             // Done
414             level.triggerEvent("loaded");
415         }
416     });
417 
418 };