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