// ---
// Copyright (c) 2010 Francesco Cottone, http://www.kesiev.com/
// ---

	var toys={
	
		// CONSTANTS
		NOOP:function(){},
		PUSH_NONE:0,
		PUSH_LEFT:1,
		PUSH_RIGHT:2,
		PUSH_UP:3,
		PUSH_DOWN:4,
		
		FACES:["up","right","down","left"],
		FACES_ANGLE:[trigo.ANGLE_UP,trigo.ANGLE_RIGHT,trigo.ANGLE_DOWN,trigo.ANGLE_LEFT],
		FACE_UP:0,
		FACE_RIGHT:1,
		FACE_DOWN:2,
		FACE_LEFT:3,
		
		// Top-view (Zelda-alike) specifics
		
		topview:{
			collides:function(fr,to,t) { // Special collision. Counts also the Z
				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;
			},
			pixelcollides:function(fr,to,t) { // Special collision. Counts also the Z
				return gbox.pixelcollides(fr,{x:to.x+to.colx,y:to.y+to.coly,h:to.colh,w:to.colw},t);
			},
			initialize:function(th,data) {
				help.mergeWithModel(
					th,
					help.mergeWithModel(
						data,
						{
							x:0, y:0,
							z:0,
							accx:0, accy:0, accz:0,
							frames:{},
							shadow:null,
							maxacc:4, controlmaxacc:4,
							responsive:0, // Responsiveness
							weapon:0, // Weapon
							camera:true,
							flipv:false, fliph:false,
							facing:toys.FACE_DOWN,
							flipside:true,
							haspushing:false,
							frame:0,
							colh:gbox.getTiles(th.tileset).tilehh,
							colw:gbox.getTiles(th.tileset).tilew,
							colx:0,
							staticspeed:0,
							nodiagonals:false,
							noreset:false
						}
					)
				);
				if (th.coly==null) th.coly=gbox.getTiles(th.tileset).tileh-th.colh;
				th.colhh=Math.floor(th.colh/2);
				th.colhw=Math.floor(th.colw/2);
				
				toys.topview.spawn(th);
			},
			spawn:function(th,data) {
				th.xpushing=toys.PUSH_NONE; // user is moving side
				th.vpushing=toys.PUSH_NONE; // user is moving side
				th.zpushing=toys.PUSH_NONE; // user is moving side
				th.counter=0; // self counter
				th.hittimer=0;
				th.killed=false;				
				help.copyModel(th,data);
				gbox.setZindex(th,th.y+th.h); // these object follows the z-index and uses ZINDEX_LAYER
			},
			controlKeys:function(th,keys) {
				var cancelx=false;
				var cancely=false;
				var idlex=false;
				var idley=false;
				
		  		if (gbox.keyIsPressed(keys.left)||keys.pressleft) {
		  			th.xpushing=toys.PUSH_LEFT;
		  			th.facing=toys.FACE_LEFT;
		  			if (th.accx>th.responsive) th.accx=th.responsive;
					if (th.staticspeed) th.accx=-th.staticspeed; else th.accx=help.limit(th.accx-1,-th.controlmaxacc,th.controlmaxacc);
					if (th.nodiagonals) { cancely=true; idley=true }
				} else if (gbox.keyIsPressed(keys.right)||keys.pressright) {
					th.xpushing=toys.PUSH_RIGHT;
					th.facing=toys.FACE_RIGHT;
					if (th.accx<-th.responsive) th.accx=-th.responsive;
					if (th.staticspeed) th.accx=th.staticspeed; else th.accx=help.limit(th.accx+1,-th.controlmaxacc,th.controlmaxacc);
					if (th.nodiagonals) { cancely=true; idley=true }
				} else idlex=true;
				
		  		if (!cancely&&(gbox.keyIsPressed(keys.up)||keys.pressup)) {
		  			th.ypushing=toys.PUSH_UP;
		  			th.facing=toys.FACE_UP;
		  			if (th.accy>th.responsive) th.accy=th.responsive;
					if (th.staticspeed) th.accy=-th.staticspeed; else th.accy=help.limit(th.accy-1,-th.controlmaxacc,th.controlmaxacc);
					if (th.nodiagonals) { cancelx=true; idlex=true; }
				} else if (!cancely&&(gbox.keyIsPressed(keys.down)||keys.pressdown)) {
					th.ypushing=toys.PUSH_DOWN;
		  			th.facing=toys.FACE_DOWN;
					if (th.accy<-th.responsive) th.accy=-th.responsive;
					if (th.staticspeed) th.accy=th.staticspeed; else th.accy=help.limit(th.accy+1,-th.controlmaxacc,th.controlmaxacc);
					if (th.nodiagonals) { cancelx=true; idlex=true; }
				} else idley=true;
				
				
				
				 if (idlex) {
					if (cancelx) th.accx=0;
					if (cancelx||!th.noreset) th.xpushing=toys.PUSH_NONE;
				}
				if (idley) {
					if (cancely) th.accy=0;				
					if (cancely||!th.noreset) th.ypushing=toys.PUSH_NONE;
				}
			},
			getNextX:function(th) { return th.x+help.limit(th.accx,-th.maxacc,th.maxacc); },
			getNextY:function(th) { return th.y+help.limit(th.accy,-th.maxacc,th.maxacc); },
			getNextZ:function(th) { return th.z+help.limit(th.accz,-th.maxacc,th.maxacc); },
			applyForces:function(th) {
				th.x=toys.topview.getNextX(th);
				th.y=toys.topview.getNextY(th);
			},
			applyGravity:function(th) {
				th.z=toys.topview.getNextZ(th);
			},
			handleAccellerations:function(th) {
				if (!th.xpushing) th.accx=help.goToZero(th.accx);
				if (!th.ypushing) th.accy=help.goToZero(th.accy);
				
			},
			handleGravity:function(th) {
				th.accz++;
			},
			setFrame:function(th) {
				var pref="stand";
				if (th.xpushing||th.ypushing) 
					if (th.haspushing&&(th.toucheddown||th.touchedup||th.touchedleft||th.touchedright)) pref="pushing"; else pref="moving";
				if (th.flipside)
					th.fliph=(th.facing==toys.FACE_RIGHT);
				th.frame=help.decideFrame(th.counter,th.frames[pref+toys.FACES[th.facing]]);
			},
			tileCollision:function(th,map,tilemap,defaulttile,data) {
				
				th.touchedup=false;
				th.toucheddown=false;
				th.touchedleft=false;
				th.touchedright=false;
				
				var tollerance=(data&&(data.tollerance!=null)?data.tollerance:6);
				var approximation=(data&&(data.approximation!=null)?data.approximation:10);
				var t=tollerance-approximation;
				do {
					t+=approximation;
					if (t>th.colw-tollerance-1) t=th.colw-tollerance-1;
					var bottom=help.getTileInMap(th.x+th.colx+t,th.y+th.coly+th.colh-1,map,defaulttile,tilemap);
					var top=help.getTileInMap(th.x+th.colx+t,th.y+th.coly,map,defaulttile,tilemap);
					if (map.tileIsSolid(th,top)) th.touchedup=true;
					if (map.tileIsSolid(th,bottom)) th.toucheddown=true;	
				} while (t!=th.colw-tollerance-1);
				
				t=tollerance-approximation;
				do {
					t+=approximation;
					if (t>th.colh-tollerance-1) t=th.colh-tollerance-1;
					var left=help.getTileInMap(th.x+th.colx,th.y+th.coly+t,map,defaulttile,tilemap);
					var right=help.getTileInMap(th.x+th.colx+th.colw-1,th.y+th.coly+t,map,defaulttile,tilemap);
					if (map.tileIsSolid(th,left)) th.touchedleft=true;
					if (map.tileIsSolid(th,right)) th.touchedright=true;
				} while (t!=th.colh-tollerance-1);
				
				if (th.touchedup) {
					th.accy=0;
					th.y=help.yPixelToTile(map,th.y+th.coly,1)-th.coly;				
				}
				if (th.toucheddown) {
					th.accy=0;
					th.y=help.yPixelToTile(map,th.y+th.coly+th.colh-1)-th.coly-th.colh;				
				}
				if (th.touchedleft) {
					th.accx=0;
					th.x=help.xPixelToTile(map,th.x+th.colx,1)-th.colx;				
				}
				if (th.touchedright) {
					th.accx=0;
					th.x=help.xPixelToTile(map,th.x+th.colx+th.colw-1)-th.colx-th.colw;				
				}
				
			},
			spritewallCollision:function(th,data) {
				var wl;
				for (var i in gbox._objects[data.group])
					if ((!gbox._objects[data.group][i].initialize)&&toys.topview.collides(th,gbox._objects[data.group][i])) {
						wl=gbox._objects[data.group][i];
						if (toys.topview.pixelcollides({x:th.x+th.colx,y:th.y+th.coly+th.colhh},wl)) {
							th.touchedleft=true;
							th.accx=0;
							th.x=wl.x+wl.colx+wl.colw-th.colx+1;
						} else if (toys.topview.pixelcollides({x:th.x+th.colx+th.colw,y:th.y+th.coly+th.colhh},wl)) {
							th.touchedright=true;
							th.accx=0;
							th.x=wl.x+wl.colx-th.colw-th.colx-1;
						}
						if (toys.topview.pixelcollides({x:th.x+th.colx+th.colhw,y:th.y+th.coly+th.colh},wl)) {
							th.toucheddown=true;
							th.accy=0;
							th.y=wl.y+wl.coly-th.colh-th.coly;
						} else if (toys.topview.pixelcollides({x:th.x+th.colx+th.colhw,y:th.y+th.coly},wl)) {
							th.touchedup=true;
							th.accy=0;
							th.y=wl.y+wl.coly+wl.colh-th.coly;
						}
					}
							
			},
			floorCollision:function(th,data) {
				th.touchedfloor=false;
				if (th.z>0) {
					th.accz=(data==null?0:-Math.floor(th.accz/data.bounce));
					th.z=0;
					th.touchedfloor=true;
				}			
			},
			adjustZindex:function(th) {
				gbox.setZindex(th,th.y+th.h);
			},
			// Helper: returns the ahead pixel (i.e. destination use action)
			getAheadPixel:function(th,data) {
				switch (th.facing) {
					case toys.FACE_RIGHT:{
						return {x:th.x+th.colx+th.colw+data.distance,y:th.y+th.coly+th.colhh};
						break;
					}
					case toys.FACE_LEFT:{
						return {x:th.x+th.colx-data.distance,y:th.y+th.coly+th.colhh};
						break;
					}
					case toys.FACE_UP:{
						return {x:th.x+th.colx+th.colhw,y:th.y+th.coly-data.distance};
						break;
					}
					case toys.FACE_DOWN:{
						return {x:th.x+th.colx+th.colhw,y:th.y+th.coly+th.colh+data.distance};
						break;
					}
				}
			},
			// Helper: trigger a method in colliding objects (i.e. "use action")
			callInColliding:function(th,data) {
				for (var i in gbox._objects[data.group])
					if ((!gbox._objects[data.group][i].initialize)&&toys.topview.pixelcollides(data,gbox._objects[data.group][i]))
						if (gbox._objects[data.group][i][data.call]) {
							gbox._objects[data.group][i][data.call](th);
							return i;
						}
				return false;
			},
			// Enemy methods
			wander:function(th,map,tilemap,defaulttile,data) {
				if ((!th.wandercounter)||(th.toucheddown||th.touchedup||th.touchedleft||th.touchedright)) {
					th.wandercounter=help.random(data.minstep,data.steprange);
					th.wanderdirection=help.random(0,4);
				} else th.wandercounter--;
				switch (th.wanderdirection) {
					case toys.FACE_LEFT: {
						th.xpushing=toys.PUSH_LEFT;
						th.ypushing=toys.PUSH_NONE;
		  				th.facing=toys.FACE_LEFT;
						th.accx=-data.speed;
						th.accy=0;
						break;
					}
					case toys.FACE_RIGHT: {
						th.xpushing=toys.PUSH_RIGHT;
						th.ypushing=toys.PUSH_NONE;
		  				th.facing=toys.FACE_RIGHT;
						th.accx=data.speed;
						th.accy=0;
						break;
					}
					case toys.FACE_UP: {
						th.ypushing=toys.PUSH_UP;
						th.xpushing=toys.PUSH_NONE;
		  				th.facing=toys.FACE_UP;
						th.accy=-data.speed;
						th.accx=0;
						break;
					}
					case toys.FACE_DOWN: {
						th.ypushing=toys.PUSH_DOWN;
						th.xpushing=toys.PUSH_NONE;
		  				th.facing=toys.FACE_DOWN;
						th.accy=data.speed;
						th.accx=0;
						break;
					}					
				}
			},
			// generators (firebullet specific for topdown - more complex than SHMUP one)
			fireBullet:function(gr,id,data) {
	
				var ts=gbox.getTiles(data.tileset);
				
							
				var obj=gbox.addObject(
					help.mergeWithModel(
						data,{
							_bullet:true,
							zindex:0,
							fliph:false, flipv:false,
							id:id,
							group:gr,
							cnt:0,
							tileset:"",
							frames:{},
							acc:0,
							angle:0,
							camera:data.from.camera,
							accx:(data.accx==null?Math.floor(trigo.translateX(0,data.angle,data.acc)):0),
							accy:(data.accy==null?Math.floor(trigo.translateY(0,data.angle,data.acc)):0),
							accz:0,
							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),
							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),
							z:(data.from.z==null?0:data.from.z),
							collidegroup:"",
							spark:toys.NOOP,
							power:1,
							lifetime:null,
							tilemap:null,
							defaulttile:0,
							applyzgravity:false,
							map:null,
							defaulttile:0,
							mapindex:"",
							spritewalls:null,
							colx:(data.fullhit?0:null),
							coly:(data.fullhit?0:null),
							colh:(data.fullhit?ts.tileh:null),
							colw:(data.fullhit?ts.tilew:null),
							duration:null,
							onWallHit:function() {
								this.spark(this);
								gbox.trashObject(this);
							},
							bulletIsAlive:function() {
								return gbox.objectIsVisible(this);
							}
						}
					)
				);
				
				obj.initialize=function() {
					toys.topview.initialize(this);
				};
				
				obj[(data.logicon==null?"first":data.logicon)]=function() {
					this.cnt=(this.cnt+1)%10;
					
					if (this.applyzgravity) toys.topview.handleGravity(this); // z-gravity					
					toys.topview.applyForces(this); // Apply forces
					if (this.applyzgravity) toys.topview.applyGravity(this); // z-gravity
					if (this.map!=null) toys.topview.tileCollision(this,this.map,this.mapindex,this.defaulttile); // tile collisions
					if (this.spritewalls!=null) toys.topview.spritewallCollision(this,{group:this.spritewalls}); // walls collisions
					if (this.applyzgravity) toys.topview.floorCollision(this); // Collision with the floor (for z-gravity)
					toys.topview.adjustZindex(this);
					if (this.duration!=null) {
						this.duration--;
						if (this.duration==0) gbox.trashObject(this);
					}
					if (!this.bulletIsAlive()) gbox.trashObject(this);
					else if (this.toucheddown||this.touchedup||this.touchedleft||this.touchedright) this.onWallHit();
					else if (this.collidegroup!=null)
						for (var i in gbox._objects[this.collidegroup])
							if ((!gbox._objects[this.collidegroup][i].initialize)&&toys.topview.collides(this,gbox._objects[this.collidegroup][i],gbox._objects[this.collidegroup][i].tollerance)) {
								if (gbox._objects[this.collidegroup][i]["hitByBullet"]!=null)
									if (!gbox._objects[this.collidegroup][i].hitByBullet(this)) {
										this.spark(this);
										gbox.trashObject(this);
									}
							}
				}
				
				obj[(data.bliton==null?"blit":data.bliton)]=function() {
					if (!gbox.objectIsTrash(this))
						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});
				}
				
				gbox.setZindex(obj,obj.y+obj.h);
				
				return obj;
			
			},
			makedoor:function(gr,id,map,data) {

				var mts=gbox.getTiles(map.tileset);
				var ts=gbox.getTiles(data.tileset);
							
				var obj=gbox.addObject(
					help.mergeWithModel(
						data,{
							zindex:0,
							fliph:false, flipv:false,
							id:id,
							group:gr,
							cnt:0,
							tileset:"",
							frames:{},
							camera:true,
							x:data.tilex*mts.tilew,
							y:data.tiley*mts.tileh,
							z:0,
							tilemap:null,
							defaulttile:0,
							map:map,
							colx:(data.fullhit?0:null),
							coly:(data.fullhit?0:null),
							colh:(data.fullhit?ts.tileh:null),
							colw:(data.fullhit?ts.tilew:null),
							opening:false,
							doorheight:ts.tileh,
							opencounter:0,
							opening:false,
							closing:false,
							doOpen:function() {
								this.opening=true;
							},
							whenClosed:toys.NOOP,
							whenOpened:toys.NOOP,
							whileMoving:toys.NOOP,
							hitByBullet:function(by) {
							
							}
						}
					)
				);
				
				// Closing animation
				if (obj.closing) obj.opencounter=obj.doorheight;
				
				obj.initialize=function() {
					toys.topview.initialize(this);
				};
				
				obj[(data.logicon==null?"first":data.logicon)]=function() {
					if (this.closing) {
						this.whileMoving();
						this.opencounter--;
						if (this.opencounter<0) {
							this.opencounter=0;
							this.closing=false;
							this.whenClosed();
						}
					}
					if (this.opening) {
						this.whileMoving();
						this.opencounter++;
						if (this.opencounter>=this.doorheight) {
							if (!this.whenOpened()) gbox.trashObject(this);
						}
					}
				}
				
				obj[(data.bliton==null?"blit":data.bliton)]=function() {
					if (!gbox.objectIsTrash(this))
						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});
				}
				
				gbox.setZindex(obj,obj.y+obj.h);
				
				return obj;
			},
			// Set the object speed making sure that the X and Y coords are multiple of the speed. Useful on maze-based games.
			setStaticSpeed:function(th,speed) {
				th.staticspeed=speed;
				th.x=Math.round(th.x/speed)*speed;
				th.y=Math.round(th.y/speed)*speed;
			}
		},
		
		
		// Shoot'em up specifics
		shmup:{
			initialize:function(th,data) {
				help.mergeWithModel(
					th,
					help.mergeWithModel(
						data,
						{
							x:0, y:0,
							accx:0, accy:0,
							frames:{},
							maxacc:5, controlmaxacc:5,
							responsive:0, // Responsiveness
							bounds:{x:0,y:0,w:gbox.getScreenW(),h:gbox.getScreenH()}, // Bounds box (ship cannot exit from there)
							weapon:0, // Weapon
							hittime:5,
							camera:false,
							flipv:false, fliph:false,
							health:1,
							tollerance:4
						}
					)
				);
				toys.shmup.spawn(th);
			},
			spawn:function(th,data) {
				th.xpushing=toys.PUSH_NONE; // user is moving side
				th.vpushing=toys.PUSH_NONE; // user is moving side
				th.counter=0; // self counter
				th.hittimer=0;
				th.killed=false;
				help.copyModel(th,data);
			},
			getNextX:function(th) { return th.x+help.limit(th.accx,-th.maxacc,th.maxacc); },
			getNextY:function(th) { return th.y+help.limit(th.accy,-th.maxacc,th.maxacc); },
		  	controlKeys:function(th,keys) {
		  		
		  		if (gbox.keyIsPressed(keys.left)) {
		  			th.xpushing=toys.PUSH_LEFT;
		  			if (th.accx>th.responsive) th.accx=th.responsive;
					th.accx=help.limit(th.accx-1,-th.controlmaxacc,th.controlmaxacc);
				} else if (gbox.keyIsPressed(keys.right)) {
					th.xpushing=toys.PUSH_RIGHT;
					if (th.accx<-th.responsive) th.accx=-th.responsive;
					th.accx=help.limit(th.accx+1,-th.controlmaxacc,th.controlmaxacc);
				} else th.xpushing=toys.PUSH_NONE;
		  		if (gbox.keyIsPressed(keys.up)) {
		  			th.ypushing=toys.PUSH_UP;
		  			if (th.accy>th.responsive) th.accy=th.responsive;
					th.accy=help.limit(th.accy-1,-th.controlmaxacc,th.controlmaxacc);
				} else if (gbox.keyIsPressed(keys.down)) {
					th.ypushing=toys.PUSH_DOWN;
					if (th.accy<-th.responsive) th.accy=-th.responsive;
					th.accy=help.limit(th.accy+1,-th.controlmaxacc,th.controlmaxacc);
				} else th.ypushing=toys.PUSH_NONE;
			},
			applyForces:function(th) {
				th.x=toys.shmup.getNextX(th);
				th.y=toys.shmup.getNextY(th);
			},
			handleAccellerations:function(th) {
				if (!th.xpushing) th.accx=help.goToZero(th.accx);
				if (!th.ypushing) th.accy=help.goToZero(th.accy);
			},
			keepInBounds:function(th) {
				if (th.x<th.bounds.x) {
					th.x=th.bounds.x;
					th.accx=0;
				} else if (th.x+th.w>th.bounds.x+th.bounds.w) {
					th.x=th.bounds.x+th.bounds.w-th.w;
					th.accx=0;				
				}
				if (th.y<th.bounds.y) {
					th.y=th.bounds.y;
					th.accy=0;
				} else if (th.y+th.h>th.bounds.y+th.bounds.h) {
					th.y=th.bounds.y+th.bounds.h-th.h;
					th.accy=0;				
				}
			},
			setFrame:function(th) {
				if (th.hittimer) th.hittimer--;
				th.frame=help.decideFrame(th.counter,(th.hittimer?th.frames.hit:th.frames.still));
			},
			
			fireBullet:function(gr,id,data) {
			
				var ts=gbox.getTiles(data.tileset);
				
				var obj=gbox.addObject(
					help.mergeWithModel(
						data,{
							_bullet:true,
							fliph:false, flipv:false,
							id:id,
							group:gr,
							cnt:0,
							tileset:"",
							frames:{},
							acc:0,
							angle:0,
							camera:false,
							accx:(data.accx==null?Math.floor(trigo.translateX(0,data.angle,data.acc)):0),
							accy:(data.accy==null?Math.floor(trigo.translateY(0,data.angle,data.acc)):0),
							x:data.from.x+data.from.hw-ts.tilehw+(data.gapx?data.gapx:0),
							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)),
							collidegroup:"",
							spark:toys.NOOP,
							power:1
						}
					)
				);
				
				obj[(data.logicon==null?"first":data.logicon)]=function() {
					this.x+=this.accx;
					this.y+=this.accy;
					this.cnt=(this.cnt+1)%10;
					if (!gbox.objectIsVisible(this)) gbox.trashObject(this);
					else if (this.collidegroup!=null)
						for (var i in gbox._objects[this.collidegroup])
							if ((!gbox._objects[this.collidegroup][i].initialize)&&gbox.collides(this,gbox._objects[this.collidegroup][i],gbox._objects[this.collidegroup][i].tollerance)) {
								if (gbox._objects[this.collidegroup][i]["hitByBullet"]!=null)
									if (!gbox._objects[this.collidegroup][i].hitByBullet(this)) {
										this.spark(this);
										gbox.trashObject(this);
									}
							}
				}
				
				obj[(data.bliton==null?"blit":data.bliton)]=function() {
					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});
				}
				
				return obj;
			
			},
			
			hitByBullet:function(th,by) {
				if (by.power) {
					th.health-=by.power;
					if (th.health<=0) th.kill(by); else  th.hittimer=th.hittime;
				}
			},
			
			generateEnemy:function(gr,id,data,model) {
				help.mergeWithModel(data,model);
				var obj=gbox.addObject(
					help.mergeWithModel(
						data,{
							id:id,
							group:gr,
							cnt:0,
							tileset:"",
							frames:{},
							acc:0,
							angle:0,
							camera:false,
							fliph:false,
							flipv:false,
							accx:(data.accx==null?Math.floor(trigo.translateX(0,data.angle,data.acc)):0),
							accy:(data.accy==null?Math.floor(trigo.translateY(0,data.angle,data.acc)):0),
							x:data.x,
							y:data.y,
							// -- spec
							animationset:"still",
							defaultanimationset:"still",
							hitanimationset:"still",
							hittime:5,
							script:toys.NOOP,
							handler:toys.NOOP,
							scriptline:(data.scriptline==null?-1:data.scriptline-1),
							newline:true,
							waitframes:0,
							doframes:0,
							mode:0,
							line:{},
							dohandler:null,
							ended:false,
							health:1,
							hittimer:0,
							kill:toys.NOOP,
							tollerance:0,
							initialize:null,
							invulnerable:false,
							hitAnimation:function(time) {
								this.hittimer=(time==null?this.hittime:time);
								this.animationset=this.hitanimationset;
							},
							goTo:function(nl) { // Jump to a line
								this.waitframes=0;
								this.doframes=0;
								this.line={};
								this.scriptline=nl-1;
							},
							hitByBullet:function(by) {
								if (!this.invulnerable&&by.power) {
									this.health-=by.power;
									if (this.health<=0) this.kill(this,by); else this.hitAnimation();
								}
							},
						}
					)
				);
				
				
				obj[(data.logicon==null?"first":data.logicon)]=function() {
					if (this.initialize!=null)  {
						this.initialize(this);
						this.initialize=null;
					}
					if (!this.ended) {
						if (!this.waitframes&&!this.doframes&&((this.line.waitfor==null)||this.line.waitfor(this))) {
							this.scriptline++;
							this.everycnt=-1;
							if (this.script[this.scriptline]==null)
								this.ended=true;
							else {
								if (this.script[this.scriptline].goto!=null) this.scriptline=this.script[this.scriptline].goto;
								this.line=this.script[this.scriptline];
								if (this.line.afterframes!=null)
									this.waitframes=this.line.afterframes;
								if (this.line.forframes!=null)
									this.doframes=this.line.forframes;
								else
									this.doframes=1;
								if (this.line.cleardo)
									this.dohandler=null;
								else if (this.line.doit!=null) {
									this.dohandler={
										actiontimes:0,
										timer:(this.line.doit.every=="keep"?this.dohandler.every:this.line.doit.every),
										every:(this.line.doit.every=="keep"?this.dohandler.every:this.line.doit.every),
										once:(this.line.doit.once=="keep"?this.dohandler.once:this.line.doit.once),
										action:(this.line.doit.action=="keep"?this.dohandler.action:this.line.doit.action),
										render:(this.line.doit.render=="keep"?this.dohandler.render:this.line.doit.render)
									}
								}
									
							}
						}
						if (!this.waitframes&&this.doframes&&!this.ended) {
							this.doframes--;
							if (this.line.setinvulnerable!=null) this.invulnerable=this.line.setinvulnerable;
							if (this.line.setx!=null) this.x=this.line.setx;
							if (this.line.sety!=null) this.y=this.line.sety;
							if (this.line.addx!=null) this.x+=this.line.addx;
							if (this.line.addy!=null) this.y+=this.line.addy;
							if (this.line.setaccx!=null) this.accx=this.line.setaccx;
							if (this.line.setaccy!=null) this.accy=this.line.setaccy;
							if (this.line.setacc!=null) {
								this.acc=this.line.setacc;
								this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
								this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
							}
							if (this.line.addaccx!=null) this.accx+=this.line.addaccx;
							if (this.line.addaccy!=null) this.accy+=this.line.addaccy;
							if (this.line.addacc!=null) {
								this.acc+=this.line.addacc;
								this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
								this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
							}
							
							if (this.line.setangle!=null) {
								this.angle=this.line.setangle;
								this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
								this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
							}
							if (this.line.addangle!=null) {
								this.angle+=this.line.addangle;
								this.accx=Math.floor(trigo.translateX(0,this.angle,this.acc));
								this.accy=Math.floor(trigo.translateY(0,this.angle,this.acc));
							}
							if (this.line.everyframe) this.waitframes=this.line.everyframe;
								
						}
						if (this.waitframes>0) this.waitframes--;
					}
					if (this.dohandler&&(this.dohandler.action!=null)) {
						if (this.dohandler.timer==this.dohandler.every) {
							this.dohandler.action(this,this.dohandler.actiontimes);
							this.dohandler.timer=0;
							this.dohandler.actiontimes++;
						} else if (!this.dohandler.once) this.dohandler.timer++;
					}
					if (this.handler!=null) this.handler(this);
					
					if (this.hittimer) {
						this.hittimer--;
						if (!this.hittimer) this.animationset=this.defaultanimationset;
					}
					
					this.x+=this.accx;
					this.y+=this.accy;
					this.cnt=(this.cnt+1)%10;
					
				}

				obj[(data.bliton==null?"blit":data.bliton)]=function() {
					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});
					if (this.dohandler&&(this.dohandler.render!=null)) this.dohandler.render(this);
				}
				
				return obj;

			},
			generateScroller:function(gr,id,data) {
				var obj=gbox.addObject(
					help.mergeWithModel(
						help.cloneObject(data),{
							id:id, group:gr,
							y:0, x:0,
							stage:{},
							speed:0,
							stop:null, // Remember to set the last stop ever! or the last loop!
							block:-1,
							bly:0,
							lblock:-1,
							lbly:0,
							lget:0,
							tbly:0,
							trb:0,
							maxwidth:0,
							loopstart:null, loopend:null, looprounds:0,
							panspeed:1, panstimer:0, destspeed:0,
							
							setLoop:function(st,en) {
								this.loopstart=st;
								this.loopend=en;
								this.lget=1;
								this.looprounds=1;
							},
							
							quitLoop:function() {
								this.setLoop(null,null);
								this.looprounds=0;
							},
						
							setSpeed:function(s) {
								this.speed=s;
								this.destspeed=s;
							},
							
							panToSpeed:function(s,pans) {
								this.destspeed=s;
								this.panspeed=pans;
							},
							
							quitStop:function() {
								this.stop=null;
							},
							
							setStop:function(s) {
								this.stop=s;
							},
							
							setX:function(x) {
								if (x<0) this.x=0; else
								if (x+gbox.getScreenW()>this.maxwidth) this.x=this.maxwidth-gbox.getScreenW();
								else this.x=x;
							}
							
						}
					)
				);
				
				obj[(data.logicon==null?"first":data.logicon)]=function() {
					if ((this.stop==null)||(this.y<this.stop)) {
						if (this.speed!=this.destspeed) {
							if (this.panstimer) {
								this.panstimer--;
							} else {
								if (this.speed<this.destspeed) this.speed++; else
								if (this.speed>this.destspeed) this.speed--;
								this.panstimer=this.panspeed;
							}
						}
						this.y+=this.speed;
						if ((this.stop!=null)&&(this.y>=this.stop)) this.y=this.stop;
						if ((this.loopend!=null)&&(this.y>this.loopend)) {
							this.y=this.loopstart+(this.y-this.loopend);
							this.looprounds++;
							if (this.lget==1) {
								this.block=0;
								this.bly=0;
								this.lget=2;
							} else {
								this.block=this.lblock;
								this.bly=this.lbly;
							}
							
						}
					}
							
					// Cerca il blocco da mostrare
					this.trb=this.block;
					this.tbly=this.bly;
					do {
						this.trb++;
						this.tbly+=gbox.getImage(this.stage[this.trb].image).height;	
					} while (this.tbly<this.y);
					
					this.block=this.trb-1;
					this.bly=this.tbly-gbox.getImage(this.stage[this.trb].image).height;
					
							
					if (this.lget==2) {
						this.lblock=this.block;
						this.lbly=this.bly;
						this.lget=3;
					}
					
				}
					
				obj[(data.bliton==null?"blit":data.bliton)]=function() {
					var dy=this.tbly-this.y;
					var done=false;
					do {
						if (dy>gbox.getScreenH()) done=true;
						gbox.blitAll(gbox.getBufferContext(),gbox.getImage(this.stage[this.trb].image),{dx:-this.x,dy:gbox.getScreenH()-dy});
						this.trb++;
						dy+=gbox.getImage(this.stage[this.trb].image).height;
					} while (!done);
				}
				
				return obj;
			}
		},
		platformer:{
			initialize:function(th,data) {
				help.mergeWithModel(
					th,
					help.mergeWithModel(
						data,
						{
							maxaccx:5, maxaccy:10,
							jumpsize:6, jumpaccy:6,
							accx:0, accy:0,
							x:0, y:0,
							frames:{},
							camera:true,
							flipv:false,
							side:false
						}
					)
				);
				toys.platformer.spawn(th);
			},
			spawn:function(th,data) {
				th.curjsize=0; // current jump size
				th.counter=0; // self counter
				th.touchedfloor=false; // touched floor
				th.touchedceil=false;
				th.touchedleftwall=false;
				th.touchedrightwall=false;
				th.pushing=toys.PUSH_NONE; // user is moving side
				th.killed=false;
				help.copyModel(th,data);
			},
			getNextX:function(th) { return th.x+th.accx; },
			getNextY:function(th) { return th.y+help.limit(th.accy,-th.maxaccy,th.maxaccy); },
			applyGravity:function(th) {
				th.x=toys.platformer.getNextX(th);
				th.y=toys.platformer.getNextY(th);
			},	
		  	horizontalKeys:function(th,keys) {
		  		if (gbox.keyIsPressed(keys.left)) {
					th.pushing=toys.PUSH_LEFT;
					th.accx=help.limit(th.accx-1,-th.maxaccx,th.maxaccx);
				} else if (gbox.keyIsPressed(keys.right)) {
					th.pushing=toys.PUSH_RIGHT;
					th.accx=help.limit(th.accx+1,-th.maxaccx,th.maxaccx);
				} else th.pushing=toys.PUSH_NONE;
			},
			verticalTileCollision:function(th,map,tilemap) {
				var bottom=help.getTileInMap(th.x+(th.w/2),th.y+th.h,map,0,tilemap);
				var top=help.getTileInMap(th.x+(th.w/2),th.y,map,0,tilemap);
				th.touchedfloor=false;
				th.touchedceil=false;
		
				if (map.tileIsSolidCeil(th,top)) {
					th.accy=0;
					th.y=help.yPixelToTile(map,th.y,1);
					th.touchedceil=true;
				}
				if (map.tileIsSolidFloor(th,bottom)) {
					th.accy=0;
					th.y=help.yPixelToTile(map,th.y+th.h)-th.h;
					th.touchedfloor=true;
				}
			},
			horizontalTileCollision:function(th,map,tilemap) {
				var left=0;
				var right=0;
				var t=0;
				
				th.touchedleftwall=false;
				th.touchedrightwall=false;
				
				while (t<th.h) {
					left=help.getTileInMap(th.x,th.y+t,map,0,tilemap);
					right=help.getTileInMap(th.x+th.w-1,th.y+t,map,0,tilemap);
						
					if ((th.accx<0)&&map.tileIsSolidFloor(th,left)) {
						th.accx=0;
						th.x=help.xPixelToTile(map,th.x-1,1);
						th.touchedleftwall=true;
					} 
					if ((th.accx>0)&&map.tileIsSolidFloor(th,right)) {
						th.accx=0;
						th.x=help.xPixelToTile(map,th.x,1)-th.w;
						th.touchedrightwall=true;
					}
					t+=gbox.getTiles(map.tileset).tileh;
				}
			},
			canJump:function(th) {
				return th.touchedfloor;
			},
			jumpKeys:function(th,key) {
				if (toys.platformer.canJump(th)&&gbox.keyIsHit(key.jump)&&(th.curjsize==0)) {
					th.accy=-th.jumpaccy;
					th.curjsize=th.jumpsize;
				} else if (th.curjsize&&gbox.keyIsHold(key.jump)) { // Jump modulation
					th.accy--;
					th.curjsize--;
				} else
					th.curjsize=0;
			},	
			bounce:function(th,data) {
				th.curjsize=0;
				th.accy=-data.jumpsize;
			},
			handleAccellerations:function(th) {
				// Gravity
				if (!th.touchedfloor) th.accy++;
				// Attrito
				if (th.pushing==toys.PUSH_NONE) if (th.accx) th.accx=help.goToZero(th.accx);
			},
			setSide:function(th) {
				if (th.accx) th.side=th.accx>0;
			},
			setFrame:function(th) {
				if (th.touchedfloor)
					if (th.pushing!=toys.PUSH_NONE)
						th.frame=help.decideFrame(th.counter,th.frames.walking);
					else
						th.frame=help.decideFrame(th.counter,th.frames.still);
				else if (th.accy>0)
					th.frame=help.decideFrame(th.counter,th.frames.falling);
				else
					th.frame=help.decideFrame(th.counter,th.frames.jumping);
			},
			// ---
			auto:{
				// Moves on a platform. It tries to do not fall down, if specified.
				// Args: (object,{moveWhileFalling:<moves while not touching the floor>,speed:<movement speed>})
				// Outs: the frame
				goomba:function(th,data) {
					if (data.moveWhileFalling||th.touchedfloor) {
						if (th.side) {
							th.pushing=toys.PUSH_LEFT;
							th.accx=-data.speed;
						} else {
							th.pushing=toys.PUSH_RIGHT;
							th.accx=data.speed;
						}
					} else th.pushing=toys.PUSH_NONE;
				},
				dontFall:function(th,map,tilemap) {
					if (th.accx) {
						var til;
						if (th.accx>0) til=help.getTileInMap(toys.platformer.getNextX(th)+th.w-1+th.accx,th.y+th.h,map,0,tilemap);				
						else til=help.getTileInMap(toys.platformer.getNextX(th),th.y+th.h,map,0,tilemap);
						if (!map.tileIsSolidFloor(th,til)) {
							th.side=!th.side;
							th.accx=0;
						}
					}
				},
				horizontalBounce:function(th) {
					if (th.touchedleftwall||th.touchedrightwall) th.side=!th.side;
				},
			}
		},
		
		// State-based toys
		// CONSTANTS
		TOY_BUSY:0,
		TOY_DONE:1,
		TOY_IDLE:2,
		
		// PRIVATE

		// Generical toys method
		resetToy:function(th,id) { if (th.toys) delete th.toys[id] },
		getToyValue:function(th,id,v,def) { return ((th.toys==null)||(th.toys[id]==null)?def:th.toys[id][v]) },
		getToyStatus:function(th,id) { return ((th.toys==null)||(th.toys[id]==null)?toys.TOY_BUSY:th.toys[id].__status) },
		
		_toydone:function(th,id) {
			if (th.toys[id].__status<toys.TOY_IDLE) th.toys[id].__status++;
			return th.toys[id].__status;
		},
		_toybusy:function(th,id) {
			th.toys[id].__status=toys.TOY_BUSY;
			return th.toys[id].__status;
		},
		_toyfrombool:function(th,id,b) { return (b?toys._toydone(th,id):toys._toybusy(th,id)) },
		_maketoy:function(th,id){
			if (!th.toys) th.toys={};
			if (!th.toys[id]) {
				th.toys[id]={__status:toys.TOY_BUSY};
				return true;
			} else return false;
		},
		// Pure timers
		timer:{
			randomly:function(th,id,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].time=help.random(data.base,data.range);
				}
				if (th.toys[id].time) {
					th.toys[id].time--;
					return toys._toybusy(th,id);
				} else {
					th.toys[id].time=help.random(data.base,data.range);
					return toys._toydone(th,id);
				}	
			},
			real:function(th,id,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].subtimer=gbox.getFps();
					th.toys[id].done=false;
					if (data.countdown)
						th.toys[id].time=data.countdown;
					else
						th.toys[id].time=0;
				}
				th.toys[id].subtimer--;
				if (!th.toys[id].subtimer) {
					th.toys[id].subtimer=gbox.getFps();
					if (data.countdown) {
						if (th.toys[id].time) th.toys[id].time--; else th.toys[id].done=true;
					} else
						th.toys[id].time++;
				}
				return toys._toyfrombool(th,id,th.toys[id].done);

			},
			every:function(th,id,frames){
				if (toys._maketoy(th,id)) th.toys[id].timer=0;
				th.toys[id].timer++;
				if (th.toys[id].timer==frames) {
					th.toys[id].timer=0;
					return toys._toydone(th,id);
				} else return toys._toybusy(th,id)
			},
			after:function(th,id,frames) {
				if (toys._maketoy(th,id)) th.toys[id].timer=0;
				if (th.toys[id].timer==frames) return toys._toydone(th,id); else {
					th.toys[id].timer++;
					return toys._toybusy(th,id);
				}
			}
		},
		// Logical helpers
		logic: {
			once:function(th,id,cond){
				if (toys._maketoy(th,id)) th.toys[id].done=false;
				if (th.toys[id].done) return false; else if (cond) th.toys[id].done=true;
				return cond;
			}
		},
		// UI
		ui:{
			menu:function(th,id,opt) {
				if (toys._maketoy(th,id)||opt.resetmenu) {
					var fd=gbox._fonts[opt.font];
					th.toys[id].selected=(opt.selected?opt.selected:0);
					th.toys[id].ok=0;
					var w=0;
					for (var i=0;i<opt.items.length;i++)
						if (opt.items[i].length>w) w=opt.items[i].length;
					gbox.createCanvas("menu-"+id,{w:w*fd.tilew,h:opt.items.length*fd.tileh});
					for (var i=0;i<opt.items.length;i++)
						gbox.blitText(gbox.getCanvasContext("menu-"+id),{font:opt.font,text:opt.items[i],dx:0,dy:fd.tileh*i});
					th.toys[id].fh=fd.tileh;
					th.toys[id].fw=fd.tilew;
				}
				if (!th.toys[id].ok) {
					if (gbox.keyIsHit(opt.keys.up)&&(th.toys[id].selected>0)) th.toys[id].selected--; else
					if (gbox.keyIsHit(opt.keys.down)&&(th.toys[id].selected<opt.items.length-1)) th.toys[id].selected++; else
					if (gbox.keyIsHit(opt.keys.ok)) th.toys[id].ok=1; else
					if (gbox.keyIsHit(opt.keys.cancel)) th.toys[id].ok=-1;
				}
				gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas("menu-"+id),{dx:opt.x+th.toys[id].fw,dy:opt.y,camera:opt.camera});
				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});
				if (th.toys[id].ok) {
					if (th.toys[id].ok>0)
						if (th.toys[id].ok<10) {
							th.toys[id].ok++;
							toys._toybusy(th,id);
						} else return toys._toydone(th,id); // selected > 0
					else return toys._toydone(th,id); // selected == -1
				} else return toys._toybusy(th,id);
			},
			
			// Returns a full customizable object for optimized huds
			hud:function(id) {
				gbox.createCanvas(id);
				return {
					w:{},
					surfaceid:id,
					
					updateWidget:function(i){
						if (!this.w[i].__hidden) {
							if (this.w[i].widget=="label") {
								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
								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
								this.w[i].text=this.w[i].value+"";
								gbox.blitText(gbox.getCanvasContext(this.surfaceid),this.w[i]);
							}
							if (this.w[i].widget=="symbols") {
								var ts=gbox.getTiles(this.w[i].tileset);
								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});
								var cnt=this.w[i].value;
								for (var x=0;x<this.w[i].maxshown;x++) {
									if (cnt>0) {
										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});
									} else
										if (this.w[i].emptytile!=null)
											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});	
									cnt-=this.w[i].tiles.length;
								}
	
							}
							if (this.w[i].widget=="stack") {
								var ts=gbox.getTiles(this.w[i].tileset);
								var bw=((this.w[i].maxshown-1)*this.w[i].gapx)+ts.tilew;
								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});
								for (var x=0;x<this.w[i].maxshown;x++)
									if (x<this.w[i].value.length)
										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});
							}
							if (this.w[i].widget=="radio") {
								var ts=gbox.getTiles(this.w[i].tileset);
								gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx,y:this.w[i].dy,w:ts.tilew,h:ts.tileh});
								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});
	
							}
							if (this.w[i].widget=="blit") {
								var ts=gbox.getTiles(this.w[i].tileset);
								gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx,y:this.w[i].dy,w:ts.tilew,h:ts.tileh});
								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});
	
							}
							if (this.w[i].widget=="bool") {
								var ts=gbox.getTiles(this.w[i].tileset);
								gbox.blitClear(gbox.getCanvasContext(this.surfaceid),{x:this.w[i].dx,y:this.w[i].dy,w:ts.tilew,h:ts.tileh});
								if (this.w[i].value)
									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});
							}
						}
					},
					
					hideWidgets:function(l) {
						for (var i=0;i<l.length;i++) this.w[l[i]].__hidden=true;
						this.redraw();
					},
					
					showWidgets:function(l) {
						for (var i=0;i<l.length;i++) this.w[l[i]].__hidden=false;
						this.redraw();
					},
					
					getValue:function(w,k) {
						return this.w[w][k];
					},
					
					setValue:function(w,k,v) {
						if (this.w[w][k]!=v) {
							if (k=="value") {
								if ((this.w[w].maxvalue!=null)&&(v>this.w[w].maxvalue)) v=this.w[w].maxvalue;
								if ((this.w[w].minvalue!=null)&&(v<this.w[w].minvalue)) v=this.w[w].minvalue;	
								if (this.w[w].widget=="radio") v=(v%this.w[w].frames.length);
							}
							this.w[w][k]=v;							
							this.updateWidget(w);
						}
					},
					
					pushValue:function(w,k,v) {
						this.w[w][k].push(v);
						this.updateWidget(w);
					},
					
					addValue:function(w,k,v) {
						if (v) this.setValue(w,k,this.w[w][k]+v);
					},
					
					setWidget:function(id,w) {
						this.w[id]=w;
						this.updateWidget(id);
					},
					
					redraw:function() {
						gbox.blitClear(gbox.getCanvasContext(this.surfaceid));
						for (var i in this.w) this.updateWidget(i);
					},
					
					blit:function() {
						gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas(this.surfaceid),{dx:0,dy:0});
					}
				
				}
			}
		},
		fullscreen:{
			fadeout:function(th,id,tox,data) {
				if (toys._maketoy(th,id)||data.resetfade) th.toys[id].fade=0;
				th.toys[id].fade+=data.fadespeed;
				if (th.toys[id].fade>1) th.toys[id].fade=1;
				data.alpha=th.toys[id].fade;
				gbox.blitFade(tox,data);
				return toys._toyfrombool(th,id,th.toys[id].fade==1)
			},
			fadein:function(th,id,tox,data) {
				if (toys._maketoy(th,id)||data.resetfade) th.toys[id].fade=1;
				th.toys[id].fade-=data.fadespeed;
				if (th.toys[id].fade<0) th.toys[id].fade=0;
				if (th.toys[id].fade) {
					data.alpha=th.toys[id].fade;
					gbox.blitFade(tox,data);
				}
				return toys._toyfrombool(th,id,th.toys[id].fade==0);
			}
		},
		text:{
			blink:function(th,id,tox,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].texttimer=0;
					th.toys[id].visible=false;
					th.toys[id].times=0;
				}
				if (th.toys[id].texttimer>=data.blinkspeed) {
					th.toys[id].texttimer=0;
					th.toys[id].visible=!th.toys[id].visible;		
					if (data.times) th.toys[id].times++;
				} else th.toys[id].texttimer++;
				if (th.toys[id].visible)
		  			gbox.blitText(tox,data);
		  		return toys._toyfrombool(th,id,(data.times?data.times<th.toys[id].times:false));
			},
			fixed:function(th,id,tox,data) {
				if (toys._maketoy(th,id))
					th.toys[id].texttimer=0;
				else
					th.toys[id].texttimer++;
		  		gbox.blitText(tox,data);
		  		return toys._toyfrombool(th,id,data.time<th.toys[id].texttimer);
			}
		},
		logos:{
			linear:function(th,id,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].x=data.sx;
					th.toys[id].y=data.sy;
				}
				if ((data.x!=th.toys[id].x)||(data.y!=th.toys[id].y))
					if ((Math.abs(data.x-th.toys[id].x)<=data.speed)&&(Math.abs(data.y-th.toys[id].y)<=data.speed)) {
						th.toys[id].x=data.x;
						th.toys[id].y=data.y;
					} else
						trigo.translate(th.toys[id],trigo.getAngle(th.toys[id],data),data.speed);
				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:th.toys[id].x,dy:th.toys[id].y});
				return toys._toyfrombool(th,id,(data.x==th.toys[id].x)&&(data.y==th.toys[id].y));
			},
			crossed:function(th,id,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].gapx=data.gapx;
					th.toys[id].lw=gbox.getImage(data.image).height;
					th.toys[id].done=false;
				}
				if (th.toys[id].gapx) {
					th.toys[id].gapx-=data.speed;
					if (th.toys[id].gapx<0) th.toys[id].gapx=0;
					gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x-th.toys[id].gapx,dy:data.y,alpha:data.alpha});
					gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x+th.toys[id].gapx,dy:data.y,alpha:data.alpha});
					return toys._toybusy(th,id);
				} else {
					gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:data.y});
					return toys._toydone(th,id);
				}
			},
			zoomout:function(th,id,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].zoom=data.zoom;
					th.toys[id].done=false;
					th.toys[id].img=gbox.getImage(data.image);
				}
				if (th.toys[id].zoom!=1) {
					th.toys[id].zoom-=data.speed;
					if (th.toys[id].zoom<1) th.toys[id].zoom=1;
					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});
					return toys._toybusy(th,id);
				} else {
					gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:data.y});
					return toys._toydone(th,id);
				}
			},
			rising:function(th,id,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].cnt=0;
					th.toys[id].lh=gbox.getImage(data.image).height;
					th.toys[id].lw=gbox.getImage(data.image).width;
					th.toys[id].done=false;
				}
				if (th.toys[id].cnt<th.toys[id].lh) {
					th.toys[id].cnt+=data.speed;
					if (th.toys[id].cnt>th.toys[id].lh) th.toys[id].gapx=th.toys[id].lh;
					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});
					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});
					
					return toys._toybusy(th,id);
				} else {
					gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:data.y});
					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});
					
					return toys._toydone(th,id);
				}
			},
			bounce:function(th,id,data) {
				if (toys._maketoy(th,id)) {
					th.toys[id].accy=data.accy;
					th.toys[id].y=data.y;
					th.toys[id].h=gbox.getImage(data.image).height;
					th.toys[id].done=false;
				}
				if (!th.toys[id].done) {
					if (th.toys[id].y+th.toys[id].h>=data.floory) {
						th.toys[id].y=data.floory-th.toys[id].h;
						th.toys[id].accy=-Math.ceil(th.toys[id].accy/(data.heavy?data.heavy:2));
						th.toys[id].done=(th.toys[id].accy==0);
					} else th.toys[id].accy--;
					th.toys[id].y-=th.toys[id].accy;
				}
				gbox.blitAll(gbox.getBufferContext(),gbox.getImage(data.image),{dx:data.x,dy:th.toys[id].y});
						
				return toys._toyfrombool(th,id,th.toys[id].done);
			}
		},
		dialogue: {
			render:function(th,id,data){
				if (toys._maketoy(th,id)) {
					th.toys[id].newscene=true;
					th.toys[id].sceneid=-1;
					th.toys[id].ended=false;
					th.toys[id].timer=0;
					th.toys[id].counter=0;
					th.toys[id].anim=0;
					gbox.createCanvas("dialogue-"+id);
				}
				if (!data.hideonend||(data.hideonend&&!th.toys[id].ended)) {
					if (th.toys[id].newscene&&!th.toys[id].ended) {
						th.toys[id].anim=0;
						th.toys[id].timer=0;
						th.toys[id].newscene=false;
						th.toys[id].sceneid++;
						th.toys[id].ended=(th.toys[id].sceneid==data.scenes.length);
						if (!th.toys[id].ended) {
							th.toys[id].letter=0;
							th.toys[id].wait=false;
							th.toys[id].scene=data.scenes[th.toys[id].sceneid];
							th.toys[id].fd=gbox.getFont((th.toys[id].scene.font?th.toys[id].scene.font:data.font));
							th.toys[id].sceneH=(th.toys[id].scene.dh?th.toys[id].scene.dh:gbox.getScreenH());
							th.toys[id].sceneW=(th.toys[id].scene.dw?th.toys[id].scene.dw:gbox.getScreenW());
							th.toys[id].sceneX=(th.toys[id].scene.dx?th.toys[id].scene.dx:0);
							th.toys[id].sceneY=(th.toys[id].scene.dy?th.toys[id].scene.dy:0);
							gbox.blitClear(gbox.getCanvasContext("dialogue-"+id));
							if (th.toys[id].scene.slide) {							
								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});
							}
							if (th.toys[id].scene.scroller) {
								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)});
								for (var i=0;i<th.toys[id].scene.scroller.length;i++)
									gbox.blitText(gbox.getCanvasContext("scroller-"+id),{
											font:th.toys[id].fd.id,
											dx:0,
											dy:i*(th.toys[id].fd.tileh+th.toys[id].scene.spacing),
											dw:th.toys[id].sceneW,
											halign:gbox.ALIGN_CENTER,
											text:th.toys[id].scene.scroller[i]
										});
							}
							if (th.toys[id].scene.bonus) {
								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)});
							}
						}
					}
					if (!th.toys[id].ended) {
						if (th.toys[id].wait) {
							if (gbox.keyIsHit(data.esckey)) th.toys[id].ended=true; else
							if (gbox.keyIsHit(data.skipkey)) th.toys[id].newscene=true;
						} else {
										
							// SKIP KEYS
							
							if (gbox.keyIsHit(data.esckey)) th.toys[id].ended=true; else
							if (gbox.keyIsHold(data.skipkey)) th.toys[id].counter=th.toys[id].scene.speed;
							else th.toys[id].counter++;
							
							// MOVING
							
							if (th.toys[id].scene.talk) { // DIALOGUES
							
							
								if (th.toys[id].counter==th.toys[id].scene.speed) {
									th.toys[id].letter++;
									th.toys[id].counter=0;
									var tmp=th.toys[id].letter;
									var row=0;
									while (tmp>th.toys[id].scene.talk[row].length) {
										tmp-=th.toys[id].scene.talk[row].length;
										row++;
										if (row==th.toys[id].scene.talk.length)  {
											row=-1;
											break;
										}
									}
									if (row>=0) {
										gbox.blitText(gbox.getCanvasContext("dialogue-"+id),{
											font:data.font,
											dx:data.who[th.toys[id].scene.who].x,
											dy:(data.who[th.toys[id].scene.who].y)+(row*th.toys[id].fd.tileh),
											text:th.toys[id].scene.talk[row].substr(0,tmp)
										});
									} else
										th.toys[id].wait=true;
								}
							
							} else if (th.toys[id].scene.scroller) { // SCROLLER (i.e. credits)
							
								if (th.toys[id].counter==th.toys[id].scene.speed) {
									th.toys[id].letter++;
									th.toys[id].counter=0;
									if (th.toys[id].letter==(gbox.getCanvas("scroller-"+id).height+th.toys[id].scene.push))
										th.toys[id].wait=true;
								}
								
							} else if (th.toys[id].scene.bonus) { // BONUS (classic bonus award screen)
								for (var row=0;row<=th.toys[id].letter;row++) {
									if (th.toys[id].scene.bonus[row].text)
										gbox.blitText(gbox.getCanvasContext("bonus-"+id),{
											font:data.font,
											dx:0,
											dy:(row*(th.toys[id].fd.tileh+th.toys[id].scene.spacing)),
											text:th.toys[id].scene.bonus[row].text
										});
									else if (th.toys[id].scene.bonus[row].mul) {
										// Mask is %VAL%e%MUL%=%TOT%
										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));
										gbox.blitText(gbox.getCanvasContext("bonus-"+id),{
											clear:true,
											font:data.font,
											dx:0,
											dy:(row*(th.toys[id].fd.tileh+th.toys[id].scene.spacing)),
											text:th.toys[id].scene.bonus[row].tmptext
										});
									}
								}
								
								if (!th.toys[id].wait) {
									var next=false;
									if (th.toys[id].scene.bonus[th.toys[id].letter].mul&&!th.toys[id].scene.bonus[th.toys[id].letter].text) {
										if (th.toys[id].counter>=th.toys[id].scene.bonus[th.toys[id].letter].speed) {
											th.toys[id].counter=0;
											th.toys[id].timer++;
											if (th.toys[id].timer>th.toys[id].scene.bonus[th.toys[id].letter].mulvalue) {
												th.toys[id].scene.bonus[th.toys[id].letter].text=th.toys[id].scene.bonus[th.toys[id].letter].tmptext;
												next=true;
											} else {
												if (th.toys[id].scene.bonus[th.toys[id].letter].callback)
													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);
											}
										}
										
									} else if (th.toys[id].counter>=th.toys[id].scene.speed) next=true;
									if (next) {
										if (th.toys[id].letter==th.toys[id].scene.bonus.length-1)
											th.toys[id].wait=true;
										else {
											th.toys[id].letter++;
											if (th.toys[id].scene.bonus[th.toys[id].letter].mul) {
												th.toys[id].scene.bonus[th.toys[id].letter].text=null;
												th.toys[id].scene.bonus[th.toys[id].letter].tmptext=null;
												th.toys[id].timer=0;
											}
											th.toys[id].counter=0;
										}
									}
								}
							}
								
						}
					
					}
					
					// RENDERING
					
					
					if (th.toys[id].scene.talk) { // DIALOGUES
						if (data.who[th.toys[id].scene.who].box)
								gbox.blitRect(gbox.getBufferContext(),data.who[th.toys[id].scene.who].box);
						if (data.who[th.toys[id].scene.who].tileset) {
							th.toys[id].anim=(th.toys[id].anim+1)%20;
							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});
						}
						gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas("dialogue-"+id),{dx:0,dy:0});
					} else if (th.toys[id].scene.scroller) // SCROLLER (i.e. credits)
						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)});
					else if (th.toys[id].scene.bonus) // BONUS (i.e. credits)
						gbox.blitAll(gbox.getBufferContext(),gbox.getCanvas("bonus-"+id),{dx:th.toys[id].sceneX,dy:th.toys[id].sceneY});
				}		
				return toys._toyfrombool(th,id,th.toys[id].ended);
			}
		},
		
		// GENERATORS
		
		generate: {
			sparks:{
				simple:function(th,group,id,data) {
					var ts=gbox.getTiles(data.tileset);
					if (data.frames==null) {
						data.frames={ speed:(data.animspeed==null?1:data.animspeed), frames:[]};
						for (var i=0;i<ts.tilerow;i++) data.frames.frames[i]=i;
					}
	
					var obj=gbox.addObject(
						help.mergeWithModel(
							data,{
								id:id,
								group:group,
								x:th.x+th.hw-ts.tilehw+(data.gapx==null?0:data.gapx),
								y:(data.valign=="top"?th.y:th.y+th.hh-ts.tilehh+(data.gapy==null?0:data.gapy)),
								tileset:data.tileset,
								alpha:null,
								accx:0, accy:0,
								frame:0,
								timer:(data.delay?-data.delay:-1),
								frames:data.frames,
								toptimer:((data.frames.frames.length)*data.frames.speed)-1,
								camera:th.camera,
								gravity:false,
								trashoffscreen:true,
								fliph:(data.fliph==null?th.fliph:data.fliph), flipv:(data.flipv==null?th.flipv:data.flipv)
							}
						)
					);

					obj[(data.logicon==null?"first":data.logicon)]=function() {
						this.timer++;
						if (this.timer>=0) {
							this.x+=this.accx;
							this.y+=this.accy;
							if (this.gravity) this.accy++;
							if ((this.timer==this.toptimer)||(this.trashoffscreen&&(!gbox.objectIsVisible(this)))) gbox.trashObject(this);
						}
					}
					
					obj[(data.bliton==null?"blit":data.bliton)]=function() {
						if (this.timer>=0)
							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});					
					}
					
					return obj;
				},
				popupText:function(th,group,id,data) {
					data.text+="";
					var fd=gbox.getFont(data.font);
					
					var obj=gbox.addObject(
						help.mergeWithModel(
							data,{
								id:id,
								group:group,
								x:Math.floor(th.x+th.hw-(fd.tilehw*data.text.length)),
								y:th.y-fd.tilehh,
								vaccy:-data.jump,
								font:"small",
								keep:0,
								text:data.text,
								cnt:0,
								camera:th.camera
							}
						)
					);
					
					obj[(data.logicon==null?"first":data.logicon)]=function() {
						if (gbox.objectIsVisible(this)) {
							if (this.vaccy)
								this.vaccy++;
							else
								this.cnt++;
							this.y+=this.vaccy;
							if (this.cnt>=this.keep) gbox.trashObject(this);
						} else gbox.trashObject(this);
					}
					
					obj[(data.bliton==null?"blit":data.bliton)]=function() {
						gbox.blitText(gbox.getBufferContext(),{font:this.font,text:this.text,dx:this.x,dy:this.y,camera:this.camera});
					}
					
					return obj;
				},
				bounceDie:function(th,group,id,data){
					var obj=gbox.addObject(
						help.mergeWithModel(
							data,{
								id:id,
								group:group,
								tileset:th.tileset,
								frame:th.frame,
								side:th.side,
								frames:th.frames.die,
								x:th.x,
								y:th.y,
								vaccy:-data.jump,
								flipv:data.flipv,
								cnt:0,
								camera:th.camera
							}
						)
					);
					
					obj[(data.logicon==null?"first":data.logicon)]=function() {
						if (gbox.objectIsVisible(this)) {
							this.vaccy++;
							this.y+=this.vaccy;
							this.cnt=(this.cnt+1)%10;
						} else gbox.trashObject(this);	
					}
					
					obj[(data.bliton==null?"blit":data.bliton)]=function() {
						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});					
					}
					
					return obj;
				}
			}
		}
	}

