1 // ---
  2 // Copyright (c) 2010 Francesco Cottone, http://www.kesiev.com/
  3 // ---
  4 
  5 /**
  6  * @namespace
  7  * Toys module provides lots of common routines during the game developing: 
  8  * from effects for screen titles to HUD handling to platform/SHMUP/RPG oriented routines, 
  9  * like jumping characters, Z-Indexed objects, bullets, sparks, staff rolls, bonus screens, dialogues etc.
 10  */
 11 var toys={
 12 
 13 	// CONSTANTS
 14 	NOOP:function(){},
 15 	PUSH_NONE:0,
 16 	PUSH_LEFT:1,
 17 	PUSH_RIGHT:2,
 18 	PUSH_UP:3,
 19 	PUSH_DOWN:4,
 20 	
 21 	FACES:["up","right","down","left"],
 22 	FACES_ANGLE:[trigo.ANGLE_UP,trigo.ANGLE_RIGHT,trigo.ANGLE_DOWN,trigo.ANGLE_LEFT],
 23 	FACE_UP:0,
 24 	FACE_RIGHT:1,
 25 	FACE_DOWN:2,
 26 	FACE_LEFT:3,
 27 	
 28 	/** 
 29 	* @namespace
 30   * Top-view RPG specific libraries.
 31 	*/
 32 	topview:{
 33 	
 34 		/**
 35 		* Checks if an object checks that both objects are on the same Z plane and if so it calls gbox.collides.
 36 		* @param {Object} fr The object which collision is being checked for. 
 37 		* <ul>
 38 		* <li>x{Integer}: (required)Objects x position</li>
 39 		* <li>y{Integer}: (required)Objects y position</li>
 40 		* <li>z{Integer}: (required)Objects z position</li>
 41 		* <li>colx{Integer}: (required)The dimension of the collision box along the x axis</li>
 42 		* <li>coly{Integer}: (required)The dimension of the collision box along the y axis</li>
 43 		* <li>colh{Integer}: (required)Collision box height</li>
 44 		* <li>colw{Integer}: (required)Collision box width</li>
 45 		* </ul>
 46 		* @param {Object} to The object that collision is being checked against.
 47 		* <ul>
 48 		* <li>x{Integer}: (required)Objects x position</li>
 49 		* <li>y{Integer}: (required)Objects y position</li>
 50 		* <li>z{Integer}: (required)Objects z position</li>
 51 		* <li>colx{Integer}: (required)Collision x</li>
 52 		* <li>coly{Integer}: (required)Collision y</li>
 53 		* <li>colh{Integer}: (required)Collision box height</li>
 54 		* <li>colw{Integer}: (required)Collision box width</li>
 55 		* </ul>
 56 		* @param {int} t This is the tollerance (or margin for error) on the collide function.
 57 		*/
 58 		collides:function(fr,to,t) { // Special collision. Counts also the Z
 59 			if (Math.abs(fr.z,to.z)<5) return gbox.collides({x:fr.x+fr.colx,y:fr.y+fr.coly,h:fr.colh,w:fr.colw},{x:to.x+to.colx,y:to.y+to.coly,h:to.colh,w:to.colw},t); else return false;
 60 		},
 61 		
 62 		/**
 63 		* Checks for pixel collisions with an offset to the X and Y of the colidable using colx and coly.
 64 		* @param {Object} fr The object which collision is being tested for.
 65 		* @param {Object} to The object (or point) which collision is being tested against.
 66 		* @param {int} t The tollerance of the collision algorithm.
 67 		*/
 68 		pixelcollides:function(fr,to,t) { // Special collision. Counts also the Z
 69 			return gbox.pixelcollides(fr,{x:to.x+to.colx,y:to.y+to.coly,h:to.colh,w:to.colw},t);
 70 		},
 71 		
 72 		/**
 73 		* Initializes the game with the variables needed for topview and whatever else you feed in through data.
 74 		* @param {Object} th Passes in the object being initialized.
 75 		* @param {Object} data This is used to pass in everything that's being initiliized. If a value is not in Data then a default value is used instead. This can pass in values which do not have a default.
 76 		* <ul>
 77 		* <li>x{Integer}: x position of the object. (defaults to 0)</li>
 78 		* <li>y{Integer}: y position of the object. (defaults to 0)</li>
 79 		* <li>z{Integer}: z index of the object. (defaults to 0)</li>
 80 		* <li>accx{Integer}: The starting x velociyt of the object. (defaults to 0)</li>
 81 		* <li>accy{Integer}: The starting y velocity of the object. (defaults to 0)</li>
 82 		* <li>accz{Integer}: The starting z velocity of the object. (defaults to 0)</li>
 83 		* <li>frames{Object}: This is stores the animation frames for the objects in a map style structure. An empty map means the default image will display with no animation frames. (defaults to an empty map)</li>
 84 		* <li>shadow: (defaults to null)</li> //incomplete
 85 		* <li>maxacc{Integer}: (defaults to )4</li>
 86 		* <li>controlmaxacc{Integer}: (defaults to 4)</li>
 87 		* <li>responsive: (defaults to 0)</li>
 88 		* <li>weapon: (defaults to 0)</li>
 89 		* <li>camera{Boolean}: (defaults to true)</li>
 90 		* <li>flipv{Boolean}: Notes if the object is flipped vertically(defaults to false)</li>
 91 		* <li>fliph{Boolean}: Notes if the object is flipped horrizontally(defaults to false)</li>
 92 		* <li>facing{Integer}: Stores the facing of the object. This is set with pre-defined Integer values from within Toys.(defaults to toys.FACE_DOWN)</li>
 93 		* <ul>
 94 		* <li>FACE_UP:0,</li>
 95 		* <li>FACE_RIGHT:1,</li>
 96 		* <li>FACE_DOWN:2,</li>
 97 		* <li>FACE_LEFT:3,</li>
 98 		* </ul>
 99 		* <li>flipside{Boolean}: (defaults to true)</li>
100 		* <li>haspushing{Boolean}: (defaults to false)</li>
101 		* <li>frame: (default to 0)</li>
102 		* <li>colh{Integer}: (defaults to gbox.getTiles(th.tileset).tilehh)</li>
103 		* <li>colw{Integer}: (defaults to gbox.getTiles(th.tileset).tilew)</li>
104 		* <li>colx{Integer}: (defaults to 0)</li>
105 		* <li>staticspeed{Integer}: (defaults to 0)</li>
106 		* <li>nodiagonals{Boolean}: (defaults to false)</li>
107 		* <li>noreset: (defaults to false)</li>
108 		* </ul>
109 		*/
110 		initialize:function(th,data) {
111 			help.mergeWithModel(
112 				th,
113 				help.mergeWithModel(
114 					data,
115 					{
116 						x:0, y:0,
117 						z:0,
118 						accx:0, accy:0, accz:0,
119 						frames:{},
120 						shadow:null,
121 						maxacc:4, controlmaxacc:4,
122 						responsive:0, // Responsiveness
123 						weapon:0, // Weapon
124 						camera:true,
125 						flipv:false, fliph:false,
126 						facing:toys.FACE_DOWN,
127 						flipside:true,
128 						haspushing:false,
129 						frame:0,
130 						colh:gbox.getTiles(th.tileset).tilehh,
131 						colw:gbox.getTiles(th.tileset).tilew,
132 						colx:0,
133 						staticspeed:0,
134 						nodiagonals:false,
135 						noreset:false
136 					}
137 				)
138 			);
139 			if (th.coly==null) th.coly=gbox.getTiles(th.tileset).tileh-th.colh;
140 			th.colhh=Math.floor(th.colh/2);
141 			th.colhw=Math.floor(th.colw/2);
142 			
143 			toys.topview.spawn(th);
144 		},
145 		
146 		/**
147 		* "Spawns" a new object in the topview namespace. This also merges parameters in data into paramaters in th using help.copyModel.
148     * This initializes some basic basic variables for the object and sets the Z index.
149 		* @param {Object} th References "this" which is the object that called the method (generally).
150 		* <ul>
151 		* <li>y {Integer}: (required)The object's y position.</li>
152 		* <li>h {Integer}: (required)The object's height.</li>
153 		* </ul>
154 		* @param {Object} data This holds variables to be merged into th's stored info.
155 		*/
156 		spawn:function(th,data) {
157 			th.xpushing=toys.PUSH_NONE; // user is moving side
158 			th.vpushing=toys.PUSH_NONE; // user is moving side
159 			th.zpushing=toys.PUSH_NONE; // user is moving side
160 			th.counter=0; // self counter
161 			th.hittimer=0;
162 			th.killed=false;				
163 			help.copyModel(th,data);
164 			gbox.setZindex(th,th.y+th.h); // these object follows the z-index and uses ZINDEX_LAYER
165 		},
166 		
167 		/**
168 		* This sets and runs the control keys for the game. 
169 		* @param {Object} th This is the object that is being controlled by the keys (assumed to be the player)
170 		* <ul>
171 		* <li>accx: the object's currect acceleration in the x direction</li>
172 		* <li>accy: the object's currect acceleration in the y direction</li>
173 		* <li>responsive: minimum movement speed</li>
174 		* <li>staticspeed: turns off acceleration</li>
175 		* <li>nodiagonals: boolean determining if the object can move along both axis at once.</li>
176 		* <li>xpushing: a boolean that notes whether the object is pushing against something in the x direction.</li>
177 		* <li>ypushing: a boolean that notes whether the object is pushing against something in the y direction.</li>
178 		* <li>controlmaxacc: max acceleration for the object along an axis</li>
179 		* <li>noreset: checks for the object being allowed to reset its pushing status (?)</li>
180 		* </ul>
181 		* @param {Object} keys These are the control keys being passed in for left, right, up, and down.
182 		* //incomplete
183 		*/
184 		controlKeys:function(th,keys) {
185 			var cancelx=false;
186 			var cancely=false;
187 			var idlex=false;
188 			var idley=false;
189 			
190 			if (gbox.keyIsPressed(keys.left)||keys.pressleft) {
191 				th.xpushing=toys.PUSH_LEFT;
192 				th.facing=toys.FACE_LEFT;
193 				if (th.accx>th.responsive) th.accx=th.responsive;
194 				if (th.staticspeed) th.accx=-th.staticspeed; else th.accx=help.limit(th.accx-1,-th.controlmaxacc,th.controlmaxacc);
195 				if (th.nodiagonals) { cancely=true; idley=true }
196 			} else if (gbox.keyIsPressed(keys.right)||keys.pressright) {
197 				th.xpushing=toys.PUSH_RIGHT;
198 				th.facing=toys.FACE_RIGHT;
199 				if (th.accx<-th.responsive) th.accx=-th.responsive;
200 				if (th.staticspeed) th.accx=th.staticspeed; else th.accx=help.limit(th.accx+1,-th.controlmaxacc,th.controlmaxacc);
201 				if (th.nodiagonals) { cancely=true; idley=true }
202 			} else idlex=true;
203 			
204 			if (!cancely&&(gbox.keyIsPressed(keys.up)||keys.pressup)) {
205 				th.ypushing=toys.PUSH_UP;
206 				th.facing=toys.FACE_UP;
207 				if (th.accy>th.responsive) th.accy=th.responsive;
208 				if (th.staticspeed) th.accy=-th.staticspeed; else th.accy=help.limit(th.accy-1,-th.controlmaxacc,th.controlmaxacc);
209 				if (th.nodiagonals) { cancelx=true; idlex=true; }
210 			} else if (!cancely&&(gbox.keyIsPressed(keys.down)||keys.pressdown)) {
211 				th.ypushing=toys.PUSH_DOWN;
212 				th.facing=toys.FACE_DOWN;
213 				if (th.accy<-th.responsive) th.accy=-th.responsive;
214 				if (th.staticspeed) th.accy=th.staticspeed; else th.accy=help.limit(th.accy+1,-th.controlmaxacc,th.controlmaxacc);
215 				if (th.nodiagonals) { cancelx=true; idlex=true; }
216 			} else idley=true;
217 			
218 			
219 			
220 			 if (idlex) {
221 				if (cancelx) th.accx=0;
222 				if (cancelx||!th.noreset) th.xpushing=toys.PUSH_NONE;
223 			}
224 			if (idley) {
225 				if (cancely) th.accy=0;				
226 				if (cancely||!th.noreset) th.ypushing=toys.PUSH_NONE;
227 			}
228 		},
229 		
230 		/**
231 		* Gets the next X position the object is going to move to.
232 		* @param {Object} th The object being checked.
233 		* <ul>
234 		* <li>x: the current x position of the object</li>
235 		* <li>accx: the object's currect acceleration in the x direction</li>
236 		* <li>maxacc: the max accleration the object can have (if accx is greater than this then this value is used instead)</li>
237 		* </ul>
238 		*/
239 		getNextX:function(th) { return th.x+help.limit(th.accx,-th.maxacc,th.maxacc); },
240 		
241 		/**
242 		* Gets the next Y position the object is going to move to.
243 		* @param {Object} th The object being checked.
244 		* <ul>
245 		* <li>y: the current y position of the object</li>
246 		* <li>accy: the object's currect acceleration in the y direction</li>
247 		* <li>maxacc: the max accleration the object can have (if accy is greater than this then this value is used instead)</li>
248 		* </ul>
249 		*/
250 		getNextY:function(th) { return th.y+help.limit(th.accy,-th.maxacc,th.maxacc); },
251 		
252 		/**
253 		* Gets the next Z position the object is going to move to.
254 		* @param {Object} th The object being checked.
255 		* <ul>
256 		* <li>z: the current z position of the object</li>
257 		* <li>accz: the object's currect acceleration in the z direction</li>
258 		* <li>maxacc: the max accleration the object can have (if accz is greater than this then this value is used instead)</li>
259 		* </ul>
260 		*/
261 		getNextZ:function(th) { return th.z+help.limit(th.accz,-th.maxacc,th.maxacc); },
262 		
263 		/**
264 		* Sets the objects current location to its next location using the getNextX and getNextY methods.
265 		* @param {Object} th The object being modified.
266 		* <ul>
267 		* <li>x: the current x position of the object</li>
268 		* <li>y: the current y position of the object</li>
269 		* <li>accx: the object's currect acceleration in the x direction</li>
270 		* <li>accy: the object's currect acceleration in the y direction</li>
271 		* <li>maxacc: the max accleration the object can have (if either acceleration is greater than this then this value is used instead for that acceleration)</li>
272 		* </ul>
273 		*/
274 		applyForces:function(th) {
275 			th.x=toys.topview.getNextX(th);
276 			th.y=toys.topview.getNextY(th);
277 		},
278 		
279 		/**
280 		* This applies acceleration in the Z direction (not nessesarily gravity but whatever the next accerlation on the Z axis is)
281 		* @param {Object} th The object being modified.
282 		* <ul>
283 		* <li>z: the current z position of the object</li>
284 		* <li>accz: the object's currect acceleration in the z direction</li>
285 		* <li>maxacc: the max accleration the object can have (if accz is greater than this then this value is used instead)</li>
286 		* </ul>
287 		*/
288 		applyGravity:function(th) {
289 			th.z=toys.topview.getNextZ(th);
290 		},
291 		
292 		/**
293 		* Degrades all accelerations on an object by one toward zero.
294 		* @param {Object} th The object being modified.
295 		* <ul>
296 		* <li>xpushing: a boolean that notes whether the object is pushing against something in the x direction.</li>
297 		* <li>ypushing: a boolean that notes whether the object is pushing against something in the y direction.</li>
298 		* <li>accx: the object's currect acceleration in the x direction</li>
299 		* <li>accy: the object's currect acceleration in the y direction</li>
300 		* </ul>
301 		*/
302 		handleAccellerations:function(th) {
303 			if (!th.xpushing) th.accx=help.goToZero(th.accx);
304 			if (!th.ypushing) th.accy=help.goToZero(th.accy);
305 			
306 		},
307 		
308 		/**
309 		* Increases the Z acceleration on the object by one.
310 		* @param {Object} th The object being modified.
311 		* <ul>
312 		* <li>accz: the acceleration on the Z axis</li>
313 		* </ul>
314 		*/
315 		handleGravity:function(th) {
316 			th.accz++;
317 		},
318 		
319 		/**
320 		* This sets which frame the object is going to display based on an agregate word that describes predefined states.
321 		* @param {Object} th The object whose frame is being set.
322 		* <ul>
323 		* <li>xpushing: a boolean that notes whether the object is pushing against something in the x direction.</li>
324 		* <li>ypushing: a boolean that notes whether the object is pushing against something in the y direction.</li>
325 		* <li>haspushing: a boolean that notes if the object changes when pushing against something.</li>
326 		* <li>toucheddown: a boolean that notes if the object is touching something below it on the screen.</li>
327 		* <li>touchedup: a boolean that notes if the object is touching something above it on the screen.<</li>
328 		* <li>touchedright: a boolean that notes if the object is touching something right of it on the screen.<</li>
329 		* <li>touchedleft: a boolean that notes if the object is touching something left of it on the screen.<</li>
330 		* <li>flipside: </li>
331 		* <li>fliph: </li>
332 		* <li>facing: </li>
333 		* <li>frames: </li>
334 		* <li>frame: </li>
335 		* <li>counter: </li>
336 		* </ul>
337 		* // incomplete
338 		*/
339 		setFrame:function(th) {
340 			var pref="stand";
341 			if (th.xpushing||th.ypushing) 
342 				if (th.haspushing&&(th.toucheddown||th.touchedup||th.touchedleft||th.touchedright)) pref="pushing"; else pref="moving";
343 			if (th.flipside)
344 				th.fliph=(th.facing==toys.FACE_RIGHT);
345 			th.frame=help.decideFrame(th.counter,th.frames[pref+toys.FACES[th.facing]]);
346 		},
347 		
348 		/**
349 		* Checks if the specified object is colliding with tiles in the map in an area defined by the object's colw and colh variables as well as the tolerance and approximation variables that are passed in through data. Only tiles in the map marked as solid are checked against. The alogrithm checks the 
350 		* @param {Object} th The object that is being checked against the tilemap.
351 		* @param {Object} map This is the asci map that the tile map is generated from.
352 		* @param {Object} tilemap This is the array of tile objects that it itterated over checking for collisions.
353 		* @param {Object} defaulttile The default tile to be returned if nothing can be found. Null can be used here.
354 		* @param {Object} data Passes is extra dat to the function. Can be set as null.
355 		* <ul>
356 		* <li>tolerance{Integer}: This is subtracted from the collision space to get the maximum collision area for the object. This defaults to 6.</li>
357 		* <li>approximation{Integer}: This is the amount that the checked values are incremented by until they reach the maximum value allowed. This defaults to 10.</li>
358 		* </ul>
359 		*/
360 		tileCollision:function(th,map,tilemap,defaulttile,data) {
361 			
362 			th.touchedup=false;
363 			th.toucheddown=false;
364 			th.touchedleft=false;
365 			th.touchedright=false;
366 			
367 			var tolerance=(data&&(data.tolerance!=null)?data.tolerance:6);
368 			var approximation=(data&&(data.approximation!=null)?data.approximation:10);
369 			var t=tolerance-approximation;
370 			do {
371 				t+=approximation;
372 				if (t>th.colw-tolerance-1) t=th.colw-tolerance-1;
373 				var bottom=help.getTileInMap(th.x+th.colx+t,th.y+th.coly+th.colh-1,map,defaulttile,tilemap);
374 				var top=help.getTileInMap(th.x+th.colx+t,th.y+th.coly,map,defaulttile,tilemap);
375 				if (map.tileIsSolid(th,top)) th.touchedup=true;
376 				if (map.tileIsSolid(th,bottom)) th.toucheddown=true;	
377 			} while (t!=th.colw-tolerance-1);
378 			
379 			t=tolerance-approximation;
380 			do {
381 				t+=approximation;
382 				if (t>th.colh-tolerance-1) t=th.colh-tolerance-1;
383 				var left=help.getTileInMap(th.x+th.colx,th.y+th.coly+t,map,defaulttile,tilemap);
384 				var right=help.getTileInMap(th.x+th.colx+th.colw-1,th.y+th.coly+t,map,defaulttile,tilemap);
385 				if (map.tileIsSolid(th,left)) th.touchedleft=true;
386 				if (map.tileIsSolid(th,right)) th.touchedright=true;
387 			} while (t!=th.colh-tolerance-1);
388 			
389 			if (th.touchedup) {
390 				th.accy=0;
391 				th.y=help.yPixelToTile(map,th.y+th.coly,1)-th.coly;				
392 			}
393 			if (th.toucheddown) {
394 				th.accy=0;
395 				th.y=help.yPixelToTile(map,th.y+th.coly+th.colh-1)-th.coly-th.colh;				
396 			}
397 			if (th.touchedleft) {
398 				th.accx=0;
399 				th.x=help.xPixelToTile(map,th.x+th.colx,1)-th.colx;				
400 			}
401 			if (th.touchedright) {
402 				th.accx=0;
403 				th.x=help.xPixelToTile(map,th.x+th.colx+th.colw-1)-th.colx-th.colw;				
404 			}
405 			
406 		},
407 		
408 		/**
409 		* @param {Object} th The object being checked for collisions.
410 		* <ul>
411 		* <li></li>
412 		* <li></li>
413 		* <li></li>
414 		* <li></li>
415 		* </ul>
416 		* @param {Object} data This is used to pass in other data and arguments.
417 		* <ul>
418 		* <li>group {String}: (required) This is the group of objects being checked against.</li>
419 		* <li></li>
420 		* <li></li>
421 		* <li></li>
422 		* <li></li>
423 		* </ul> //incomplete
424 		*/
425 		spritewallCollision:function(th,data) {
426 			var wl;
427 			for (var i in gbox._objects[data.group])
428 				if ((!gbox._objects[data.group][i].initialize)&&toys.topview.collides(th,gbox._objects[data.group][i])) {
429 					wl=gbox._objects[data.group][i];
430 					if (toys.topview.pixelcollides({x:th.x+th.colx,y:th.y+th.coly+th.colhh},wl)) {
431 						th.touchedleft=true;
432 						th.accx=0;
433 						th.x=wl.x+wl.colx+wl.colw-th.colx;
434 					} else if (toys.topview.pixelcollides({x:th.x+th.colx+th.colw,y:th.y+th.coly+th.colhh},wl)) {
435 						th.touchedright=true;
436 						th.accx=0;
437 						th.x=wl.x+wl.colx-th.colw-th.colx;
438 					}
439 					if (toys.topview.pixelcollides({x:th.x+th.colx+th.colhw,y:th.y+th.coly+th.colh},wl)) {
440 						th.toucheddown=true;
441 						th.accy=0;
442 						th.y=wl.y+wl.coly-th.colh-th.coly;
443 					} else if (toys.topview.pixelcollides({x:th.x+th.colx+th.colhw,y:th.y+th.coly},wl)) {
444 						th.touchedup=true;
445 						th.accy=0;
446 						th.y=wl.y+wl.coly+wl.colh-th.coly;
447 					}
448 				}
449 						
450 		},
451 		
452 		/**
453 		* This checks if the object's z index is 0 which means it has hit the floor. If this has occured it also plays an impact or bounce noise if one is passed in. Note: The area above the floor is in the negative z index space so a value of 1 for z will return that the object has collided with the floor and z will then be set to zero.
454 		* @param {Object} th The object being checked for collision.
455 		* <ul>
456 		* <li>touchedfloor{boolean}: This value is not passed in but is created or set in the function. This contains the function's return value.</li>
457 		* <li></li>
458 		* <li></li>
459 		* <li></li>
460 		* </ul>
461 		* @param {Object} data This is used to pass in extra parameters.
462 		* <ul>
463 		* <li></li>
464 		* </ul>
465 		*/
466 		floorCollision:function(th,data) {
467 			th.touchedfloor=false;
468 			if (th.z>0) {
469 				th.accz=(data==null?0:-Math.floor(th.accz/data.bounce));
470 				if (data&&data.audiobounce&&th.accz) gbox.hitAudio(data.audiobounce);
471 				th.z=0;
472 				th.touchedfloor=true;
473 			}			
474 		},
475 		
476 		/**
477 		* 
478 		*/
479 		adjustZindex:function(th) {
480 			gbox.setZindex(th,th.y+th.h);
481 		},
482 		
483 		/**
484 		* 
485 		*/
486 		// Helper: returns the ahead pixel (i.e. destination use action)
487 		getAheadPixel:function(th,data) {
488 			switch (th.facing) {
489 				case toys.FACE_RIGHT:{
490 					return {x:th.x+th.colx+th.colw+data.distance,y:th.y+th.coly+th.colhh};
491 					break;
492 				}
493 				case toys.FACE_LEFT:{
494 					return {x:th.x+th.colx-data.distance,y:th.y+th.coly+th.colhh};
495 					break;
496 				}
497 				case toys.FACE_UP:{
498 					return {x:th.x+th.colx+th.colhw,y:th.y+th.coly-data.distance};
499 					break;
500 				}
501 				case toys.FACE_DOWN:{
502 					return {x:th.x+th.colx+th.colhw,y:th.y+th.coly+th.colh+data.distance};
503 					break;
504 				}
505 			}
506 		},
507 		
508 		/**
509 		* 
510 		*/
511 		// Helper: trigger a method in colliding objects (i.e. "use action")
512 		callInColliding:function(th,data) {
513 			for (var i in gbox._objects[data.group])
514 				if ((!gbox._objects[data.group][i].initialize)&&toys.topview.pixelcollides(data,gbox._objects[data.group][i]))
515 					if (gbox._objects[data.group][i][data.call]) {
516 						gbox._objects[data.group][i][data.call](th);
517 						return i;
518 					}
519 			return false;
520 		},
521 		
522 		/**
523 		* 
524 		*/
525 		// Enemy methods
526 		wander:function(th,map,tilemap,defaulttile,data) {
527 			if ((!th.wandercounter)||(th.toucheddown||th.touchedup||th.touchedleft||th.touchedright)) {
528 				th.wandercounter=help.random(data.minstep,data.steprange);
529 				th.wanderdirection=help.random(0,4);
530 			} else th.wandercounter--;
531 			switch (th.wanderdirection) {
532 				case toys.FACE_LEFT: {
533 					th.xpushing=toys.PUSH_LEFT;
534 					th.ypushing=toys.PUSH_NONE;
535 					th.facing=toys.FACE_LEFT;
536 					th.accx=-data.speed;
537 					th.accy=0;
538 					break;
539 				}
540 				case toys.FACE_RIGHT: {
541 					th.xpushing=toys.PUSH_RIGHT;
542 					th.ypushing=toys.PUSH_NONE;
543 					th.facing=toys.FACE_RIGHT;
544 					th.accx=data.speed;
545 					th.accy=0;
546 					break;
547 				}
548 				case toys.FACE_UP: {
549 					th.ypushing=toys.PUSH_UP;
550 					th.xpushing=toys.PUSH_NONE;
551 					th.facing=toys.FACE_UP;
552 					th.accy=-data.speed;
553 					th.accx=0;
554 					break;
555 				}
556 				case toys.FACE_DOWN: {
557 					th.ypushing=toys.PUSH_DOWN;
558 					th.xpushing=toys.PUSH_NONE;
559 					th.facing=toys.FACE_DOWN;
560 					th.accy=data.speed;
561 					th.accx=0;
562 					break;
563 				}					
564 			}
565 		},
566 		
567 		/**
568 		* 
569 		*/
570 		// generators (firebullet specific for topdown - more complex than SHMUP one)
571 		fireBullet:function(gr,id,data) {
572 
573 			var ts=gbox.getTiles(data.tileset);
574 			
575 						
576 			var obj=gbox.addObject(
577 				help.mergeWithModel(
578 					data,{
579 						_bullet:true,
580 						zindex:0,
581 						fliph:false, flipv:false,
582 						id:id,
583 						group:gr,
584 						cnt:0,
585 						tileset:"",
586 						frames:{},
587 						acc:0,
588 						angle:0,
589 						camera:data.from.camera,
590 						accx:(data.accx==null?Math.floor(trigo.translateX(0,data.angle,data.acc)):0),
591 						accy:(data.accy==null?Math.floor(trigo.translateY(0,data.angle,data.acc)):0),
592 						accz:0,
593 						x:(data.sidex==toys.FACE_LEFT?data.from.x-ts.tilehw:(data.sidex==toys.FACE_RIGHT?data.from.x+data.from.w-ts.tilehw:data.from.x+data.from.hw-ts.tilehw))+(data.gapx?data.gapx:0),
594 						y:(data.sidey==toys.FACE_UP?data.from.y-ts.tilehh:(data.sidey==toys.FACE_DOWN?data.from.y+data.from.h-ts.tilehh:data.from.y+data.from.hh-ts.tilehh))+(data.gapy?data.gapy:0),
595 						z:(data.from.z==null?0:data.from.z),
596 						collidegroup:"",
597 						spark:toys.NOOP,
598 						power:1,
599 						lifetime:null,
600 						tilemap:null,
601 						defaulttile:0,
602 						applyzgravity:false,
603 						map:null,
604 						defaulttile:0,
605 						mapindex:"",
606 						spritewalls:null,
607 						colx:(data.fullhit?0:null),
608 						coly:(data.fullhit?0:null),
609 						colh:(data.fullhit?ts.tileh:null),
610 						colw:(data.fullhit?ts.tilew:null),
611 						duration:null,
612 						onWallHit:function() {
613 							this.spark(this);
614 							gbox.trashObject(this);
615 						},
616 						bulletIsAlive:function() {
617 							return gbox.objectIsVisible(this);
618 						}
619 					}
620 				)
621 			);
622 			
623 			obj.initialize=function() {
624 				toys.topview.initialize(this);
625 			};
626 			
627 			obj[(data.logicon==null?"first":data.logicon)]=function() {
628 				this.cnt=(this.cnt+1)%10;
629 				
630 				if (this.applyzgravity) toys.topview.handleGravity(this); // z-gravity					
631 				toys.topview.applyForces(this); // Apply forces
632 				if (this.applyzgravity) toys.topview.applyGravity(this); // z-gravity
633 				if (this.map!=null) toys.topview.tileCollision(this,this.map,this.mapindex,this.defaulttile); // tile collisions
634 				if (this.spritewalls!=null) toys.topview.spritewallCollision(this,{group:this.spritewalls}); // walls collisions
635 				if (this.applyzgravity) toys.topview.floorCollision(this); // Collision with the floor (for z-gravity)
636 				toys.topview.adjustZindex(this);
637 				if (this.duration!=null) {
638 					this.duration--;
639 					if (this.duration==0) gbox.trashObject(this);
640 				}
641 				if (!this.bulletIsAlive()) gbox.trashObject(this);
642 				else if (this.toucheddown||this.touchedup||this.touchedleft||this.touchedright) this.onWallHit();
643 				else if (this.collidegroup!=null)
644 					for (var i in gbox._objects[this.collidegroup])
645 						if ((!gbox._objects[this.collidegroup][i].initialize)&&toys.topview.collides(this,gbox._objects[this.collidegroup][i],gbox._objects[this.collidegroup][i].tolerance)) {
646 							if (gbox._objects[this.collidegroup][i]["hitByBullet"]!=null)
647 								if (!gbox._objects[this.collidegroup][i].hitByBullet(this)) {
648 									this.spark(this);
649 									gbox.trashObject(this);
650 								}
651 						}
652 			}
653 			
654 			obj[(data.bliton==null?"blit":data.bliton)]=function() {
655 				if (!gbox.objectIsTrash(this))
656 					gbox.blitTile(gbox.getBufferContext(),{tileset:this.tileset,tile:help.decideFrame(this.cnt,this.frames),dx:this.x,dy:this.y+this.z,camera:this.camera,fliph:this.fliph,flipv:this.flipv});
657 			}
658 			
659 			gbox.setZindex(obj,obj.y+obj.h);
660 			
661 			return obj;
662 		
663 		},
664 		
665 		/**
666 		* 
667 		*/
668 		makedoor:function(gr,id,map,data) {
669 
670 			var mts=gbox.getTiles(map.tileset);
671 			var ts=gbox.getTiles(data.tileset);
672 						
673 			var obj=gbox.addObject(
674 				help.mergeWithModel(
675 					data,{
676 						zindex:0,
677 						fliph:false, flipv:false,
678 						id:id,
679 						group:gr,
680 						cnt:0,
681 						tileset:"",
682 						frames:{},
683 						camera:true,
684 						x:data.tilex*mts.tilew,
685 						y:data.tiley*mts.tileh,
686 						z:0,
687 						tilemap:null,
688 						defaulttile:0,
689 						map:map,
690 						colx:(data.fullhit?0:null),
691 						coly:(data.fullhit?0:null),
692 						colh:(data.fullhit?ts.tileh:null),
693 						colw:(data.fullhit?ts.tilew:null),
694 						opening:false,
695 						doorheight:ts.tileh,
696 						opencounter:0,
697 						opening:false,
698 						closing:false,
699 						audiobefore:null,
700 						audioafter:null,
701 						doOpen:function() {
702 							this.opening=true;
703 						},
704 						whenClosed:toys.NOOP,
705 						whenOpened:toys.NOOP,
706 						whileMoving:toys.NOOP,
707 						hitByBullet:function(by) {
708 						
709 						}
710 					}
711 				)
712 			);
713 			
714 			// Closing animation
715 			if (obj.closing) obj.opencounter=obj.doorheight;
716 			
717 			obj.initialize=function() {
718 				this.ismoving=false;
719 				toys.topview.initialize(this);
720 			};
721 			
722 			obj[(data.logicon==null?"first":data.logicon)]=function() {
723 				if (this.closing) {
724 					if (!this.ismoving) {
725 						if (this.audiobefore) gbox.hitAudio(this.audiobefore);
726 						this.ismoving=true;
727 					}
728 					this.whileMoving();
729 					this.opencounter--;
730 					if (this.opencounter<0) {
731 						if (this.audioafter) gbox.hitAudio(this.audioafter);
732 						this.ismoving=false;
733 						this.opencounter=0;
734 						this.closing=false;
735 						this.whenClosed();
736 					}
737 				}
738 				if (this.opening) {
739 					if (!this.ismoving) {
740 						if (this.audiobefore) gbox.hitAudio(this.audiobefore);
741 						this.ismoving=true;
742 					}
743 					this.whileMoving();
744 					this.opencounter++;
745 					if (this.opencounter>=this.doorheight) {
746 						if (this.audioafter) gbox.hitAudio(this.audioafter);
747 						this.ismoving=false;
748 						if (!this.whenOpened()) gbox.trashObject(this);
749 					}
750 				}
751 			}
752 			
753 			obj[(data.bliton==null?"blit":data.bliton)]=function() {
754 				if (!gbox.objectIsTrash(this))
755 					gbox.blitTile(gbox.getBufferContext(),{tileset:this.tileset,tile:help.decideFrame(this.cnt,this.frames),dx:this.x,dy:this.y+this.z+this.opencounter,h:this.h-this.opencounter,camera:this.camera,fliph:this.fliph,flipv:this.flipv});
756 			}
757 			
758 			gbox.setZindex(obj,obj.y+obj.h);
759 			
760 			return obj;
761 		},
762 		// Set the object speed making sure that the X and Y coords are multiple of the speed. Useful on maze-based games.
763 		setStaticSpeed:function(th,speed) {
764 			th.staticspeed=speed;
765 			th.x=Math.round(th.x/speed)*speed;
766 			th.y=Math.round(th.y/speed)*speed;
767 		}
768 	},
769 	
770 	
771 	/**
772 	* @namespace shmup The libraries for a 2D top-down Shmup game.
773 	*/
774 	// Shoot'em up specifics
775 	shmup:{
776 		
777 		/**
778 		* 
779 		*/
780 		initialize:function(th,data) {
781 			help.mergeWithModel(
782 				th,
783 				help.mergeWithModel(
784 					data,
785 					{
786 						x:0, y:0,
787 						accx:0, accy:0,
788 						frames:{},
789 						maxacc:5, controlmaxacc:5,
790 						responsive:0, // Responsiveness
791 						bounds:{x:0,y:0,w:gbox.getScreenW(),h:gbox.getScreenH()}, // Bounds box (ship cannot exit from there)
792 						weapon:0, // Weapon
793 						hittime:5,
794 						camera:false,
795 						flipv:false, fliph:false,
796 						health:1,
797 						tolerance:4
798 					}
799 				)
800 			);
801 			toys.shmup.spawn(th);
802 		},
803 		
804 		/**
805 		* 
806 		*/
807 		spawn:function(th,data) {
808 			th.xpushing=toys.PUSH_NONE; // user is moving side
809 			th.vpushing=toys.PUSH_NONE; // user is moving side
810 			th.counter=0; // self counter
811 			th.hittimer=0;
812 			th.killed=false;
813 			help.copyModel(th,data);
814 		},
815 		
816 		/**
817 		* 
818 		*/
819 		getNextX:function(th) { return th.x+help.limit(th.accx,-th.maxacc,th.maxacc); },
820 		
821 		/**
822 		* 
823 		*/
824 		getNextY:function(th) { return th.y+help.limit(th.accy,-th.maxacc,th.maxacc); },
825 		
826 		/**
827 		* 
828 		*/
829 		controlKeys:function(th,keys) {
830 			
831 			if (gbox.keyIsPressed(keys.left)) {
832 				th.xpushing=toys.PUSH_LEFT;
833 				if (th.accx>th.responsive) th.accx=th.responsive;
834 				th.accx=help.limit(th.accx-1,-th.controlmaxacc,th.controlmaxacc);
835 			} else if (gbox.keyIsPressed(keys.right)) {
836 				th.xpushing=toys.PUSH_RIGHT;
837 				if (th.accx<-th.responsive) th.accx=-th.responsive;
838 				th.accx=help.limit(th.accx+1,-th.controlmaxacc,th.controlmaxacc);
839 			} else th.xpushing=toys.PUSH_NONE;
840 			if (gbox.keyIsPressed(keys.up)) {
841 				th.ypushing=toys.PUSH_UP;
842 				if (th.accy>th.responsive) th.accy=th.responsive;
843 				th.accy=help.limit(th.accy-1,-th.controlmaxacc,th.controlmaxacc);
844 			} else if (gbox.keyIsPressed(keys.down)) {
845 				th.ypushing=toys.PUSH_DOWN;
846 				if (th.accy<-th.responsive) th.accy=-th.responsive;
847 				th.accy=help.limit(th.accy+1,-th.controlmaxacc,th.controlmaxacc);
848 			} else th.ypushing=toys.PUSH_NONE;
849 		},
850 		
851 		/**
852 		* 
853 		*/
854 		applyForces:function(th) {
855 			th.x=toys.shmup.getNextX(th);
856 			th.y=toys.shmup.getNextY(th);
857 		},
858 		
859 		/**
860 		* 
861 		*/
862 		handleAccellerations:function(th) {
863 			if (!th.xpushing) th.accx=help.goToZero(th.accx);
864 			if (!th.ypushing) th.accy=help.goToZero(th.accy);
865 		},
866 		
867 		/**
868 		* 
869 		*/
870 		keepInBounds:function(th) {
871 			if (th.x<th.bounds.x) {
872 				th.x=th.bounds.x;
873 				th.accx=0;
874 			} else if (th.x+th.w>th.bounds.x+th.bounds.w) {
875 				th.x=th.bounds.x+th.bounds.w-th.w;
876 				th.accx=0;				
877 			}
878 			if (th.y<th.bounds.y) {
879 				th.y=th.bounds.y;
880 				th.accy=0;
881 			} else if (th.y+th.h>th.bounds.y+th.bounds.h) {
882 				th.y=th.bounds.y+th.bounds.h-th.h;
883 				th.accy=0;				
884 			}
885 		},
886 		
887 		/**
888 		* 
889 		*/
890 		setFrame:function(th) {
891 			if (th.hittimer) th.hittimer--;
892 			th.frame=help.decideFrame(th.counter,(th.hittimer?th.frames.hit:th.frames.still));
893 		},
894 		
895 		/**
896 		* 
897 		*/
898 		fireBullet:function(gr,id,data) {
899 		
900 			var ts=gbox.getTiles(data.tileset);
901 			
902 			var obj=gbox.addObject(
903 				help.mergeWithModel(
904 					data,{
905 						_bullet:true,
906 						fliph:false, flipv:false,
907 						id:id,
908 						group:gr,
909 						cnt:0,
910 						tileset:"",
911 						frames:{},
912 						acc:0,
913 						angle:0,
914 						camera:false,
915 						accx:(data.accx==null?Math.floor(trigo.translateX(0,data.angle,data.acc)):0),
916 						accy:(data.accy==null?Math.floor(trigo.translateY(0,data.angle,data.acc)):0),
917 						x:data.from.x+data.from.hw-ts.tilehw+(data.gapx?data.gapx:0),
918 						y:(data.upper?data.from.y-ts.tilehh+(data.gapy?data.gapy:0):data.from.y+data.from.h-ts.tilehh-(data.gapy?data.gapy:0)),
919 						collidegroup:"",
920 						spark:toys.NOOP,
921 						power:1
922 					}
923 				)
924 			);
925 			
926 			obj[(data.logicon==null?"first":data.logicon)]=function() {
927 				this.x+=this.accx;
928 				this.y+=this.accy;
929 				this.cnt=(this.cnt+1)%10;
930 				if (!gbox.objectIsVisible(this)) gbox.trashObject(this);
931 				else if (this.collidegroup!=null)
932 					for (var i in gbox._objects[this.collidegroup])
933 						if ((!gbox._objects[this.collidegroup][i].initialize)&&gbox.collides(this,gbox._objects[this.collidegroup][i],gbox._objects[this.collidegroup][i].tolerance)) {
934 							if (gbox._objects[this.collidegroup][i]["hitByBullet"]!=null)
935 								if (!gbox._objects[this.collidegroup][i].hitByBullet(this)) {
936 									this.spark(this);
937 									gbox.trashObject(this);
938 								}
939 						}
940 			}
941 			
942 			obj[(data.bliton==null?"blit":data.bliton)]=function() {
943 				gbox.blitTile(gbox.getBufferContext(),{tileset:this.tileset,tile:help.decideFrame(this.cnt,this.frames),dx:this.x,dy:this.y,camera:this.camera,fliph:this.side,flipv:this.flipv});
944 			}
945 			
946 			return obj;
947 		
948 		},
949 		
950 		/**
951 		* 
952 		*/
953 		hitByBullet:function(th,by) {
954 			if (by.power) {
955 				th.health-=by.power;
956 				if (th.health<=0) th.kill(by); else  th.hittimer=th.hittime;
957 			}
958 		},
959 		
960 		/**
961 		* 
962 		*/
963 		generateEnemy:function(gr,id,data,model) {
964 			help.mergeWithModel(data,model);
965 			var obj=gbox.addObject(
966 				help.mergeWithModel(
967 					data,{
968 						id:id,
969 						group:gr,
970 						cnt:0,
971 						tileset:"",
972 						frames:{},
973 						acc:0,
974 						angle:0,
975 						camera:false,
976 						fliph:false,
977 						flipv:false,
978 						accx:(data.accx==null?Math.floor(trigo.translateX(0,data.angle,data.acc)):0),
979 						accy:(data.accy==null?Math.floor(trigo.translateY(0,data.angle,data.acc)):0),
980 						x:data.x,
981 						y:data.y,
982 						// -- spec
983 						animationset:"still",
984 						defaultanimationset:"still",
985 						hitanimationset:"still",
986 						hittime:5,
987 						script:toys.NOOP,
988 						handler:toys.NOOP,
989 						scriptline:(data.scriptline==null?-1:data.scriptline-1),
990 						newline:true,
991 						waitframes:0,
992 						doframes:0,
993 						mode:0,
994 						line:{},
995 						dohandler:null,
996 						ended:false,
997 						health:1,
998 						hittimer:0,
999 						kill:toys.NOOP,
1000 						tolerance:0,
1001 						initialize:null,
1002 						invulnerable:false,
1003 						hitAnimation:function(time) {
1004 							this.hittimer=(time==null?this.hittime:time);
1005 							this.animationset=this.hitanimationset;
1006 						},
1007 						goTo:function(nl) { // Jump to a line
1008 							this.waitframes=0;
1009 							this.doframes=0;
1010 							this.line={};
1011 							this.scriptline=nl-1;
1012 						},
1013 						hitByBullet:function(by) {
1014 							if (!this.invulnerable&&by.power) {
1015 								this.health-=by.power;
1016 								if (this.health<=0) this.kill(this,by); else this.hitAnimation();
1017 							}
1018 						},
1019 					}
1020 				)
1021 			);
1022 			
1023 			
1024 			obj[(data.logicon==null?"first":data.logicon)]=function() {
1025 				if (this.initialize!=null)  {
1026 					this.initialize(this);
1027 					this.initialize=null;
1028 				}
1029 				if (!this.ended) {
1030 					if (!this.waitframes&&!this.doframes&&((this.line.waitfor==null)||this.line.waitfor(this))) {
1031 						this.scriptline++;
1032 						this.everycnt=-1;
1033 						if (this.script[this.scriptline]==null)
1034 							this.ended=true;
1035 						else {
1036 							if (this.script[this.scriptline].goto!=null) this.scriptline=this.script[this.scriptline].goto;
1037 							this.line=this.script[this.scriptline];
1038 							if (this.line.afterframes!=null)
1039 								this.waitframes=this.line.afterframes;
1040 							if (this.line.forframes!=null)
1041 								this.doframes=this.line.forframes;
1042 							else
1043 								this.doframes=1;
1044 							if (this.line.cleardo)
1045 								this.dohandler=null;
1046 							else if (this.line.doit!=null) {
1047 								this.dohandler={
1048 									actiontimes:0,
1049 									timer:(this.line.doit.every=="keep"?this.dohandler.every:this.line.doit.every),
1050 									every:(this.line.doit.every=="keep"?this.dohandler.every:this.line.doit.every),
1051 									once:(this.line.doit.once=="keep"?this.dohandler.once:this.line.doit.once),
1052 									action:(this.line.doit.action=="keep"?this.dohandler.action:this.line.doit.action),
1053 									render:(this.line.doit.render=="keep"?this.dohandler.render:this.line.doit.render)
1054 								}
1055 							}
1056 								
1057 						}
1058 					}
1059 					if (!this.waitframes&&this.doframes&&!this.ended) {
1060 						this.doframes--;
1061 						if (this.line.setinvulnerable!=null) this.invulnerable=this.line.setinvulnerable;
1062 						if (this.line.setx!=null) this.x=this.line.setx;
1063 						if (this.line.sety!=null) this.y=this.line.sety;
1064 						if (this.line.addx!=null) this.x+=this.line.addx;
1065 						if (this.line.addy!=null) this.y+=this.line.addy;
1066 						if (this.line.setaccx!=null) this.accx=this.line.setaccx;
1067 						if (this.line.setaccy!=null) this.accy=this.line.setaccy;
1068 						if (this.line.setacc!=null) {
1069 							this.acc=this.line.setacc;
1070 							this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
1071 							this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
1072 						}
1073 						if (this.line.addaccx!=null) this.accx+=this.line.addaccx;
1074 						if (this.line.addaccy!=null) this.accy+=this.line.addaccy;
1075 						if (this.line.addacc!=null) {
1076 							this.acc+=this.line.addacc;
1077 							this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
1078 							this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
1079 						}
1080 						
1081 						if (this.line.setangle!=null) {
1082 							this.angle=this.line.setangle;
1083 							this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
1084 							this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
1085 						}
1086 						if (this.line.addangle!=null) {
1087 							this.angle+=this.line.addangle;
1088 							this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
1089 							this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
1090 						}
1091 						if (this.line.everyframe) this.waitframes=this.line.everyframe;
1092 							
1093 					}
1094 					if (this.waitframes>0) this.waitframes--;
1095 				}
1096 				if (this.dohandler&&(this.dohandler.action!=null)) {
1097 					if (this.dohandler.timer==this.dohandler.every) {
1098 						this.dohandler.action(this,this.dohandler.actiontimes);
1099 						this.dohandler.timer=0;
1100 						this.dohandler.actiontimes++;
1101 					} else if (!this.dohandler.once) this.dohandler.timer++;
1102 				}
1103 				if (this.handler!=null) this.handler(this);
1104 				
1105 				if (this.hittimer) {
1106 					this.hittimer--;
1107 					if (!this.hittimer) this.animationset=this.defaultanimationset;
1108 				}
1109 				
1110 				this.x+=this.accx;
1111 				this.y+=this.accy;
1112 				this.cnt=(this.cnt+1)%10;
1113 				
1114 			}
1115 
1116 			obj[(data.bliton==null?"blit":data.bliton)]=function() {
1117 				gbox.blitTile(gbox.getBufferContext(),{tileset:this.tileset,tile:help.decideFrame(this.cnt,this.frames[this.animationset]),dx:this.x,dy:this.y,camera:this.camera,fliph:this.side,flipv:this.flipv});
1118 				if (this.dohandler&&(this.dohandler.render!=null)) this.dohandler.render(this);
1119 			}
1120 			
1121 			return obj;
1122 
1123 		},
1124 		
1125 		/**
1126 		* 
1127 		*/
1128 		generateScroller:function(gr,id,data) {
1129 			var obj=gbox.addObject(
1130 				help.mergeWithModel(
1131 					help.cloneObject(data),{
1132 						id:id, group:gr,
1133 						y:0, x:0,
1134 						stage:{},
1135 						speed:0,
1136 						stop:null, // Remember to set the last stop ever! or the last loop!
1137 						block:-1,
1138 						bly:0,
1139 						lblock:-1,
1140 						lbly:0,
1141 						lget:0,
1142 						tbly:0,
1143 						trb:0,
1144 						maxwidth:0,
1145 						loopstart:null, loopend:null, looprounds:0,
1146 						panspeed:1, panstimer:0, destspeed:0,
1147 						
1148 						setLoop:function(st,en) {
1149 							this.loopstart=st;
1150 							this.loopend=en;
1151 							this.lget=1;
1152 							this.looprounds=1;
1153 						},
1154 						
1155 						quitLoop:function() {
1156 							this.setLoop(null,null);
1157 							this.looprounds=0;
1158 						},
1159 					
1160 						setSpeed:function(s) {
1161 							this.speed=s;
1162 							this.destspeed=s;
1163 						},
1164 						
1165 						panToSpeed:function(s,pans) {
1166 							this.destspeed=s;
1167 							this.panspeed=pans;
1168 						},
1169 						
1170 						quitStop:function() {
1171 							this.stop=null;
1172 						},
1173 						
1174 						setStop:function(s) {
1175 							this.stop=s;
1176 						},
1177 						
1178 						setX:function(x) {
1179 							if (x<0) this.x=0; else
1180 							if (x+gbox.getScreenW()>this.maxwidth) this.x=this.maxwidth-gbox.getScreenW();
1181 							else this.x=x;
1182 						}
1183 						
1184 					}
1185 				)
1186 			);
1187 			
1188 			obj[(data.logicon==null?"first":data.logicon)]=function() {
1189 				if ((this.stop==null)||(this.y<this.stop)) {
1190 					if (this.speed!=this.destspeed) {
1191 						if (this.panstimer) {
1192 							this.panstimer--;
1193 						} else {
1194 							if (this.speed<this.destspeed) this.speed++; else
1195 							if (this.speed>this.destspeed) this.speed--;
1196 							this.panstimer=this.panspeed;
1197 						}
1198 					}
1199 					this.y+=this.speed;
1200 					if ((this.stop!=null)&&(this.y>=this.stop)) this.y=this.stop;
1201 					if ((this.loopend!=null)&&(this.y>this.loopend)) {
1202 						this.y=this.loopstart+(this.y-this.loopend);
1203 						this.looprounds++;
1204 						if (this.lget==1) {
1205 							this.block=0;
1206 							this.bly=0;
1207 							this.lget=2;
1208 						} else {
1209 							this.block=this.lblock;
1210 							this.bly=this.lbly;
1211 						}
1212 						
1213 					}
1214 				}
1215 						
1216 				// Cerca il blocco da mostrare
1217 				this.trb=this.block;
1218 				this.tbly=this.bly;
1219 				do {
1220 					this.trb++;
1221 					this.tbly+=gbox.getImage(this.stage[this.trb].image).height;	
1222 				} while (this.tbly<this.y);
1223 				
1224 				this.block=this.trb-1;
1225 				this.bly=this.tbly-gbox.getImage(this.stage[this.trb].image).height;
1226 				
1227 						
1228 				if (this.lget==2) {
1229 					this.lblock=this.block;
1230 					this.lbly=this.bly;
1231 					this.lget=3;
1232 				}
1233 				
1234 			}
1235 				
1236 			obj[(data.bliton==null?"blit":data.bliton)]=function() {
1237 				var dy=this.tbly-this.y;
1238 				var done=false;
1239 				do {
1240 					if (dy>gbox.getScreenH()) done=true;
1241 					gbox.blitAll(gbox.getBufferContext(),gbox.getImage(this.stage[this.trb].image),{dx:-this.x,dy:gbox.getScreenH()-dy});
1242 					this.trb++;
1243 					dy+=gbox.getImage(this.stage[this.trb].image).height;
1244 				} while (!done);
1245 			}
1246 			
1247 			return obj;
1248 		}
1249 	},
1250 		
1251 	/**
1252 	* @namespace platformer The libraries for generating a 2D platformer game.
1253 	*/
1254 	platformer:{
1255 		
1256 		/**
1257 		* 
1258 		*/
1259 		initialize:function(th,data) {
1260 			help.mergeWithModel(
1261 				th,
1262 				help.mergeWithModel(
1263 					data,
1264 					{
1265 						maxaccx:5, maxaccy:10,
1266 						jumpsize:6, jumpaccy:6,
1267 						accx:0, accy:0,
1268 						x:0, y:0,
1269 						frames:{},
1270 						camera:true,
1271 						flipv:false,
1272 						side:false
1273 					}
1274 				)
1275 			);
1276 			toys.platformer.spawn(th);
1277 		},
1278 		
1279 		/**
1280 		* 
1281 		*/
1282 		spawn:function(th,data) {
1283 			th.curjsize=0; // current jump size
1284 			th.counter=0; // self counter
1285 			th.touchedfloor=false; // touched floor
1286 			th.touchedceil=false;
1287 			th.touchedleftwall=false;
1288 			th.touchedrightwall=false;
1289 			th.pushing=toys.PUSH_NONE; // user is moving side
1290 			th.killed=false;
1291 			help.copyModel(th,data);
1292 		},
1293 		
1294 		/**
1295 		* 
1296 		*/
1297 		getNextX:function(th) { return th.x+th.accx; },
1298 		
1299 		/**
1300 		* 
1301 		*/
1302 		getNextY:function(th) { return th.y+help.limit(th.accy,-th.maxaccy,th.maxaccy); },
1303 		
1304 		/**
1305 		* 
1306 		*/
1307 		applyGravity:function(th) {
1308 			th.x=toys.platformer.getNextX(th);
1309 			th.y=toys.platformer.getNextY(th);
1310 		},	
1311 		
1312 		/**
1313 		* 
1314 		*/
1315 		horizontalKeys:function(th,keys) {
1316 			if (gbox.keyIsPressed(keys.left)) {
1317 				th.pushing=toys.PUSH_LEFT;
1318 				th.accx=help.limit(th.accx-1,-th.maxaccx,th.maxaccx);
1319 			} else if (gbox.keyIsPressed(keys.right)) {
1320 				th.pushing=toys.PUSH_RIGHT;
1321 				th.accx=help.limit(th.accx+1,-th.maxaccx,th.maxaccx);
1322 			} else th.pushing=toys.PUSH_NONE;
1323 		},
1324 		
1325 		/**
1326 		* 
1327 		*/
1328 		verticalTileCollision:function(th,map,tilemap) {
1329 			var bottom=help.getTileInMap(th.x+(th.w/2),th.y+th.h,map,0,tilemap);
1330 			var top=help.getTileInMap(th.x+(th.w/2),th.y,map,0,tilemap);
1331 			th.touchedfloor=false;
1332 			th.touchedceil=false;
1333 	
1334 			if (map.tileIsSolidCeil(th,top)) {
1335 				th.accy=0;
1336 				th.y=help.yPixelToTile(map,th.y,1);
1337 				th.touchedceil=true;
1338 			}
1339 			if (map.tileIsSolidFloor(th,bottom)) {
1340 				th.accy=0;
1341 				th.y=help.yPixelToTile(map,th.y+th.h)-th.h;
1342 				th.touchedfloor=true;
1343 			}
1344 		},
1345 		
1346 		/**
1347 		* 
1348 		*/
1349 		horizontalTileCollision:function(th,map,tilemap) {
1350 			var left=0;
1351 			var right=0;
1352 			var t=0;
1353 			
1354 			th.touchedleftwall=false;
1355 			th.touchedrightwall=false;
1356 			
1357 			while (t<th.h) {
1358 				left=help.getTileInMap(th.x,th.y+t,map,0,tilemap);
1359 				right=help.getTileInMap(th.x+th.w-1,th.y+t,map,0,tilemap);
1360 					
1361 				if ((th.accx<0)&&map.tileIsSolidFloor(th,left)) {
1362 					th.accx=0;
1363 					th.x=help.xPixelToTile(map,th.x-1,1);
1364 					th.touchedleftwall=true;
1365 				} 
1366 				if ((th.accx>0)&&map.tileIsSolidFloor(th,right)) {
1367 					th.accx=0;
1368 					th.x=help.xPixelToTile(map,th.x+th.w)-th.w;
1369 					th.touchedrightwall=true;
1370 				}
1371 				t+=gbox.getTiles(map.tileset).tileh;
1372 			}
1373 		},
1374 		
1375 		/**
1376 		* Checks if the passed object is touching the floor and can therefore jump at present.
1377 		* @param th This is the object being checked for jump ability at the time of calling.
1378 		*/
1379 		canJump:function(th) {
1380 			return th.touchedfloor;
1381 		},
1382 		
1383 		/**
1384 		* 
1385 		*/
1386 		jumpKeys:function(th,key) {
1387 			if ((toys.platformer.canJump(th)||(key.doublejump&&(th.accy>=0)))&&gbox.keyIsHit(key.jump)&&(th.curjsize==0)) {
1388 				if (key.audiojump) gbox.hitAudio(key.audiojump);
1389 				th.accy=-th.jumpaccy;
1390 				th.curjsize=th.jumpsize;
1391 				return true;
1392 			} else if (th.curjsize&&gbox.keyIsHold(key.jump)) { // Jump modulation
1393 				th.accy--;
1394 				th.curjsize--;
1395 			} else
1396 				th.curjsize=0;
1397 			return false;
1398 		},
1399 		
1400 		/**
1401 		* 
1402 		*/	
1403 		bounce:function(th,data) {
1404 			th.curjsize=0;
1405 			th.accy=-data.jumpsize;
1406 		},
1407 		
1408 		/**
1409 		* 
1410 		*/
1411 		handleAccellerations:function(th) {
1412 			// Gravity
1413 			if (!th.touchedfloor) th.accy++;
1414 			// Attrito
1415 			if (th.pushing==toys.PUSH_NONE) if (th.accx) th.accx=help.goToZero(th.accx);
1416 		},
1417 		
1418 		/**
1419 		* 
1420 		*/
1421 		setSide:function(th) {
1422 			if (th.accx) th.side=th.accx>0;
1423 		},
1424 		
1425 		/**
1426 		* 
1427 		*/
1428 		setFrame:function(th) {
1429 			if (th.touchedfloor)
1430 				if (th.pushing!=toys.PUSH_NONE)
1431 					th.frame=help.decideFrame(th.counter,th.frames.walking);
1432 				else
1433 					th.frame=help.decideFrame(th.counter,th.frames.still);
1434 			else if (th.accy>0)
1435 				th.frame=help.decideFrame(th.counter,th.frames.falling);
1436 			else
1437 				th.frame=help.decideFrame(th.counter,th.frames.jumping);
1438 		},
1439 		
1440 		/**
1441 		* 
1442 		*/
1443 		auto:{
1444 			// Moves on a platform. It tries to do not fall down, if specified.
1445 			// Args: (object,{moveWhileFalling:<moves while not touching the floor>,speed:<movement speed>})
1446 			// Outs: the frame
1447 			goomba:function(th,data) {
1448 				if (data.moveWhileFalling||th.touchedfloor) {
1449 					if (th.side) {
1450 						th.pushing=toys.PUSH_LEFT;
1451 						th.accx=-data.speed;
1452 					} else {
1453 						th.pushing=toys.PUSH_RIGHT;
1454 						th.accx=data.speed;
1455 					}
1456 				} else th.pushing=toys.PUSH_NONE;
1457 			},
1458 			dontFall:function(th,map,tilemap) {
1459 				if (th.accx&&th.touchedfloor) {
1460 					var til;
1461 					if (th.accx>0) til=help.getTileInMap(toys.platformer.getNextX(th)+th.w-1+th.accx,th.y+th.h,map,0,tilemap);				
1462 					else til=help.getTileInMap(toys.platformer.getNextX(th),th.y+th.h,map,0,tilemap);
1463 					if (!map.tileIsSolidFloor(th,til)) {
1464 						th.side=!th.side;
1465 						th.accx=0;
1466 					}
1467 				}
1468 			},
1469 			horizontalBounce:function(th) {
1470 				if (th.touchedleftwall||th.touchedrightwall) th.side=!th.side;
1471 			},
1472 		}
1473 	},
1474 	
1475 	// State-based toys
1476 	// CONSTANTS
1477 	TOY_BUSY:0,
1478 	TOY_DONE:1,
1479 	TOY_IDLE:2,
1480 	
1481 	// PRIVATE
1482 
1483 	// Generical toys method
1484 		
1485 	/**
1486 	* 
1487 	*/
1488 	resetToy:function(th,id) { if (th.toys) delete th.toys[id] },
1489 	
1490 	/**
1491 	* 
1492 	*/
1493 	getToyValue:function(th,id,v,def) { return ((th.toys==null)||(th.toys[id]==null)?def:th.toys[id][v]) },
1494 	
1495 	/**
1496 	* 
1497 	*/
1498 	getToyStatus:function(th,id) { return ((th.toys==null)||(th.toys[id]==null)?toys.TOY_BUSY:th.toys[id].__status) },
1499 	
1500 	/**
1501 	* 
1502 	*/
1503 	_toydone:function(th,id) {
1504 		if (th.toys[id].__status<toys.TOY_IDLE) th.toys[id].__status++;
1505 		return th.toys[id].__status;
1506 	},
1507 	
1508 	/**
1509 	* 
1510 	*/
1511 	_toybusy:function(th,id) {
1512 		th.toys[id].__status=toys.TOY_BUSY;
1513 		return th.toys[id].__status;
1514 	},
1515 	
1516 	/**
1517 	* 
1518 	*/
1519 	_toyfrombool:function(th,id,b) { return (b?toys._toydone(th,id):toys._toybusy(th,id)) },
1520 	
1521 	/**
1522 	* 
1523 	*/
1524 	_maketoy:function(th,id){
1525 		if (!th.toys) th.toys={};
1526 		if (!th.toys[id]) {
1527 			th.toys[id]={__status:toys.TOY_BUSY};
1528 			return true;
1529 		} else return false;
1530 	},
1531 	
1532 	/**
1533 	* @namespace timer Timer functionality based methods
1534 	*/
1535 	// Pure timers
1536 	timer:{
1537 		
1538 		/**
1539 		* 
1540 		*/
1541 		randomly:function(th,id,data) {
1542 			if (toys._maketoy(th,id)) {
1543 				th.toys[id].time=help.random(data.base,data.range);
1544 			}
1545 			if (th.toys[id].time) {
1546 				th.toys[id].time--;
1547 				return toys._toybusy(th,id);
1548 			} else {
1549 				th.toys[id].time=help.random(data.base,data.range);
1550 				return toys._toydone(th,id);
1551 			}	
1552 		},
1553 		
1554 		/**
1555 		* 
1556 		*/
1557 		real:function(th,id,data) {
1558 			if (toys._maketoy(th,id)) {
1559 				th.toys[id].subtimer=gbox.getFps();
1560 				th.toys[id].done=false;
1561 				if (data.countdown)
1562 					th.toys[id].time=data.countdown;
1563 				else
1564 					th.toys[id].time=0;
1565 			}
1566 			th.toys[id].subtimer--;
1567 			if (!th.toys[id].subtimer) {
1568 				th.toys[id].subtimer=gbox.getFps();
1569 				if (data.countdown) {
1570 					if (th.toys[id].time) {
1571 						th.toys[id].time--; 
1572 						if (data.audiocritical&&(th.toys[id].time<=data.critical))
1573 							gbox.hitAudio(data.audiocritical);
1574 					} else th.toys[id].done=true;
1575 				} else
1576 					th.toys[id].time++;
1577 			}
1578 			return toys._toyfrombool(th,id,th.toys[id].done);
1579 
1580 		},
1581 		
1582 		/**
1583 		* 
1584 		*/
1585 		every:function(th,id,frames){
1586 			if (toys._maketoy(th,id)) th.toys[id].timer=0;
1587 			th.toys[id].timer++;
1588 			if (th.toys[id].timer==frames) {
1589 				th.toys[id].timer=0;
1590 				return toys._toydone(th,id);
1591 			} else return toys._toybusy(th,id)
1592 		},
1593 		
1594 		/**
1595 		* 
1596 		*/
1597 		after:function(th,id,frames) {
1598 			if (toys._maketoy(th,id)) th.toys[id].timer=0;
1599 			if (th.toys[id].timer==frames) return toys._toydone(th,id); else {
1600 				th.toys[id].timer++;
1601 				return toys._toybusy(th,id);
1602 			}
1603 		}
1604 	},
1605 	
1606 	/**
1607 	* 
1608 	*/
1609 	// Logical helpers
1610 	logic: {
1611 		
1612 		/**
1613 		* 
1614 		*/
1615 		once:function(th,id,cond){
1616 			if (toys._maketoy(th,id)) th.toys[id].done=false;
1617 			if (th.toys[id].done) return false; else if (cond) th.toys[id].done=true;
1618 			return cond;
1619 		}
1620 	},
1621 	
1622 	/**
1623 	* 
1624 	*/
1625 	// UI
1626 	ui:{
1627 		
1628 		/**
1629 		* 
1630 		*/
1631 		menu:function(th,id,opt) {
1632 			if (toys._maketoy(th,id)||opt.resetmenu) {
1633 				var fd=gbox.getFont(opt.font);
1634 				th.toys[id].selected=(opt.selected?opt.selected:0);
1635 				th.toys[id].ok=0;
1636 				var w=0;
1637 				for (var i=0;i<opt.items.length;i++)
1638 					if (opt.items[i].length>w) w=opt.items[i].length;
1639 				gbox.createCanvas("menu-"+id,{w:w*fd.tilew,h:opt.items.length*fd.tileh});
1640 				for (var i=0;i<opt.items.length;i++)
1641 					gbox.blitText(gbox.getCanvasContext("menu-"+id),{font:opt.font,text:opt.items[i],dx:0,dy:fd.tileh*i});
1642 				th.toys[id].fh=fd.tileh;
1643 				th.toys[id].fw=fd.tilew;
1644 			}
1645 			if (!th.toys[id].ok) {
1646 				if (gbox.keyIsHit(opt.keys.up)&&(th.toys[id].selected>0)) {
1647 					if (opt.audiooption) gbox.hitAudio(opt.audiooption);
1648 					th.toys[id].selected--;
1649 				} else
1650 				if (gbox.keyIsHit(opt.keys.down)&&(th.toys[id].selected<opt.items.length-1)) {
1651 					if (opt.audiooption) gbox.hitAudio(opt.audiooption);
1652 					th.toys[id].selected++;
1653 				} else
1654 				if (gbox.keyIsHit(opt.keys.ok)) {
1655 					if (opt.audioconfirm) gbox.hitAudio(opt.audioconfirm);
1656 					th.toys[id].ok=1;
1657 				} else
1658 				if (gbox.keyIsHit(opt.keys.cancel)) th.toys[id].ok=-1;
1659 			}
1660 			gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas("menu-"+id),{dx:opt.x+th.toys[id].fw,dy:opt.y,camera:opt.camera});
1661 			if (!(th.toys[id].ok%2)) gbox.blitText(gbox.getBufferContext(),{font:opt.font,text:opt.selector,dx:opt.x,dy:opt.y+th.toys[id].selected*th.toys[id].fh,camera:opt.camera});
1662 			if (th.toys[id].ok) {
1663 				if (th.toys[id].ok>0)
1664 					if (th.toys[id].ok<10) {
1665 						th.toys[id].ok++;
1666 						toys._toybusy(th,id);
1667 					} else return toys._toydone(th,id); // selected > 0
1668 				else return toys._toydone(th,id); // selected == -1
1669 			} else return toys._toybusy(th,id);
1670 		},
1671 		
1672 		/**
1673 		* 
1674 		*/
1675 		// Returns a full customizable object for optimized huds
1676 		hud:function(id) {
1677 			gbox.createCanvas(id);
1678 			return {
1679 				w:{},
1680 				surfaceid:id,
1681 				
1682 				/**
1683 				* 
1684 				*/
1685 				updateWidget:function(i){
1686 					if (!this.w[i].__hidden) {
1687 						if (this.w[i].widget=="label") {
1688 							if (this.w[i].prepad!=null) this.w[i].text=help.prepad(this.w[i].value,this.w[i].prepad,this.w[i].padwith); else
1689 							if (this.w[i].postpad!=null) this.w[i].text=help.postpad(this.w[i].value,this.w[i].postpad,this.w[i].padwith); else
1690 							this.w[i].text=this.w[i].value+"";
1691 							gbox.blitText(gbox.getCanvasContext(this.surfaceid),this.w[i]);
1692 						}
1693 						if (this.w[i].widget=="symbols") {
1694 							var ts=gbox.getTiles(this.w[i].tileset);
1695 							gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx,y:this.w[i].dy,w:((this.w[i].maxshown-1)*this.w[i].gapx)+ts.tilew,h:((this.w[i].maxshown-1)*this.w[i].gapy)+ts.tileh});
1696 							var cnt=this.w[i].value;
1697 							for (var x=0;x<this.w[i].maxshown;x++) {
1698 								if (cnt>0) {
1699 									gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:this.w[i].tiles[(cnt>this.w[i].tiles.length?this.w[i].tiles.length-1:cnt-1)],dx:this.w[i].dx+(x*this.w[i].gapx),dy:this.w[i].dy+(x*this.w[i].gapy),fliph:this.w[i].fliph,flipv:this.w[i].flipv});
1700 								} else
1701 									if (this.w[i].emptytile!=null)
1702 										gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:this.w[i].emptytile,dx:this.w[i].dx+(x*this.w[i].gapx),dy:this.w[i].dy+(x*this.w[i].gapy),fliph:this.w[i].fliph,flipv:this.w[i].flipv});	
1703 								cnt-=this.w[i].tiles.length;
1704 							}
1705 
1706 						}
1707 						if (this.w[i].widget=="stack") {
1708 							var ts=gbox.getTiles(this.w[i].tileset);
1709 							var bw=((this.w[i].maxshown-1)*this.w[i].gapx)+ts.tilew;
1710 							gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx-(this.w[i].rightalign?bw:0),y:this.w[i].dy,w:bw,h:((this.w[i].maxshown-1)*this.w[i].gapy)+ts.tileh});
1711 							for (var x=0;x<this.w[i].maxshown;x++)
1712 								if (x<this.w[i].value.length)
1713 									gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:this.w[i].value[x],dx:(this.w[i].rightalign?this.w[i].dx-ts.tileh-(this.w[i].gapx*x):this.w[i].dx+(x*this.w[i].gapx)),dy:this.w[i].dy+(x*this.w[i].gapy),fliph:this.w[i].fliph,flipv:this.w[i].flipv});
1714 						}
1715 						if (this.w[i].widget=="radio") {
1716 							var ts=gbox.getTiles(this.w[i].tileset);
1717 							gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx,y:this.w[i].dy,w:ts.tilew,h:ts.tileh});
1718 							gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:this.w[i].frames[this.w[i].value],dx:this.w[i].dx,dy:this.w[i].dy,fliph:this.w[i].fliph,flipv:this.w[i].flipv});
1719 
1720 						}
1721 						if (this.w[i].widget=="blit") {
1722 							var ts=gbox.getTiles(this.w[i].tileset);
1723 							gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx,y:this.w[i].dy,w:ts.tilew,h:ts.tileh});
1724 							gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:this.w[i].value,dx:this.w[i].dx,dy:this.w[i].dy,fliph:this.w[i].fliph,flipv:this.w[i].flipv});
1725 
1726 						}
1727 						if (this.w[i].widget=="bool") {
1728 							var ts=gbox.getTiles(this.w[i].tileset);
1729 							gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx,y:this.w[i].dy,w:ts.tilew,h:ts.tileh});
1730 							if (this.w[i].value)
1731 								gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:this.w[i].frame,dx:this.w[i].dx,dy:this.w[i].dy,fliph:this.w[i].fliph,flipv:this.w[i].flipv});
1732 						}
1733 						if (this.w[i].widget=="gauge") {
1734 							var ts=gbox.getTiles(this.w[i].tileset);
1735 							gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:0,dx:this.w[i].dx,dy:this.w[i].dy});						
1736 							gbox.blitTile(gbox.getCanvasContext(this.surfaceid),{tileset:this.w[i].tileset,tile:1,dx:this.w[i].dx,dy:this.w[i].dy,w:(this.w[i].value*ts.tilew)/this.w[i].maxvalue});
1737 						}
1738 					}
1739 				},
1740 				
1741 				/**
1742 				* 
1743 				*/
1744 				hideWidgets:function(l) {
1745 					for (var i=0;i<l.length;i++) this.w[l[i]].__hidden=true;
1746 					this.redraw();
1747 				},
1748 				
1749 				/**
1750 				* 
1751 				*/
1752 				showWidgets:function(l) {
1753 					for (var i=0;i<l.length;i++) this.w[l[i]].__hidden=false;
1754 					this.redraw();
1755 				},
1756 				
1757 				/**
1758 				* 
1759 				*/
1760 				getValue:function(w,k) {
1761 					return this.w[w][k];
1762 				},
1763 				
1764 				/**
1765 				* 
1766 				*/
1767 				getNumberValue:function(w,k) {
1768 					return this.w[w][k]*1;
1769 				},
1770 
1771 				/**
1772 				* 
1773 				*/
1774 				setValue:function(w,k,v) {
1775 					if (this.w[w][k]!=v) {
1776 						if (k=="value") {
1777 							if ((this.w[w].maxvalue!=null)&&(v>this.w[w].maxvalue)) v=this.w[w].maxvalue;
1778 							if ((this.w[w].minvalue!=null)&&(v<this.w[w].minvalue)) v=this.w[w].minvalue;	
1779 							if (this.w[w].widget=="radio") v=(v%this.w[w].frames.length);
1780 						}
1781 						this.w[w][k]=v;							
1782 						this.updateWidget(w);
1783 					}
1784 				},
1785 				
1786 				/**
1787 				* 
1788 				*/
1789 				pushValue:function(w,k,v) {
1790 					this.w[w][k].push(v);
1791 					this.updateWidget(w);
1792 				},
1793 				
1794 				/**
1795 				* 
1796 				*/
1797 				addValue:function(w,k,v) {
1798 					if (v) this.setValue(w,k,this.w[w][k]+v);
1799 				},
1800 				
1801 				/**
1802 				* 
1803 				*/
1804 				setWidget:function(id,w) {
1805 					this.w[id]=w;
1806 					this.updateWidget(id);
1807 				},
1808 				
1809 				/**
1810 				* 
1811 				*/
1812 				redraw:function() {
1813 					gbox.blitClear(gbox.getCanvasContext(this.surfaceid));
1814 					for (var i in this.w) this.updateWidget(i);
1815 				},
1816 				
1817 				/**
1818 				* 
1819 				*/
1820 				blit:function() {
1821 					gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas(this.surfaceid),{dx:0,dy:0});
1822 				}
1823 			
1824 			}
1825 		}
1826 	},
1827 	
1828 	/**
1829 	* 
1830 	*/
1831 	fullscreen:{
1832 	
1833 		/**
1834 		* 
1835 		*/
1836 		fadeout:function(th,id,tox,data) {
1837 			if (toys._maketoy(th,id)||data.resetfade) {
1838 				th.toys[id].fade=0;
1839 				if (data.audiofade) th.toys[id].stv=gbox.getAudioVolume(data.audiofade);
1840 				if (data.audiochannelfade) th.toys[id].chv=gbox.getChannelVolume(data.audiochannelfade);
1841 			}
1842 			th.toys[id].fade+=data.fadespeed;
1843 			if (th.toys[id].fade>1) th.toys[id].fade=1;
1844 			data.alpha=th.toys[id].fade;
1845 			gbox.blitFade(tox,data);
1846 			if (data.audiofade) gbox.setAudioVolume(data.audiofade,th.toys[id].stv*(1-data.alpha));
1847 			if (data.audiochannelfade) 
1848 				if (data.alpha==1)
1849 					gbox.stopChannel(data.audiochannelfade);
1850 				else
1851 					gbox.setChannelVolume(data.audiochannelfade,th.toys[id].chv*(1-data.alpha));
1852 			return toys._toyfrombool(th,id,th.toys[id].fade==1)
1853 		},
1854 		
1855 		/**
1856 		* 
1857 		*/
1858 		fadein:function(th,id,tox,data) {
1859 			if (toys._maketoy(th,id)||data.resetfade) {
1860 				th.toys[id].fade=1;
1861 				if (data.audiofade) th.toys[id].stv=gbox.getAudioVolume(data.audiofade);
1862 				if (data.audiochannelfade) th.toys[id].chv=gbox.getChannelDefaultVolume(data.audiochannelfade);
1863 			}
1864 			th.toys[id].fade-=data.fadespeed;
1865 			if (th.toys[id].fade<0) th.toys[id].fade=0;
1866 			if (th.toys[id].fade) {
1867 				data.alpha=th.toys[id].fade;
1868 				if (data.audiofade) gbox.setAudioVolume(data.audiofade,th.toys[id].stv*(1-data.alpha));
1869 				if (data.audiochannelfade) gbox.setChannelVolume(data.audiochannelfade,th.toys[id].chv*(1-data.alpha));
1870 				gbox.blitFade(tox,data);
1871 			}
1872 			return toys._toyfrombool(th,id,th.toys[id].fade==0);
1873 		}
1874 	},
1875 	
1876 	/**
1877 	* 
1878 	*/
1879 	text:{
1880 		
1881 		/**
1882 		* 
1883 		*/
1884 		blink:function(th,id,tox,data) {
1885 			if (toys._maketoy(th,id)) {
1886 				th.toys[id].texttimer=0;
1887 				th.toys[id].visible=false;
1888 				th.toys[id].times=0;
1889 			}
1890 			if (th.toys[id].texttimer>=data.blinkspeed) {
1891 				th.toys[id].texttimer=0;
1892 				th.toys[id].visible=!th.toys[id].visible;		
1893 				if (data.times) th.toys[id].times++;
1894 			} else th.toys[id].texttimer++;
1895 			if (th.toys[id].visible)
1896 				gbox.blitText(tox,data);
1897 			return toys._toyfrombool(th,id,(data.times?data.times<th.toys[id].times:false));
1898 		},
1899 		
1900 		/**
1901 		* 
1902 		*/
1903 		fixed:function(th,id,tox,data) {
1904 			if (toys._maketoy(th,id))
1905 				th.toys[id].texttimer=0;
1906 			else
1907 				th.toys[id].texttimer++;
1908 			gbox.blitText(tox,data);
1909 			return toys._toyfrombool(th,id,data.time<th.toys[id].texttimer);
1910 		}
1911 	},
1912 	
1913 	/**
1914 	* 
1915 	*/
1916 	logos:{
1917 		
1918 		/**
1919 		* 
1920 		*/
1921 		linear:function(th,id,data) {
1922 			if (toys._maketoy(th,id)) {
1923 				th.toys[id].x=data.sx;
1924 				th.toys[id].y=data.sy;
1925 				th.toys[id].every=data.every;
1926 				th.toys[id].played=false;
1927 			}
1928 			if (!th.toys[id].every) {
1929 				if ((data.x!=th.toys[id].x)||(data.y!=th.toys[id].y))
1930 					if ((Math.abs(data.x-th.toys[id].x)<=data.speed)&&(Math.abs(data.y-th.toys[id].y)<=data.speed)) {
1931 						th.toys[id].x=data.x;
1932 						th.toys[id].y=data.y;
1933 					} else
1934 						trigo.translate(th.toys[id],trigo.getAngle(th.toys[id],data),data.speed);
1935 				else
1936 					if (!th.toys[id].played) {
1937 						if (data.audioreach) gbox.hitAudio(data.audioreach);
1938 						th.toys[id].played=true;
1939 					}
1940 				th.toys[id].every=data.every;
1941 			} else th.toys[id].every--;
1942 			if (data.text)
1943 				gbox.blitText(gbox.getBufferContext(),{
1944 					font:data.font,
1945 					dx:th.toys[id].x,dy:th.toys[id].y,
1946 					text:data.text
1947 				});
1948 			else if (data.tileset)
1949 				gbox.blitTile(gbox.getBufferContext(),{tileset:data.tileset,tile:data.tile,dx:th.toys[id].x,dy:th.toys[id].y,camera:data.camera,fliph:data.fliph,flipv:data.flipv,alpha:data.alpha});			
1950 			else
1951 				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:th.toys[id].x,dy:th.toys[id].y,alpha:data.alpha});
1952 			return toys._toyfrombool(th,id,(data.x==th.toys[id].x)&&(data.y==th.toys[id].y));
1953 		},
1954 		
1955 		/**
1956 		* 
1957 		*/
1958 		crossed:function(th,id,data) {
1959 			if (toys._maketoy(th,id)) {
1960 				th.toys[id].gapx=data.gapx;
1961 				th.toys[id].lw=gbox.getImage(data.image).height;
1962 				th.toys[id].done=false;
1963 			}
1964 			if (th.toys[id].gapx) {
1965 				th.toys[id].gapx-=data.speed;
1966 				if (th.toys[id].gapx<0) th.toys[id].gapx=0;
1967 				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x-th.toys[id].gapx,dy:data.y,alpha:data.alpha});
1968 				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x+th.toys[id].gapx,dy:data.y,alpha:data.alpha});
1969 				return toys._toybusy(th,id);
1970 			} else {
1971 				if (!th.toys[id].done) {
1972 					th.toys[id].done=true;
1973 					if (data.audioreach) gbox.hitAudio(data.audioreach);					
1974 				}
1975 				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:data.y});
1976 				return toys._toydone(th,id);
1977 			}
1978 		},
1979 		
1980 		/**
1981 		* 
1982 		*/
1983 		zoomout:function(th,id,data) {
1984 			if (toys._maketoy(th,id)) {
1985 				th.toys[id].zoom=data.zoom;
1986 				th.toys[id].done=false;
1987 				th.toys[id].img=gbox.getImage(data.image);
1988 			}
1989 			if (th.toys[id].zoom!=1) {
1990 				th.toys[id].zoom-=data.speed;
1991 				if (th.toys[id].zoom<=1) {
1992 					th.toys[id].zoom=1;
1993 					if (data.audioreach) gbox.hitAudio(data.audioreach);
1994 				}
1995 				gbox.blit(gbox.getBufferContext(),gbox.getImage(data.image),{h:th.toys[id].img.height,w:th.toys[id].img.width,dx:data.x-Math.floor(th.toys[id].img.width*(th.toys[id].zoom-1)/2),dy:data.y-Math.floor(th.toys[id].img.height*(th.toys[id].zoom-1)/2),dh:Math.floor(th.toys[id].img.height*th.toys[id].zoom),dw:Math.floor(th.toys[id].img.width*th.toys[id].zoom),alpha:1/th.toys[id].zoom});
1996 				return toys._toybusy(th,id);
1997 			} else {
1998 				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:data.y});
1999 				return toys._toydone(th,id);
2000 			}
2001 		},
2002 		
2003 		/**
2004 		* 
2005 		*/
2006 		rising:function(th,id,data) {
2007 			if (toys._maketoy(th,id)) {
2008 				th.toys[id].cnt=0;
2009 				th.toys[id].lh=gbox.getImage(data.image).height;
2010 				th.toys[id].lw=gbox.getImage(data.image).width;
2011 				th.toys[id].done=false;
2012 			}
2013 			if (th.toys[id].cnt<th.toys[id].lh) {
2014 				th.toys[id].cnt+=data.speed;
2015 				if (th.toys[id].cnt>th.toys[id].lh) th.toys[id].gapx=th.toys[id].lh;
2016 				gbox.blit(gbox.getBufferContext(),gbox.getImage(data.image),{dh:th.toys[id].cnt,dw:th.toys[id].lw,dx:data.x,dy:data.y+th.toys[id].lh-th.toys[id].cnt,alpha:data.alpha});
2017 				if (data.reflex) gbox.blit(gbox.getBufferContext(),gbox.getImage(data.image),{dh:th.toys[id].cnt,dw:th.toys[id].lw,dx:data.x,dy:data.y+th.toys[id].lh,alpha:data.reflex,flipv:true});
2018 				if (th.toys[id].cnt>=th.toys[id].lh)
2019 					if (data.audioreach) gbox.hitAudio(data.audioreach);					
2020 				return toys._toybusy(th,id);
2021 			} else {
2022 				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:data.y});
2023 				if (data.reflex) gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:data.y+th.toys[id].lh,alpha:data.reflex,flipv:true});
2024 				
2025 				return toys._toydone(th,id);
2026 			}
2027 		},
2028 		
2029 		/**
2030 		* 
2031 		*/
2032 		bounce:function(th,id,data) {
2033 			if (toys._maketoy(th,id)) {
2034 				th.toys[id].accy=data.accy;
2035 				th.toys[id].y=data.y;
2036 				th.toys[id].h=gbox.getImage(data.image).height;
2037 				th.toys[id].done=false;
2038 			}
2039 			if (!th.toys[id].done) {
2040 				if (th.toys[id].y+th.toys[id].h>=data.floory) {
2041 					if (data.audiobounce) gbox.hitAudio(data.audiobounce);
2042 					th.toys[id].y=data.floory-th.toys[id].h;
2043 					th.toys[id].accy=-Math.ceil(th.toys[id].accy/(data.heavy?data.heavy:2));
2044 					th.toys[id].done=(th.toys[id].accy==0);
2045 				} else th.toys[id].accy--;
2046 				th.toys[id].y-=th.toys[id].accy;
2047 			}
2048 			gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:th.toys[id].y});
2049 					
2050 			return toys._toyfrombool(th,id,th.toys[id].done);
2051 		}
2052 	},
2053 	
2054 	/**
2055 	* 
2056 	*/
2057 	dialogue: {
2058 		
2059 		/**
2060 		* 
2061 		*/
2062 		render:function(th,id,data){
2063 			if (toys._maketoy(th,id)) {
2064 				th.toys[id].newscene=true;
2065 				th.toys[id].sceneid=-1;
2066 				th.toys[id].ended=false;
2067 				th.toys[id].timer=0;
2068 				th.toys[id].counter=0;
2069 				th.toys[id].anim=0;
2070 				gbox.createCanvas("dialogue-"+id);
2071 			}
2072 			if (!data.hideonend||(data.hideonend&&!th.toys[id].ended)) {
2073 				if (th.toys[id].newscene&&!th.toys[id].ended) {
2074 					th.toys[id].anim=0;
2075 					th.toys[id].timer=0;
2076 					th.toys[id].newscene=false;
2077 					th.toys[id].sceneid++;
2078 					th.toys[id].ended=(th.toys[id].sceneid==data.scenes.length);
2079 					if (!th.toys[id].ended) {
2080 						th.toys[id].letter=0;
2081 						th.toys[id].wait=false;
2082 						th.toys[id].scene=data.scenes[th.toys[id].sceneid];
2083 						th.toys[id].fd=gbox.getFont((th.toys[id].scene.font?th.toys[id].scene.font:data.font));
2084 						th.toys[id].sceneH=(th.toys[id].scene.dh?th.toys[id].scene.dh:gbox.getScreenH());
2085 						th.toys[id].sceneW=(th.toys[id].scene.dw?th.toys[id].scene.dw:gbox.getScreenW());
2086 						th.toys[id].sceneX=(th.toys[id].scene.dx?th.toys[id].scene.dx:0);
2087 						th.toys[id].sceneY=(th.toys[id].scene.dy?th.toys[id].scene.dy:0);
2088 						gbox.blitClear(gbox.getCanvasContext("dialogue-"+id));
2089 						if (th.toys[id].scene.slide) {							
2090 							gbox.blitAll(gbox.getCanvasContext("dialogue-"+id),gbox.getImage(th.toys[id].scene.slide.image),{dx:th.toys[id].scene.slide.x,dy:th.toys[id].scene.slide.y});
2091 						}
2092 						if (th.toys[id].scene.scroller) {
2093 							gbox.createCanvas("scroller-"+id,{w:th.toys[id].sceneW,h:(th.toys[id].scene.scroller.length)*(th.toys[id].fd.tileh+th.toys[id].scene.spacing)});
2094 							for (var i=0;i<th.toys[id].scene.scroller.length;i++)
2095 								gbox.blitText(gbox.getCanvasContext("scroller-"+id),{
2096 										font:th.toys[id].fd.id,
2097 										dx:0,
2098 										dy:i*(th.toys[id].fd.tileh+th.toys[id].scene.spacing),
2099 										dw:th.toys[id].sceneW,
2100 										halign:gbox.ALIGN_CENTER,
2101 										text:th.toys[id].scene.scroller[i]
2102 									});
2103 						}
2104 						if (th.toys[id].scene.bonus) {
2105 							gbox.createCanvas("bonus-"+id,{w:th.toys[id].sceneW,h:(th.toys[id].scene.bonus.length)*(th.toys[id].fd.tileh+th.toys[id].scene.spacing)});
2106 						}
2107 						if (th.toys[id].scene.audiomusic) gbox.hitAudio(th.toys[id].scene.audiomusic);
2108 					}
2109 				}
2110 				if (!th.toys[id].ended) {
2111 					if (th.toys[id].wait) {
2112 						if (gbox.keyIsHit(data.esckey)) th.toys[id].ended=true; else
2113 						if (gbox.keyIsHit(data.skipkey)) th.toys[id].newscene=true;
2114 					} else {
2115 									
2116 						// SKIP KEYS
2117 						
2118 						if (gbox.keyIsHit(data.esckey)) th.toys[id].ended=true; else
2119 						if (gbox.keyIsHold(data.skipkey)) th.toys[id].counter=th.toys[id].scene.speed;
2120 						else th.toys[id].counter++;
2121 						
2122 						// MOVING
2123 						
2124 						if (th.toys[id].scene.talk) { // DIALOGUES
2125 						
2126 						
2127 							if (th.toys[id].counter==th.toys[id].scene.speed) {
2128 								th.toys[id].letter++;
2129 								th.toys[id].counter=0;
2130 								if (th.toys[id].scene.audio&&!(th.toys[id].letter%3)) gbox.hitAudio(th.toys[id].scene.audio);
2131 								var tmp=th.toys[id].letter;
2132 								var row=0;
2133 								while (tmp>th.toys[id].scene.talk[row].length) {
2134 									tmp-=th.toys[id].scene.talk[row].length;
2135 									row++;
2136 									if (row==th.toys[id].scene.talk.length)  {
2137 										row=-1;
2138 										break;
2139 									}
2140 								}
2141 								if (row>=0) {
2142 									gbox.blitText(gbox.getCanvasContext("dialogue-"+id),{
2143 										font:data.font,
2144 										dx:data.who[th.toys[id].scene.who].x,
2145 										dy:(data.who[th.toys[id].scene.who].y)+(row*th.toys[id].fd.tileh),
2146 										text:th.toys[id].scene.talk[row].substr(0,tmp)
2147 									});
2148 								} else
2149 									th.toys[id].wait=true;
2150 							}
2151 						
2152 						} else if (th.toys[id].scene.scroller) { // SCROLLER (i.e. credits)
2153 						
2154 							if (th.toys[id].counter==th.toys[id].scene.speed) {
2155 								th.toys[id].letter++;
2156 								th.toys[id].counter=0;
2157 								if (th.toys[id].letter==(gbox.getCanvas("scroller-"+id).height+th.toys[id].scene.push))
2158 									th.toys[id].wait=true;
2159 							}
2160 							
2161 						} else if (th.toys[id].scene.bonus) { // BONUS (classic bonus award screen)
2162 							for (var row=0;row<=th.toys[id].letter;row++) {
2163 								if (th.toys[id].scene.bonus[row].text)
2164 									gbox.blitText(gbox.getCanvasContext("bonus-"+id),{
2165 										font:data.font,
2166 										dx:0,
2167 										dy:(row*(th.toys[id].fd.tileh+th.toys[id].scene.spacing)),
2168 										text:th.toys[id].scene.bonus[row].text
2169 									});
2170 								else if (th.toys[id].scene.bonus[row].mul) {
2171 									// Mask is %VAL%e%MUL%=%TOT%
2172 									th.toys[id].scene.bonus[row].tmptext=th.toys[id].scene.bonus[row].mask.replace(/%VAL%/,th.toys[id].timer).replace(/%MUL%/,th.toys[id].scene.bonus[row].mul).replace(/%TOT%/,(th.toys[id].timer*th.toys[id].scene.bonus[row].mul));
2173 									gbox.blitText(gbox.getCanvasContext("bonus-"+id),{
2174 										clear:true,
2175 										font:data.font,
2176 										dx:0,
2177 										dy:(row*(th.toys[id].fd.tileh+th.toys[id].scene.spacing)),
2178 										text:th.toys[id].scene.bonus[row].tmptext
2179 									});
2180 								}
2181 							}
2182 							
2183 							if (!th.toys[id].wait) {
2184 								var next=false;
2185 								if (th.toys[id].scene.bonus[th.toys[id].letter].mul&&!th.toys[id].scene.bonus[th.toys[id].letter].text) {
2186 									if (th.toys[id].counter>=th.toys[id].scene.bonus[th.toys[id].letter].speed) {
2187 										th.toys[id].counter=0;
2188 										th.toys[id].timer++;
2189 										if (th.toys[id].timer>th.toys[id].scene.bonus[th.toys[id].letter].mulvalue) {
2190 											th.toys[id].scene.bonus[th.toys[id].letter].text=th.toys[id].scene.bonus[th.toys[id].letter].tmptext;
2191 											next=true;
2192 										} else {
2193 											if (th.toys[id].scene.bonus[th.toys[id].letter].callback)
2194 												th.toys[id].scene.bonus[th.toys[id].letter].callback(th.toys[id].scene.bonus[th.toys[id].letter],th.toys[id].scene.bonus[th.toys[id].letter].arg);
2195 										}
2196 									}
2197 									
2198 								} else if (th.toys[id].counter>=th.toys[id].scene.speed) next=true;
2199 								if (next) {
2200 									if (th.toys[id].letter==th.toys[id].scene.bonus.length-1)
2201 										th.toys[id].wait=true;
2202 									else {
2203 										th.toys[id].letter++;
2204 										if (th.toys[id].scene.bonus[th.toys[id].letter].mul) {
2205 											th.toys[id].scene.bonus[th.toys[id].letter].text=null;
2206 											th.toys[id].scene.bonus[th.toys[id].letter].tmptext=null;
2207 											th.toys[id].timer=0;
2208 										}
2209 										th.toys[id].counter=0;
2210 									}
2211 								}
2212 							}
2213 						}
2214 							
2215 					}
2216 				
2217 				}
2218 				
2219 				// RENDERING
2220 				
2221 				
2222 				if (th.toys[id].scene.talk) { // DIALOGUES
2223 					if (data.who[th.toys[id].scene.who].box)
2224 							gbox.blitRect(gbox.getBufferContext(),data.who[th.toys[id].scene.who].box);
2225 					if (data.who[th.toys[id].scene.who].tileset) {
2226 						th.toys[id].anim=(th.toys[id].anim+1)%20;
2227 						gbox.blitTile(gbox.getBufferContext(),{tileset:data.who[th.toys[id].scene.who].tileset,tile:help.decideFrame(th.toys[id].anim,data.who[th.toys[id].scene.who].frames),dx:data.who[th.toys[id].scene.who].portraitx,dy:data.who[th.toys[id].scene.who].portraity,camera:false,fliph:data.who[th.toys[id].scene.who].fliph,flipv:data.who[th.toys[id].scene.who].flipv});
2228 					}
2229 					gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas("dialogue-"+id),{dx:0,dy:0});
2230 				} else if (th.toys[id].scene.scroller) // SCROLLER (i.e. credits)
2231 					gbox.blit(gbox.getBufferContext(),gbox.getCanvas("scroller-"+id),{dx:th.toys[id].sceneX,dy:th.toys[id].sceneY+(th.toys[id].letter<th.toys[id].sceneH?th.toys[id].sceneH-th.toys[id].letter:0),dw:th.toys[id].sceneW,y:(th.toys[id].letter<th.toys[id].sceneH?0:th.toys[id].letter-th.toys[id].sceneH),dh:(th.toys[id].letter<th.toys[id].sceneH?th.toys[id].letter:th.toys[id].sceneH)});
2232 				else if (th.toys[id].scene.bonus) // BONUS (i.e. credits)
2233 					gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas("bonus-"+id),{dx:th.toys[id].sceneX,dy:th.toys[id].sceneY});
2234 			}		
2235 			return toys._toyfrombool(th,id,th.toys[id].ended);
2236 		}
2237 	},
2238 	
2239 	// GENERATORS
2240 	
2241 		
2242 	/**
2243 	* 
2244 	*/
2245 	generate: {
2246 		
2247 		/**
2248 		* 
2249 		*/
2250 		sparks:{
2251 			simple:function(th,group,id,data) {
2252 				var ts=gbox.getTiles(data.tileset);
2253 				if (data.frames==null) {
2254 					data.frames={ speed:(data.animspeed==null?1:data.animspeed), frames:[]};
2255 					for (var i=0;i<ts.tilerow;i++) data.frames.frames[i]=i;
2256 				}
2257 
2258 				var obj=gbox.addObject(
2259 					help.mergeWithModel(
2260 						data,{
2261 							id:id,
2262 							group:group,
2263 							x:th.x+th.hw-ts.tilehw+(data.gapx==null?0:data.gapx),
2264 							y:(data.valign=="top"?th.y:th.y+th.hh-ts.tilehh+(data.gapy==null?0:data.gapy)),
2265 							tileset:data.tileset,
2266 							alpha:null,
2267 							accx:0, accy:0,
2268 							frame:0,
2269 							timer:(data.delay?-data.delay:-1),
2270 							frames:data.frames,
2271 							toptimer:((data.frames.frames.length)*data.frames.speed)-1,
2272 							camera:th.camera,
2273 							gravity:false,
2274 							trashoffscreen:true,
2275 							fliph:(data.fliph==null?th.fliph:data.fliph), flipv:(data.flipv==null?th.flipv:data.flipv),
2276 							blinkspeed:0
2277 						}
2278 					)
2279 				);
2280 
2281 				obj[(data.logicon==null?"first":data.logicon)]=function() {
2282 					this.timer++;
2283 					if (this.timer>=0) {
2284 						this.x+=this.accx;
2285 						this.y+=this.accy;
2286 						if (this.gravity) this.accy++;
2287 						if ((this.timer==this.toptimer)||(this.trashoffscreen&&(!gbox.objectIsVisible(this)))) gbox.trashObject(this);
2288 					}
2289 				}
2290 				
2291 				obj[(data.bliton==null?"blit":data.bliton)]=function() {
2292 					if ((this.timer>=0)&&(!this.blinkspeed||(Math.floor(this.timer/this.blinkspeed)%2)))
2293 						gbox.blitTile(gbox.getBufferContext(),{tileset:this.tileset,tile:help.decideFrame(this.timer,this.frames),dx:this.x,dy:this.y,camera:this.camera,fliph:this.fliph,flipv:this.flipv,alpha:this.alpha});					
2294 				}
2295 				
2296 				return obj;
2297 			},
2298 		
2299 			/**
2300 			* 
2301 			*/
2302 			popupText:function(th,group,id,data) {
2303 				data.text+="";
2304 				var fd=gbox.getFont(data.font);
2305 				
2306 				var obj=gbox.addObject(
2307 					help.mergeWithModel(
2308 						data,{
2309 							id:id,
2310 							group:group,
2311 							x:Math.floor(th.x+th.hw-(fd.tilehw*data.text.length)),
2312 							y:th.y-fd.tilehh,
2313 							vaccy:-data.jump,
2314 							font:"small",
2315 							keep:0,
2316 							text:data.text,
2317 							cnt:0,
2318 							camera:th.camera
2319 						}
2320 					)
2321 				);
2322 				
2323 				obj[(data.logicon==null?"first":data.logicon)]=function() {
2324 					if (gbox.objectIsVisible(this)) {
2325 						if (this.vaccy)
2326 							this.vaccy++;
2327 						else
2328 							this.cnt++;
2329 						this.y+=this.vaccy;
2330 						if (this.cnt>=this.keep) gbox.trashObject(this);
2331 					} else gbox.trashObject(this);
2332 				}
2333 				
2334 				obj[(data.bliton==null?"blit":data.bliton)]=function() {
2335 					gbox.blitText(gbox.getBufferContext(),{font:this.font,text:this.text,dx:this.x,dy:this.y,camera:this.camera});
2336 				}
2337 				
2338 				return obj;
2339 			},
2340 		
2341 			/**
2342 			* 
2343 			*/
2344 			bounceDie:function(th,group,id,data){
2345 				var obj=gbox.addObject(
2346 					help.mergeWithModel(
2347 						data,{
2348 							id:id,
2349 							group:group,
2350 							tileset:th.tileset,
2351 							frame:th.frame,
2352 							side:th.side,
2353 							frames:th.frames.die,
2354 							x:th.x,
2355 							y:th.y,
2356 							vaccy:-data.jump,
2357 							accx:0,
2358 							flipv:data.flipv,
2359 							cnt:0,
2360 							blinkspeed:0,
2361 							camera:th.camera
2362 						}
2363 					)
2364 				);
2365 				
2366 				obj[(data.logicon==null?"first":data.logicon)]=function() {
2367 					if (gbox.objectIsVisible(this)) {
2368 						this.vaccy++;
2369 						this.y+=this.vaccy;
2370 						this.x+=this.accx;
2371 						this.cnt++;
2372 					} else gbox.trashObject(this);	
2373 				}
2374 				
2375 				obj[(data.bliton==null?"blit":data.bliton)]=function() {
2376 					if (!this.blinkspeed||(Math.floor(this.cnt/this.blinkspeed)%2))
2377 						gbox.blitTile(gbox.getBufferContext(),{tileset:this.tileset,tile:help.decideFrame(this.cnt,this.frames),dx:this.x,dy:this.y,camera:this.camera,fliph:this.side,flipv:this.flipv});					
2378 				}
2379 				
2380 				return obj;
2381 			}
2382 		},
2383 		
2384 		/**
2385 		* 
2386 		*/
2387 		audio:{
2388 		
2389 			/**
2390 			* 
2391 			*/
2392 			fadeOut:function(th,group,id,data){
2393 				var obj=gbox.addObject(
2394 					help.mergeWithModel(
2395 						data,{
2396 							id:id,
2397 							group:group,
2398 							fadespeed:-0.02*(data.fadein?-1:1),
2399 							stoponmute:true,
2400 							audio:null,
2401 							channel:null,
2402 							destination:null
2403 						}
2404 					)
2405 				);
2406 
2407 				obj[(data.logicon==null?"first":data.logicon)]=function() {
2408 					if (this.destination==null)
2409 						if (this.audio)
2410 							if (this.fadespeed>0) this.destination=1; else this.destination=0;
2411 						else
2412 							if (this.fadespeed>0) this.destination=gbox.getChannelDefaultVolume(this.channel); else this.destination=0;
2413 					if (this.fadespeed>0) gbox.playAudio(this.audio);
2414 				}
2415 				
2416 				obj[(data.bliton==null?"blit":data.bliton)]=function() {
2417 					if (this.audio) gbox.changeAudioVolume(this.audio,this.fadespeed);
2418 					if (this.channel) gbox.changeChannelVolume(this.channel,this.fadespeed);
2419 					if (
2420 						(this.audio&&(
2421 							((this.fadespeed<0)&&(gbox.getAudioVolume(this.audio)<=this.destination))||
2422 							((this.fadespeed>0)&&(gbox.getAudioVolume(this.audio)>=this.destination))
2423 					   ))||
2424 						(this.channel&&(
2425 							((this.fadespeed<0)&&(gbox.getChannelVolume(this.channel)<=this.destination))||
2426 							((this.fadespeed>0)&&(gbox.getChannelVolume(this.channel)>=this.destination))
2427 					   ))
2428 					) {
2429 						if (this.channel&&this.stoponmute&&(this.fadespeed<0)) gbox.stopChannel(this.channel);
2430 						if (this.audio&&this.stoponmute&&(this.fadespeed<0)) gbox.stopAudio(this.audio);
2431 						gbox.trashObject(this);
2432 					}
2433 				}
2434 			}
2435 		
2436 		}
2437 		
2438 	}
2439 	
2440 
2441 }
2442