1 // ---
  2 // Copyright (c) 2010 Francesco Cottone, http://www.kesiev.com/
  3 // ---
  4 
  5 var dynalist={
  6 	create:function() {
  7 		return {
  8 			first:null,
  9 			last:null,
 10 			data:[],
 11 			dl:0,
 12 			gar:[],
 13 			disconnect:function(obd) {
 14 				if (this.data[obd].__first!=null)  this.data[this.data[obd].__first].__next=this.data[obd].__next; else this.first=this.data[obd].__next;
 15 				if (this.data[obd].__next!=null)  this.data[this.data[obd].__next].__first=this.data[obd].__first; else this.last=this.data[obd].__first;
 16 			},
 17 			addObject:function(obj,prio) {
 18 				var nid=this.gar.pop();
 19 				if (nid==null) {
 20 					nid=this.dl;
 21 					this.dl++;
 22 				}
 23 				if (this.first==null) { // First element
 24 					obj.__next=null;
 25 					obj.__first=null;
 26 					this.first=nid;
 27 					this.last=nid;
 28 				} else { // Chain next
 29 					var i=this.first;
 30 					while (i!=null)
 31 						if (this.data[i].__prio>prio) break; else i=this.data[i].__next;
 32 					if (i==null) { // if last, chain in queue
 33 						obj.__next=null;
 34 						obj.__first=this.last;								
 35 						this.data[this.last].__next=nid;
 36 						this.last=nid;								
 37 					} else { // else reconnect objects
 38 						obj.__first=this.data[i].__first;
 39 						obj.__next=i;
 40 						this.data[i].__first=nid;
 41 						if (obj.__first!=null) this.data[obj.__first].__next=nid; else this.first=nid;
 42 					}
 43 					
 44 				}
 45 				obj.__prio=prio;
 46 				obj.__id=nid;
 47 				this.data[nid]=obj;
 48 				return nid;
 49 			},
 50 			setPrio:function(obd,prio) {
 51 				if (this.data[obd].__prio==prio) return;
 52 				if (this.first!=this.last)
 53 				if (this.data[obd].__prio<prio) {
 54 					if (this.data[obd].__id!=this.last) {
 55 						var i=this.data[obd].__next;
 56 						while (i!=null)
 57 							if (this.data[i].__prio>=prio) break; else i=this.data[i].__next;
 58 						if ((i==null)||(this.data[i].__first!=this.data[obd].__id)) {
 59 							// disconnect
 60 							this.disconnect(obd);
 61 							// Reconnect
 62 							if (i==null) {
 63 								this.data[this.last].__next=this.data[obd].__id;
 64 								this.data[obd].__first=this.last;
 65 								this.data[obd].__next=null;
 66 								this.last=this.data[obd].__id;
 67 							} else {
 68 								this.data[obd].__first=this.data[i].__first;
 69 								this.data[obd].__next=i;
 70 								this.data[i].__first=this.data[obd].__id;
 71 								if (this.data[obd].__first!=null) this.data[this.data[obd].__first].__next=this.data[obd].__id; else this.first=this.data[obd].__id;
 72 							}
 73 						}
 74 					}
 75 				} else {
 76 					if (this.data[obd].__id!=this.first) {
 77 						var i=this.data[obd].__first;
 78 						while (i!=null)
 79 							if (this.data[i].__prio<=prio) break; else i=this.data[i].__first;
 80 						if ((i==null)||(this.data[i].__next!=this.data[obd].__id)) {
 81 							// disconnect
 82 							this.disconnect(obd);
 83 							if (i==null) {
 84 								this.data[this.first].__first=this.data[obd].__id;
 85 								this.data[obd].__first=null;
 86 								this.data[obd].__next=this.first;
 87 								this.first=this.data[obd].__id;
 88 							} else {
 89 								this.data[obd].__first=i;
 90 								this.data[obd].__next=this.data[i].__next;
 91 								this.data[i].__next=this.data[obd].__id;
 92 								if (this.data[obd].__next!=null) this.data[this.data[obd].__next].__first=this.data[obd].__id; else this.last=this.data[obd].__id;
 93 							}
 94 						}
 95 					}
 96 				}
 97 				this.data[obd].__prio=prio;
 98 			},
 99 			remove:function(obd) {
100 				this.disconnect(obd);
101 				this.gar.push(this.data[obd].__id);
102 				delete this.data[this.data[obd].__id];
103 			}
104 		}
105 	}
106 }
107 
108 // A special circular queue with some features useful for the resource loader
109 var cyclelist={
110 	create:function(size) {
111 		return {
112 			_head:0,
113 			_tail:0,
114 			_data:[],
115 			_size:(size?size:10),
116 			_total:0,
117 			_done:0,
118 			_current:null,
119 			getTotal:function(){return this._total}, // Number of elements to be "poped"
120 			getDone:function(){return this._done}, // Number of popped elements since the last empty
121 			getSize:function(){return this._size}, // The maximum number of elements in the queue
122 			isProcessing:function(){return this._current!=null}, // The last pop was not a null (i.e. the queue returned a valid object)
123 			isEnded:function(){return (this._head==this._tail)}, // There are other elements in the queue
124 			isBusy:function(){return this.isProcessing()||!this.isEnded()}, // There are elements in the queue/the last one pop returned an object that is being processed
125 			getCurrent:function(){return this._current}, // Return the last popped element
126 			push:function(d) {
127 				this._data[this._head]=d;
128 				this._head=(this._head+1)%this._size;
129 				this._total++;
130 			},
131 			pop:function() {
132 				if (this.isEnded()) {
133 					this._total=0;
134 					this._done=0;
135 					this._current=null;
136 				} else {
137 					this._current=this._data[this._tail];
138 					this._tail=(this._tail+1)%this._size;
139 					this._done++;
140 				}
141 				return this._current;
142 			},
143 			dump:function() {
144 				var r="";
145 				for (var i=0;i<this._size;i++) {
146 					r+=i+") "+this._data[i]+" | "+(i==this._head?"HEAD ":"")+(i==this._tail?"TAIL ":"")+"\n";
147 				}
148 				r+="\n\n"+this._done+"/"+this._total;
149 				return r;
150 			}
151 		}
152 	}
153 }
154 
155 // A simple circular cache handler
156 var cachelist={
157 	create:function(size) {
158 		return {
159 			_cache:{},
160 			_queue:[],
161 			_head:0,
162 			_size:(size?size:10),
163 			add:function(k,v) {
164 				if (!this._cache[k]) {
165 					if (this._queue[this._head])
166 						delete this._cache[this._queue[this._head]];
167 					this._queue[this._head]=k;
168 					this._cache[k]={pos:this._head,value:v};
169 					this._head=(this._head+1)%this._size;
170 				} else this._cache[k].value=v;
171 			},
172 			read:function(k) {
173 				return (this._cache[k]?this._cache[k].value:null);
174 			},
175 			clear:function() {
176 				this._cache={};
177 				this._queue=[];
178 				this._head=0;
179 			}
180 		}
181 	}
182 }
183 
184 /**
185  * @namespace
186  * Gamebox module allows multiple grouped objects to move simultaneously, it helps with collisions, 
187  * rendering and moving objects. It also provides monospaced pixel-font rendering, keyboard handling,  
188  * audio, double buffering and eventually FSEs. Gamebox can also store and load data from cookies! 
189  */
190 var gbox={
191 	// CONSTANTS
192 	ALIGN_CENTER:0,
193 	ALIGN_MIDDLE:0,
194 	ALIGN_RIGHT:1,
195 	ALIGN_BOTTOM:1,
196 	COLOR_BLACK:'rgb(0,0,0)',
197 	COLOR_WHITE:'rgb(255,255,255)',
198 	ZINDEX_LAYER:-1,
199 	
200 	// VARS
201 	_autoid:0,
202 	_cb:null, // callback for loadAll()
203 	_keyboard:[],
204 	_keymap:{
205 		up:38,
206 		down:40,
207 		right:39,
208 		left:37,
209 		a:90,
210 		b:88,
211 		c:67
212 	},
213 	_flags:{
214 		experimental:false,
215 		noaudio:false
216 	},
217 	_fonts:{},
218 	_tiles:{},
219 	_images:{},
220 	_camera:{},
221 	_screen:0,
222 	_screenposition:0,
223 	_keyboardpicker:0,
224 	_screenh:0,
225 	_screenw:0,
226 	_screenhh:0,
227 	_screenhw:0,
228 	_zoom:1,
229 	_canvas:{},
230 	_objects:{},
231 	_groups:[],
232 	_renderorder:[],
233 	_groupplay:{},
234 	_actionqueue:["first","then","blit","after"], // initialize is executed once
235 	_mspf:0,
236 	_fps:0,
237 	_gametimer:0,
238 	_frameskip:0,
239 	_autoskip:{min:0,max:5,lowidle:0,hiidle:5}, // minimum frameskip, maximum frameskip, minimum idle time allowed for increasing frameskip, maximum idle time allowed for decreasing frameskip
240 	_fskid:0,
241 	_statbar:0,
242 	_border:0,
243 	_garbage:[],
244 	_zindexch:[],
245 	_framestart:0,
246 	_zindex:dynalist.create(),
247 	_db:false,
248 	_systemcookie:"__gboxsettings",
249 	_sessioncache:"",
250 	_breakcacheurl:function(a) {return a+(a.indexOf("?")==-1?"?":"&")+"_brc="+gbox._sessioncache; },
251 	_forcedidle:0,
252 	_gamewaiting:0,
253 	_canlog:false,
254 	_splash:{
255 		gaugeLittleColor:"rgb(255,240,0)",
256 		gaugeLittleBackColor:"rgb(255,255,255)",
257 		gaugeBorderColor:"rgb(0,0,0)",
258 		gaugeBackColor:"rgb(100,100,100)",
259 		gaugeColor:"rgb(255,240,0)",
260 		gaugeHeight:10,
261 		background:null,
262 		minimalTime:0,
263 		footnotes:null,
264 		footnotesSpacing:1
265 	},
266 	_minimalexpired:0, // 0: not triggered, 1: triggered, 2: done
267 	setCanLog:function(c) { this._canlog=c&&window.console; },
268 	canLog:function() { return this._canlog},
269 	log:function() {}, // Overridden if console is really available
270 	_safedrawimage:function(tox,img,sx,sy,sw,sh,dx,dy,dw,dh) {
271 		if (!img||!tox) return;
272 		if (sx<0) { dx-=(dw/sw)*sx;sw+=sx; sx=0; }
273 		if (sy<0) { dy-=(dh/sh)*sy;sh+=sy; sy=0; }
274 		if (sx+sw>img.width) { dw=(dw/sw)*(img.width-sx);sw=img.width-sx;}
275 		if (sy+sh>img.height) { dh=(dh/sh)*(img.height-sy);sh=img.height-sy;}
276 		try { if ((sh>0)&&(sw>0)&&(sx<img.width)&&(sy<img.height)) tox.drawImage(img, sx,sy,sw,sh,dx,dy,dw,dh); } catch(e){}
277 	},
278 	_keydown:function(e){
279 		var key=(e.fake||window.event?e.keyCode:e.which);
280 		if (!gbox._keyboard[key]) gbox._keyboard[key]=1;
281 	},
282 	_keyup:function(e){
283 		var key=(e.fake||window.event?e.keyCode:e.which);
284 		gbox._keyboard[key]=-1;
285 	},
286 	_resetkeys:function() {
287 		for (var key in gbox._keymap)
288 			gbox._keyup({fake:1,keyCode:gbox._keymap[key]});
289 	},
290 	_showkeyboardpicker:function(){
291 		gbox._keyboardpicker.value="Click/Tap here to enable the keyboard";
292 		gbox._keyboardpicker.style.left=(gbox._screenposition.x+5)+"px";
293 		gbox._keyboardpicker.style.top=(gbox._screenposition.y+5)+"px";
294 		gbox._keyboardpicker.style.width=(gbox._screenposition.w-10)+"px";
295 		gbox._keyboardpicker.style.height="30px";
296 		this._keyboardpicker.style.border="1px dashed white";
297 		this._keyboardpicker.readOnly=null;
298 	},
299 	_hidekeyboardpicker:function(){
300 		this._keyboardpicker.style.zIndex=100;
301 		this._keyboardpicker.readOnly="yes";
302 		this._keyboardpicker.style.position="absolute";
303 		this._keyboardpicker.style.textAlign="center";
304 		this._keyboardpicker.style.backgroundColor="#000000";
305 		this._keyboardpicker.style.color="#fefefe";
306 		this._keyboardpicker.style.cursor="pointer";			
307 		this._keyboardpicker.value="";
308 		this._keyboardpicker.style.left="0px";
309 		this._keyboardpicker.style.top="0px";
310 		this._keyboardpicker.style.height="0px";
311 		this._keyboardpicker.style.width="0px";
312 		this._keyboardpicker.style.border="0px";
313 		this._keyboardpicker.style.padding="0px";
314 		this._keyboardpicker.style.margin="0px";
315 	},
316 	_domgetabsposition:function(oElement) {
317 		var sizes={x:0,y:0,h:0,w:0};
318 		sizes.h=oElement.offsetHeight;
319 		sizes.w=oElement.offsetWidth;
320 		while( oElement != null) {
321 			sizes.y += oElement.offsetTop;
322 			sizes.x += oElement.offsetLeft;
323 			oElement = oElement.offsetParent;
324 		}
325 		return sizes;
326 	},
327   
328   /**
329   * Sets the gbox._forcedidle property.
330   * @param {Boolean} f The value to write to gbox._forcedidle.
331   */	  
332 	setForcedIdle:function(f) { this._forcedidle=f},
333   
334   /**
335   * Returns a gbox flag at index f.
336   * @param {Object} f The index of the flag you want returned.
337   */	  
338 	getFlag:function(f) { return this._flags[f] },
339   
340   /**
341   * Sets the gbox._statbar property. Only useful if called before gbox.initScreen. Debugging funtionality.
342   * Much easier to access if you add '?statusbar=1' to your URL.
343   * @param {Boolean} f The value to write to gbox._statbar.
344   */	
345   setStatusBar:function(a) { this._statbar=a },
346 	setScreenBorder:function(a) { this._border=a},
347   
348   /**
349   * Initializes the screen to a certain width and height, applies zoom attributes, populates the 
350   * body of the HTML document including the canvas element, sets an initial camera, creates a '_buffer'
351   * canvas, sets keyboard event listeners, and many other initialization functions.
352   * @param {Integer} w The width of the main canvas.
353   * @param {Integer} h The height of the main canvas.
354   */	
355 	initScreen:function(w,h) {
356 		document.body.style.textAlign="center";
357 		var container=document.createElement("div");
358 		container.style.width="100%";
359 		container.style.height="100%";
360 		container.style.display="table";
361 		this._box=document.createElement("div");
362 		this._box.style.display="table-cell";
363 		this._box.style.width="100%";
364 		this._box.style.textAlign="center";
365 		this._box.style.verticalAlign="middle";
366 		
367 		this._screen=document.createElement("canvas");
368 		if (this._border) this._screen.style.border="1px solid black";
369 		this._screen.setAttribute('height',h);
370 		this._screen.setAttribute('width',w);
371 		this._screen.style.width=(w*this._zoom)+"px";
372 		this._screen.style.height=(h*this._zoom)+"px";
373 		this._screenh=h;
374 		this._screenw=w;
375 		this._screenhh=Math.floor(h/2);
376 		this._screenhw=Math.floor(w/2);
377 		this._camera.x=0;
378 		this._camera.y=0;
379 		this._camera.h=h;
380 		this._camera.w=w;
381 		this._box.appendChild(this._screen);
382 		container.appendChild(this._box);
383 		document.body.appendChild(container);
384 
385 		this.createCanvas("_buffer");
386 		window.addEventListener('keydown', this._keydown,false);
387 		window.addEventListener('keyup', this._keyup,false);
388 		if (this._statbar) {
389 			this._statbar=document.createElement("div");
390 			if (this._border) this._statbar.style.border="1px solid black";
391 			this._statbar.style.margin="auto";
392 			this._statbar.style.backgroundColor="#ffffff";
393 			this._statbar.style.fontSize="10px";
394 			this._statbar.style.fontFamily="sans-serif";
395 			this._statbar.style.width=(w*this._zoom)+"px";
396 			this._box.appendChild(this._statbar);
397 		}
398 		// Keyboard support on devices that needs focus (like iPad) - actually is not working for a bug on WebKit's "focus" command.
399 		this._keyboardpicker=document.createElement("input");
400 		this._keyboardpicker.onclick=function(evt) { gbox._hidekeyboardpicker();evt.preventDefault();evt.stopPropagation();};
401 		this._hidekeyboardpicker(this._keyboardpicker);
402 		
403 		gbox._box.appendChild(this._keyboardpicker);
404 		gbox._screen.ontouchstart=function(evt) { gbox._screenposition=gbox._domgetabsposition(gbox._screen);if (evt.touches[0].pageY-gbox._screenposition.y<30) gbox._showkeyboardpicker();else gbox._hidekeyboardpicker();evt.preventDefault();evt.stopPropagation();};
405 		gbox._screen.ontouchend=function(evt) {evt.preventDefault();evt.stopPropagation();};
406 		gbox._screen.ontouchmove=function(evt) { evt.preventDefault();evt.stopPropagation();};
407 		gbox._screen.onmousedown=function(evt) {gbox._screenposition=gbox._domgetabsposition(gbox._screen);if (evt.pageY-gbox._screenposition.y<30)  gbox._showkeyboardpicker(); else gbox._hidekeyboardpicker();evt.preventDefault();evt.stopPropagation();};
408 		
409 		var d=new Date();
410 		gbox._sessioncache=d.getDate()+"-"+d.getMonth()+"-"+d.getFullYear()+"-"+d.getHours()+"-"+d.getMinutes()+"-"+d.getSeconds();
411 		
412 		gbox._loadsettings(); // Load default configuration
413 		gbox.setCanAudio(true); // Tries to enable audio by default
414 	},
415 
416   /**
417   * Sets the gbox._db property. Turns on an off double buffering.
418   * @param {Boolean} db The value to write to gbox._db. True enables double buffering, false disables.
419   */	  
420 	setDoubleBuffering:function(db){this._db=db},
421   
422   /**
423   * Writes text to the status bar, but only if the status bar is enabled.
424   * @param {String} txt The text to write to the status bar.
425   */	  
426 	setStatBar:function(txt){ if (gbox._statbar) this._statbar.innerHTML=(txt?txt:" ")},
427 
428   /**
429   * Set the frames per second rate.
430   * @param {Integer} f Total frames per second for the game to run at.
431   */	  
432 	setFps:function(f){
433 		this._fps=f;
434 		this._mspf=Math.floor(1000/f)
435 	},
436   
437   /**
438   * Get the frames per second rate (default is 25).
439   * @returns {Integer} Returns the frames per second.
440   */
441 	getFps:function() { return this._fps },
442 	setAutoskip:function(f){this._autoskip=f},
443 	setFrameskip:function(f){this._frameskip=f},
444   
445   /**
446   * Get the screen height.
447   * @returns {Integer} Screen height in pixels.
448   */
449 	getScreenH:function(){return this._screenh},
450 
451   /**
452   * Get the screen width.
453   * @returns {Integer} Screen width in pixels.
454   */
455 	getScreenW:function(){return this._screenw},
456   
457   /**
458   * Get the screen half-height.
459   * @returns {Integer} Screen half-height in pixels.
460   */
461 	getScreenHH:function(){return this._screenhh},
462 
463   /**
464   * Get the screen half-width.
465   * @returns {Integer} Screen half-width in pixels.
466   */
467 	getScreenHW:function(){return this._screenhw},
468   
469   /**
470   * Sets the gbox._zoom parameter, only works before gbox.initScreen is called.
471   * @param {Integer} z Zoom factor.
472   */
473 	setZoom:function(z) { this._zoom=z},
474 
475   /**
476   * Deprecated: gbox._cb is now set by passing it directly into gbox.loadAll(). Left in for backwards compatibility.
477   * @param {String} cb The name of the function to be called once gbox.loadAll is completed.
478   */
479 	setCallback:function(cb) { this._cb=cb; },
480 
481 	_playobject:function(g,obj,a) {
482 		if (gbox._objects[g][obj].initialize) {
483 			gbox._objects[g][obj].initialize(obj);
484 			delete gbox._objects[g][obj].initialize;
485 		}
486 		if (gbox._objects[g][obj][a]) gbox._objects[g][obj][a](obj,a);
487 	},
488 
489 	_nextframe:function(){
490 		gbox._framestart=gbox._mspf-(new Date().getTime()-gbox._framestart);
491 		if (gbox._autoskip)
492 			if ((gbox._framestart<gbox._autoskip.lowidle)&&(gbox._frameskip<gbox._autoskip.max)) gbox.setFrameskip(gbox._frameskip+1); else
493 			if ((gbox._framestart>gbox._autoskip.hiidle)&&(gbox._frameskip>gbox._autoskip.min)) gbox.setFrameskip(gbox._frameskip-1);
494 		if (gbox._statbar) gbox.debugGetstats();
495 		this._gametimer=setTimeout(gbox.go,(gbox._framestart<=0?1:gbox._framestart));		
496 	},
497 
498   /**
499   * This function is called once per frame. This is where the basic rendering and processing of groups occurs.
500   */
501 	go:function() {
502 		if (gbox._loaderqueue.isBusy()) {
503 			if (gbox._gamewaiting==1) {
504 				gbox.blitFade(gbox._screen.getContext("2d"),{alpha:0.5});
505 				gbox.blitText(gbox._screen.getContext("2d"),{font:"_dbf",dx:2,dy:2,text:"LOADING..."});
506 				gbox._gamewaiting=true;
507 			}
508 			if (gbox._gamewaiting<=1) {
509 				var bw=Math.floor(((gbox.getScreenW()-4)*gbox._loaderqueue.getDone())/gbox._loaderqueue.getSize());
510 				gbox._screen.getContext("2d").globalAlpha=1;
511 				gbox._screen.getContext("2d").fillStyle = gbox._splash.gaugeLittleBackColor;
512 				gbox._screen.getContext("2d").fillRect(0,4+gbox.getFont("_dbf").tileh,gbox.getScreenW(),1);
513 				gbox._screen.getContext("2d").fillStyle = gbox._splash.gaugeLittleColor;
514 				gbox._screen.getContext("2d").fillRect(0,4+gbox.getFont("_dbf").tileh,(bw>0?bw:0),1);
515 				gbox._screen.getContext("2d").restore();
516 				gbox.setStatBar("Loading... ("+gbox._loaderqueue.getDone()+"/"+gbox._loaderqueue.getTotal()+")");
517 			}
518 			if (gbox._gamewaiting) gbox._gamewaiting--;
519 			setTimeout(gbox.go,1000);
520 		} else {
521 			gbox._gamewaiting=3;
522 			gbox._framestart=new Date().getTime();
523 			var gr="";
524 			for (var g=0;g<gbox._renderorder.length;g++)
525 				if (gbox._groupplay[gbox._renderorder[g]])
526 					if (gbox._renderorder[g]==gbox.ZINDEX_LAYER) {
527 						var id;
528 						for (var i=0;i<gbox._actionqueue.length;i++) {
529 							id=gbox._zindex.first;
530 							while (id!=null) {
531 								if (gbox._groupplay[gbox._zindex.data[id].g])
532 									gbox._playobject(gbox._zindex.data[id].g,gbox._zindex.data[id].o,gbox._actionqueue[i]);
533 								id=gbox._zindex.data[id].__next;
534 							}
535 						}
536 					} else
537 						for (var i=0;i<gbox._actionqueue.length;i++)
538 							for (var obj in gbox._objects[gbox._renderorder[g]])
539 								gbox._playobject(gbox._renderorder[g],obj,gbox._actionqueue[i]);
540 			if (gbox._fskid>=gbox._frameskip) {
541 				if (gbox._db) gbox.blitImageToScreen(gbox.getBuffer());
542 				gbox._fskid=0;
543 			} else gbox._fskid++;
544 			
545 			gbox.purgeGarbage();
546 	
547 			if (gbox._zindexch.length) {
548 				
549 				for (var i=0;i<gbox._zindexch.length;i++) {
550 					if (gbox._objects[gbox._zindexch[i].o.g][gbox._zindexch[i].o.o])
551 						if (gbox._objects[gbox._zindexch[i].o.g][gbox._zindexch[i].o.o].__zt==null)
552 							gbox._objects[gbox._zindexch[i].o.g][gbox._zindexch[i].o.o].__zt=gbox._zindex.addObject(gbox._zindexch[i].o,gbox._zindexch[i].z);
553 						else
554 							gbox._zindex.setPrio(gbox._objects[gbox._zindexch[i].o.g][gbox._zindexch[i].o.o].__zt,gbox._zindexch[i].z);
555 				}
556 				gbox._zindexch=[];
557 			}
558 			
559 			
560 			// Handle holding
561 			for (var key in gbox._keymap)
562 				if (gbox._keyboard[gbox._keymap[key]]==-1) gbox._keyboard[gbox._keymap[key]]=0; else
563 				if (gbox._keyboard[gbox._keymap[key]]&&(gbox._keyboard[gbox._keymap[key]]<100))	gbox._keyboard[gbox._keymap[key]]++;
564 			if (gbox._forcedidle)
565 				this._gametimer=setTimeout(gbox._nextframe,gbox._forcedidle); // Wait for the browser
566 			else
567 				gbox._nextframe();
568 		}
569 	},
570   
571   /**
572   * Displays basic audio, object, and performance statistics in the status bar. Automatically called each frame if the status bar is enabled.
573   */
574 	debugGetstats:function() {
575 		var statline="Idle: "+gbox._framestart+"/"+gbox._mspf+(gbox._frameskip>0?" ("+gbox._frameskip+"skip)":"")+" | ";
576 		var cnt=0;
577 		for (var g=0;g<gbox._groups.length;g++)
578 			if (gbox._groupplay[gbox._groups[g]]) {
579 				cnt=0;
580 				for (var obj in gbox._objects[gbox._groups[g]]) cnt++;
581 				if (cnt) statline+=gbox._groups[g]+"["+cnt+"] ";
582 			}
583 		var cnt=0;
584 		var ply=0;
585 		for (var g in gbox._audio.aud) 
586 			for (var x=0;x<gbox._audio.aud[g].length;x++) {
587 				cnt++;
588 				if (!gbox._audio.aud[g][x].paused&&!gbox._audio.aud[g][x].ended) ply++;
589 			}
590 		statline+="| audio: "+ply+"/"+cnt+":"+this._audioteam;
591 			/*
592 			statline+="<br><br>";
593 		var id=gbox._zindex.first;
594 			while (id!=null) {
595 				if (gbox._groupplay[gbox._zindex.data[id].g]) statline+=gbox._zindex.data[id].g+" | "+gbox._zindex.data[id].o+" ("+gbox._zindex.data[id].__prio+")<br>";
596 				id=gbox._zindex.data[id].__next;
597 			}
598 			*/
599 		gbox.setStatBar(statline);
600 	},
601   
602 	setZindex:function(th,z) {
603 		if ((th.__zt==null)||(th.zindex!=z)) {
604 			th.zindex=z;
605 			this._zindexch.push({o:{g:th.group,o:th.id},z:z});
606 		}
607 	},
608 
609   /**
610   * Returns true if a given key in this._keymap is pressed. Only returns true on the transition from unpressed to pressed.
611   * @param {String} id A key in the keymap. By default, one of: "up" "down" "left" "right" "a" "b" "c"
612   * @returns {Boolean} True if the given key is transitioning from unpressed to pressed in this frame.
613   */
614 	keyIsHit:function(id) { return this._keyboard[this._keymap[id]]==1},
615   
616   /**
617   * Returns true if a given key in this._keymap is being held down. Returns true as long as the key is held down.
618   * @param {String} id A key in the keymap. By default, one of: "up" "down" "left" "right" "a" "b" "c"
619   * @returns {Boolean} True if the given key is held down.
620   */  
621 	keyIsPressed:function(id) { return this._keyboard[this._keymap[id]]>0},
622 
623   /**
624   * Returns true if a given key in this._keymap has been held down for at least one frame. Will not return true if a key
625   * is quickly tapped, only once it has been held down for a frame.
626   * @param {String} id A key in the keymap. By default, one of: "up" "down" "left" "right" "a" "b" "c"
627   * @returns {Boolean} True if the given key has been held down for at least one frame.
628   */  
629 	keyIsHold:function(id) { return this._keyboard[this._keymap[id]]>1},
630   
631   /**
632   * Returns true if a given key in this._keymap is released. Only returns true on the transition from pressed to unpressed.
633   * @param {String} id A key in the keymap. By default, one of: "up" "down" "left" "right" "a" "b" "c"
634   * @returns {Boolean} True if the given key is transitioning from pressed to unpressed in this frame.
635   */
636 	keyIsReleased:function(id) { return this._keyboard[this._keymap[id]]==-1},
637   
638 	_savesettings:function() {
639 		var saved="";
640 		for (var k in this._keymap) saved+="keymap-"+k+":"+this._keymap[k]+"~";
641 		for (var f in this._flags) saved+="flag-"+f+":"+(this._flags[f]?1:0)+"~";
642 		this.dataSave("sys",saved);
643 	},
644 	_loadsettings:function() {
645 		var cfg=this.dataLoad("sys");
646 		if (cfg!==null) {
647 			cfg=cfg.split("~");
648 			var kv;
649 			var mk;
650 			for (var i=0;i<cfg.length;i++) {
651 				kv=cfg[i].split(":");
652 				mk=kv[0].split("-");
653 				switch (mk[0]) {
654 					case "keymap": { this._keymap[mk[1]]=kv[1]*1; break }
655 					case "flag": { this._flags[mk[1]]=kv[1]*1; break }
656 				}
657 			}
658 		}
659 	},
660 
661   /**
662   * Saves data to a browser cookie as a key-value pair, which can be restored later using gbox.dataLoad. Only 
663   * works if user has cookies enabled.
664   * @param {String} k The key which identifies the value you are storing.
665   * @param {String} v The value you wish to store. Needs to be a string!
666   * @param {String} d A date offset, to be added to the current date. Defines the cookie's expiration date. By default this is set to 10 years.
667   * @example
668   * // (from Capman)
669   * gbox.dataSave("capman-hiscore",maingame.hud.getNumberValue("score","value"));
670   */
671 	dataSave:function(k,v,d) { 
672 		var date = new Date();
673 		date.setTime(date.getTime()+((d?d:365*10)*24*60*60*1000));
674 		document.cookie =this._systemcookie+"~"+k+"="+v+"; expires="+date.toGMTString()+"; path=/";
675 	},
676   
677   /**
678   * Loads data from a browser cookie. Send it a key and it returns a value (if available). Only works if user has cookies enabled.
679   * @param {String} k The key which identifies the value you are loading.
680   * @param {String} a A switch to determine whether a string or a number is returned. By default a string is returned.
681   * @returns {Object} A string or a number loaded from the cookie.
682   * @example
683   * hiscore = gbox.dataLoad("hiscore");
684   */
685 	dataLoad:function(k,a) {
686 		var nameeq=this._systemcookie+"~"+k+"=";
687 		var ca = document.cookie.split(";");
688 		var rt;
689 		for (var i=0;i<ca.length;i++) {
690 			var c=ca[i];
691 			while (c.charAt(0)==' ') c=c.substring(1,c.length);
692 			if (c.indexOf(nameeq)==0) {
693 				rt=c.substring(nameeq.length,c.length);
694 				if (a&&a.number) return rt*1; else return rt;
695 				if (a&&a.number) return rt*1; else return rt;
696 			}
697 		}
698 		return null;
699 	},
700 
701   /**
702   * Clears a value stored in a  key-value pair in a browser cookie. Sets value to "". Only works if user has cookies enabled.
703   * @param {String} k The key which identifies the value you are clearing.
704   */
705 	dataClear:function(k) { this.dataSave(k,"",-1) },
706   
707   /**
708   * Gets the current camera object.
709   * @returns {Object} The camera object.
710   */
711 	getCamera:function() { return this._camera; },
712   
713   /**
714   * Sets the y value of the current camera object.
715   * @param {Integer} y The camera object's new y value.
716   * @param {Object} viewdata An object containing parameters h and w, which are a bounding box that the camera is
717   * not supposed to leave. For example, to use your map as a bounding area for the camera, pass along {w: map.w, h: map.h}.
718   */
719 	setCameraY:function(y,viewdata) {
720 		this._camera.y=y;
721 		if (this._camera.y+this._camera.h>viewdata.h) this._camera.y=viewdata.h-this._screenh;
722 		if (this._camera.y<0) this._camera.y=0;
723 	},
724 
725   /**
726   * Sets the x value of the current camera object.
727   * @param {Integer} x The camera object's new x value.
728   * @param {Object} viewdata An object containing parameters h and w, which are a bounding box that the camera is
729   * not supposed to leave. For example, to use your map as a bounding area for the camera, pass along {w: map.w, h: map.h}.
730   */  
731 	setCameraX:function(x,viewdata) {
732 		this._camera.x=x;
733 		if (this._camera.x+this._camera.w>viewdata.w) this._camera.x=viewdata.w-this._screenw;
734 		if (this._camera.x<0) this._camera.x=0;
735 	},
736   
737   /**
738   * Centers the camera.
739   * @param {Object} data An object containing x and y parameters -- typically the object you wish to center the camera on.
740   * @param {Object} viewdata An object containing parameters h and w, which are a bounding box that the camera is
741   * not supposed to leave. For example, to use your map as a bounding area for the camera, pass along {w: map.w, h: map.h}.
742   * @example
743   * // Center the camera on the player object
744   * gbox.centerCamera(gbox.getObject('player', 'player_id'), {w: map.w, h: map.h});
745   */
746 	centerCamera:function(data,viewdata) {
747 		this.setCameraX(data.x-this._screenhw,viewdata);
748 		this.setCameraY(data.y-this._screenhh,viewdata);
749 	},
750 
751   /**
752   * Get an array containing the names of each group in the game, in order of rendering.
753   * @returns {Array} An array of group names.
754   * @example
755   * grouplist = gbox.getGroups();
756   * grouplist; // => ["background", "player", "enemy", "game"]	
757   */
758   getGroups:function() { return this._groups; },
759   
760   /**
761   * Defines the names of each group in the game along with their rendering order.
762   * @param {Array} g An array of strings of group names, in the order in which the groups should be rendered. So
763   * g[0] will contain the first group to render, g[1] the second group to render, etc.
764   */
765 	setGroups:function(g){
766 		this._groups=g;
767 		this._groupplay[gbox.ZINDEX_LAYER]=true;
768 		for (var i=0;i<g.length;i++)
769 			if (!this._objects[g[i]]) {
770 				this._objects[g[i]]={};
771 				this._groupplay[g[i]]=true;
772 				this._renderorder[i]=g[i];
773 			}
774 	},
775   
776   /**
777   * A method of setting the render order of groups independently of gbox.setGroups. Sets gbox._renderorder, 
778   * which by default is equivalent to gbox._groups. However, gbox._renderorder is what ultimately determines
779   * the rendering order of groups. If you need to change your rendering order on the fly, use this function 
780   * by passing it a reordered array of group names.
781   * @param {Array} g An array of strings of group names, in the order in which the groups should be rendered. So
782   * g[0] will contain the first group to render, g[1] the second group to render, etc.
783   */
784 	setRenderOrder:function(g) { this._renderorder=g; },
785   
786   /**
787   * If a group is disabled, this will enable the group.
788   * @param {String} gid The id of the group.
789   */
790 	playGroup:function(gid){this._groupplay[gid]=true;},
791 
792   /**
793   * If a group is enabled, this will disable the group.
794   * @param {String} gid The id of the group.
795   */
796 	stopGroup:function(gid){this._groupplay[gid]=false;},
797   
798   /**
799   * Toggles a group between enabled and disabled status.
800   * @param {String} gid The id of the group.
801   */
802 	toggleGroup:function(gid){this._groupplay[gid]=!this._groupplay[gid];},
803   
804   /**
805   * Turns off all groups except for the one specified. 
806   * @param {String} gid The id of the group.
807   */
808 	soloGroup:function(gid) {
809 		for (var i=0;i<this._groups.length;i++)
810 			if (this._groups[i]==gid) this.playGroup(this._groups[i]); else this.stopGroup(this._groups[i]);
811 	},
812   
813   /**
814   * Enables all groups, toggling any groups that are currently disabled.
815   */
816 	playAllGroups:function() { for (var i=0;i<this._groups.length;i++) this.playGroup(this._groups[i]); },
817 
818   /**
819   * Destroys all objects in a given group.
820   * @param {String} gid The id of the group.
821   */
822 	clearGroup:function(group) {
823 		for (var obj in this._objects[group]) {
824 			if (this._objects[group][obj].__zt!=null) this._zindex.remove(this._objects[group][obj].__zt);
825 			delete this._objects[group][obj];
826 		}
827 	},
828 	playGroups:function(gid){for (var i=0;i<gid.length;i++)this.playGroup(gid[i])},
829 	stopGroups:function(gid){for (var i=0;i<gid.length;i++)this.stopGroup(gid[i])},
830 	toggleGroups:function(gid){for (var i=0;i<gid.length;i++)this.toggleGroup(gid[i])},
831   
832   /**
833   * Given a group and an id for a particular object instance, this returns the instance requested.
834   * <b>NOTE:</b> this does not return a copy of the object you've requested! Any modifications you make
835   * to the object returned are directly modifying the object you requested.
836   * @param {String} group The id of the group that contains the object.
837   * @param {String} id The id of the instance of the object.
838   * @returns {Object} The object requested.
839   * @example
840   * // Find the player and reduce health by half.
841   * playertemp = gbox.getObject('player','player_id');
842   * player.health = player.health/2;
843   */ 
844 	getObject:function(group,id) {return this._objects[group][id]},
845 
846   /**
847   * Creates a font.
848   * @param {Object} data An object containing: <ul><li>id: the id of the font</li>
849   * <li>image: reference to the font image loaded (must contain font character tiles in ASCII order)</li>
850   * <li>firstletter: the ASCII character that the font image's first character corresponds to</li>
851   * <li>tileh: height in pixels of the character tiles</li>
852   * <li>tilew: width in pixels of the character tiles</li>
853   * <li>tilerow: width in pixels of each row in the font image</li>
854   * <li>gapx: x-coord gap between tile columns, in pixels</li>
855   * <li>gapy: y-coord gap between tile rows, in pixels</li></ul>
856   * @example
857   * gbox.addImage('font', 'font.png');
858   * gbox.addFont({ id: 'small', image: 'font', firstletter: ' ', tileh: 8, tilew: 8, tilerow: 255, gapx: 0, gapy: 0 });
859   */ 
860 	addFont:function(data) {
861 		data.tilehh=Math.floor(data.tileh/2);
862 		data.tilehw=Math.floor(data.tilew/2);
863 		this._fonts[data.id]=data;
864 		this._fonts[data.id].firstascii=data.firstletter.charCodeAt(0);
865 	},
866   
867   /**
868   * Returns a font object containing data about the font.
869   * @param {String} id The id of the font, as set in gbox.addFont.
870   */   
871 	getFont:function(id) {
872 		return this._fonts[id];
873 	},
874 
875   /**
876   * Deletes an object, keeping a record of its group and id in gbox._garbage.
877   * @param {Object} obj The object you wish to delete.
878   */  
879 	trashObject:function(obj) {
880 		if (!this._garbage[obj.group]) this._garbage[obj.group]={};
881 		this._garbage[obj.group][obj.id]=1;
882 		obj.__trashing=true;
883 	},
884   
885   /**
886   * Clears the record held in gbox._garbage of what has been deleted.
887   * @param {Object} obj The object you wish to delete.
888   */    
889 	purgeGarbage:function() {
890 		for (var group in this._garbage)
891 			for (var id in this._garbage[group]) {
892 				if (this._objects[group][id].__zt!=null)
893 					this._zindex.remove(this._objects[group][id].__zt)
894 				delete this._objects[group][id];
895 			}
896 		gbox._garbage={};
897 	},
898   
899   /**
900   * Deletes every object in a given group.
901   * @param {String} group The group id.
902   */    
903 	trashGroup:function(group) {
904 		if (!this._garbage[group]) this._garbage[group]={};
905 		for (var obj in this._objects[group])
906 			this._garbage[group][obj]=1;
907 	},
908   
909   /**
910   * Returns whether an object is due to be trashed. Useful in cases you want to check if
911   * an object is marked as trash before it is actually deleted.
912   * @param {Object} o The object you're checking.
913   * @returns {Boolean} True if the object is marked as trash.
914   */      
915 	objectIsTrash:function(o) { return o.__trashing },
916   
917   /**
918   * Creates a new game object. Generally speaking you pass a fully-defined object as the parameter (including a group, id, tileset, and so on).
919   * A group must be specified, or the program will crash. If no id is specified, then it will automatically provide 
920   * an id of 'obj-XXXX' where 'XXXX' is an automatically incrementing integer. This is where the <i>initialize</i>, <i>first</i>, and <i>blit</i>
921   * functions are defined, as well.
922   * @param {Object} data The object you wish to create.
923   * @returns {Object} The object you created.
924   * @example
925   * data = {
926   *   group: 'player',
927   *   id: 'player_id',
928   *   tileset: 'player_tiles', 
929   *   x: 0,
930   *   y: 0,
931   *   initialize: function() {
932       this.x = 10;
933       this.y = 10;
934       },
935   * };
936   * gbox.addObject(data);
937   */    
938 	addObject:function(data) {
939 		// Extras
940 		if (!data.id) {
941 			data.id="obj-"+this._autoid;
942 			this._autoid=(this._autoid+1)%1000;
943 		}
944 		if (data.tileset) {
945 			if (data.h==null) data.h=this._tiles[data.tileset].tileh;
946 			if (data.w==null) data.w=this._tiles[data.tileset].tilew;
947 			if (data.hw==null) data.hw=this._tiles[data.tileset].tilehw;
948 			if (data.hh==null) data.hh=this._tiles[data.tileset].tilehh;
949 		}
950 		this._objects[data.group][data.id]=data;
951 		if (data.zindex!=null)
952 			this.setZindex(this._objects[data.group][data.id],data.zindex);
953 		return this._objects[data.group][data.id];
954 	},
955   
956    /**
957   * Returns whether a given group contains no objets.
958   * @param {String} gid The group you're checking.
959   * @returns {Boolean} True if the group contains no objects.
960   */    
961 	groupIsEmpty:function(gid) { for (var i in this._objects[gid]) return false; return true; },
962   
963   /**
964   * Creates a new canvas. By default, the width and height is the current gbox._screenw and gbox._screenh,
965   * but it can also be set by passing in a data object with the appropriate parameters.
966   * @param {String} id The id of the new canvas.
967   * @param {Object} data (Optional) The height and width of the new canvas, contained in data.h and data.w parameters.
968   * @example
969   * gbox.createCanvas('newCanvas', {w: 640, h: 480});
970   */    
971 	createCanvas:function(id,data) {
972 		this.deleteCanvas(id);
973 		this._canvas[id]=document.createElement("canvas");
974 		this._canvas[id].setAttribute('height',(data&&data.h?data.h:this._screenh));
975 		this._canvas[id].setAttribute('width',(data&&data.w?data.w:this._screenw));
976 	},
977   
978   /**
979   * Deletes a given canvas.
980   * @param {String} id The id of the canvas to be deleted.
981   */  
982 	deleteCanvas:function(id) {
983 		if (this._canvas[id]) delete this._canvas[id];	
984 	},
985   
986   /**
987   * Checks to see if an image was successfully loaded.
988   * @param {String} id The id of the image.
989   * @returns {Boolean} True if the image has been loaded.
990   */    
991 	imageIsLoaded:function(id){ return this._images[id]&&(this._images[id].getAttribute("wasloaded"))&&this._images[id].width },
992   
993   /**
994   * Gets information about a loaded image.
995   * @param {String} id The id of the image.
996   * @returns {Object} A DOM Image element, including the URL and last modified date of the image, its ID, and whether it was loaded successfully.
997   * @example
998   * image = gbox.getImage('logo');
999   * image; // => <img src=?"logo.png?_brc=5-7-2010-15-48-42" src_org=?"logo.png" id=?"logo" wasloaded=?"true">?
1000   */
1001 	getImage:function(id){return this._images[id]},
1002   
1003   /**
1004   * Gets the buffer canvas (automatically created by gbox.initScreen).
1005   * @returns {Object} A DOM Canvas element, including the width and height of the canvas.
1006   */
1007 	getBuffer:function(){return this.getCanvas("_buffer")},
1008 
1009   /**
1010   * Gets the buffer canvas context.
1011   * @returns {Object} A DOM Canvas context object.
1012   */
1013 	getBufferContext:function(){ return (gbox._fskid>=gbox._frameskip?(this._db?this.getCanvasContext("_buffer"):this._screen.getContext("2d")):null) },
1014   
1015   /**
1016   * Gets a given canvas.
1017   * @param {Object} id The identifier of the canvas.
1018   * @returns {Object} A DOM Canvas element, including the width and height of the canvas.
1019   */
1020 	getCanvas:function(id){return this._canvas[id]},
1021   
1022   /**
1023   * Gets the two-dimensional canvas context of a given canvas. The object it returns contains all the drawing functions for the canvas.
1024   * See <a href = "http://dev.w3.org/html5/spec/Overview.html#the-canvas-element">W3C</a> and
1025   * <a href = "https://developer.mozilla.org/en/canvas_tutorial/basic_usage">Mozilla Developer Center</a> for details.
1026   * @param {Object} id The identifier of the canvas.
1027   * @returns {Object} A DOM Canvas context object.
1028   */
1029 	getCanvasContext:function(id){return this.getCanvas(id).getContext("2d");},
1030   
1031   /**
1032   * Adds an image file to the loader, assigning it to an ID. If adding an image to an existing ID, it checks to see if the file you're
1033   * adding is different than the one currently assigned to the ID. If it's different, it overwrites the old image. If it's the same, then
1034   * no action is taken.
1035   * @param {String} id The identifier of the image.
1036   * @param {String} filename The file name of the image.
1037   */
1038 	addImage:function(id,filename) {
1039 		if (this._images[id])
1040 			if (this._images[id].getAttribute("src_org")==filename)
1041 				return;
1042 			else
1043 				delete this._images[id];
1044 		this._addtoloader({type:"image",id:id,filename:filename});
1045 	},
1046   
1047   /**
1048   * Deletes an image currently in use. Does not delete the image file, but removes it from Akihabara's image list.
1049   * @param {String} id The identifier of the image.
1050   */
1051 	deleteImage:function(id) {
1052 		delete this._images[id];
1053 	},
1054   
1055   /**
1056   * Creates a new Akihabara tileset, adding it to the engine.
1057   * @param {Object} t An object containing: <ul><li>id {String}: the new id of the tileset</li>
1058   * <li>image {String}: reference to the tileset image loaded</li>
1059   * <li>tileh {Integer}: height in pixels of the tiles</li>
1060   * <li>tilew {Integer}: width in pixels of the tiles</li>
1061   * <li>tilerow {Integer}: width in pixels of each row in the font image</li>
1062   * <li>gapx {Integer}: x-coord gap between tile columns, in pixels</li>
1063   * <li>gapy {Integer}: y-coord gap between tile rows, in pixels</li></ul>
1064   */
1065 	addTiles:function(t) { 
1066 		t.tilehh=Math.floor(t.tileh/2);
1067 		t.tilehw=Math.floor(t.tilew/2);
1068 		this._tiles[t.id]=t;
1069 	},
1070 
1071   /**
1072   * Gets an Akihabara tileset, adding it to the engine.
1073   * @param {String} t The ID of a tileset.
1074   * @returns An object containing: <ul><li>id {String}: the new id of the tileset</li>
1075   * <li>image {String}: reference to the tileset image loaded</li>
1076   * <li>tileh {Integer}: height in pixels of the tiles</li>
1077   * <li>tilew {Integer}: width in pixels of the tiles</li>
1078   * <li>tilerow {Integer}: width in pixels of each row in the font image</li>
1079   * <li>gapx {Integer}: x-coord gap between tile columns, in pixels</li>
1080   * <li>gapy {Integer}: y-coord gap between tile rows, in pixels</li></ul>
1081   */
1082 	getTiles:function(t) { return this._tiles[t] },
1083   
1084   /**
1085   * Loads the initial splash screen and debugging font, then calls gbox._waitforloaded which adds to the game all the previously
1086   * defined resources. Once gbox._waitforloaded is done, it calls the callback function cb.
1087   * @params {String} cb The name of the function to be called when all assets are done loading.
1088   */
1089 	loadAll:function(cb) {
1090 		// Setup logger
1091 		if (this._canlog) this.log=console.log;
1092 		// Set the callback function, which is called after the resources are loaded.
1093 		if (!this._cb) this._cb = cb;
1094 		// Default stuff
1095 		this.addImage("_dbf","akihabara/debugfont.png");
1096 		if (this._splash.background) this.addImage("_splash",this._splash.background);
1097 		gbox.addFont({id:"_dbf",image:"_dbf",firstletter:" ",tileh:5,tilew:4,tilerow:16,gapx:0,gapy:0});
1098 		if (!gbox._splash.minimalTime)
1099 			gbox._minimalexpired=2;
1100 		this._waitforloaded();
1101 	},
1102   
1103 	_implicitsargs:function(data) {
1104 		if (data.camera) {
1105 			data.dx-=this._camera.x;
1106 			data.dy-=this._camera.y;	
1107 		}
1108 		if (data.sourcecamera) {
1109 			data.x=this._camera.x*(data.parallaxx?data.parallaxx:1);
1110 			data.y=this._camera.y*(data.parallaxy?data.parallaxy:1);	
1111 		}
1112 	},
1113   
1114   /**
1115   * Draws a tile to a canvas context
1116   * @param {Object} tox The canvas context to be drawn on.
1117   * @param {Object} data An object containing data about the tile to be drawn, including:
1118   * <ul><li>tileset {String}: the id of the tileset</li>
1119   * <li>tile {Integer}: the index of the tile within the tileset to be drawn</li>
1120   * <li>dx {Integer}: x coordinate to draw the tile at</li>
1121   * <li>dy {Integer}: y coordinate to draw the tile at</li>
1122   * <li>fliph {Integer}: horizontal flip, either 1 or -1</li>
1123   * <li>flipv {Integer}: vertical flip, either 1 or -1</li>
1124   * <li>alpha {Float}: alpha value (0 is transparent, 1 is opaque)</li></ul>
1125   * @example
1126   * // from capman, draws an current object's tile, called from inside its blit function
1127   * gbox.blitTile(gbox.getBufferContext(),{tileset:this.tileset,tile:this.frame,dx:this.x,dy:this.y,fliph:this.fliph,flipv:this.flipv,camera:this.camera,alpha:1});
1128   */
1129 	blitTile:function(tox,data) {
1130 		if (tox==null) return;
1131 		var ts=this._tiles[data.tileset];
1132 		var img=this.getImage(ts.image);
1133 		this._implicitsargs(data);
1134 		tox.save();
1135 		tox.globalAlpha=(data.alpha?data.alpha:1);
1136 		tox.translate((data.fliph?ts.tilew:0), (data.flipv?ts.tileh:0)); tox.scale((data.fliph?-1:1), (data.flipv?-1:1));
1137 		this._safedrawimage(tox,img, ts.gapx+(ts.tilew*(data.tile%ts.tilerow)),ts.gapy+(ts.tileh*Math.floor(data.tile/ts.tilerow)),(data.w==null?ts.tilew:data.w),(data.h==null?ts.tileh:data.h),data.dx*(data.fliph?-1:1),data.dy*(data.flipv?-1:1),(data.w?data.w:ts.tilew),(data.h?data.h:ts.tileh));
1138 		tox.restore();
1139 	},
1140 
1141   /**
1142   * Draws an image to a canvas context
1143   * @param {Object} tox The canvas context to be drawn on.
1144   * @param {Object} image The image to draw. Must be a DOM Image element, typicallly accessed via gbox.getImage
1145   * @param {Object} data An object containing data about the tile to be drawn, including:
1146   * <ul><li>dx {Integer}: (required) x coordinate to draw the image at</li>
1147   * <li>dy {Integer}: (required) y coordinate to draw the image at</li>
1148   * <li>fliph {Integer}: horizontal flip, either 1 or -1</li>
1149   * <li>flipv {Integer}: vertical flip, either 1 or -1</li>
1150   * <li>alpha {Float}: alpha value (0 is transparent, 1 is opaque)</li></ul>
1151   * @example
1152   * // draw an image at (100,100)
1153   * gbox.blitAll(gbox.getBufferContext(),gbox.getImage("image_id"),{dx:100,dy:100});
1154   */
1155 	blitAll:function(tox,image,data) {
1156 		if (tox==null) return;
1157 		this._implicitsargs(data);
1158 		tox.save();
1159 		tox.globalAlpha=(data.alpha?data.alpha:1);
1160 		tox.translate((data.fliph?image.width:0), (data.flipv?image.height:0)); tox.scale((data.fliph?-1:1), (data.flipv?-1:1));
1161 		this._safedrawimage(tox,image, 0,0, image.width,image.height,data.dx*(data.fliph?-1:1),data.dy*(data.flipv?-1:1),image.width,image.height);
1162 		tox.restore();
1163 	},
1164   
1165 	blit:function(tox,image,data) {
1166 		if (tox==null) return;
1167 		this._implicitsargs(data);
1168 		tox.save();
1169 		tox.globalAlpha=(data.alpha?data.alpha:1);
1170 		tox.translate((data.fliph?data.dw:0), (data.flipv?data.dh:0)); tox.scale((data.fliph?-1:1), (data.flipv?-1:1));
1171 		this._safedrawimage(tox,image,(data.x?data.x:0), (data.y?data.y:0),(data.w?data.w:data.dw),(data.h?data.h:data.dh),data.dx*(data.fliph?-1:1),data.dy*(data.flipv?-1:1),data.dw,data.dh);
1172 		tox.restore();
1173 	},
1174   
1175   
1176   /**
1177   * Draws a tilemap to a canvas context
1178   * @param {Object} tox The canvas context to be drawn on.
1179   * @param {Object} data An object containing a set of tilemap data, including:
1180   * <ul><li>tileset {String}: (required) the id of the tileset the tilemap is based on</li>
1181   * <li>map {Array}: an array whose x and y coord represent the tilemap coordinates, containing integers that correspond to the index of a given tile (or null for no tile)</li></ul>
1182   */
1183 	blitTilemap:function(tox,data) {
1184 		if (tox==null) return;
1185 		var ts=this._tiles[data.tileset];
1186 		for (var y=0;y<data.map.length;y++)
1187 			for (var x=0;x<data.map[y].length;x++)
1188 				if (data.map[y][x]!=null) this.blitTile(tox,{tileset:data.tileset,tile:data.map[y][x],dx:x*ts.tilew,dy:y*ts.tilew});
1189 	},
1190   
1191   
1192   /**
1193   * Draws text to a canvas context
1194   * @param {Object} tox The canvas context to be drawn on.
1195   * @param {Object} data An object containing a set of data, including:
1196   * <ul><li>font {String}: (required) the id of font to draw the text with</li>
1197   * <li>text {String}: (required) the text to display</li>
1198   * <li>dx {Integer}: (required) the x coordinate to draw the text at</li>
1199   * <li>dy {Integer}: (required) the y coordinate to draw the text at</li>
1200   * <li>dw {Integer}: the width of the text area -- required if you define data.halign</li>
1201   * <li>dh {Integer}: the height of the text area -- required if you define data.valign</li>
1202   * <li>valign {Integer}: either gbox.ALIGN_BOTTOM (aligns from the bottom of the text area) or gbox.ALIGN_MIDDLE (vertically centers text in text area)</li>
1203   * <li>halign {Integer}: either gbox.ALIGN_RIGHT (aligns to the right hand side of text area) or gbox.ALIGN_CENTER (horizontallly centers text in text area)</li>
1204   * <li>alpha {Float}: alpha value (0 is transparent, 1 is opaque)</li></ul>
1205   */
1206 	blitText:function(tox,data) {
1207 		if (tox==null) return;
1208 		data.text+=""; // Convert to string.
1209 		var fn=this._fonts[data.font];
1210 		var tile=0;
1211 		this._implicitsargs(data);
1212 		var dx=data.dx;
1213 		var dy=data.dy;			
1214 		if (data.valign==gbox.ALIGN_BOTTOM) dy = dy+data.dh-fn.tileh;
1215 		else if (data.valign==gbox.ALIGN_MIDDLE) dy = dy+Math.floor(data.dh/2)-fn.tileh;
1216 		if (data.halign==gbox.ALIGN_RIGHT) dx = dx+data.dw-(data.text.length*fn.tilew);
1217 		else if (data.halign==gbox.ALIGN_CENTER) dx = dx+Math.floor((data.dw-(data.text.length*fn.tilew))/2);
1218 		tox.save();
1219 		tox.globalAlpha=(data.alpha?data.alpha:1);
1220 		for (var y=0;y<data.text.length;y++) {
1221 			tile=data.text.charCodeAt(y)-fn.firstascii;
1222 			if (tile>=0) {
1223 				if (data.clear) tox.clearRect(dx+(y*fn.tilew),dy,(data.w?data.w:fn.tilew),(data.h?data.h:fn.tileh));
1224 				this._safedrawimage(tox,this.getImage(fn.image), fn.gapx+(fn.tilew*(tile%fn.tilerow)),
1225 				fn.gapy+(fn.tileh*Math.floor(tile/fn.tilerow)),fn.tilew,fn.tileh,dx+(y*fn.tilew),dy,(data.w?data.w:fn.tilew),(data.h?data.h:fn.tileh));
1226 			}
1227 		}
1228 		tox.restore();
1229 	},
1230   
1231   /**
1232   * Clears a rectangular area of a canvas context.
1233   * @param {Object} image The canvas context to be drawn on.
1234   * @param {Object} data An object containing a set of data, including:
1235   * <ul><li>x {Integer}: (required) the x coordinate of the top-left corner of the rectangle</li>
1236   * <li>y {Integer}: (required) the y coordinate of the top-left corner of the rectangle</li>
1237   * <li>w {Integer}: the width of the box; defaults to canvas width</li>
1238   * <li>h {Integer}: the height the box; defaults to canvas height</li></ul>
1239   */
1240 	blitClear:function(image,data) {
1241 		if (image==null) return;
1242 		if (data==null) data={x:0,y:0};
1243 		this._implicitsargs(data);
1244 		image.clearRect(data.x,data.y,(data.w==null?image.canvas.width:data.w),(data.h==null?image.canvas.height:data.h));
1245 	},
1246   
1247   /**
1248   * Draws an image directly to the screen's current canvas context. Used internally in gbox.go(). Probably shouldn't be used otherwise.
1249   */
1250 	blitImageToScreen:function(image) {
1251 		this._screen.getContext("2d").drawImage(image,0,0);
1252 	},
1253   
1254    /**
1255   * Draws a filled rectangle over an entire canvas context.
1256   * @param {Object} tox The canvas context to be filled.
1257   * @param {Object} data An object containing a set of data, including:
1258   * <ul><li>alpha {Float}: the alpha value of the rectangle; defaults to 1</li>
1259   * <li>color {Object}: the color of the box, formatted rgb(rValue, gValue, bValue); default black</li></ul>
1260   */
1261 	blitFade:function(tox,data) { 
1262 		if (tox) this.blitRect(tox,{x:0,y:0,w:tox.canvas.width,h:tox.canvas.height,alpha:data.alpha,color:data.color});
1263 	},
1264   
1265   /**
1266   * Draws a filled rectangle to a canvas context.
1267   * @param {Object} tox The canvas context to be drawn on.
1268   * @param {Object} data An object containing a set of data, including:
1269   * <ul><li>x {Integer}: (required) the x coordinate of the top-left corner of the rectangle</li>
1270   * <li>y {Integer}: (required) the y coordinate of the top-left corner of the rectangle</li>
1271   * <li>w {Integer}: (required) the width of the box</li>
1272   * <li>h {Integer}: (required) the height the box</li>
1273   * <li>alpha {Float}: the alpha value of the rectangle; defaults to 1</li>
1274   * <li>color {Object}: the color of the box, formatted rgb(rValue, gValue, bValue); default black</li></ul>
1275   */
1276 	blitRect:function(tox,data) {
1277 		if (tox==null) return;
1278 		tox.save();
1279 		tox.globalAlpha=(data.alpha?data.alpha:1);
1280 		tox.fillStyle = (data.color?data.color:gbox.COLOR_BLACK);
1281 		tox.fillRect(data.x,data.y,data.w,data.h);
1282 		tox.restore();
1283 	},
1284   
1285   /**
1286   * Calculates a box collision between two collision boxes within a given tolerance. A higher tolerance means less precise collision.
1287   * @param {Object} o1 A collision box you're testing for collision. Must contain:
1288   * <ul><li>x {Integer}: (required) the x coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1289   * <li>y {Integer}: (required) the y coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1290   * <li>w {Integer}: (required) the width of the box</li>
1291   * <li>h {Integer}: (required) the height the box</li></ul>
1292   * @param {Object} o2 A collision box you're testing for collision. Must contain:
1293   * <ul><li>x {Integer}: (required) the x coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1294   * <li>y {Integer}: (required) the y coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1295   * <li>w {Integer}: (required) the width of the box</li>
1296   * <li>h {Integer}: (required) the height the box</li></ul>
1297   * @param {Integer} t The tolerance for the collision, in pixels. A value of 0 means pixel-perfect box collision. A value of 2 would mean that the
1298   * boxes could overlap by up to 2 pixels without being considered a collision.
1299   * @returns True if the two collision boxes are colliding within the given tolerance.
1300   */  
1301 	collides:function(o1,o2,t) {
1302 		if (!t) t=0;
1303 		return !((o1.y+o1.h-1-t<o2.y+t) || (o1.y+t> o2.y+o2.h-1-t) || (o1.x+o1.w-1-t<o2.x+t) || (o1.x+t>o2.x+o2.w-1-t));
1304 	},
1305   
1306   /**
1307   * Calculates a point-box collision between a point and a collision box within a given tolerance. A higher tolerance means less precise collision.
1308   * @param {Object} o1 A point you're testing for collision. Must contain:
1309   * <ul><li>x {Integer}: (required) the x coordinate of the point</li>
1310   * <li>y {Integer}: (required) the y coordinate of the point</li></ul>
1311   * @param {Object} o2 A collision box you're testing for collision. Must contain:
1312   * <ul><li>x {Integer}: (required) the x coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1313   * <li>y {Integer}: (required) the y coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1314   * <li>w {Integer}: (required) the width of the box</li>
1315   * <li>h {Integer}: (required) the height the box</li></ul>
1316   * @param {Integer} t The tolerance for the collision, in pixels. A value of 0 means pixel-perfect collision. A value of 2 would mean that the
1317   * point could exist within the outermost 2 pixels of the box without being considered a collision.
1318   * @returns True if the point is colliding with the box within the given tolerance.
1319   */  
1320 	pixelcollides:function(o1,o2,t) {
1321 		if (!t) t=0;
1322 		return !((o1.y<o2.y+t) || (o1.y> o2.y+o2.h-1-t) || (o1.x<o2.x+t) || (o1.x>o2.x+o2.w-1-t));
1323 	},
1324   
1325   /**
1326   * Determines whether an object is visible by seeing if it collides with the camera's viewport.
1327   * @param {Object} obj The object you're testing to see if it's visible. Must contain:
1328   * <ul><li>x {Integer}: (required) the x coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1329   * <li>y {Integer}: (required) the y coordinate of the object's origin; assumes the Akihabara default of top-left being the origin</li>
1330   * <li>w {Integer}: (required) the width of the object's collision box</li>
1331   * <li>h {Integer}: (required) the height the object's box</li></ul>
1332   * @returns True if the object's collision box is within the camera's viewport.
1333   */  
1334 	objectIsVisible:function(obj) { return this.collides(obj,this._camera,0); },
1335 	
1336 	// --- 
1337 	// --- 
1338 	// ---  AUDIO ENGINE
1339 	// --- 
1340 	// --- 
1341 	
1342 	_audiochannels:{},
1343 	_audiomastervolume:1.0,
1344 	_canaudio:false,
1345 	_audiodequeuetime:0,
1346 	_audioprefetch:0.5,
1347 	_audiocompatmode:0, // 0: pause/play, 1: google chrome compatibility, 2: ipad compatibility (single channel)
1348 	_createmode:0, // 0: clone, 1: rehinstance
1349 	_fakecheckprogressspeed:100, // Frequency of fake audio monitoring
1350 	_fakestoptime:1, // Fake audio stop for compatibility mode
1351 	_audioteam:2,
1352 	_loweraudioteam:1,
1353 	_audio:{lding:null,qtimer:false,aud:{},ast:{}},
1354 	_audioactions:[],
1355 	_showplayers:false,
1356 	_singlechannelname:"bgmusic",
1357 	_positiondelay:0,
1358 	_playerforcer:0,
1359 	_audiomutevolume:0.0001, // Zero is still not accepted by everyone :(
1360 	_rawstopaudio:function(su) {
1361 		if (gbox._audiocompatmode==1) {
1362 			if (su.duration-su.currentTime>gbox._fakestoptime)
1363 				su.currentTime=su.duration-gbox._fakestoptime;
1364 			su.muted=true;
1365 		} else
1366 			su.pause();
1367 
1368 	},
1369 	_rawplayaudio:function(su) {
1370 		if (gbox._audiocompatmode==1) {
1371 			try { su.currentTime=0; } catch (e) {}
1372 			su.muted=false;
1373 			su.play();
1374 		} else if (gbox._audiocompatmode==2) {
1375 			su.load();
1376 			gbox._playerforcer=setInterval(function(e){try{su.play();clearInterval(gbox._playerforcer)}catch(e){}},1000);
1377 		} else {
1378 			try { su.currentTime=0; } catch (e) {}
1379 			su.play();
1380 		}
1381 	},
1382 	_finalizeaudio:function(ob,who,donext){
1383 	
1384 		var cur=(who?who:this);
1385 		cur.removeEventListener('ended', gbox._finalizeaudio,false);
1386 		cur.removeEventListener('timeupdate', gbox._checkprogress,false);
1387 		
1388 		cur.addEventListener('ended', gbox._playbackended,false);
1389 		if (donext) gbox._loaderloaded();
1390 	},
1391 	_audiodoload:function() {
1392 		if (gbox._audiocompatmode==1) gbox._audio.lding.muted=true;
1393 		else if (gbox._audiocompatmode==2)
1394 			gbox._finalizeaudio(null,gbox._audio.lding,true);
1395 		else {
1396 			gbox._audio.lding.load();
1397 			gbox._audio.lding.play();
1398 		}
1399 	},
1400 	_timedfinalize:function() {
1401 		gbox._rawstopaudio(gbox._audio.lding);
1402 		gbox._finalizeaudio(null,gbox._audio.lding,true);	
1403 	},
1404 	_checkprogress:function() {
1405 		if (gbox._audio.lding.currentTime>gbox._audioprefetch) gbox._timedfinalize();
1406 	},
1407 	_fakecheckprogress:function() {
1408 		if (gbox._audio.lding.currentTime>gbox._audioprefetch) gbox._timedfinalize(); else setTimeout(gbox._fakecheckprogress,gbox._fakecheckprogressspeed);	
1409 	},
1410 	_audiofiletomime:function(f) {
1411 		var fsp=f.split(".");
1412 		switch (fsp.pop().toLowerCase()) {
1413 			case "ogg": { return "audio/ogg"; break }
1414 			case "mp3": { return "audio/mpeg"; break }
1415 			default: {
1416 				return "audio/mpeg";
1417 			}
1418 		}
1419 	},
1420 	_pushaudio:function(){try {this.currentTime=1.0} catch(e){} },
1421 	_createnextaudio:function(cau) {
1422 		if (cau.def) {
1423 			gbox.deleteAudio(cau.id);
1424 			this._audio.aud[cau.id]=[];
1425 			this._audio.ast[cau.id]={cy:-1,volume:1,channel:null,play:false,mute:false,filename:cau.filename[0]};
1426 			if (cau.def) for (var a in cau.def) this._audio.ast[cau.id][a]=cau.def[a];
1427 		}
1428 		if ((gbox._createmode==0)&&(cau.team>0)) {
1429 			var ael =this._audio.aud[cau.id][0].cloneNode(true);
1430 			gbox._finalizeaudio(null,ael,false);
1431 		} else {
1432 			var ael=document.createElement('audio');
1433 			ael.volume=gbox._audiomutevolume;
1434 		}
1435 		if (!gbox._showplayers) {
1436 			ael.style.display="none";
1437 			ael.style.visibility="hidden";
1438 			ael.style.width="1px";
1439 			ael.style.height="1px";
1440 			ael.style.position="absolute";
1441 			ael.style.left="0px";
1442 			ael.style.top="-1000px";
1443 		}
1444 		ael.setAttribute('controls',gbox._showplayers);
1445 		ael.setAttribute('aki_id',cau.id);
1446 		ael.setAttribute('aki_cnt',cau.team);
1447 		ael.addEventListener('loadedmetadata', gbox._pushaudio,false); // Push locked audio in safari
1448 		if (((gbox._createmode==0)&&(cau.team==0))||(gbox._createmode==1)) {
1449 			if (ael.canPlayType) {
1450 				var cmime;
1451 				for (var i=0;i<cau.filename.length;i++) {
1452 					cmime=gbox._audiofiletomime(cau.filename[i]);
1453 					if (("no" != ael.canPlayType(cmime)) && ("" != ael.canPlayType(cmime))) {
1454 						ael.src=gbox._breakcacheurl(cau.filename[i]);
1455 						break;
1456 					}
1457 				}
1458 			} else {
1459 				for (var i=0;i<cau.filename.length;i++) {
1460 					var src=document.createElement('source');
1461 					src.setAttribute('src', gbox._breakcacheurl(cau.filename[i]));
1462 					ael.appendChild(src);
1463 				}
1464 			}
1465 			ael.addEventListener('ended',this._finalizeaudio,false);
1466 			if (gbox._audiocompatmode==1)
1467 				setTimeout(gbox._fakecheckprogress,gbox._fakecheckprogressspeed);
1468 			else
1469 				ael.addEventListener('timeupdate',this._checkprogress,false);
1470 			ael.setAttribute('buffering',"auto");
1471 			ael.volume=0;
1472 			this._audio.aud[cau.id].push(ael);
1473 			document.body.appendChild(ael);
1474 			gbox._audio.lding=ael;
1475 			setTimeout(gbox._audiodoload,1);
1476 		} else {
1477 			this._audio.aud[cau.id].push(ael);
1478 			document.body.appendChild(ael);
1479 			gbox._loaderloaded();
1480 		}
1481 	},
1482 	_playbackended:function(e) {
1483 		if (gbox._audio.ast[this.getAttribute('aki_id')].cy==this.getAttribute('aki_cnt')) {
1484 			if (gbox._audio.ast[this.getAttribute('aki_id')].play&&gbox._audio.ast[this.getAttribute('aki_id')].loop)
1485 				if (gbox._audiocompatmode==2)
1486 					gbox._rawplayaudio(this);
1487 				else
1488 					this.currentTime=0;
1489 			else
1490 				gbox._audio.ast[this.getAttribute('aki_id')].play=false; 
1491 		} else if (gbox._audiocompatmode==1) {
1492 			this.pause();
1493 			this.muted=false;
1494 		}
1495 	},
1496 	_updateaudio:function(a) {
1497 		if (this._audio.ast[a].play) {
1498 			this._audio.aud[a][this._audio.ast[a].cy].volume=(this._audio.ast[a].mute?this._audiomutevolume: 
1499 				this._audiomastervolume*
1500 				(this._audio.ast[a].volume!=null?this._audio.ast[a].volume:1)*
1501 				((this._audio.ast[a].channel!=null)&&(this._audiochannels[this._audio.ast[a].channel]!=null)&&(this._audiochannels[this._audio.ast[a].channel].volume!=null)?this._audiochannels[this._audio.ast[a].channel].volume:1)
1502 			)
1503 		}
1504 	},
1505 	_minimaltimeexpired:function() { gbox._minimalexpired=2; },
1506 	_splashscreeniscompleted:function() { return (gbox._splash.background?gbox.imageIsLoaded("_splash"):true) && (gbox._splash.minilogo?gbox.imageIsLoaded("logo"):true) && (gbox._splash.footnotes?gbox.imageIsLoaded("_dbf"):true) },
1507 	_addqueue:function(a) {
1508 		if (!gbox._audiodequeuetime)
1509 			gbox._dequeueaudio(null,a);
1510 		else {
1511 			gbox._audioactions.push(a);
1512 			if (!gbox._audio.qtimer) {
1513 				gbox._audio.qtimer=true;
1514 				setTimeout(gbox._dequeueaudio,gbox._audiodequeuetime);
1515 			}
1516 		}
1517 	},
1518 	_dequeueaudio:function(k,rt) {
1519 			var ac=(rt?rt:gbox._audioactions.pop());
1520 			switch (ac.t) {
1521 				case 0: {
1522 					gbox._updateaudio(ac.a.getAttribute("aki_id"));
1523 					gbox._rawplayaudio(ac.a);
1524 					break
1525 				}
1526 				case 1: {
1527 					gbox._rawstopaudio(ac.a);
1528 					break;
1529 				}
1530 				case 2: {
1531 					gbox._updateaudio(ac.a.getAttribute("aki_id"));
1532 					break;
1533 				}
1534 			}
1535 			if (!rt&&gbox._audioactions.length) {
1536 				gbox._audio.qtimer=true;
1537 				setTimeout(gbox._dequeueaudio,gbox._audiodequeuetime);
1538 			} else gbox._audio.qtimer=false;
1539 	
1540 	},
1541 	getAudioIsSingleChannel:function() { return this._audiocompatmode==2; },
1542 	setAudioPositionDelay:function(m) { gbox._positiondelay=m },
1543 	setAudioDequeueTime:function(m) { gbox._audiodequeuetime=m },
1544 	setShowPlayers:function(m) { gbox._showplayers=m},
1545 	setAudioCompatMode:function(m) { gbox._audiocompatmode=m },
1546 	setAudioCreateMode:function(m) { gbox._createmode=m },
1547 	addAudio:function(id,filename,def) {
1548 		if (gbox._canaudio) {
1549 			if (gbox._audio.aud[id])
1550 				if (gbox._audio.ast[id].filename==filename[0])
1551 					return;
1552 				else
1553 					gbox.deleteAudio(id);
1554 			if ((gbox._audiocompatmode!=2)||(def.channel==gbox._singlechannelname)) {
1555 				var grsize=(def.channel==gbox._singlechannelname?gbox._loweraudioteam:(def.background?gbox._loweraudioteam:gbox._audioteam));
1556 				for (var i=0;i<grsize;i++)
1557 					gbox._addtoloader({type:"audio",data:{id:id,filename:filename,def:(i==0?def:null),team:i}});
1558 			}
1559 		}
1560 	},
1561 	deleteAudio:function(id) {
1562 		if (gbox._audio.aud[id]) {
1563 			for (var i=0;i<gbox._audio.aud[id].length;i++) {
1564 				try {document.body.removeChild(gbox._audio.aud[id][i]);}catch(e){}
1565 				delete gbox._audio.aud[id][i];
1566 			}
1567 			delete gbox._audio.aud[id];
1568 			if (gbox._audio.ast[id]) delete gbox._audio.ast[id];
1569 		}
1570 	},
1571 	playAudio:function(a,data) {
1572 		if (this._canaudio&&this._audio.ast[a])
1573 			if (!this._audio.ast[a].play) this.hitAudio(a,data);
1574 	},
1575 	hitAudio:function(a,data) {
1576 		if (this._canaudio&&this._audio.ast[a]) {
1577 			var ael;
1578 			if (this._audio.ast[a].cy!=-1)
1579 				this.stopAudio(a,true);
1580 			this._audio.ast[a].cy=(this._audio.ast[a].cy+1)%this._audio.aud[a].length;
1581 			ael=this._audio.aud[a][this._audio.ast[a].cy];
1582 			if (data) 
1583 				for (var n in data) this._audio.ast[a][n]=data[n];
1584 			this._audio.ast[a].play=true;
1585 			this._addqueue({t:0,a:ael});
1586 		}
1587 	},
1588 	stopAudio:function(a,permissive) {
1589 		if (this._canaudio) {
1590 			var ael;
1591 			if (this._canaudio&&this._audio.ast[a]&&this._audio.ast[a].play) {
1592 				this._audio.ast[a].play=false;
1593 				ael=this._audio.aud[a][this._audio.ast[a].cy];
1594 				if (ael.duration-1.5>0)
1595 					this._addqueue({t:1,a:ael});
1596 			}
1597 		}
1598 	},
1599 	setSplashSettings:function(a) { for (var n in a) this._splash[n]=a[n]; },
1600 	resetChannel:function(ch) {
1601 		if (this._canaudio&&this._audiochannels[ch])
1602 			if (ch=="master")
1603 				for (var ch in this._audiochannels)
1604 					this.setChannelVolume(ch,this._audiochannels[ch]._def.volume);
1605 			else if (this._audiochannels[ch])
1606 				this.setChannelVolume(ch,this._audiochannels[ch]._def.volume);
1607 	},
1608 	getChannelDefaultVolume:function(ch) {
1609 		if (this._canaudio&&this._audiochannels[ch]) return this._audiochannels[ch]._def.volume; else return null;
1610 	},
1611 	setChannelVolume:function(ch,a) {
1612 		if (this._canaudio&&this._audiochannels[ch]) {
1613 			if (ch=="master") this._audiomastervolume=a; else this._audiochannels[ch].volume=a
1614 			for (var j in gbox._audio.aud)
1615 				if (this._audio.ast[j].cy>-1) this._updateaudio(j);
1616 		}
1617 	},
1618 	getChannelVolume:function(ch) { if (ch=="master") return this._audiomastervolume; else if (this._audiochannels[ch]) return this._audiochannels[ch].volume; else return 0 },
1619 	changeChannelVolume:function(ch,a) {
1620 		if (this._canaudio&&this._audiochannels[ch]) {
1621 			var vol=this.getChannelVolume(ch)+a;
1622 			if (vol>1) vol=1; else if (vol<0) vol=0;
1623 			this.setChannelVolume(ch,vol);
1624 		}
1625 	},
1626 	stopChannel:function(ch) {
1627 		if (this._canaudio)
1628 			for (var j in gbox._audio.aud)
1629 				if (this._audio.ast[j].cy>-1&&gbox._audio.ast[j].play&&((ch=="master")||(this._audio.ast[j].channel==ch)))
1630 					this.stopAudio(j);
1631 	},
1632 	
1633 	setAudioUnmute:function(a) { if (this._canaudio&&this._audio.ast[a]) { this._audio.ast[a].mute=false; this._updateaudio(a); } },
1634 	setAudioMute:function(a) { if (this._canaudio&&this._audio.ast[a]) { this._audio.ast[a].mute=true; this._updateaudio(a); } },
1635 	getAudioMute:function(a) { if (this._canaudio&&this._audio.ast[a]) return this._audio.ast[a].mute; else return null},
1636 	
1637 	setAudioVolume:function(a,vol) { if (this._canaudio&&this._audio.ast[a]) { this._audio.ast[a].volume=vol; this._updateaudio(a); } },
1638 	getAudioVolume:function(a,vol) { if (this._canaudio&&this._audio.ast[a]) return this._audio.ast[a].volume; else return null},
1639 	
1640 	setAudioPosition:function(a,p) {  if (this._canaudio&&this._audio.ast[a]&&this._audio.aud[a][this._audio.ast[a].cy]) this._audio.aud[a][this._audio.ast[a].cy].currentTime=p;},
1641 	getAudioPosition:function(a) {if (this._canaudio&&this._audio.ast[a]&&this._audio.aud[a][this._audio.ast[a].cy]) if (this._audio.aud[a][this._audio.ast[a].cy].currentTime>this._positiondelay) return this._audio.aud[a][this._audio.ast[a].cy].currentTime-this._positiondelay; else return 0; else return 0},
1642 
1643 	getAudioDuration:function(a) {if (this._canaudio&&this._audio.ast[a]&&this._audio.aud[a][this._audio.ast[a].cy]) return this._audio.aud[a][this._audio.ast[a].cy].duration; else return 0},
1644 
1645 	changeAudioVolume:function(a,vol) { if (this._canaudio&&this._audio.ast[a]) { if (this._audio.ast[a].volume+vol>1) this._audio.ast[a].volume=1; else  if (this._audio.ast[a].volume+vol<0) this._audio.ast[a].volume=0; else this._audio.ast[a].volume+=vol; this._updateaudio(a); } },
1646 	setCanAudio:function(a) { this._canaudio=!this._flags.noaudio&&a;},
1647 	setAudioChannels:function(a){
1648 		this._audiochannels=a;
1649 		for (var ch in a) {
1650 			this._audiochannels[ch]._def={};
1651 			for (var attr in this._audiochannels[ch])
1652 				if (attr!="_def") this._audiochannels[ch]._def[attr]=this._audiochannels[ch][attr];
1653 		}
1654 	},
1655 	setAudioTeam:function(a){ this._audioteam=a; },
1656 	setLowerAudioTeam:function(a){ this._loweraudioteam=a; },
1657 	
1658 	// ---
1659 	// ---
1660 	// ---  DYNAMIC SCRIPT INCLUSION
1661 	// ---
1662 	// ---
1663 	
1664 	addScript:function(call) {
1665 		gbox._addtoloader({type:"script",call:call});
1666 	},
1667 	
1668 	// ---
1669 	// ---
1670 	// ---  BUNDLES
1671 	// ---
1672 	// ---
1673 	
1674 	addBundle:function(call){
1675 		gbox._addtoloader({type:"bundle",call:call});
1676 	},
1677 	
1678 	readBundleData:function(pack,call) {
1679 		// Local resources first
1680 		if (pack.setObject) for (var i=0;i<pack.setObject.length;i++) eval("("+pack.setObject[i].object+")")[pack.setObject[i].property]=pack.setObject[i].value;
1681 		if (pack.addFont) for (var i=0;i<pack.addFont.length;i++) gbox.addFont(pack.addFont[i]);
1682 		if (pack.addTiles) for (var i=0;i<pack.addTiles.length;i++) gbox.addTiles(pack.addTiles[i]);
1683 		// Remote resources for last
1684 		if (pack.addImage) for (var i=0;i<pack.addImage.length;i++) gbox.addImage(pack.addImage[i][0],pack.addImage[i][1]);
1685 		if (pack.addAudio) for (var i=0;i<pack.addAudio.length;i++) gbox.addAudio(pack.addAudio[i][0],pack.addAudio[i][1],pack.addAudio[i][2]);
1686 		if (pack.addBundle) for (var i=0;i<pack.addBundle.length;i++) gbox.addBundle(pack.addBundle[i]);
1687 		if (pack.addScript) for (var i=0;i<pack.addScript.length;i++) gbox.addScript(pack.addScript[i]);
1688 		// Trigger the onLoad events in resource and loader
1689 		if (pack.onLoad) gbox._addtoloader({type:"exec-onl",func:pack.onLoad,call:call,pack:pack});				
1690 		if (call.onLoad) gbox._addtoloader({type:"exec-onl",func:call.onLoad,call:call,pack:pack});	
1691 	},
1692 	
1693 	// --- 
1694 	// --- 
1695 	// ---  DATA LOADER
1696 	// --- 
1697 	// --- 
1698 
1699 	_xmlhttp:null,
1700 	_loaderqueue:cyclelist.create(200),
1701 	_loadercache:cachelist.create(30),
1702 	
1703 	// Callback for loaded image
1704 	_loaderimageloaded:function() {
1705 		this.setAttribute('wasloaded',true);
1706 		this.hheight=Math.floor(this.height/2);
1707 		this.hwidth=Math.floor(this.width/2);
1708 		gbox._loaderloaded();	
1709 	},
1710 	// Callback for loaded bundle
1711 	_loaderhmlhttploading:function(){
1712 		if(this.readyState == 4 && (this.status == 0||this.status == 200)) {
1713 			if (this.responseText) {
1714 				if (!gbox._loaderqueue.getCurrent().call.skipCacheSave)
1715 					gbox._loadercache.add(gbox._loaderqueue.getCurrent().call.file,this.responseText);
1716 				var pack=eval("("+this.responseText+")");
1717 				gbox.readBundleData(pack,gbox._loaderqueue.getCurrent().call);
1718 				// Keep loading the other resources.
1719 				gbox._loaderloaded();
1720 			}	
1721 		}
1722 	},
1723 	
1724 	// Loader code
1725 	_addtoloader:function(d) { // type:xx, data:yy
1726 		gbox._loaderqueue.push(d);
1727 		if (!gbox._loaderqueue.isProcessing())
1728 			gbox._loadnext();
1729 	},
1730 	_loaderloaded:function() {
1731 		setTimeout(gbox._loadnext,10);
1732 	},
1733 	_loaderscript:function() {
1734 		if (gbox._loaderqueue.getCurrent().call.onLoad) gbox._addtoloader({type:"exec-onl",func:gbox._loaderqueue.getCurrent().call.onLoad,call:gbox._loaderqueue.getCurrent().call});
1735 		gbox._loadnext();
1736 	},
1737 	_loadnext:function() {
1738 		var current=gbox._loaderqueue.pop();
1739 		if (gbox._loaderqueue.isProcessing()) {
1740 			switch (gbox._loaderqueue.getCurrent().type) {
1741 				case "image":{
1742 					gbox._images[current.id]=new Image();
1743 					gbox._images[current.id].addEventListener('load', gbox._loaderimageloaded,false);
1744 					gbox._images[current.id].src=gbox._breakcacheurl(current.filename);
1745 					gbox._images[current.id].setAttribute('src_org',current.filename);
1746 					gbox._images[current.id].setAttribute('id',current.id);
1747 					gbox._images[current.id].setAttribute('wasloaded',false);
1748 					break;
1749 				}
1750 				case "bundle":{
1751 					var done=false;
1752 					if (!current.call.skipCacheLoad) {
1753 						var data=gbox._loadercache.read(current.call.file);
1754 						if (data) {
1755 							var pack=eval("("+data+")");
1756 							gbox.readBundleData(pack,current.call);
1757 							// Keep loading the other resources.
1758 							gbox._loaderloaded();
1759 							done=true;
1760 						}
1761 					}
1762 					if (!done) {
1763 						gbox._xmlhttp=new XMLHttpRequest();
1764 						gbox._xmlhttp.open((current.call.data?"POST":"GET"), gbox._breakcacheurl(current.call.file),true);
1765 						gbox._xmlhttp.onreadystatechange = gbox._loaderhmlhttploading;
1766 						if (current.call.data) {
1767 							gbox._xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1768 							gbox._xmlhttp.send(current.call.data);
1769 						} else gbox._xmlhttp.send();
1770 					}
1771 					break;
1772 				}
1773 				case "audio":{
1774 					gbox._createnextaudio(current.data);
1775 					break;
1776 				}
1777 				case "exec-onl":{
1778 					current.func(current.call,current.pack);
1779 					gbox._loaderloaded();
1780 					break;
1781 				}
1782 				case "script":{
1783 					var script= document.createElement('script');
1784 					script.type="text/javascript";
1785 					script.onload=gbox._loaderscript;
1786 					script.src=current.call.file;
1787 					document.getElementsByTagName('body')[0].appendChild(script);
1788 					break;
1789 				}
1790 			}
1791 		}
1792 	
1793 	},
1794 	_waitforloaded:function() {
1795 		var aul;
1796 		if (gbox._loaderqueue.isBusy()||(gbox._minimalexpired!=2)) {
1797 			var tox=gbox._screen.getContext("2d");
1798 			tox.save();
1799 			gbox.blitFade(tox,{alpha:1});
1800 			if (!gbox._minimalexpired&&gbox._splashscreeniscompleted()) {
1801 				gbox._minimalexpired=1;
1802 				setTimeout(gbox._minimaltimeexpired,gbox._splash.minimalTime);
1803 			}
1804 			if (gbox._splash.loading) gbox._splash.loading(tox,gbox._loaderqueue.getDone(),gbox._loaderqueue.getTotal());
1805 			if (gbox._splash.background&&gbox.imageIsLoaded("_splash"))
1806 				gbox.blit(tox,gbox.getImage("_splash"),{w:gbox.getImage("_splash").width,h:gbox.getImage("_splash").height,dx:0,dy:0,dw:gbox.getScreenW(),dh:gbox.getScreenH()});
1807 			if (gbox._splash.minilogo&&gbox.imageIsLoaded("logo")) {
1808 				var dw=gbox.getScreenW()/4;
1809 				var dh=(gbox.getImage("logo").height*dw)/gbox.getImage("logo").width
1810 				gbox.blit(tox,gbox.getImage(gbox._splash.minilogo),{w:gbox.getImage("logo").width,h:gbox.getImage("logo").height,dx:gbox.getScreenW()-dw-5,dy:gbox.getScreenH()-dh-5,dw:dw,dh:dh});
1811 			}
1812 			if (gbox._splash.footnotes&&gbox.imageIsLoaded("_dbf")) {
1813 				if (!gbox.getCanvas("_footnotes")) {
1814 					var fd=gbox.getFont("_dbf");
1815 					gbox.createCanvas("_footnotes",{w:gbox.getScreenW()-5,h:(gbox._splash.footnotes.length)*(fd.tileh+gbox._splash.footnotesSpacing)});
1816 					for (var i=0;i<gbox._splash.footnotes.length;i++)
1817 						gbox.blitText(gbox.getCanvasContext("_footnotes"),{
1818 										font:"_dbf",
1819 										dx:0,
1820 										dy:i*(fd.tileh+gbox._splash.footnotesSpacing),
1821 										text:gbox._splash.footnotes[i]
1822 									});
1823 				}
1824 				gbox.blitAll(tox,gbox.getCanvas("_footnotes"),{dx:5,dy:gbox.getScreenH()-gbox.getCanvas("_footnotes").height-5});
1825 			}
1826 			if (gbox._loaderqueue.isBusy()) {
1827 				var bw=Math.floor(((gbox.getScreenW()-4)*gbox._loaderqueue.getDone())/gbox._loaderqueue.getTotal());
1828 				tox.globalAlpha=1;
1829 				tox.fillStyle = gbox._splash.gaugeBorderColor;
1830 				tox.fillRect(0,Math.floor((gbox.getScreenH()-gbox._splash.gaugeHeight)/2),gbox.getScreenW(),gbox._splash.gaugeHeight);
1831 				tox.fillStyle = gbox._splash.gaugeBackColor;
1832 				tox.fillRect(1,Math.floor(((gbox.getScreenH()-gbox._splash.gaugeHeight)/2)+1),gbox.getScreenW()-4,gbox._splash.gaugeHeight-2);
1833 				tox.fillStyle = gbox._splash.gaugeColor;
1834 				tox.fillRect(1,Math.floor(((gbox.getScreenH()-gbox._splash.gaugeHeight)/2)+1),(bw>0?bw:0),gbox._splash.gaugeHeight-2);
1835 			}
1836 			tox.restore();		
1837 			gbox.setStatBar("Loading... ("+gbox._loaderqueue.getDone()+"/"+gbox._loaderqueue.getTotal()+")");
1838 			setTimeout(gbox._waitforloaded,50);
1839 		} else {
1840 			gbox.deleteImage("_splash");
1841 			gbox.setStatBar();
1842 			gbox._cb();
1843 		}
1844 	},
1845 	clearCache:function() { this._loadercache.clear(); }
1846 
1847 };
1848 
1849