diff --git a/dev/index.html b/dev/index.html new file mode 100755 index 0000000..5deb3f3 --- /dev/null +++ b/dev/index.html @@ -0,0 +1,32 @@ + + + + Impact Game + + + + + + + + + diff --git a/dev/lib/game/main.js b/dev/lib/game/main.js new file mode 100755 index 0000000..e9932da --- /dev/null +++ b/dev/lib/game/main.js @@ -0,0 +1,45 @@ +ig.module( + 'game.main' +) +.requires( + 'impact.game', + 'impact.font' +) +.defines(function(){ + +MyGame = ig.Game.extend({ + + // Load a font + font: new ig.Font( 'media/04b03.font.png' ), + + + init: function() { + // Initialize your game here; bind keys etc. + }, + + update: function() { + // Update all entities and backgroundMaps + this.parent(); + + // Add your own, additional update code here + }, + + draw: function() { + // Draw all entities and backgroundMaps + this.parent(); + + + // Add your own drawing code here + var x = ig.system.width/2, + y = ig.system.height/2; + + this.font.draw( 'It Works!', x, y, ig.Font.ALIGN.CENTER ); + } +}); + + +// Start the Game with 60fps, a resolution of 320x240, scaled +// up by a factor of 2 +ig.main( '#canvas', MyGame, 60, 320, 240, 2 ); + +}); diff --git a/dev/lib/impact/animation.js b/dev/lib/impact/animation.js new file mode 100755 index 0000000..d36f054 --- /dev/null +++ b/dev/lib/impact/animation.js @@ -0,0 +1,127 @@ +ig.module( + 'impact.animation' +) +.requires( + 'impact.timer', + 'impact.image' +) +.defines(function(){ + +ig.AnimationSheet = ig.Class.extend({ + width: 8, + height: 8, + image: null, + + init: function( path, width, height ) { + this.width = width; + this.height = height; + + this.image = new ig.Image( path ); + } +}); + + + +ig.Animation = ig.Class.extend({ + sheet: null, + timer: null, + + sequence: [], + flip: {x: false, y: false}, + pivot: {x: 0, y: 0}, + + frame: 0, + tile: 0, + loopCount: 0, + alpha: 1, + angle: 0, + + + init: function( sheet, frameTime, sequence, stop ) { + this.sheet = sheet; + this.pivot = {x: sheet.width/2, y: sheet.height/2 }; + this.timer = new ig.Timer(); + + this.frameTime = frameTime; + this.sequence = sequence; + this.stop = !!stop; + this.tile = this.sequence[0]; + }, + + + rewind: function() { + this.timer.reset(); + this.loopCount = 0; + this.tile = this.sequence[0]; + return this; + }, + + + gotoFrame: function( f ) { + this.timer.set( this.frameTime * -f ); + this.update(); + }, + + + gotoRandomFrame: function() { + this.gotoFrame( Math.floor(Math.random() * this.sequence.length) ) + }, + + + update: function() { + var frameTotal = Math.floor(this.timer.delta() / this.frameTime); + this.loopCount = Math.floor(frameTotal / this.sequence.length); + if( this.stop && this.loopCount > 0 ) { + this.frame = this.sequence.length - 1; + } + else { + this.frame = frameTotal % this.sequence.length; + } + this.tile = this.sequence[ this.frame ]; + }, + + + draw: function( targetX, targetY ) { + var bbsize = Math.max(this.sheet.width, this.sheet.height); + + // On screen? + if( + targetX > ig.system.width || targetY > ig.system.height || + targetX + bbsize < 0 || targetY + bbsize < 0 + ) { + return; + } + + if( this.alpha != 1) { + ig.system.context.globalAlpha = this.alpha; + } + + if( this.angle == 0 ) { + this.sheet.image.drawTile( + targetX, targetY, + this.tile, this.sheet.width, this.sheet.height, + this.flip.x, this.flip.y + ); + } + else { + ig.system.context.save(); + ig.system.context.translate( + ig.system.getDrawPos(targetX + this.pivot.x), + ig.system.getDrawPos(targetY + this.pivot.y) + ); + ig.system.context.rotate( this.angle ); + this.sheet.image.drawTile( + -this.pivot.x, -this.pivot.y, + this.tile, this.sheet.width, this.sheet.height, + this.flip.x, this.flip.y + ); + ig.system.context.restore(); + } + + if( this.alpha != 1) { + ig.system.context.globalAlpha = 1; + } + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/impact/background-map.js b/dev/lib/impact/background-map.js new file mode 100755 index 0000000..9219798 --- /dev/null +++ b/dev/lib/impact/background-map.js @@ -0,0 +1,242 @@ +ig.module( + 'impact.background-map' +) +.requires( + 'impact.map', + 'impact.image' +) +.defines(function(){ + +ig.BackgroundMap = ig.Map.extend({ + tiles: null, + scroll: {x: 0, y:0}, + distance: 1, + repeat: false, + tilesetName: '', + foreground: false, + enabled: true, + + preRender: false, + preRenderedChunks: null, + chunkSize: 512, + debugChunks: false, + + + anims: {}, + + + init: function( tilesize, data, tileset ) { + this.parent( tilesize, data ); + this.setTileset( tileset ); + }, + + + setTileset: function( tileset ) { + this.tilesetName = tileset instanceof ig.Image ? tileset.path : tileset; + this.tiles = new ig.Image( this.tilesetName ); + this.preRenderedChunks = null; + }, + + + setScreenPos: function( x, y ) { + this.scroll.x = x / this.distance; + this.scroll.y = y / this.distance; + }, + + + preRenderMapToChunks: function() { + var totalWidth = this.width * this.tilesize * ig.system.scale, + totalHeight = this.height * this.tilesize * ig.system.scale; + + var chunkCols = Math.ceil(totalWidth / this.chunkSize), + chunkRows = Math.ceil(totalHeight / this.chunkSize); + + this.preRenderedChunks = []; + for( var y = 0; y < chunkRows; y++ ) { + this.preRenderedChunks[y] = []; + + for( var x = 0; x < chunkCols; x++ ) { + + + var chunkWidth = (x == chunkCols-1) + ? totalWidth - x * this.chunkSize + : this.chunkSize; + + var chunkHeight = (y == chunkRows-1) + ? totalHeight - y * this.chunkSize + : this.chunkSize; + + this.preRenderedChunks[y][x] = this.preRenderChunk( x, y, chunkWidth, chunkHeight ); + } + } + }, + + + preRenderChunk: function( cx, cy, w, h ) { + var tw = w / this.tilesize / ig.system.scale + 1; + th = h / this.tilesize / ig.system.scale + 1; + + var nx = (cx * this.chunkSize / ig.system.scale) % this.tilesize, + ny = (cy * this.chunkSize / ig.system.scale) % this.tilesize; + + var tx = Math.floor(cx * this.chunkSize / this.tilesize / ig.system.scale), + ty = Math.floor(cy * this.chunkSize / this.tilesize / ig.system.scale); + + + var chunk = ig.$new( 'canvas'); + chunk.width = w; + chunk.height = h; + + var oldContext = ig.system.context; + ig.system.context = chunk.getContext("2d"); + + for( var x = 0; x < tw; x++ ) { + for( var y = 0; y < th; y++ ) { + if( x + tx < this.width && y + ty < this.height ) { + var tile = this.data[y+ty][x+tx]; + if( tile ) { + this.tiles.drawTile( + x * this.tilesize - nx, y * this.tilesize - ny, + tile - 1, this.tilesize + ); + } + } + } + } + ig.system.context = oldContext; + + return chunk; + }, + + + draw: function() { + if( !this.tiles.loaded || !this.enabled ) { + return; + } + + if( this.preRender ) { + this.drawPreRendered(); + } + else { + this.drawTiled(); + } + }, + + + drawPreRendered: function() { + if( !this.preRenderedChunks ) { + this.preRenderMapToChunks(); + } + + var dx = ig.system.getDrawPos(this.scroll.x), + dy = ig.system.getDrawPos(this.scroll.y); + + + if( this.repeat ) { + dx %= this.width * this.tilesize * ig.system.scale; + dy %= this.height * this.tilesize * ig.system.scale; + } + + var minChunkX = Math.max( Math.floor(dx / this.chunkSize), 0 ), + minChunkY = Math.max( Math.floor(dy / this.chunkSize), 0 ), + maxChunkX = Math.ceil((dx+ig.system.realWidth) / this.chunkSize), + maxChunkY = Math.ceil((dy+ig.system.realHeight) / this.chunkSize), + maxRealChunkX = this.preRenderedChunks[0].length, + maxRealChunkY = this.preRenderedChunks.length; + + + if( !this.repeat ) { + maxChunkX = Math.min( maxChunkX, maxRealChunkX ); + maxChunkY = Math.min( maxChunkY, maxRealChunkY ); + } + + + var nudgeY = 0; + for( var cy = minChunkY; cy < maxChunkY; cy++ ) { + + var nudgeX = 0; + for( var cx = minChunkX; cx < maxChunkX; cx++ ) { + var chunk = this.preRenderedChunks[cy % maxRealChunkY][cx % maxRealChunkX]; + + var x = -dx + cx * this.chunkSize - nudgeX; + var y = -dy + cy * this.chunkSize - nudgeY; + ig.system.context.drawImage( chunk, x, y); + ig.Image.drawCount++; + + if( this.debugChunks ) { + ig.system.context.strokeStyle = '#f0f'; + ig.system.context.strokeRect( x, y, this.chunkSize, this.chunkSize ); + } + + // If we repeat in X and this chunks width wasn't the full chunk size + // and the screen is not already filled, we need to draw anohter chunk + // AND nudge it to be flush with the last chunk + if( this.repeat && chunk.width < this.chunkSize && x + chunk.width < ig.system.realWidth ) { + nudgeX = this.chunkSize - chunk.width; + maxChunkX++; + } + } + + // Same as above, but for Y + if( this.repeat && chunk.height < this.chunkSize && y + chunk.height < ig.system.realHeight ) { + nudgeY = this.chunkSize - chunk.height; + maxChunkY++; + } + } + }, + + + drawTiled: function() { + var tile = 0, + anim = null, + tileOffsetX = (this.scroll.x / this.tilesize).toInt(), + tileOffsetY = (this.scroll.y / this.tilesize).toInt(), + pxOffsetX = this.scroll.x % this.tilesize, + pxOffsetY = this.scroll.y % this.tilesize, + pxMinX = -pxOffsetX - this.tilesize, + pxMinY = -pxOffsetY - this.tilesize, + pxMaxX = ig.system.width + this.tilesize - pxOffsetX, + pxMaxY = ig.system.height + this.tilesize - pxOffsetY; + + + // FIXME: could be sped up for non-repeated maps: restrict the for loops + // to the map size instead of to the screen size and skip the 'repeat' + // checks inside the loop. + + for( var mapY = -1, pxY = pxMinY; pxY < pxMaxY; mapY++, pxY += this.tilesize) { + var tileY = mapY + tileOffsetY; + + // Repeat Y? + if( tileY >= this.height || tileY < 0 ) { + if( !this.repeat ) { continue; } + tileY = tileY > 0 + ? tileY % this.height + : ((tileY+1) % this.height) + this.height - 1; + } + + for( var mapX = -1, pxX = pxMinX; pxX < pxMaxX; mapX++, pxX += this.tilesize ) { + var tileX = mapX + tileOffsetX; + + // Repeat X? + if( tileX >= this.width || tileX < 0 ) { + if( !this.repeat ) { continue; } + tileX = tileX > 0 + ? tileX % this.width + : ((tileX+1) % this.width) + this.width - 1; + } + + // Draw! + if( (tile = this.data[tileY][tileX]) ) { + if( (anim = this.anims[tile-1]) ) { + anim.draw( pxX, pxY ); + } + else { + this.tiles.drawTile( pxX, pxY, tile-1, this.tilesize ); + } + } + } // end for x + } // end for y + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/impact/collision-map.js b/dev/lib/impact/collision-map.js new file mode 100755 index 0000000..71ff1ab --- /dev/null +++ b/dev/lib/impact/collision-map.js @@ -0,0 +1,258 @@ +ig.module( + 'impact.collision-map' +) +.requires( + 'impact.map' +) +.defines(function(){ + +ig.CollisionMap = ig.Map.extend({ + + init: function( tilesize, data, tiledef ) { + this.parent( tilesize, data ); + this.tiledef = tiledef || ig.CollisionMap.defaultTileDef; + }, + + + trace: function( x, y, vx, vy, objectWidth, objectHeight ) { + // Set up the trace-result + var res = { + collision: {x: false, y: false, slope: false}, + pos: {x: x, y: y}, + tile: {x: 0, y: 0} + }; + + // Break the trace down into smaller steps if necessary + var steps = Math.ceil(Math.max(Math.abs(vx), Math.abs(vy)) / this.tilesize); + if( steps > 1 ) { + var sx = vx / steps; + var sy = vy / steps; + + for( var i = 0; i < steps && (sx || sy); i++ ) { + this._traceStep( res, x, y, sx, sy, objectWidth, objectHeight, vx, vy, i ); + + x = res.pos.x; + y = res.pos.y; + if( res.collision.x ) { sx = 0; vx = 0; } + if( res.collision.y ) { sy = 0; vy = 0; } + if( res.collision.slope ) { break; } + } + } + + // Just one step + else { + this._traceStep( res, x, y, vx, vy, objectWidth, objectHeight, vx, vy, 0 ); + } + + return res; + }, + + + _traceStep: function( res, x, y, vx, vy, width, height, rvx, rvy, step ) { + + res.pos.x += vx; + res.pos.y += vy; + + var t = 0; + + // Horizontal collision (walls) + if( vx ) { + var pxOffsetX = (vx > 0 ? width : 0); + var tileOffsetX = (vx < 0 ? this.tilesize : 0); + + var firstTileY = Math.max( Math.floor(y / this.tilesize), 0 ); + var lastTileY = Math.min( Math.ceil((y + height) / this.tilesize), this.height ); + var tileX = Math.floor( (res.pos.x + pxOffsetX) / this.tilesize ); + + // We need to test the new tile position as well as the current one, as we + // could still collide with the current tile if it's a line def. + // We can skip this test if this is not the first step or the new tile position + // is the same as the current one. + var prevTileX = Math.floor( (x + pxOffsetX) / this.tilesize ); + if( step > 0 || tileX == prevTileX || prevTileX < 0 || prevTileX >= this.width ) { + prevTileX = -1; + } + + // Still inside this collision map? + if( tileX >= 0 && tileX < this.width ) { + for( var tileY = firstTileY; tileY < lastTileY; tileY++ ) { + if( prevTileX != -1 ) { + t = this.data[tileY][prevTileX]; + if( t > 1 && this._checkTileDef(res, t, x, y, rvx, rvy, width, height, prevTileX, tileY) ) { + break; + } + } + + t = this.data[tileY][tileX]; + if( + t == 1 || // fully solid tile? + (t > 1 && this._checkTileDef(res, t, x, y, rvx, rvy, width, height, tileX, tileY)) // slope? + ) { + if( t > 1 && res.collision.slope ) { + break; + } + + // full tile collision! + res.collision.x = true; + res.tile.x = t; + res.pos.x = tileX * this.tilesize - pxOffsetX + tileOffsetX; + break; + } + } + } + } + + // Vertical collision (floor, ceiling) + if( vy ) { + var pxOffsetY = (vy > 0 ? height : 0); + var tileOffsetY = (vy < 0 ? this.tilesize : 0); + + var firstTileX = Math.max( Math.floor(res.pos.x / this.tilesize), 0 ); + var lastTileX = Math.min( Math.ceil((res.pos.x + width) / this.tilesize), this.width ); + var tileY = Math.floor( (res.pos.y + pxOffsetY) / this.tilesize ); + + var prevTileY = Math.floor( (y + pxOffsetY) / this.tilesize ); + if( step > 0 || tileY == prevTileY || prevTileY < 0 || prevTileY >= this.height ) { + prevTileY = -1; + } + + // Still inside this collision map? + if( tileY >= 0 && tileY < this.height ) { + for( var tileX = firstTileX; tileX < lastTileX; tileX++ ) { + if( prevTileY != -1 ) { + t = this.data[prevTileY][tileX]; + if( t > 1 && this._checkTileDef(res, t, x, y, rvx, rvy, width, height, tileX, prevTileY) ) { + break; + } + } + + t = this.data[tileY][tileX]; + if( + t == 1 || // fully solid tile? + (t > 1 && this._checkTileDef(res, t, x, y, rvx, rvy, width, height, tileX, tileY)) // slope? + ) { + if( t > 1 && res.collision.slope ) { + break; + } + + // full tile collision! + res.collision.y = true; + res.tile.y = t; + res.pos.y = tileY * this.tilesize - pxOffsetY + tileOffsetY; + break; + } + } + } + } + + // res is changed in place, nothing to return + }, + + + _checkTileDef: function( res, t, x, y, vx, vy, width, height, tileX, tileY ) { + var def = this.tiledef[t]; + if( !def ) { return false; } + + var lx = (tileX + def[0]) * this.tilesize, + ly = (tileY + def[1]) * this.tilesize, + lvx = (def[2] - def[0]) * this.tilesize, + lvy = (def[3] - def[1]) * this.tilesize, + solid = def[4]; + + // Find the box corner to test, relative to the line + var tx = x + vx + (lvy < 0 ? width : 0) - lx, + ty = y + vy + (lvx > 0 ? height : 0) - ly; + + // Is the box corner behind the line? + if( lvx * ty - lvy * tx > 0 ) { + + // Lines are only solid from one side - find the dot product of + // line normal and movement vector and dismiss if wrong side + if( vx * -lvy + vy * lvx < 0 ) { + return solid; + } + + // Find the line normal + var length = Math.sqrt(lvx * lvx + lvy * lvy); + var nx = lvy/length, + ny = -lvx/length; + + // Project out of the line + var proj = tx * nx + ty * ny; + var px = nx * proj, + py = ny * proj; + + // If we project further out than we moved in, then this is a full + // tile collision + if( px*px+py*py >= vx*vx+vy*vy ) { + return true; + } + + res.pos.x = x + vx - px; + res.pos.y = y + vy - py; + res.collision.slope = {x: lvx, y: lvy, nx: nx, ny: ny}; + return true; + } + + return false; + } +}); + + +// Default Slope Tile definition. Each tile is defined by an array of 5 vars: +// - 4 for the line in tile coordinates (0 -- 1) +// - 1 specifing whether the tile is 'filled' behind the line or not +// [ x1, y1, x2, y2, solid ] + +// Defining 'half', 'one third' and 'two thirds' as vars makes it a bit +// easier to read... I hope. +var H = 1/2, + N = 1/3, + M = 2/3, + SOLID = true, + NON_SOLID = false; + +ig.CollisionMap.defaultTileDef = { + /* 15 NE */ 5: [0,1, 1,M, SOLID], 6: [0,M, 1,N, SOLID], 7: [0,N, 1,0, SOLID], + /* 22 NE */ 3: [0,1, 1,H, SOLID], 4: [0,H, 1,0, SOLID], + /* 45 NE */ 2: [0,1, 1,0, SOLID], + /* 67 NE */ 10: [H,1, 1,0, SOLID], 21: [0,1, H,0, SOLID], + /* 75 NE */ 32: [M,1, 1,0, SOLID], 43: [N,1, M,0, SOLID], 54: [0,1, N,0, SOLID], + + /* 15 SE */ 27: [0,0, 1,N, SOLID], 28: [0,N, 1,M, SOLID], 29: [0,M, 1,1, SOLID], + /* 22 SE */ 25: [0,0, 1,H, SOLID], 26: [0,H, 1,1, SOLID], + /* 45 SE */ 24: [0,0, 1,1, SOLID], + /* 67 SE */ 11: [0,0, H,1, SOLID], 22: [H,0, 1,1, SOLID], + /* 75 SE */ 33: [0,0, N,1, SOLID], 44: [N,0, M,1, SOLID], 55: [M,0, 1,1, SOLID], + + /* 15 NW */ 16: [1,N, 0,0, SOLID], 17: [1,M, 0,N, SOLID], 18: [1,1, 0,M, SOLID], + /* 22 NW */ 14: [1,H, 0,0, SOLID], 15: [1,1, 0,H, SOLID], + /* 45 NW */ 13: [1,1, 0,0, SOLID], + /* 67 NW */ 8: [H,1, 0,0, SOLID], 19: [1,1, H,0, SOLID], + /* 75 NW */ 30: [N,1, 0,0, SOLID], 41: [M,1, N,0, SOLID], 52: [1,1, M,0, SOLID], + + /* 15 SW */ 38: [1,M, 0,1, SOLID], 39: [1,N, 0,M, SOLID], 40: [1,0, 0,N, SOLID], + /* 22 SW */ 36: [1,H, 0,1, SOLID], 37: [1,0, 0,H, SOLID], + /* 45 SW */ 35: [1,0, 0,1, SOLID], + /* 67 SW */ 9: [1,0, H,1, SOLID], 20: [H,0, 0,1, SOLID], + /* 75 SW */ 31: [1,0, M,1, SOLID], 42: [M,0, N,1, SOLID], 53: [N,0, 0,1, SOLID], + + /* Go N */ 12: [0,0, 1,0, NON_SOLID], + /* Go S */ 23: [1,1, 0,1, NON_SOLID], + /* Go E */ 34: [1,0, 1,1, NON_SOLID], + /* Go W */ 45: [0,1, 0,0, NON_SOLID] + + // Now that was fun! +}; + + +// Static Dummy CollisionMap; never collides +ig.CollisionMap.staticNoCollision = { trace: function( x, y, vx, vy ) { + return { + collision: {x: false, y: false, slope: false}, + pos: {x: x+vx, y: y+vy}, + tile: {x: 0, y: 0} + }; +}}; + +}); \ No newline at end of file diff --git a/dev/lib/impact/debug/debug.css b/dev/lib/impact/debug/debug.css new file mode 100755 index 0000000..1dd204a --- /dev/null +++ b/dev/lib/impact/debug/debug.css @@ -0,0 +1,148 @@ +.ig_debug { + position: fixed; + left: 0; + bottom: 0; + width: 100%; + background-color: #000; + border-top: 2px solid #f57401; + font-size: 12px; + color: #fff; + z-index: 1000; + -webkit-user-select: none; +} + +.ig_debug_panel_menu { + height: 28px; + background: #222; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0,#000000), color-stop(1,#333)); + background: -moz-linear-gradient(center bottom, #000000 0%, #333 100%); + background: -o-linear-gradient(#333, #000000); +} + +.ig_debug_panel_menu div { + float: left; + height: 22px; + padding: 6px 8px 0 8px; + border-right: 1px solid #333; +} + +.ig_debug_panel_menu .ig_debug_head { + font-weight: bold; + color: #888; +} + +.ig_debug_menu_item:hover { + cursor: pointer; + background-color: #fff; + color: #000; +} + +.ig_debug_menu_item.active, .ig_debug_menu_item.active:hover { + background-color: #000; + color: #fff; +} + +.ig_debug_stats { + position: absolute; + right: 0; + top: 0; + float: right; + color: #888; + border-left: 1px solid #333; + text-align: right; +} + +.ig_debug_stats span { + width: 3em; + display:inline-block; + color: #fff !important; + margin-right: 0.2em; + margin-left: 0.3em; + font-family: bitstream vera sans mono, courier new; + white-space: nowrap; +} + +.ig_debug_panel { + height: 152px; + overflow: auto; + position: relative; +} + +.ig_debug_panel canvas { + border-bottom: 1px solid #444; +} + +.ig_debug_panel .ig_debug_panel { + padding: 8px; + height: auto; + float: left; + background-color: #000; + border-right: 2px solid #222; +} + +.ig_debug_option { + padding: 2px 0 2px 8px; + cursor: pointer; +} + +.ig_debug_option:first-child { + margin-top: 8px; +} + +.ig_debug_option:hover { + background-color: #111; +} + +.ig_debug_graph_mark { + position: absolute; + color: #888; + left: 4px; + font-size: 10px; + margin-top: -12px; +} + +.ig_debug_legend { + color: #ccc; +} + +.ig_debug_label_mark { + display: inline-block; + width: 10px; + height: 10px; + margin-right: 4px; + -webkit-transition: 0.1s linear; + -moz-transition: 0.1s linear; +} + +.ig_debug_legend_color { + display: inline-block; + width: 6px; + height: 10px; + margin-right: 4px; + margin-left: 16px; +} + +.ig_debug_legend_number { + width: 3em; + display: inline-block; + text-align: right; + font-family: bitstream vera sans mono, courier new; + color: #fff; + margin-right: 0.2em; +} + + +.ig_debug_map_container { + position: relative; + overflow: hidden; + border: 1px solid #888; +} + +.ig_debug_map_container canvas { + position: absolute; +} + +.ig_debug_map_screen { + position: absolute; + border: 1px solid #f0f; +} diff --git a/dev/lib/impact/debug/debug.js b/dev/lib/impact/debug/debug.js new file mode 100755 index 0000000..f660143 --- /dev/null +++ b/dev/lib/impact/debug/debug.js @@ -0,0 +1,13 @@ +ig.module( + 'impact.debug.debug' +) +.requires( + 'impact.debug.entities-panel', + 'impact.debug.maps-panel', + 'impact.debug.graph-panel' +) +.defines(function(){ + +/* Empty module to require all debug panels */ + +}); \ No newline at end of file diff --git a/dev/lib/impact/debug/entities-panel.js b/dev/lib/impact/debug/entities-panel.js new file mode 100755 index 0000000..7b25824 --- /dev/null +++ b/dev/lib/impact/debug/entities-panel.js @@ -0,0 +1,129 @@ +ig.module( + 'impact.debug.entities-panel' +) +.requires( + 'impact.debug.menu', + 'impact.entity' +) +.defines(function(){ + + +ig.Entity.inject({ + colors: { + names: '#fff', + velocities: '#0f0', + boxes: '#f00' + }, + + draw: function() { + this.parent(); + + // Collision Boxes + if( ig.Entity._debugShowBoxes ) { + ig.system.context.strokeStyle = this.colors.boxes; + ig.system.context.lineWidth = 1.0; + ig.system.context.strokeRect( + ig.system.getDrawPos(this.pos.x.round() - ig.game.screen.x) - 0.5, + ig.system.getDrawPos(this.pos.y.round() - ig.game.screen.y) - 0.5, + this.size.x * ig.system.scale, + this.size.y * ig.system.scale + ); + } + + // Velocities + if( ig.Entity._debugShowVelocities ) { + var x = this.pos.x + this.size.x/2; + var y = this.pos.y + this.size.y/2; + + this._debugDrawLine( this.colors.velocities, x, y, x + this.vel.x, y + this.vel.y ); + } + + // Names & Targets + if( ig.Entity._debugShowNames ) { + if( this.name ) { + ig.system.context.fillStyle = this.colors.names; + ig.system.context.fillText( + this.name, + ig.system.getDrawPos(this.pos.x - ig.game.screen.x), + ig.system.getDrawPos(this.pos.y - ig.game.screen.y) + ); + } + + if( typeof(this.target) == 'object' ) { + for( var t in this.target ) { + var ent = ig.game.getEntityByName( this.target[t] ); + if( ent ) { + this._debugDrawLine( this.colors.names, + this.pos.x + this.size.x/2, this.pos.y + this.size.y/2, + ent.pos.x + ent.size.x/2, ent.pos.y + ent.size.y/2 + ); + } + } + } + } + }, + + + _debugDrawLine: function( color, sx, sy, dx, dy ) { + ig.system.context.strokeStyle = color; + ig.system.context.lineWidth = 1.0; + + ig.system.context.beginPath(); + ig.system.context.moveTo( + ig.system.getDrawPos(sx - ig.game.screen.x), + ig.system.getDrawPos(sy - ig.game.screen.y) + ); + ig.system.context.lineTo( + ig.system.getDrawPos(dx - ig.game.screen.x), + ig.system.getDrawPos(dy - ig.game.screen.y) + ); + ig.system.context.stroke(); + ig.system.context.closePath(); + } +}); + + +ig.Entity._debugEnableChecks = true; +ig.Entity._debugShowBoxes = false; +ig.Entity._debugShowVelocities = false; +ig.Entity._debugShowNames = false; + +ig.Entity.oldCheckPair = ig.Entity.checkPair; +ig.Entity.checkPair = function( a, b ) { + if( !ig.Entity._debugEnableChecks ) { + return; + } + ig.Entity.oldCheckPair( a, b ); +}; + + +ig.debug.addPanel({ + type: ig.DebugPanel, + name: 'entities', + label: 'Entities', + options: [ + { + name: 'Checks & Collisions', + object: ig.Entity, + property: '_debugEnableChecks' + }, + { + name: 'Show Collision Boxes', + object: ig.Entity, + property: '_debugShowBoxes' + }, + { + name: 'Show Velocities', + object: ig.Entity, + property: '_debugShowVelocities' + }, + { + name: 'Show Names & Targets', + object: ig.Entity, + property: '_debugShowNames' + } + ] +}); + + +}); \ No newline at end of file diff --git a/dev/lib/impact/debug/graph-panel.js b/dev/lib/impact/debug/graph-panel.js new file mode 100755 index 0000000..46f3794 --- /dev/null +++ b/dev/lib/impact/debug/graph-panel.js @@ -0,0 +1,198 @@ +ig.module( + 'impact.debug.graph-panel' +) +.requires( + 'impact.debug.menu', + 'impact.system', + 'impact.game', + 'impact.image' +) +.defines(function(){ + + +ig.Game.inject({ + draw: function() { + ig.graph.beginClock('draw'); + this.parent(); + ig.graph.endClock('draw'); + }, + + + update: function() { + ig.graph.beginClock('update'); + this.parent(); + ig.graph.endClock('update'); + }, + + + checkEntities: function() { + ig.graph.beginClock('checks'); + this.parent(); + ig.graph.endClock('checks'); + } +}); + + + +ig.DebugGraphPanel = ig.DebugPanel.extend({ + clocks: {}, + marks: [], + textY: 0, + height: 128, + ms: 64, + timeBeforeRun: 0, + + + init: function( name, label ) { + this.parent( name, label ); + + this.mark16ms = (this.height - (this.height/this.ms) * 16).round(); + this.mark33ms = (this.height - (this.height/this.ms) * 33).round(); + this.msHeight = this.height/this.ms; + + this.graph = ig.$new('canvas'); + this.graph.width = window.innerWidth; + this.graph.height = this.height; + this.container.appendChild( this.graph ); + this.ctx = this.graph.getContext('2d'); + + this.ctx.fillStyle = '#444'; + this.ctx.fillRect( 0, this.mark16ms, this.graph.width, 1 ); + this.ctx.fillRect( 0, this.mark33ms, this.graph.width, 1 ); + + this.addGraphMark( '16ms', this.mark16ms ); + this.addGraphMark( '33ms', this.mark33ms ); + + this.addClock( 'draw', 'Draw', '#13baff' ); + this.addClock( 'update', 'Entity Update', '#bb0fff' ); + this.addClock( 'checks', 'Entity Checks & Collisions', '#a2e908' ); + this.addClock( 'lag', 'System Lag', '#f26900' ); + + ig.mark = this.mark.bind(this); + ig.graph = this; + }, + + + addGraphMark: function( name, height ) { + var span = ig.$new('span'); + span.className = 'ig_debug_graph_mark'; + span.textContent = name; + span.style.top = height.round() + 'px'; + this.container.appendChild( span ); + }, + + + addClock: function( name, description, color ) { + var mark = ig.$new('span'); + mark.className = 'ig_debug_legend_color'; + mark.style.backgroundColor = color; + + var number = ig.$new('span'); + number.className = 'ig_debug_legend_number'; + number.appendChild( document.createTextNode('0') ); + + var legend = ig.$new('span'); + legend.className = 'ig_debug_legend'; + legend.appendChild( mark ); + legend.appendChild( document.createTextNode(description +' (') ); + legend.appendChild( number ); + legend.appendChild( document.createTextNode('ms)') ); + + this.container.appendChild( legend ); + + this.clocks[name] = { + description: description, + color: color, + current: 0, + start: Date.now(), + avg: 0, + html: number + }; + }, + + + beginClock: function( name, offset ) { + this.clocks[name].start = Date.now() + (offset || 0); + }, + + + endClock: function( name ) { + var c = this.clocks[name]; + c.current = Math.round(Date.now() - c.start); + c.avg = c.avg * 0.8 + c.current * 0.2; + }, + + + mark: function( msg, color ) { + if( this.active ) { + this.marks.push( {msg:msg, color:(color||'#fff')} ); + } + }, + + + beforeRun: function() { + this.endClock('lag'); + this.timeBeforeRun = Date.now(); + }, + + + afterRun: function() { + var frameTime = Date.now() - this.timeBeforeRun; + var nextFrameDue = (1000/ig.system.fps) - frameTime; + this.beginClock('lag', Math.max(nextFrameDue, 0)); + + + var x = this.graph.width-1; + var y = this.height; + + this.ctx.drawImage( this.graph, -1, 0 ); + + this.ctx.fillStyle = '#000'; + this.ctx.fillRect( x, 0, 1, this.height ); + + this.ctx.fillStyle = '#444'; + this.ctx.fillRect( x, this.mark16ms, 1, 1 ); + + this.ctx.fillStyle = '#444'; + this.ctx.fillRect( x, this.mark33ms, 1, 1 ); + + for( var ci in this.clocks ) { + var c = this.clocks[ci]; + c.html.textContent = c.avg.toFixed(2); + + if( c.color && c.current > 0 ) { + this.ctx.fillStyle = c.color; + var h = c.current * this.msHeight; + y -= h; + this.ctx.fillRect( x, y, 1, h ); + c.current = 0; + } + } + + this.ctx.textAlign = 'right'; + this.ctx.textBaseline = 'top'; + this.ctx.globalAlpha = 0.5; + + for( var i = 0; i < this.marks.length; i++ ) { + var m = this.marks[i]; + this.ctx.fillStyle = m.color; + this.ctx.fillRect( x, 0, 1, this.height ); + if( m.msg ) { + this.ctx.fillText( m.msg, x-1, this.textY ); + this.textY = (this.textY+8)%32; + } + } + this.ctx.globalAlpha = 1; + this.marks = []; + } +}); + + +ig.debug.addPanel({ + type: ig.DebugGraphPanel, + name: 'graph', + label: 'Performance' +}); + + +}); \ No newline at end of file diff --git a/dev/lib/impact/debug/maps-panel.js b/dev/lib/impact/debug/maps-panel.js new file mode 100755 index 0000000..ff9c582 --- /dev/null +++ b/dev/lib/impact/debug/maps-panel.js @@ -0,0 +1,154 @@ +ig.module( + 'impact.debug.maps-panel' +) +.requires( + 'impact.debug.menu', + 'impact.game', + 'impact.background-map' +) +.defines(function(){ + + +ig.Game.inject({ + loadLevel: function( data ) { + this.parent(data); + ig.debug.panels.maps.load(this); + } +}); + + +ig.DebugMapsPanel = ig.DebugPanel.extend({ + maps: [], + mapScreens: [], + + + init: function( name, label ) { + this.parent( name, label ); + this.load(); + }, + + + load: function( game ) { + this.options = []; + this.panels = []; + + if( !game || !game.backgroundMaps.length ) { + this.container.innerHTML = 'No Maps Loaded'; + return; + } + + this.maps = game.backgroundMaps; + this.mapScreens = []; + this.container.innerHTML = ''; + + for( var m = 0; m < this.maps.length; m++ ) { + var map = this.maps[m]; + + var subPanel = new ig.DebugPanel( m, 'Layer '+m ); + + var head = new ig.$new('strong'); + head.textContent = m +': ' + map.tiles.path; + subPanel.container.appendChild( head ); + + subPanel.addOption( new ig.DebugOption('Enabled', map, 'enabled') ); + subPanel.addOption( new ig.DebugOption('Pre Rendered', map, 'preRender') ); + subPanel.addOption( new ig.DebugOption('Show Chunks', map, 'debugChunks') ); + + this.generateMiniMap( subPanel, map, m ); + this.addPanel( subPanel ); + } + }, + + + generateMiniMap: function( panel, map, id ) { + var s = ig.system.scale; // we'll need this a lot + + // resize the tileset, so that one tile is 's' pixels wide and high + var ts = ig.$new('canvas'); + var tsctx = ts.getContext('2d'); + + var w = map.tiles.width * s; + var h = map.tiles.height * s; + var ws = w / map.tilesize; + var hs = h / map.tilesize; + tsctx.drawImage( map.tiles.data, 0, 0, w, h, 0, 0, ws, hs ); + + // create the minimap canvas + var mapCanvas = ig.$new('canvas'); + mapCanvas.width = map.width * s; + mapCanvas.height = map.height * s; + var ctx = mapCanvas.getContext('2d'); + + if( ig.game.clearColor ) { + ctx.fillStyle = ig.game.clearColor; + ctx.fillRect(0, 0, w, h); + } + + // draw the map + var tile = 0; + for( var x = 0; x < map.width; x++ ) { + for( var y = 0; y < map.height; y++ ) { + if( (tile = map.data[y][x]) ) { + ctx.drawImage( + ts, + Math.floor(((tile-1) * s) % ws), + Math.floor((tile-1) * s / ws) * s, + s, s, + x * s, y * s, + s, s + ); + } + } + } + + var mapContainer = ig.$new('div'); + mapContainer.className = 'ig_debug_map_container'; + mapContainer.style.width = map.width * s + 'px'; + mapContainer.style.height = map.height * s + 'px'; + + var mapScreen = ig.$new('div'); + mapScreen.className = 'ig_debug_map_screen'; + mapScreen.style.width = ((ig.system.width / map.tilesize) * s - 2) + 'px'; + mapScreen.style.height = ((ig.system.height / map.tilesize) * s - 2) + 'px'; + this.mapScreens[id] = mapScreen; + + mapContainer.appendChild( mapCanvas ); + mapContainer.appendChild( mapScreen ); + panel.container.appendChild( mapContainer ); + }, + + + afterRun: function() { + // Update the screen position DIV for each mini-map + var s = ig.system.scale; + for( var m = 0; m < this.maps.length; m++ ) { + var map = this.maps[m]; + var screen = this.mapScreens[m]; + + if( !map || !screen ) { // Quick sanity check + continue; + } + + var x = map.scroll.x / map.tilesize; + var y = map.scroll.y / map.tilesize; + + if( map.repeat ) { + x %= map.width; + y %= map.height; + } + + screen.style.left = (x * s) + 'px'; + screen.style.top = (y * s) + 'px'; + } + } +}); + + +ig.debug.addPanel({ + type: ig.DebugMapsPanel, + name: 'maps', + label: 'Background Maps' +}); + + +}); \ No newline at end of file diff --git a/dev/lib/impact/debug/menu.js b/dev/lib/impact/debug/menu.js new file mode 100755 index 0000000..edef71a --- /dev/null +++ b/dev/lib/impact/debug/menu.js @@ -0,0 +1,292 @@ +ig.module( + 'impact.debug.menu' +) +.requires( + 'dom.ready', + 'impact.system' +) +.defines(function(){ + + +ig.System.inject({ + run: function() { + ig.debug.beforeRun(); + this.parent(); + ig.debug.afterRun(); + }, + + setGameNow: function( gameClass ) { + this.parent( gameClass ); + ig.debug.ready(); + } +}); + + +ig.Debug = ig.Class.extend({ + options: {}, + panels: {}, + numbers:{}, + container: null, + panelMenu: null, + activePanel: null, + + debugTime: 0, + debugTickAvg: 0.016, + debugRealTime: Date.now(), + + init: function() { + // Inject the Stylesheet + var style = ig.$new('link'); + style.rel = 'stylesheet'; + style.type = 'text/css'; + style.href = 'lib/impact/debug/debug.css'; + ig.$('body')[0].appendChild( style ); + + // Create the Debug Container + this.container = ig.$new('div'); + this.container.className ='ig_debug'; + ig.$('body')[0].appendChild( this.container ); + + // Create and add the Menu Container + this.panelMenu = ig.$new('div'); + this.panelMenu.innerHTML = '
Impact.Debug:
'; + this.panelMenu.className ='ig_debug_panel_menu'; + + this.container.appendChild( this.panelMenu ); + + // Create and add the Stats Container + this.numberContainer = ig.$new('div'); + this.numberContainer.className ='ig_debug_stats'; + this.panelMenu.appendChild( this.numberContainer ); + + // Set ig.log() and ig.show() + if( window.console && window.console.log ) { + ig.log = window.console.log.bind(window.console); + } + ig.show = this.showNumber.bind(this); + }, + + + addNumber: function( name, width ) { + var number = ig.$new('span'); + this.numberContainer.appendChild( number ); + this.numberContainer.appendChild( document.createTextNode(name) ); + + this.numbers[name] = number; + }, + + + showNumber: function( name, number, width ) { + if( !this.numbers[name] ) { + this.addNumber( name, width ); + } + this.numbers[name].textContent = number; + }, + + + addPanel: function( panelDef ) { + // Create the panel and options + var panel = new (panelDef.type)( panelDef.name, panelDef.label ); + if( panelDef.options ) { + for( var i = 0; i < panelDef.options.length; i++ ) { + var opt = panelDef.options[i]; + panel.addOption( new ig.DebugOption(opt.name, opt.object, opt.property) ); + } + } + + this.panels[ panel.name ] = panel; + panel.container.style.display = 'none'; + this.container.appendChild( panel.container ); + + + // Create the menu item + var menuItem = ig.$new('div'); + menuItem.className = 'ig_debug_menu_item'; + menuItem.textContent = panel.label; + menuItem.addEventListener( + 'click', + (function(ev){ this.togglePanel(ev, panel); }).bind(this), + false + ); + panel.menuItem = menuItem; + + // Insert menu item in alphabetical order into the menu + var inserted = false; + for( var i = 1; i < this.panelMenu.childNodes.length; i++ ) { + var cn = this.panelMenu.childNodes[i]; + if( cn.textContent > panel.label ) { + this.panelMenu.insertBefore( menuItem, cn ); + inserted = true; + break; + } + } + if( !inserted ) { + // Not inserted? Append at the end! + this.panelMenu.appendChild( menuItem ); + } + }, + + + togglePanel: function( ev, panel ) { + if( panel != this.activePanel && this.activePanel ) { + this.activePanel.toggle( false ); + this.activePanel.menuItem.className = 'ig_debug_menu_item'; + this.activePanel = null; + } + + var dsp = panel.container.style.display; + var active = (dsp != 'block'); + panel.toggle( active ); + ev.target.className = 'ig_debug_menu_item' + (active ? ' active' : ''); + + if( active ) { + this.activePanel = panel; + } + }, + + + ready: function() { + for( var p in this.panels ) { + this.panels[p].ready(); + } + }, + + + beforeRun: function() { + var timeBeforeRun = Date.now(); + this.debugTickAvg = this.debugTickAvg * 0.8 + (timeBeforeRun - this.debugRealTime) * 0.2; + this.debugRealTime = timeBeforeRun; + + if( this.activePanel ) { + this.activePanel.beforeRun(); + } + }, + + + afterRun: function() { + var frameTime = Date.now() - this.debugRealTime; + var nextFrameDue = (1000/ig.system.fps) - frameTime; + + this.debugTime = this.debugTime * 0.8 + frameTime * 0.2; + + + if( this.activePanel ) { + this.activePanel.afterRun(); + } + + this.showNumber( 'ms', this.debugTime.toFixed(2) ); + this.showNumber( 'fps', Math.round(1000/this.debugTickAvg) ); + this.showNumber( 'draws', ig.Image.drawCount ); + if( ig.game && ig.game.entities ) { + this.showNumber( 'entities', ig.game.entities.length ); + } + ig.Image.drawCount = 0; + } +}); + + + +ig.DebugPanel = ig.Class.extend({ + active: false, + container: null, + options: [], + panels: [], + label: '', + name: '', + + + init: function( name, label ) { + this.name = name; + this.label = label; + this.container = ig.$new('div'); + this.container.className = 'ig_debug_panel ' + this.name; + }, + + + toggle: function( active ) { + this.active = active; + this.container.style.display = active ? 'block' : 'none'; + }, + + + addPanel: function( panel ) { + this.panels.push( panel ); + this.container.appendChild( panel.container ); + }, + + + addOption: function( option ) { + this.options.push( option ); + this.container.appendChild( option.container ); + }, + + + ready: function(){}, + beforeRun: function(){}, + afterRun: function(){} +}); + + + +ig.DebugOption = ig.Class.extend({ + name: '', + labelName: '', + className: 'ig_debug_option', + label: null, + mark: null, + container: null, + active: false, + + colors: { + enabled: '#fff', + disabled: '#444' + }, + + + init: function( name, object, property ) { + this.name = name; + this.object = object; + this.property = property; + + this.active = this.object[this.property]; + + this.container = ig.$new('div'); + this.container.className = 'ig_debug_option'; + + this.label = ig.$new('span'); + this.label.className = 'ig_debug_label'; + this.label.textContent = this.name; + + this.mark = ig.$new('span'); + this.mark.className = 'ig_debug_label_mark'; + + this.container.appendChild( this.mark ); + this.container.appendChild( this.label ); + this.container.addEventListener( 'click', this.click.bind(this), false ); + + this.setLabel(); + }, + + + setLabel: function() { + this.mark.style.backgroundColor = this.active ? this.colors.enabled : this.colors.disabled; + }, + + + click: function( ev ) { + this.active = !this.active; + this.object[this.property] = this.active; + this.setLabel(); + + ev.stopPropagation(); + ev.preventDefault(); + return false; + } +}); + + + +// Create the debug instance! +ig.debug = new ig.Debug(); + +}); \ No newline at end of file diff --git a/dev/lib/impact/entity.js b/dev/lib/impact/entity.js new file mode 100755 index 0000000..a40cd18 --- /dev/null +++ b/dev/lib/impact/entity.js @@ -0,0 +1,415 @@ +ig.module( + 'impact.entity' +) +.requires( + 'impact.animation', + 'impact.impact' +) +.defines(function(){ + +ig.Entity = ig.Class.extend({ + id: 0, + settings: {}, + + size: {x: 16, y:16}, + offset: {x: 0, y: 0}, + + pos: {x: 0, y:0}, + last: {x: 0, y:0}, + vel: {x: 0, y: 0}, + accel: {x: 0, y: 0}, + friction: {x: 0, y: 0}, + maxVel: {x: 100, y: 100}, + zIndex: 0, + gravityFactor: 1, + standing: false, + bounciness: 0, + minBounceVelocity: 40, + + anims: {}, + animSheet: null, + currentAnim: null, + health: 10, + + type: 0, // TYPE.NONE + checkAgainst: 0, // TYPE.NONE + collides: 0, // COLLIDES.NEVER + + _killed: false, + + slopeStanding: {min: (44).toRad(), max: (136).toRad() }, + + init: function( x, y, settings ) { + this.id = ++ig.Entity._lastId; + this.pos.x = x; + this.pos.y = y; + + ig.merge( this, settings ); + }, + + addAnim: function( name, frameTime, sequence, stop ) { + if( !this.animSheet ) { + throw( 'No animSheet to add the animation '+name+' to.' ); + } + var a = new ig.Animation( this.animSheet, frameTime, sequence, stop ); + this.anims[name] = a; + if( !this.currentAnim ) { + this.currentAnim = a; + } + + return a; + }, + + update: function() { + this.last.x = this.pos.x; + this.last.y = this.pos.y; + this.vel.y += ig.game.gravity * ig.system.tick * this.gravityFactor; + + this.vel.x = this.getNewVelocity( this.vel.x, this.accel.x, this.friction.x, this.maxVel.x ); + this.vel.y = this.getNewVelocity( this.vel.y, this.accel.y, this.friction.y, this.maxVel.y ); + + // movement & collision + var mx = this.vel.x * ig.system.tick; + var my = this.vel.y * ig.system.tick; + var res = ig.game.collisionMap.trace( + this.pos.x, this.pos.y, mx, my, this.size.x, this.size.y + ); + this.handleMovementTrace( res ); + + if( this.currentAnim ) { + this.currentAnim.update(); + } + }, + + + getNewVelocity: function( vel, accel, friction, max ) { + if( accel ) { + return ( vel + accel * ig.system.tick ).limit( -max, max ); + } + else if( friction ) { + var delta = friction * ig.system.tick; + + if( vel - delta > 0) { + return vel - delta; + } + else if( vel + delta < 0 ) { + return vel + delta; + } + else { + return 0; + } + } + return vel.limit( -max, max ); + }, + + + handleMovementTrace: function( res ) { + this.standing = false; + + if( res.collision.y ) { + if( this.bounciness > 0 && Math.abs(this.vel.y) > this.minBounceVelocity ) { + this.vel.y *= -this.bounciness; + } + else { + if( this.vel.y > 0 ) { + this.standing = true; + } + this.vel.y = 0; + } + } + if( res.collision.x ) { + if( this.bounciness > 0 && Math.abs(this.vel.x) > this.minBounceVelocity ) { + this.vel.x *= -this.bounciness; + } + else { + this.vel.x = 0; + } + } + if( res.collision.slope ) { + var s = res.collision.slope; + + if( this.bounciness > 0 ) { + var proj = this.vel.x * s.nx + this.vel.y * s.ny; + + this.vel.x -= s.nx * proj * 2; + this.vel.y -= s.ny * proj * 2; + + this.vel.x *= this.bounciness; + this.vel.y *= this.bounciness; + } + else { + var lengthSquared = s.x * s.x + s.y * s.y; + var dot = (this.vel.x * s.x + this.vel.y * s.y)/lengthSquared; + + this.vel.x = s.x * dot; + this.vel.y = s.y * dot; + + var angle = Math.atan2( s.x, s.y ); + if( angle > this.slopeStanding.min && angle < this.slopeStanding.max ) { + this.standing = true; + } + } + } + + this.pos = res.pos; + }, + + + draw: function() { + if( this.currentAnim ) { + this.currentAnim.draw( + this.pos.x - this.offset.x - ig.game._rscreen.x, + this.pos.y - this.offset.y - ig.game._rscreen.y + ); + } + }, + + + kill: function() { + ig.game.removeEntity( this ); + }, + + + receiveDamage: function( amount, from ) { + this.health -= amount; + if( this.health <= 0 ) { + this.kill(); + } + }, + + + touches: function( other ) { + return !( + this.pos.x >= other.pos.x + other.size.x || + this.pos.x + this.size.x <= other.pos.x || + this.pos.y >= other.pos.y + other.size.y || + this.pos.y + this.size.y <= other.pos.y + ); + }, + + + distanceTo: function( other ) { + var xd = (this.pos.x + this.size.x/2) - (other.pos.x + other.size.x/2); + var yd = (this.pos.y + this.size.y/2) - (other.pos.y + other.size.y/2); + return Math.sqrt( xd*xd + yd*yd ); + }, + + + angleTo: function( other ) { + return Math.atan2( + (other.pos.y + other.size.y/2) - (this.pos.y + this.size.y/2), + (other.pos.x + other.size.x/2) - (this.pos.x + this.size.x/2) + ); + }, + + + check: function( other ) {}, + collideWith: function( other, axis ) {}, + ready: function() {} +}); + + +// Last used entity id; incremented with each spawned entity + +ig.Entity._lastId = 0; + + +// Collision Types - Determine if and how entities collide with each other + +// In ACTIVE vs. LITE or FIXED vs. ANY collisions, only the "weak" entity moves, +// while the other one stays fixed. In ACTIVE vs. ACTIVE and ACTIVE vs. PASSIVE +// collisions, both entities are moved. LITE or PASSIVE entities don't collide +// with other LITE or PASSIVE entities at all. The behaiviour for FIXED vs. +// FIXED collisions is undefined. + +ig.Entity.COLLIDES = { + NEVER: 0, + LITE: 1, + PASSIVE: 2, + ACTIVE: 4, + FIXED: 8 +}; + + +// Entity Types - used for checks + +ig.Entity.TYPE = { + NONE: 0, + A: 1, + B: 2, + BOTH: 3 +}; + + + +ig.Entity.checkPair = function( a, b ) { + + // Do these entities want checks? + if( a.checkAgainst & b.type ) { + a.check( b ); + } + + if( b.checkAgainst & a.type ) { + b.check( a ); + } + + // If this pair allows collision, solve it! At least one entity must + // collide ACTIVE or FIXED, while the other one must not collide NEVER. + if( + a.collides && b.collides && + a.collides + b.collides > ig.Entity.COLLIDES.ACTIVE + ) { + ig.Entity.solveCollision( a, b ); + } +}; + + +ig.Entity.solveCollision = function( a, b ) { + + // If one entity is FIXED, or the other entity is LITE, the weak + // (FIXED/NON-LITE) entity won't move in collision response + var weak = null; + if( + a.collides == ig.Entity.COLLIDES.LITE || + b.collides == ig.Entity.COLLIDES.FIXED + ) { + weak = a; + } + else if( + b.collides == ig.Entity.COLLIDES.LITE || + a.collides == ig.Entity.COLLIDES.FIXED + ) { + weak = b; + } + + + // Did they already overlap on the X-axis in the last frame? If so, + // this must be a vertical collision! + if( + a.last.x + a.size.x > b.last.x && + a.last.x < b.last.x + b.size.x + ) { + // Which one is on top? + if( a.last.y < b.last.y ) { + ig.Entity.seperateOnYAxis( a, b, weak ); + } + else { + ig.Entity.seperateOnYAxis( b, a, weak ); + } + a.collideWith( b, 'y' ); + b.collideWith( a, 'y' ); + } + + // Horizontal collision + else if( + a.last.y + a.size.y > b.last.y && + a.last.y < b.last.y + b.size.y + ){ + // Which one is on the left? + if( a.last.x < b.last.x ) { + ig.Entity.seperateOnXAxis( a, b, weak ); + } + else { + ig.Entity.seperateOnXAxis( b, a, weak ); + } + a.collideWith( b, 'x' ); + b.collideWith( a, 'x' ); + } +}; + + +// FIXME: This is a mess. Instead of doing all the movements here, the entities +// should get notified of the collision (with all details) and resolve it +// themselfs. + +ig.Entity.seperateOnXAxis = function( left, right, weak ) { + var nudge = (left.pos.x + left.size.x - right.pos.x); + + // We have a weak entity, so just move this one + if( weak ) { + var strong = left === weak ? right : left; + weak.vel.x = -weak.vel.x * weak.bounciness + strong.vel.x; + + var resWeak = ig.game.collisionMap.trace( + weak.pos.x, weak.pos.y, weak == left ? -nudge : nudge, 0, weak.size.x, weak.size.y + ); + weak.pos.x = resWeak.pos.x; + } + + // Normal collision - both move + else { + var v2 = (left.vel.x - right.vel.x)/2; + left.vel.x = -v2; + right.vel.x = v2; + + var resLeft = ig.game.collisionMap.trace( + left.pos.x, left.pos.y, -nudge/2, 0, left.size.x, left.size.y + ); + left.pos.x = Math.floor(resLeft.pos.x); + + var resRight = ig.game.collisionMap.trace( + right.pos.x, right.pos.y, nudge/2, 0, right.size.x, right.size.y + ); + right.pos.x = Math.ceil(resRight.pos.x); + } +}; + + +ig.Entity.seperateOnYAxis = function( top, bottom, weak ) { + var nudge = (top.pos.y + top.size.y - bottom.pos.y); + + // We have a weak entity, so just move this one + if( weak ) { + var strong = top === weak ? bottom : top; + weak.vel.y = -weak.vel.y * weak.bounciness + strong.vel.y; + + // Riding on a platform? + var nudgeX = 0; + if( weak == top && Math.abs(weak.vel.y - strong.vel.y) < weak.minBounceVelocity ) { + weak.standing = true; + nudgeX = strong.vel.x * ig.system.tick; + } + + var resWeak = ig.game.collisionMap.trace( + weak.pos.x, weak.pos.y, nudgeX, weak == top ? -nudge : nudge, weak.size.x, weak.size.y + ); + weak.pos.y = resWeak.pos.y; + weak.pos.x = resWeak.pos.x; + } + + // Bottom entity is standing - just bounce the top one + else if( ig.game.gravity && (bottom.standing || top.vel.y > 0) ) { + var resTop = ig.game.collisionMap.trace( + top.pos.x, top.pos.y, 0, -(top.pos.y + top.size.y - bottom.pos.y), top.size.x, top.size.y + ); + top.pos.y = resTop.pos.y; + + if( top.bounciness > 0 && top.vel.y > top.minBounceVelocity ) { + top.vel.y *= -top.bounciness; + } + else { + top.standing = true; + top.vel.y = 0; + } + } + + // Normal collision - both move + else { + var v2 = (top.vel.y - bottom.vel.y)/2; + top.vel.y = -v2; + bottom.vel.y = v2; + + var nudgeX = bottom.vel.x * ig.system.tick; + var resTop = ig.game.collisionMap.trace( + top.pos.x, top.pos.y, nudgeX, -nudge/2, top.size.x, top.size.y + ); + top.pos.y = resTop.pos.y; + + var resBottom = ig.game.collisionMap.trace( + bottom.pos.x, bottom.pos.y, 0, nudge/2, bottom.size.x, bottom.size.y + ); + bottom.pos.y = resBottom.pos.y; + } +}; + +}); \ No newline at end of file diff --git a/dev/lib/impact/font.js b/dev/lib/impact/font.js new file mode 100755 index 0000000..2c3fcc1 --- /dev/null +++ b/dev/lib/impact/font.js @@ -0,0 +1,129 @@ +ig.module( + 'impact.font' +) +.requires( + 'impact.image' +) +.defines(function(){ + + +ig.Font = ig.Image.extend({ + widthMap: [], + indices: [], + firstChar: 32, + height: 0, + + + onload: function( ev ) { + this._loadMetrics( this.data ); + this.parent( ev ); + }, + + + widthForString: function( s ) { + var width = 0; + for( var i = 0; i < s.length; i++ ) { + width += this.widthMap[s.charCodeAt(i) - this.firstChar] + 1; + } + return width; + }, + + + draw: function( text, x, y, align ) { + if( typeof(text) != 'string' ) { + text = text.toString(); + } + + // Multiline? + if( text.indexOf('\n') !== -1 ) { + var lines = text.split( '\n' ); + for( i = 0; i < lines.length; i++ ) { + this.draw( lines[i], x, y + i * this.height, align ); + } + return; + } + + if( align == ig.Font.ALIGN.RIGHT || align == ig.Font.ALIGN.CENTER ) { + var width = 0; + for( var i = 0; i < text.length; i++ ) { + var c = text.charCodeAt(i); + width += this.widthMap[c - this.firstChar] + 1; + } + x -= align == ig.Font.ALIGN.CENTER ? width/2 : width; + } + + + for( var i = 0; i < text.length; i++ ) { + var c = text.charCodeAt(i); + x += this._drawChar( c - this.firstChar, x, y ); + } + ig.Image.drawCount += text.length; + }, + + + _drawChar: function( c, targetX, targetY ) { + if( !this.loaded || c < 0 || c >= this.indices.length ) { return 0; } + + var scale = ig.system.scale; + + + var charX = this.indices[c] * scale; + var charY = 0; + var charWidth = this.widthMap[c] * scale; + var charHeight = (this.height-2) * scale; + + ig.system.context.drawImage( + this.data, + charX, charY, + charWidth, charHeight, + ig.system.getDrawPos(targetX), ig.system.getDrawPos(targetY), + charWidth, charHeight + ); + + return this.widthMap[c] + 1; + }, + + + _loadMetrics: function( image ) { + // Draw the bottommost line of this font image into an offscreen canvas + // and analyze it pixel by pixel. + // A run of non-transparent pixels represents a character and its width + + this.height = image.height-1; + this.widthMap = []; + this.indices = []; + + var canvas = ig.$new('canvas'); + canvas.width = image.width; + canvas.height = image.height; + var ctx = canvas.getContext('2d'); + ctx.drawImage( image, 0, 0 ); + var px = ctx.getImageData(0, image.height-1, image.width, 1); + + var currentChar = 0; + var currentWidth = 0; + for( var x = 0; x < image.width; x++ ) { + var index = x * 4 + 3; // alpha component of this pixel + if( px.data[index] != 0 ) { + currentWidth++; + } + else if( px.data[index] == 0 && currentWidth ) { + this.widthMap.push( currentWidth ); + this.indices.push( x-currentWidth ); + currentChar++; + currentWidth = 0; + } + } + this.widthMap.push( currentWidth ); + this.indices.push( x-currentWidth ); + } +}); + + +ig.Font.ALIGN = { + LEFT: 0, + RIGHT: 1, + CENTER: 2 +}; + +}); \ No newline at end of file diff --git a/dev/lib/impact/game.js b/dev/lib/impact/game.js new file mode 100755 index 0000000..110fada --- /dev/null +++ b/dev/lib/impact/game.js @@ -0,0 +1,304 @@ +ig.module( + 'impact.game' +) +.requires( + 'impact.impact', + 'impact.entity', + 'impact.collision-map', + 'impact.background-map' +) +.defines(function(){ + +ig.Game = ig.Class.extend({ + + clearColor: '#000000', + gravity: 0, + screen: {x: 0, y: 0}, + _rscreen: {x: 0, y: 0}, + + entities: [], + + namedEntities: {}, + collisionMap: ig.CollisionMap.staticNoCollision, + backgroundMaps: [], + backgroundAnims: {}, + + autoSort: false, + sortBy: null, + + cellSize: 64, + + _deferredKill: [], + _levelToLoad: null, + _doSortEntities: false, + + + staticInstantiate: function() { + this.sortBy = ig.Game.SORT.Z_INDEX; + ig.game = this; + return null; + }, + + + loadLevel: function( data ) { + this.screen = {x: 0, y: 0}; + + // Entities + this.entities = []; + this.namedEntities = {}; + for( var i = 0; i < data.entities.length; i++ ) { + var ent = data.entities[i]; + this.spawnEntity( ent.type, ent.x, ent.y, ent.settings ); + } + this.sortEntities(); + + // Map Layer + this.collisionMap = ig.CollisionMap.staticNoCollision; + this.backgroundMaps = []; + for( var i = 0; i < data.layer.length; i++ ) { + var ld = data.layer[i]; + if( ld.name == 'collision' ) { + this.collisionMap = new ig.CollisionMap(ld.tilesize, ld.data ); + } + else { + var newMap = new ig.BackgroundMap(ld.tilesize, ld.data, ld.tilesetName); + newMap.anims = this.backgroundAnims[ld.tilesetName] || {}; + newMap.repeat = ld.repeat; + newMap.distance = ld.distance; + newMap.foreground = !!ld.foreground; + newMap.preRender = !!ld.preRender; + this.backgroundMaps.push( newMap ); + } + } + + // Call post-init ready function on all entities + for( var i = 0; i < this.entities.length; i++ ) { + this.entities[i].ready(); + } + }, + + + loadLevelDeferred: function( data ) { + this._levelToLoad = data; + }, + + + getEntityByName: function( name ) { + return this.namedEntities[name]; + }, + + + getEntitiesByType: function( type ) { + var entityClass = typeof(type) === 'string' + ? ig.global[type] + : type; + + var a = []; + for( var i = 0; i < this.entities.length; i++ ) { + var ent = this.entities[i]; + if( ent instanceof entityClass && !ent._killed ) { + a.push( ent ); + } + } + return a; + }, + + + spawnEntity: function( type, x, y, settings ) { + var entityClass = typeof(type) === 'string' + ? ig.global[type] + : type; + + if( !entityClass ) { + throw("Can't spawn entity of type " + type); + } + var ent = new (entityClass)( x, y, settings || {} ); + this.entities.push( ent ); + if( ent.name ) { + this.namedEntities[ent.name] = ent; + } + return ent; + }, + + + sortEntities: function() { + this.entities.sort( this.sortBy ); + }, + + + sortEntitiesDeferred: function() { + this._doSortEntities = true; + }, + + + removeEntity: function( ent ) { + // Remove this entity from the named entities + if( ent.name ) { + delete this.namedEntities[ent.name]; + } + + // We can not remove the entity from the entities[] array in the midst + // of an update cycle, so remember all killed entities and remove + // them later. + // Also make sure this entity doesn't collide anymore and won't get + // updated or checked + ent._killed = true; + ent.checkAgainst = ig.Entity.TYPE.NONE; + ent.collides = ig.Entity.COLLIDES.NEVER; + this._deferredKill.push( ent ); + }, + + + run: function() { + this.update(); + this.draw(); + }, + + + update: function(){ + // load new level? + if( this._levelToLoad ) { + this.loadLevel( this._levelToLoad ); + this._levelToLoad = null; + } + + // sort entities? + if( this._doSortEntities || this.autoSort ) { + this.sortEntities(); + this._doSortEntities = false; + } + + // update entities + this.updateEntities(); + this.checkEntities(); + + // remove all killed entities + for( var i = 0; i < this._deferredKill.length; i++ ) { + this.entities.erase( this._deferredKill[i] ); + } + this._deferredKill = []; + + // update background animations + for( var tileset in this.backgroundAnims ) { + var anims = this.backgroundAnims[tileset]; + for( var a in anims ) { + anims[a].update(); + } + } + }, + + + updateEntities: function() { + for( var i = 0; i < this.entities.length; i++ ) { + var ent = this.entities[i]; + if( !ent._killed ) { + ent.update(); + } + } + }, + + + draw: function(){ + if( this.clearColor ) { + ig.system.clear( this.clearColor ); + } + + this._rscreen.x = Math.round(this.screen.x * ig.system.scale)/ig.system.scale; + this._rscreen.y = Math.round(this.screen.y * ig.system.scale)/ig.system.scale; + + var mapIndex; + for( mapIndex = 0; mapIndex < this.backgroundMaps.length; mapIndex++ ) { + var map = this.backgroundMaps[mapIndex]; + if( map.foreground ) { + // All foreground layers are drawn after the entities + break; + } + map.setScreenPos( this.screen.x, this.screen.y ); + map.draw(); + } + + + this.drawEntities(); + + + for( mapIndex; mapIndex < this.backgroundMaps.length; mapIndex++ ) { + var map = this.backgroundMaps[mapIndex]; + map.setScreenPos( this.screen.x, this.screen.y ); + map.draw(); + } + }, + + + drawEntities: function() { + for( var i = 0; i < this.entities.length; i++ ) { + this.entities[i].draw(); + } + }, + + + checkEntities: function() { + // Insert all entities into a spatial hash and check them against any + // other entity that already resides in the same cell. Entities that are + // bigger than a single cell, are inserted into each one they intersect + // with. + + // A list of entities, which the current one was already checked with, + // is maintained for each entity. + + var hash = {}; + for( var e = 0; e < this.entities.length; e++ ) { + var entity = this.entities[e]; + + // Skip entities that don't check, don't get checked and don't collide + if( + entity.type == ig.Entity.TYPE.NONE && + entity.checkAgainst == ig.Entity.TYPE.NONE && + entity.collides == ig.Entity.COLLIDES.NEVER + ) { + continue; + } + + var checked = {}, + xmin = Math.floor( entity.pos.x/this.cellSize ), + ymin = Math.floor( entity.pos.y/this.cellSize ), + xmax = Math.floor( (entity.pos.x+entity.size.x)/this.cellSize ) + 1, + ymax = Math.floor( (entity.pos.y+entity.size.y)/this.cellSize ) + 1; + + for( var x = xmin; x < xmax; x++ ) { + for( var y = ymin; y < ymax; y++ ) { + + // Current cell is empty - create it and insert! + if( !hash[x] ) { + hash[x] = {}; + hash[x][y] = [entity]; + } + else if( !hash[x][y] ) { + hash[x][y] = [entity]; + } + + // Check against each entity in this cell, then insert + else { + var cell = hash[x][y]; + for( var c = 0; c < cell.length; c++ ) { + + // Intersects and wasn't already checkd? + if( entity.touches(cell[c]) && !checked[cell[c].id] ) { + checked[cell[c].id] = true; + ig.Entity.checkPair( entity, cell[c] ); + } + } + cell.push(entity); + } + } // end for y size + } // end for x size + } // end for entities + } +}); + +ig.Game.SORT = { + Z_INDEX: function( a, b ){ return a.zIndex - b.zIndex; }, + POS_X: function( a, b ){ return a.pos.x - b.pos.x; }, + POS_Y: function( a, b ){ return a.pos.y - b.pos.y; } +}; + +}); \ No newline at end of file diff --git a/dev/lib/impact/image.js b/dev/lib/impact/image.js new file mode 100755 index 0000000..dce846f --- /dev/null +++ b/dev/lib/impact/image.js @@ -0,0 +1,183 @@ +ig.module( + 'impact.image' +) +.defines(function(){ + +ig.Image = ig.Class.extend({ + data: null, + width: 0, + height: 0, + loaded: false, + failed: false, + loadCallback: null, + path: '', + + + staticInstantiate: function( path ) { + return ig.Image.cache[path] || null; + }, + + + init: function( path ) { + this.path = path; + this.load(); + }, + + + load: function( loadCallback ) { + if( this.loaded ) { + if( loadCallback ) { + loadCallback( this.path, true ); + } + return; + } + else if( !this.loaded && ig.ready ) { + this.loadCallback = loadCallback || null; + + this.data = new Image(); + this.data.onload = this.onload.bind(this); + this.data.onerror = this.onerror.bind(this); + this.data.src = this.path + ig.nocache; + } + else { + ig.addResource( this ); + } + + ig.Image.cache[this.path] = this; + }, + + + reload: function() { + this.loaded = false; + this.data = new Image(); + this.data.onload = this.onload.bind(this); + this.data.src = this.path + '?' + Date.now(); + }, + + + onload: function( event ) { + this.width = this.data.width; + this.height = this.data.height; + + if( ig.system.scale != 1 ) { + this.resize( ig.system.scale ); + } + this.loaded = true; + + if( this.loadCallback ) { + this.loadCallback( this.path, true ); + } + }, + + + onerror: function( event ) { + this.failed = true; + + if( this.loadCallback ) { + this.loadCallback( this.path, false ); + } + }, + + + resize: function( scale ) { + // Nearest-Neighbor scaling + + // The original image is drawn into an offscreen canvas of the same size + // and copied into another offscreen canvas with the new size. + // The scaled offscreen canvas becomes the image (data) of this object. + + var widthScaled = this.width * scale; + var heightScaled = this.height * scale; + + var orig = ig.$new('canvas'); + orig.width = this.width; + orig.height = this.height; + var origCtx = orig.getContext('2d'); + origCtx.drawImage( this.data, 0, 0, this.width, this.height, 0, 0, this.width, this.height ); + var origPixels = origCtx.getImageData(0, 0, this.width, this.height); + + var scaled = ig.$new('canvas'); + scaled.width = widthScaled; + scaled.height = heightScaled; + var scaledCtx = scaled.getContext('2d'); + var scaledPixels = scaledCtx.getImageData( 0, 0, widthScaled, heightScaled ); + + for( var y = 0; y < heightScaled; y++ ) { + for( var x = 0; x < widthScaled; x++ ) { + var index = (Math.floor(y / scale) * this.width + Math.floor(x / scale)) * 4; + var indexScaled = (y * widthScaled + x) * 4; + scaledPixels.data[ indexScaled ] = origPixels.data[ index ]; + scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ]; + scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ]; + scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ]; + } + } + scaledCtx.putImageData( scaledPixels, 0, 0 ); + this.data = scaled; + }, + + + draw: function( targetX, targetY, sourceX, sourceY, width, height ) { + if( !this.loaded ) { return; } + + var scale = ig.system.scale; + sourceX = sourceX ? sourceX * scale : 0; + sourceY = sourceY ? sourceY * scale : 0; + width = (width ? width : this.width) * scale; + height = (height ? height : this.height) * scale; + + ig.system.context.drawImage( + this.data, sourceX, sourceY, width, height, + ig.system.getDrawPos(targetX), + ig.system.getDrawPos(targetY), + width, height + ); + + ig.Image.drawCount++; + }, + + + drawTile: function( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY ) { + tileHeight = tileHeight ? tileHeight : tileWidth; + + if( !this.loaded || tileWidth > this.width || tileHeight > this.height ) { return; } + + var scale = ig.system.scale; + var tileWidthScaled = tileWidth * scale; + var tileHeightScaled = tileHeight * scale; + + var scaleX = flipX ? -1 : 1; + var scaleY = flipY ? -1 : 1; + + if( flipX || flipY ) { + ig.system.context.save(); + ig.system.context.scale( scaleX, scaleY ); + } + ig.system.context.drawImage( + this.data, + ( Math.floor(tile * tileWidth) % this.width ) * scale, + ( Math.floor(tile * tileWidth / this.width) * tileHeight ) * scale, + tileWidthScaled, + tileHeightScaled, + ig.system.getDrawPos(targetX) * scaleX - (flipX ? tileWidthScaled : 0), + ig.system.getDrawPos(targetY) * scaleY - (flipY ? tileHeightScaled : 0), + tileWidthScaled, + tileHeightScaled + ); + if( flipX || flipY ) { + ig.system.context.restore(); + } + + ig.Image.drawCount++; + } +}); + +ig.Image.drawCount = 0; +ig.Image.cache = {}; +ig.Image.reloadCache = function() { + for( path in ig.Image.cache ) { + ig.Image.cache[path].reload(); + } +}; + +}); \ No newline at end of file diff --git a/dev/lib/impact/impact.js b/dev/lib/impact/impact.js new file mode 100755 index 0000000..81c4def --- /dev/null +++ b/dev/lib/impact/impact.js @@ -0,0 +1,476 @@ + +// ----------------------------------------------------------------------------- +// Impact Game Library 1.19 +// http://impactjs.com/ +// ----------------------------------------------------------------------------- + + + +// ----------------------------------------------------------------------------- +// Native Object extensions + +Number.prototype.map = function(istart, istop, ostart, ostop) { + return ostart + (ostop - ostart) * ((this - istart) / (istop - istart)); +}; + +Number.prototype.limit = function(min, max) { + return Math.min(max, Math.max(min, this)); +}; + +Number.prototype.round = function(precision) { + precision = Math.pow(10, precision || 0); + return Math.round(this * precision) / precision; +}; + +Number.prototype.floor = function() { + return Math.floor(this); +}; + +Number.prototype.ceil = function() { + return Math.ceil(this); +}; + +Number.prototype.toInt = function() { + return (this | 0); +}; + +Number.prototype.toRad = function() { + return (this / 180) * Math.PI; +}; + +Number.prototype.toDeg = function() { + return (this * 180) / Math.PI; +}; + +Array.prototype.erase = function(item) { + for( var i = this.length; i--; ) { + if( this[i] === item ) { + this.splice(i, 1); + } + } + return this; +}; + +Array.prototype.random = function() { + return this[ Math.floor(Math.random() * this.length) ]; +}; + +Function.prototype.bind = function(bind) { + var self = this; + return function(){ + var args = Array.prototype.slice.call(arguments); + return self.apply(bind || null, args); + }; +}; + + + +// ----------------------------------------------------------------------------- +// ig Namespace + +(function(window){ + +window.ig = { + game: null, + debug: null, + version: '1.19', + global: window, + modules: {}, + resources: [], + ready: false, + baked: false, + nocache: '', + ua: {}, + lib: 'lib/', + + _current: null, + _loadQueue: [], + _waitForOnload: 0, + + + $: function( selector ) { + return selector.charAt(0) == '#' + ? document.getElementById( selector.substr(1) ) + : document.getElementsByTagName( selector ); + }, + + + $new: function( name ) { + return document.createElement( name ); + }, + + + copy: function( object ) { + if( + !object || typeof(object) != 'object' || + object instanceof HTMLElement || + object instanceof ig.Class + ) { + return object; + } + else if( object instanceof Array ) { + var c = []; + for( var i = 0, l = object.length; i < l; i++) { + c[i] = ig.copy(object[i]); + } + return c; + } + else { + var c = {}; + for( var i in object ) { + c[i] = ig.copy(object[i]); + } + return c; + } + }, + + + merge: function( original, extended ) { + for( var key in extended ) { + var ext = extended[key]; + if( + typeof(ext) != 'object' || + ext instanceof HTMLElement || + ext instanceof ig.Class + ) { + original[key] = ext; + } + else { + if( !original[key] || typeof(original[key]) != 'object' ) { + original[key] = (ext instanceof Array) ? [] : {}; + } + ig.merge( original[key], ext ); + } + } + return original; + }, + + + ksort: function( obj ) { + if( !obj || typeof(obj) != 'object' ) { + return []; + } + + var keys = [], values = []; + for( var i in obj ) { + keys.push(i); + } + + keys.sort(); + for( var i = 0; i < keys.length; i++ ) { + values.push( obj[keys[i]] ); + } + + return values; + }, + + + module: function( name ) { + if( ig._current ) { + throw( "Module '"+ig._current.name+"' defines nothing" ); + } + if( ig.modules[name] && ig.modules[name].body ) { + throw( "Module '"+name+"' is already defined" ); + } + + ig._current = {name: name, requires: [], loaded: false, body: null}; + ig.modules[name] = ig._current; + ig._loadQueue.push(ig._current); + ig._initDOMReady(); + return ig; + }, + + + requires: function() { + ig._current.requires = Array.prototype.slice.call(arguments); + return ig; + }, + + + defines: function( body ) { + name = ig._current.name; + ig._current.body = body; + ig._current = null; + ig._execModules(); + }, + + + addResource: function( resource ) { + ig.resources.push( resource ); + }, + + + setNocache: function( set ) { + ig.nocache = set + ? '?' + Date.now() + : ''; + }, + + + // Stubs for ig.Debug + log: function() {}, + show: function( name, number ) {}, + mark: function( msg, color ) {}, + + + _loadScript: function( name, requiredFrom ) { + ig.modules[name] = {name: name, requires:[], loaded: false, body: null}; + ig._waitForOnload++; + + var path = ig.lib + name.replace(/\./g, '/') + '.js' + ig.nocache; + var script = ig.$new('script'); + script.type = 'text/javascript'; + script.src = path; + script.onload = function() { + ig._waitForOnload--; + ig._execModules(); + }; + script.onerror = function() { + throw( + 'Failed to load module '+name+' at ' + path + ' ' + + 'required from ' + requiredFrom + ); + }; + ig.$('head')[0].appendChild(script); + }, + + + _execModules: function() { + var modulesLoaded = false; + for( var i = 0; i < ig._loadQueue.length; i++ ) { + var m = ig._loadQueue[i]; + var dependenciesLoaded = true; + + for( var j = 0; j < m.requires.length; j++ ) { + var name = m.requires[j]; + if( !ig.modules[name] ) { + dependenciesLoaded = false; + ig._loadScript( name, m.name ); + } + else if( !ig.modules[name].loaded ) { + dependenciesLoaded = false; + } + } + + if( dependenciesLoaded && m.body ) { + ig._loadQueue.splice(i, 1); + m.loaded = true; + m.body(); + modulesLoaded = true; + i--; + } + } + + if( modulesLoaded ) { + ig._execModules(); + } + + // No modules executed, no more files to load but loadQueue not empty? + // Must be some unresolved dependencies! + else if( !ig.baked && ig._waitForOnload == 0 && ig._loadQueue.length != 0 ) { + var unresolved = []; + for( var i = 0; i < ig._loadQueue.length; i++ ) { + + // Which dependencies aren't loaded? + var unloaded = []; + var requires = ig._loadQueue[i].requires; + for( var j = 0; j < requires.length; j++ ) { + var m = ig.modules[ requires[j] ]; + if( !m || !m.loaded ) { + unloaded.push( requires[j] ); + } + } + unresolved.push( ig._loadQueue[i].name + ' (requires: ' + unloaded.join(', ') + ')'); + } + + throw( + 'Unresolved (circular?) dependencies. ' + + "Most likely there's a name/path mismatch for one of the listed modules:\n" + + unresolved.join('\n') + ); + } + }, + + + _DOMReady: function() { + if( !ig.modules['dom.ready'].loaded ) { + if ( !document.body ) { + return setTimeout( ig._DOMReady, 13 ); + } + ig.modules['dom.ready'].loaded = true; + ig._waitForOnload--; + ig._execModules(); + } + return 0; + }, + + + _boot: function() { + if( document.location.href.match(/\?nocache/) ) { + ig.setNocache( true ); + } + + // Probe user agent string + ig.ua.pixelRatio = window.devicePixelRatio || 1; + ig.ua.viewport = { + width: window.innerWidth, + height: window.innerHeight + }; + ig.ua.screen = { + width: window.screen.availWidth * ig.ua.pixelRatio, + height: window.screen.availHeight * ig.ua.pixelRatio + }; + + ig.ua.iPhone = /iPhone/i.test(navigator.userAgent); + ig.ua.iPhone4 = (ig.ua.iPhone && ig.ua.pixelRatio == 2); + ig.ua.iPad = /iPad/i.test(navigator.userAgent); + ig.ua.android = /android/i.test(navigator.userAgent); + ig.ua.iOS = ig.ua.iPhone || ig.ua.iPad; + ig.ua.mobile = ig.ua.iOS || ig.ua.android; + }, + + + _initDOMReady: function() { + if( ig.modules['dom.ready'] ) { return; } + + ig._boot(); + + + ig.modules['dom.ready'] = { requires: [], loaded: false, body: null }; + ig._waitForOnload++; + if ( document.readyState === 'complete' ) { + ig._DOMReady(); + } + else { + document.addEventListener( 'DOMContentLoaded', ig._DOMReady, false ); + window.addEventListener( 'load', ig._DOMReady, false ); + } + } +}; + + +// ----------------------------------------------------------------------------- +// Class object based on John Resigs code; inspired by base2 and Prototype +// http://ejohn.org/blog/simple-javascript-inheritance/ + +var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\bparent\b/ : /.*/; + +window.ig.Class = function(){}; +var inject = function(prop) { + var proto = this.prototype; + var parent = {}; + for( var name in prop ) { + if( + typeof(prop[name]) == "function" && + typeof(proto[name]) == "function" && + fnTest.test(prop[name]) + ) { + parent[name] = proto[name]; // save original function + proto[name] = (function(name, fn){ + return function() { + var tmp = this.parent; + this.parent = parent[name]; + var ret = fn.apply(this, arguments); + this.parent = tmp; + return ret; + }; + })( name, prop[name] ); + } + else { + proto[name] = prop[name]; + } + } +}; + +window.ig.Class.extend = function(prop) { + var parent = this.prototype; + + initializing = true; + var prototype = new this(); + initializing = false; + + for( var name in prop ) { + if( + typeof(prop[name]) == "function" && + typeof(parent[name]) == "function" && + fnTest.test(prop[name]) + ) { + prototype[name] = (function(name, fn){ + return function() { + var tmp = this.parent; + this.parent = parent[name]; + var ret = fn.apply(this, arguments); + this.parent = tmp; + return ret; + }; + })( name, prop[name] ); + } + else { + prototype[name] = prop[name]; + } + } + + function Class() { + if( !initializing ) { + + // If this class has a staticInstantiate method, invoke it + // and check if we got something back. If not, the normal + // constructor (init) is called. + if( this.staticInstantiate ) { + var obj = this.staticInstantiate.apply(this, arguments); + if( obj ) { + return obj; + } + } + for( var p in this ) { + if( typeof(this[p]) == 'object' ) { + this[p] = ig.copy(this[p]); // deep copy! + } + } + if( this.init ) { + this.init.apply(this, arguments); + } + } + return this; + } + + Class.prototype = prototype; + Class.constructor = Class; + Class.extend = arguments.callee; + Class.inject = inject; + + return Class; +}; + +})(window); + + + +// ----------------------------------------------------------------------------- +// The main() function creates the system, input, sound and game objects, +// creates a preloader and starts the run loop + +ig.module( + 'impact.impact' +) +.requires( + 'dom.ready', + 'impact.loader', + 'impact.system', + 'impact.input', + 'impact.sound' +) +.defines(function(){ + +ig.main = function( canvasId, gameClass, fps, width, height, scale, loaderClass ) { + ig.system = new ig.System( canvasId, fps, width, height, scale || 1 ); + ig.input = new ig.Input(); + ig.soundManager = new ig.SoundManager(); + ig.music = new ig.Music(); + ig.ready = true; + + var loader = new (loaderClass || ig.Loader)( gameClass, ig.resources ); + loader.load(); +}; + +}); \ No newline at end of file diff --git a/dev/lib/impact/input.js b/dev/lib/impact/input.js new file mode 100755 index 0000000..209ef50 --- /dev/null +++ b/dev/lib/impact/input.js @@ -0,0 +1,308 @@ +ig.module( + 'impact.input' +) +.defines(function(){ + +ig.KEY = { + 'MOUSE1': -1, + 'MOUSE2': -3, + 'MWHEEL_UP': -4, + 'MWHEEL_DOWN': -5, + + 'BACKSPACE': 8, + 'TAB': 9, + 'ENTER': 13, + 'PAUSE': 19, + 'CAPS': 20, + 'ESC': 27, + 'SPACE': 32, + 'PAGE_UP': 33, + 'PAGE_DOWN': 34, + 'END': 35, + 'HOME': 36, + 'LEFT_ARROW': 37, + 'UP_ARROW': 38, + 'RIGHT_ARROW': 39, + 'DOWN_ARROW': 40, + 'INSERT': 45, + 'DELETE': 46, + '_0': 48, + '_1': 49, + '_2': 50, + '_3': 51, + '_4': 52, + '_5': 53, + '_6': 54, + '_7': 55, + '_8': 56, + '_9': 57, + 'A': 65, + 'B': 66, + 'C': 67, + 'D': 68, + 'E': 69, + 'F': 70, + 'G': 71, + 'H': 72, + 'I': 73, + 'J': 74, + 'K': 75, + 'L': 76, + 'M': 77, + 'N': 78, + 'O': 79, + 'P': 80, + 'Q': 81, + 'R': 82, + 'S': 83, + 'T': 84, + 'U': 85, + 'V': 86, + 'W': 87, + 'X': 88, + 'Y': 89, + 'Z': 90, + 'NUMPAD_0': 96, + 'NUMPAD_1': 97, + 'NUMPAD_2': 98, + 'NUMPAD_3': 99, + 'NUMPAD_4': 100, + 'NUMPAD_5': 101, + 'NUMPAD_6': 102, + 'NUMPAD_7': 103, + 'NUMPAD_8': 104, + 'NUMPAD_9': 105, + 'MULTIPLY': 106, + 'ADD': 107, + 'SUBSTRACT': 109, + 'DECIMAL': 110, + 'DIVIDE': 111, + 'F1': 112, + 'F2': 113, + 'F3': 114, + 'F4': 115, + 'F5': 116, + 'F6': 117, + 'F7': 118, + 'F8': 119, + 'F9': 120, + 'F10': 121, + 'F11': 122, + 'F12': 123, + 'SHIFT': 16, + 'CTRL': 17, + 'ALT': 18, + 'PLUS': 187, + 'COMMA': 188, + 'MINUS': 189, + 'PERIOD': 190 +}; + + +ig.Input = ig.Class.extend({ + bindings: {}, + actions: {}, + presses: {}, + locks: {}, + delayedKeyup: {}, + + isUsingMouse: false, + isUsingKeyboard: false, + isUsingAccelerometer: false, + mouse: {x: 0, y: 0}, + accel: {x: 0, y: 0, z: 0}, + + + initMouse: function() { + if( this.isUsingMouse ) { return; } + this.isUsingMouse = true; + window.addEventListener('mousewheel', this.mousewheel.bind(this), false ); + ig.system.canvas.addEventListener('contextmenu', this.contextmenu.bind(this), false ); + ig.system.canvas.addEventListener('mousedown', this.keydown.bind(this), false ); + ig.system.canvas.addEventListener('mouseup', this.keyup.bind(this), false ); + ig.system.canvas.addEventListener('mousemove', this.mousemove.bind(this), false ); + + ig.system.canvas.addEventListener('touchstart', this.keydown.bind(this), false ); + ig.system.canvas.addEventListener('touchend', this.keyup.bind(this), false ); + ig.system.canvas.addEventListener('touchmove', this.mousemove.bind(this), false ); + }, + + + initKeyboard: function() { + if( this.isUsingKeyboard ) { return; } + this.isUsingKeyboard = true; + window.addEventListener('keydown', this.keydown.bind(this), false ); + window.addEventListener('keyup', this.keyup.bind(this), false ); + }, + + + initAccelerometer: function() { + if( this.isUsingAccelerometer ) { return; } + window.addEventListener('devicemotion', this.devicemotion.bind(this), false ); + }, + + + mousewheel: function( event ) { + var code = event.wheel > 0 ? ig.KEY.MWHEEL_UP : ig.KEY.MWHEEL_DOWN; + var action = this.bindings[code]; + if( action ) { + this.actions[action] = true; + this.presses[action] = true; + event.stopPropagation(); + this.delayedKeyup[action] = true; + } + }, + + + mousemove: function( event ) { + var el = ig.system.canvas; + var pos = {left: 0, top: 0}; + while( el != null ) { + pos.left += el.offsetLeft; + pos.top += el.offsetTop; + el = el.offsetParent; + } + var tx = event.pageX; + var ty = event.pageY; + if( event.touches ) { + tx = event.touches[0].clientX; + ty = event.touches[0].clientY; + } + + this.mouse.x = (tx - pos.left) / ig.system.scale; + this.mouse.y = (ty - pos.top) / ig.system.scale; + }, + + + contextmenu: function( event ) { + if( this.bindings[ig.KEY.MOUSE2] ) { + event.stopPropagation(); + event.preventDefault(); + } + }, + + + keydown: function( event ) { + if( event.target.type == 'text' ) { return; } + + var code = event.type == 'keydown' + ? event.keyCode + : (event.button == 2 ? ig.KEY.MOUSE2 : ig.KEY.MOUSE1); + + if( event.type == 'touchstart' || event.type == 'mousedown' ) { + this.mousemove( event ); + } + + var action = this.bindings[code]; + if( action ) { + this.actions[action] = true; + if( !this.locks[action] ) { + this.presses[action] = true; + this.locks[action] = true; + } + event.stopPropagation(); + event.preventDefault(); + } + }, + + + keyup: function( event ) { + if( event.target.type == 'text' ) { return; } + + var code = event.type == 'keyup' + ? event.keyCode + : (event.button == 2 ? ig.KEY.MOUSE2 : ig.KEY.MOUSE1); + + var action = this.bindings[code]; + if( action ) { + this.delayedKeyup[action] = true; + event.stopPropagation(); + event.preventDefault(); + } + }, + + + devicemotion: function( event ) { + this.accel = event.accelerationIncludingGravity; + }, + + + bind: function( key, action ) { + if( key < 0 ) { this.initMouse(); } + else if( key > 0 ) { this.initKeyboard(); } + this.bindings[key] = action; + }, + + + bindTouch: function( selector, action ) { + var element = ig.$( selector ); + + var that = this; + element.addEventListener('touchstart', function(ev) { + that.touchStart( ev, action ); + }, false); + + element.addEventListener('touchend', function(ev) { + that.touchEnd( ev, action ); + }, false); + }, + + + unbind: function( key ) { + var action = this.bindings[key]; + this.delayedKeyup[action] = true; + + this.bindings[key] = null; + }, + + + unbindAll: function() { + this.bindings = {}; + this.actions = {}; + this.presses = {}; + this.locks = {}; + this.delayedKeyup = {}; + }, + + + state: function( action ) { + return this.actions[action]; + }, + + + pressed: function( action ) { + return this.presses[action]; + }, + + released: function( action ) { + return this.delayedKeyup[action]; + }, + + clearPressed: function() { + for( var action in this.delayedKeyup ) { + this.actions[action] = false; + this.locks[action] = false; + } + this.delayedKeyup = {}; + this.presses = {}; + }, + + touchStart: function( event, action ) { + this.actions[action] = true; + this.presses[action] = true; + + event.stopPropagation(); + event.preventDefault(); + return false; + }, + + + touchEnd: function( event, action ) { + this.delayedKeyup[action] = true; + event.stopPropagation(); + event.preventDefault(); + return false; + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/impact/loader.js b/dev/lib/impact/loader.js new file mode 100755 index 0000000..5017cd6 --- /dev/null +++ b/dev/lib/impact/loader.js @@ -0,0 +1,101 @@ +ig.module( + 'impact.loader' +) +.requires( + 'impact.image', + 'impact.font', + 'impact.sound' +) +.defines(function(){ + +ig.Loader = ig.Class.extend({ + resources: [], + + gameClass: null, + status: 0, + done: false, + + _unloaded: [], + _drawStatus: 0, + _intervalId: 0, + _loadCallbackBound: null, + + + init: function( gameClass, resources ) { + this.gameClass = gameClass; + this.resources = resources; + this._loadCallbackBound = this._loadCallback.bind(this); + + for( var i = 0; i < this.resources.length; i++ ) { + this._unloaded.push( this.resources[i].path ); + } + }, + + + load: function() { + ig.system.clear( '#000' ); + + if( !this.resources.length ) { + this.end(); + return; + } + + for( var i = 0; i < this.resources.length; i++ ) { + this.loadResource( this.resources[i] ); + } + this._intervalId = setInterval( this.draw.bind(this), 16 ); + }, + + + loadResource: function( res ) { + res.load( this._loadCallbackBound ); + }, + + + end: function() { + if( this.done ) { return; } + + this.done = true; + clearInterval( this._intervalId ); + ig.system.setGame( this.gameClass ); + }, + + + draw: function() { + this._drawStatus += (this.status - this._drawStatus)/5; + var s = ig.system.scale; + var w = ig.system.width * 0.6; + var h = ig.system.height * 0.1; + var x = ig.system.width * 0.5-w/2; + var y = ig.system.height * 0.5-h/2; + + ig.system.context.fillStyle = '#000'; + ig.system.context.fillRect( 0, 0, 480, 320 ); + + ig.system.context.fillStyle = '#fff'; + ig.system.context.fillRect( x*s, y*s, w*s, h*s ); + + ig.system.context.fillStyle = '#000'; + ig.system.context.fillRect( x*s+s, y*s+s, w*s-s-s, h*s-s-s ); + + ig.system.context.fillStyle = '#fff'; + ig.system.context.fillRect( x*s, y*s, w*s*this._drawStatus, h*s ); + }, + + + _loadCallback: function( path, status ) { + if( status ) { + this._unloaded.erase( path ); + } + else { + throw( 'Failed to load resource: ' + path ); + } + + this.status = 1 - (this._unloaded.length / this.resources.length); + if( this._unloaded.length == 0 ) { // all done? + setTimeout( this.end.bind(this), 250 ); + } + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/impact/map.js b/dev/lib/impact/map.js new file mode 100755 index 0000000..5cbfd84 --- /dev/null +++ b/dev/lib/impact/map.js @@ -0,0 +1,48 @@ +ig.module( + 'impact.map' +) +.defines(function(){ + +ig.Map = ig.Class.extend({ + tilesize: 8, + width: 1, + height: 1, + data: [[]], + + + init: function( tilesize, data ) { + this.tilesize = tilesize; + this.data = data; + this.height = data.length; + this.width = data[0].length; + }, + + + getTile: function( x, y ) { + var tx = Math.floor( x / this.tilesize ); + var ty = Math.floor( y / this.tilesize ); + if( + (tx >= 0 && tx < this.width) && + (ty >= 0 && ty < this.height) + ) { + return this.data[ty][tx]; + } + else { + return 0; + } + }, + + + setTile: function( x, y, tile ) { + var tx = Math.floor( x / this.tilesize ); + var ty = Math.floor( y / this.tilesize ); + if( + (tx >= 0 && tx < this.width) && + (ty >= 0 && ty < this.height) + ) { + this.data[ty][tx] = tile; + } + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/impact/sound.js b/dev/lib/impact/sound.js new file mode 100755 index 0000000..a3ac314 --- /dev/null +++ b/dev/lib/impact/sound.js @@ -0,0 +1,333 @@ +ig.module( + 'impact.sound' +) +.defines(function(){ + +ig.SoundManager = ig.Class.extend({ + clips: {}, + volume: 1, + format: null, + + init: function() { + // Probe sound formats and determine the file extension to load + var probe = new Audio(); + for( var i = 0; i < ig.Sound.use.length; i++ ) { + var format = ig.Sound.use[i]; + if( probe.canPlayType(format.mime) ) { + this.format = format; + break; + } + } + + // No compatible format found? -> Disable sound + if( !this.format ) { + ig.Sound.enabled = false; + } + }, + + + load: function( path, multiChannel, loadCallback ) { + + // Path to the soundfile with the right extension (.ogg or .mp3) + var realPath = path.match(/^(.*)\.[^\.]+$/)[1] + '.' + this.format.ext + ig.nocache; + + // Sound file already loaded? + if( this.clips[path] ) { + + // Only loaded as single channel and now requested as multichannel? + if( multiChannel && this.clips[path].length < ig.Sound.channels ) { + for( var i = this.clips[path].length; i < ig.Sound.channels; i++ ) { + var a = new Audio( realPath ); + a.load(); + this.clips[path].push( a ); + } + } + return this.clips[path][0]; + } + + var clip = new Audio( realPath ); + if( loadCallback ) { + + // The canplaythrough event is dispatched when the browser determines + // that the sound can be played without interuption, provided the + // download rate doesn't change. + // FIXME: Mobile Safari doesn't seem to dispatch this event at all? + clip.addEventListener( 'canplaythrough', function(ev){ + this.removeEventListener('canplaythrough', arguments.callee, false); + loadCallback( path, true, ev ); + }, false ); + + // FIXME: Sometimes Firefox aborts loading sounds for no reason(?), + // tell the callback that everything went fine anyway + // Haven't been able to determine when or why this happens :/ + // Update: Firefox4 doesn't have this problem anymore, but + // now IE9 does :( + clip.addEventListener( 'error', function(ev){ + loadCallback( path, true, ev ); // should pass 'false' + }, false); + } + clip.load(); + + this.clips[path] = [clip]; + if( multiChannel ) { + for( var i = 1; i < ig.Sound.channels; i++ ) { + var a = new Audio(realPath); + a.load(); + this.clips[path].push( a ); + } + } + + return clip; + }, + + + get: function( path ) { + // Find and return a channel that is not currently playing + var channels = this.clips[path]; + for( var i = 0, clip; clip = channels[i++]; ) { + if( clip.paused || clip.ended ) { + if( clip.ended ) { + clip.currentTime = 0; + } + return clip; + } + } + + // Still here? Pause and rewind the first channel + channels[0].pause(); + channels[0].currentTime = 0; + return channels[0]; + } +}); + + + +ig.Music = ig.Class.extend({ + tracks: [], + namedTracks: {}, + currentTrack: null, + currentIndex: 0, + random: false, + + _volume: 1, + _loop: false, + _fadeInterval: 0, + _fadeTimer: null, + _endedCallbackBound: null, + + + init: function() { + this._endedCallbackBound = this._endedCallback.bind(this); + + if( Object.defineProperty ) { // Standard + Object.defineProperty(this,"volume", { + get: this.getVolume.bind(this), + set: this.setVolume.bind(this) + }); + + Object.defineProperty(this,"loop", { + get: this.getLooping.bind(this), + set: this.setLooping.bind(this) + }); + } + else if( this.__defineGetter__ ) { // Non-standard + this.__defineGetter__('volume', this.getVolume.bind(this)); + this.__defineSetter__('volume', this.setVolume.bind(this)); + + this.__defineGetter__('loop', this.getLooping.bind(this)); + this.__defineSetter__('loop', this.setLooping.bind(this)); + } + }, + + + add: function( music, name ) { + if( !ig.Sound.enabled ) { + return; + } + + var path = music instanceof ig.Sound ? music.path : music; + + var track = ig.soundManager.load(path, false); + track.loop = this._loop; + track.volume = this._volume; + track.addEventListener( 'ended', this._endedCallbackBound, false ); + this.tracks.push( track ); + + if( name ) { + this.namedTracks[name] = track; + } + + if( !this.currentTrack ) { + this.currentTrack = track; + } + }, + + + next: function() { + if( !this.tracks.length ) { return; } + + this.stop(); + this.currentIndex = this.random + ? Math.floor(Math.random() * this.tracks.length) + : (this.currentIndex + 1) % this.tracks.length; + this.currentTrack = this.tracks[this.currentIndex]; + this.play(); + }, + + + pause: function() { + if( !this.currentTrack ) { return; } + this.currentTrack.pause(); + }, + + + stop: function() { + if( !this.currentTrack ) { return; } + this.currentTrack.pause(); + this.currentTrack.currentTime = 0; + }, + + + play: function( name ) { + // If a name was provided, stop playing the current track (if any) + // and play the named track + if( name && this.namedTracks[name] ) { + var newTrack = this.namedTracks[name]; + if( newTrack != this.currentTrack ) { + this.stop(); + this.currentTrack = newTrack; + } + } + else if( !this.currentTrack ) { + return; + } + this.currentTrack.play(); + }, + + + getLooping: function() { + return this._loop; + }, + + + setLooping: function( l ) { + this._loop = l; + for( var i in this.tracks ) { + this.tracks[i].loop = l; + } + }, + + + getVolume: function() { + return this._volume; + }, + + + setVolume: function( v ) { + this._volume = v.limit(0,1); + for( var i in this.tracks ) { + this.tracks[i].volume = this._volume; + } + }, + + + fadeOut: function( time ) { + if( !this.currentTrack ) { return; } + + clearInterval( this._fadeInterval ); + this.fadeTimer = new ig.Timer( time ); + this._fadeInterval = setInterval( this._fadeStep.bind(this), 50 ); + }, + + + _fadeStep: function() { + var v = this.fadeTimer.delta() + .map(-this.fadeTimer.target, 0, 1, 0) + .limit( 0, 1 ) + * this._volume; + + if( v <= 0.01 ) { + this.stop(); + this.currentTrack.volume = this._volume; + clearInterval( this._fadeInterval ); + } + else { + this.currentTrack.volume = v; + } + }, + + _endedCallback: function() { + if( this._loop ) { + this.play(); + } + else { + this.next(); + } + } +}); + + + +ig.Sound = ig.Class.extend({ + path: '', + volume: 1, + currentClip: null, + multiChannel: true, + + + init: function( path, multiChannel ) { + this.path = path; + this.multiChannel = (multiChannel !== false); + + this.load(); + }, + + + load: function( loadCallback ) { + if( !ig.Sound.enabled ) { + if( loadCallback ) { + loadCallback( this.path, true ); + } + return; + } + + if( ig.ready ) { + ig.soundManager.load( this.path, this.multiChannel, loadCallback ); + } + else { + ig.addResource( this ); + } + }, + + + play: function() { + if( !ig.Sound.enabled ) { + return; + } + + this.currentClip = ig.soundManager.get( this.path ); + this.currentClip.volume = ig.soundManager.volume * this.volume; + this.currentClip.play(); + }, + + + stop: function() { + if( this.currentClip ) { + this.currentClip.pause(); + this.currentClip.currentTime = 0; + } + } +}); + +ig.Sound.FORMAT = { + MP3: {ext: 'mp3', mime: 'audio/mpeg'}, + M4A: {ext: 'm4a', mime: 'audio/mp4; codecs=mp4a'}, + OGG: {ext: 'ogg', mime: 'audio/ogg; codecs=vorbis'}, + WEBM: {ext: 'webm', mime: 'audio/webm; codecs=vorbis'}, + CAF: {ext: 'caf', mime: 'audio/x-caf'} +}; +ig.Sound.use = [ig.Sound.FORMAT.OGG, ig.Sound.FORMAT.MP3]; +ig.Sound.channels = 4; +ig.Sound.enabled = true; + +}); \ No newline at end of file diff --git a/dev/lib/impact/system.js b/dev/lib/impact/system.js new file mode 100755 index 0000000..7c2ac2d --- /dev/null +++ b/dev/lib/impact/system.js @@ -0,0 +1,116 @@ +ig.module( + 'impact.system' +) +.requires( + 'impact.timer', + 'impact.image' +) +.defines(function(){ + +ig.System = ig.Class.extend({ + fps: 30, + width: 320, + height: 240, + realWidth: 320, + realHeight: 240, + scale: 1, + + tick: 0, + intervalId: 0, + newGameClass: null, + running: false, + + delegate: null, + clock: null, + canvas: null, + context: null, + + smoothPositioning: true, + + init: function( canvasId, fps, width, height, scale ) { + this.fps = fps; + + this.clock = new ig.Timer(); + this.canvas = ig.$(canvasId); + this.resize( width, height, scale ); + this.context = this.canvas.getContext('2d'); + }, + + + resize: function( width, height, scale ) { + this.width = width; + this.height = height; + this.scale = scale || this.scale; + + this.realWidth = this.width * this.scale; + this.realHeight = this.height * this.scale; + this.canvas.width = this.realWidth; + this.canvas.height = this.realHeight; + }, + + + setGame: function( gameClass ) { + if( this.running ) { + this.newGameClass = gameClass; + } + else { + this.setGameNow( gameClass ); + } + }, + + + setGameNow: function( gameClass ) { + ig.game = new (gameClass)(); + ig.system.setDelegate( ig.game ); + }, + + + setDelegate: function( object ) { + if( typeof(object.run) == 'function' ) { + this.delegate = object; + this.startRunLoop(); + } else { + throw( 'System.setDelegate: No run() function in object' ); + } + }, + + + stopRunLoop: function() { + clearInterval( this.intervalId ); + this.running = false; + }, + + + startRunLoop: function() { + this.stopRunLoop(); + this.intervalId = setInterval( this.run.bind(this), 1000 / this.fps ); + this.running = true; + }, + + + clear: function( color ) { + this.context.fillStyle = color; + this.context.fillRect( 0, 0, this.realWidth, this.realHeight ); + }, + + + run: function() { + ig.Timer.step(); + this.tick = this.clock.tick(); + + this.delegate.run(); + ig.input.clearPressed(); + + if( this.newGameClass ) { + this.setGameNow( this.newGameClass ); + this.newGameClass = null; + } + }, + + + getDrawPos: function( p ) { + return this.smoothPositioning ? Math.round(p * this.scale) : Math.round(p) * this.scale; + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/impact/timer.js b/dev/lib/impact/timer.js new file mode 100755 index 0000000..dc99315 --- /dev/null +++ b/dev/lib/impact/timer.js @@ -0,0 +1,54 @@ +ig.module( + 'impact.timer' +) +.defines(function(){ + +ig.Timer = ig.Class.extend({ + target: 0, + base: 0, + last: 0, + + init: function( seconds ) { + this.base = ig.Timer.time; + this.last = ig.Timer.time; + + this.target = seconds || 0; + }, + + + set: function( seconds ) { + this.target = seconds || 0; + this.base = ig.Timer.time; + }, + + + reset: function() { + this.base = ig.Timer.time; + }, + + + tick: function() { + var delta = ig.Timer.time - this.last; + this.last = ig.Timer.time; + return delta; + }, + + + delta: function() { + return ig.Timer.time - this.base - this.target; + } +}); + +ig.Timer._last = 0; +ig.Timer.time = 0; +ig.Timer.timeScale = 1; +ig.Timer.maxStep = 0.05; + +ig.Timer.step = function() { + var current = Date.now(); + var delta = (current - ig.Timer._last) / 1000; + ig.Timer.time += Math.min(delta, ig.Timer.maxStep) * ig.Timer.timeScale; + ig.Timer._last = current; +}; + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/api/browse.php b/dev/lib/weltmeister/api/browse.php new file mode 100755 index 0000000..2f29053 --- /dev/null +++ b/dev/lib/weltmeister/api/browse.php @@ -0,0 +1,37 @@ + $f ) { + $files[$i] = substr( $f, $fileRootLength ); +} +foreach( $dirs as $i => $d ) { + $dirs[$i] = substr( $d, $fileRootLength ); +} + +$parent = substr($_GET['dir'], 0, strrpos($_GET['dir'], '/')); +echo json_encode( array( + 'parent' => (empty($_GET['dir']) ? false : $parent), + 'dirs' => $dirs, + 'files' => $files +)); + +?> \ No newline at end of file diff --git a/dev/lib/weltmeister/api/config.php b/dev/lib/weltmeister/api/config.php new file mode 100755 index 0000000..119b0f6 --- /dev/null +++ b/dev/lib/weltmeister/api/config.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/dev/lib/weltmeister/api/glob.php b/dev/lib/weltmeister/api/glob.php new file mode 100755 index 0000000..61b4d4c --- /dev/null +++ b/dev/lib/weltmeister/api/glob.php @@ -0,0 +1,18 @@ + $f ) { + $files[$i] = substr( $f, $fileRootLength ); +} + +echo json_encode( $files ); + +?> \ No newline at end of file diff --git a/dev/lib/weltmeister/api/save.php b/dev/lib/weltmeister/api/save.php new file mode 100755 index 0000000..725d14b --- /dev/null +++ b/dev/lib/weltmeister/api/save.php @@ -0,0 +1,34 @@ + 0); + +if( !empty($_POST['path']) && !empty($_POST['data']) ) { + $path = WM_Config::$fileRoot . str_replace( '..', '', $_POST['path'] ); + + if( preg_match('/\.js$/', $path) ) { + $success = @file_put_contents( $path, $_POST['data'] ); + if( $success === false ) { + $result = array( + 'error' => '2', + 'msg' => "Couldn't write to file: $path" + ); + } + } + else { + $result = array( + 'error' => '3', + 'msg' => "File must have a .js suffix" + ); + } +} +else { + $result = array( + 'error' => '1', + 'msg' => "No Data or Path specified" + ); +} + +echo json_encode($result); + +?> \ No newline at end of file diff --git a/dev/lib/weltmeister/arrow.png b/dev/lib/weltmeister/arrow.png new file mode 100755 index 0000000..e9d2f0d Binary files /dev/null and b/dev/lib/weltmeister/arrow.png differ diff --git a/dev/lib/weltmeister/collisiontiles-64.png b/dev/lib/weltmeister/collisiontiles-64.png new file mode 100755 index 0000000..55efd3c Binary files /dev/null and b/dev/lib/weltmeister/collisiontiles-64.png differ diff --git a/dev/lib/weltmeister/config.js b/dev/lib/weltmeister/config.js new file mode 100755 index 0000000..fd41aef --- /dev/null +++ b/dev/lib/weltmeister/config.js @@ -0,0 +1,72 @@ +ig.module( + 'weltmeister.config' +) +.defines(function(){ + +wm.config = { + project: { + 'modulePath': 'lib/', + 'entityFiles': 'lib/game/entities/*.js', + 'levelPath': 'lib/game/levels/', + 'outputFormat': 'module', // 'module' or 'json' + 'prettyPrint': false + }, + + 'layerDefaults': { + 'width': 30, + 'height': 20, + 'tilesize': 8 + }, + + 'askBeforeClose': true, + 'loadLastLevel': true, + + 'entityGrid': 4, + 'undoLevels': 50, + + 'binds': { + 'MOUSE1': 'draw', + 'MOUSE2': 'drag', + 'SHIFT': 'select', + 'CTRL': 'drag', + 'SPACE': 'menu', + 'DELETE': 'delete', + 'BACKSPACE': 'delete', + 'G': 'grid', + 'C': 'clone', + 'Z': 'undo', + 'Y': 'redo' + }, + + 'view': { + 'zoom': 2, + 'grid': false + }, + + 'labels': { + 'draw': true, + 'step': 32, + 'font': '10px Bitstream Vera Sans Mono, Monaco, sans-serif' + }, + + 'colors': { + 'clear': '#000000', + 'highlight': '#ceff36', + 'primary': '#ffffff', + 'secondary': '#555555', + 'selection': '#ff9933' + }, + + 'collisionTiles': { + 'path': 'lib/weltmeister/collisiontiles-64.png', + 'tilesize': 64 + }, + + 'api': { + 'save': 'lib/weltmeister/api/save.php', + 'browse': 'lib/weltmeister/api/browse.php', + 'glob': 'lib/weltmeister/api/glob.php' + } +}; + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/edit-entities.js b/dev/lib/weltmeister/edit-entities.js new file mode 100755 index 0000000..648733c --- /dev/null +++ b/dev/lib/weltmeister/edit-entities.js @@ -0,0 +1,667 @@ +ig.module( + 'weltmeister.edit-entities' +) +.requires( + 'impact.background-map', + 'weltmeister.config', + 'weltmeister.tile-select', + 'weltmeister.entities' +) +.defines(function(){ + +wm.EditEntities = ig.Class.extend({ + visible: true, + active: true, + + div: null, + currentTile: 0, + hotkey: -1, + ignoreLastClick: false, + name: 'entities', + + entities: [], + namedEntities: {}, + selectedEntity: null, + entityClasses: {}, + menuDiv: null, + selector: {size:{x:2, y:2}, pos:{x:0,y:0}, offset:{x:0,y:0}}, + wasSelectedOnScaleBorder: false, + gridSize: wm.config.entityGrid, + entityDefinitions: null, + + + + init: function( div ) { + this.div = div; + div.bind( 'mouseup', this.click.bind(this) ); + this.div.children('.visible').bind( 'mousedown', this.toggleVisibilityClick.bind(this) ); + + this.menu = $('#entityMenu'); + this.importEntityClass( wm.entityModules ); + this.entityDefinitions = $('#entityDefinitions'); + + $('#entityKey').bind( 'keydown', function(ev){ + if( ev.which == 13 ){ + $('#entityValue').focus(); + return false; + } + return true; + }); + $('#entityValue').bind( 'keydown', this.setEntitySetting.bind(this) ); + }, + + + clear: function() { + this.entities = []; + this.selectEntity( null ); + }, + + + + + // ------------------------------------------------------------------------- + // Loading, Saving + + + fileNameToClassName: function( name ) { + var typeName = '-' + name.replace(/^.*\/|\.js/g,''); + typeName = typeName.replace(/-(\w)/g, function( m, a ) { + return a.toUpperCase(); + }); + return 'Entity' + typeName; + }, + + + importEntityClass: function( modules ) { + var unloadedClasses = []; + for( var m in modules ) { + var className = this.fileNameToClassName(modules[m]); + var entityName = className.replace(/^Entity/, ''); + + // ig.global[className] should be the actual class object + if( className && ig.global[className] ) { + var a = $( '
', { + 'id': className, + 'href': '#', + 'html': entityName, + 'mouseup': this.newEntityClick.bind(this) + }); + this.menu.append( a ); + this.entityClasses[className] = m; + } + else { + unloadedClasses.push( modules[m] + ' (expected name: ' + className + ')' ); + } + } + + if( unloadedClasses.length > 0 ) { + var warning = 'The following entity classes were not loaded due to\n' + + 'file and class name mismatches: \n\n' + + unloadedClasses.join( '\n' ); + alert( warning ); + } + }, + + + getEntityByName: function( name ) { + return this.namedEntities[name]; + }, + + + getSaveData: function() { + var ents = []; + for( var i = 0; i < this.entities.length; i++ ) { + var ent = this.entities[i]; + var type = ent._wmClassName; + var data = {type:type,x:ent.pos.x,y:ent.pos.y}; + + var hasSettings = false; + for( p in ent._wmSettings ) { + hasSettings = true; + } + if( hasSettings ) { + data.settings = ent._wmSettings; + } + + ents.push( data ); + } + return ents; + }, + + + + + // ------------------------------------------------------------------------- + // Selecting + + + selectEntityAt: function( x, y ) { + this.selector.pos = { x: x, y: y }; + + for( var i = 0; i < this.entities.length; i++ ) { + var ent = this.entities[i]; + if( ent.touches(this.selector) ) { + this.selector.offset = {x: (x - ent.pos.x + ent.offset.x), y: (y - ent.pos.y + ent.offset.y)}; + this.selectEntity( ent ); + this.wasSelectedOnScaleBorder = this.isOnScaleBorder( ent, this.selector ); + return ent; + } + } + this.selectEntity( null ); + return false; + }, + + + selectEntity: function( entity ) { + if( entity && entity != this.selectedEntity ) { + this.selectedEntity = entity; + $('#entitySettings').fadeOut(100,(function(){ + this.loadEntitySettings(); + $('#entitySettings').fadeIn(100); + }).bind(this)); + } + else if( !entity ) { + $('#entitySettings').fadeOut(100); + } + + this.selectedEntity = entity; + $('#entityKey').val(''); + $('#entityValue').val(''); + }, + + + + + // ------------------------------------------------------------------------- + // Creating, Deleting, Moving + + + deleteSelectedEntity: function() { + if( !this.selectedEntity ) { + return false; + } + + ig.game.undo.commitEntityDelete( this.selectedEntity ); + + this.removeEntity( this.selectedEntity ); + this.selectEntity( null ); + return true; + }, + + + removeEntity: function( ent ) { + if( ent.name ) { + delete this.namedEntities[ent.name]; + } + this.entities.erase( ent ); + }, + + + cloneSelectedEntity: function() { + if( !this.selectedEntity ) { + return false; + } + + var className = this.selectedEntity._wmClassName; + var settings = ig.copy(this.selectedEntity._wmSettings); + if( settings.name ) { + settings.name = settings.name + '_clone'; + } + var x = this.selectedEntity.pos.x + this.gridSize; + var y = this.selectedEntity.pos.y; + var newEntity = this.spawnEntity( className, x, y, settings ); + newEntity._wmSettings = settings; + this.selectEntity( newEntity ); + + ig.game.undo.commitEntityCreate( newEntity ); + + return true; + }, + + + dragOnSelectedEntity: function( x, y ) { + if( !this.selectedEntity ) { + return false; + } + + + // scale or move? + if( this.selectedEntity._wmScalable && this.wasSelectedOnScaleBorder ) { + this.scaleSelectedEntity( x, y ); + } + else { + this.moveSelectedEntity( x, y ) + } + + ig.game.undo.pushEntityEdit( this.selectedEntity ); + return true; + }, + + + moveSelectedEntity: function( x, y ) { + x = + Math.round( (x - this.selector.offset.x ) / this.gridSize ) + * this.gridSize + this.selectedEntity.offset.x; + y = + Math.round( (y - this.selector.offset.y ) / this.gridSize ) + * this.gridSize + this.selectedEntity.offset.y; + + // new position? + if( this.selectedEntity.pos.x != x || this.selectedEntity.pos.y != y ) { + $('#entityDefinitionPosX').text( x ); + $('#entityDefinitionPosY').text( y ); + + this.selectedEntity.pos.x = x; + this.selectedEntity.pos.y = y; + } + }, + + + scaleSelectedEntity: function( x, y ) { + var scale = this.wasSelectedOnScaleBorder; + + var w = Math.round( x / this.gridSize ) * this.gridSize - this.selectedEntity.pos.x; + + if( !this.selectedEntity._wmSettings.size ) { + this.selectedEntity._wmSettings.size = {}; + } + + if( scale == 'n' ) { + var h = this.selectedEntity.pos.y - Math.round( y / this.gridSize ) * this.gridSize; + if( this.selectedEntity.size.y + h <= this.gridSize ) { + h = (this.selectedEntity.size.y - this.gridSize) * -1; + } + this.selectedEntity.size.y += h; + this.selectedEntity.pos.y -= h; + } + else if( scale == 's' ) { + var h = Math.round( y / this.gridSize ) * this.gridSize - this.selectedEntity.pos.y; + this.selectedEntity.size.y = Math.max( this.gridSize, h ); + } + else if( scale == 'e' ) { + var w = Math.round( x / this.gridSize ) * this.gridSize - this.selectedEntity.pos.x; + this.selectedEntity.size.x = Math.max( this.gridSize, w ); + } + else if( scale == 'w' ) { + var w = this.selectedEntity.pos.x - Math.round( x / this.gridSize ) * this.gridSize; + if( this.selectedEntity.size.x + w <= this.gridSize ) { + w = (this.selectedEntity.size.x - this.gridSize) * -1; + } + this.selectedEntity.size.x += w; + this.selectedEntity.pos.x -= w; + } + this.selectedEntity._wmSettings.size.x = this.selectedEntity.size.x; + this.selectedEntity._wmSettings.size.y = this.selectedEntity.size.y; + + this.loadEntitySettings(); + }, + + + newEntityClick: function( ev ) { + this.hideMenu(); + var newEntity = this.spawnEntity( ev.target.id, 0, 0, {} ); + this.selectEntity( newEntity ); + this.moveSelectedEntity( this.selector.pos.x, this.selector.pos.y ); + ig.editor.setModified(); + + ig.game.undo.commitEntityCreate( newEntity ); + }, + + + spawnEntity: function( className, x, y, settings ) { + settings = settings || {}; + var entityClass = ig.global[ className ]; + if( entityClass ) { + var newEntity = new (entityClass)( x, y, settings ); + newEntity._wmInEditor = true; + newEntity._wmClassName = className; + newEntity._wmSettings = {}; + for( s in settings ) { + newEntity._wmSettings[s] = settings[s]; + } + this.entities.push( newEntity ); + if( settings.name ) { + this.namedEntities[settings.name] = newEntity; + } + return newEntity; + } + return null; + }, + + + isOnScaleBorder: function( entity, selector ) { + var border = 2; + var w = selector.pos.x - entity.pos.x; + var h = selector.pos.y - entity.pos.y; + + if( w < border ) return 'w'; + if( w > entity.size.x - border ) return 'e'; + + if( h < border ) return 'n'; + if( h > entity.size.y - border ) return 's'; + + return false; + }, + + + + + // ------------------------------------------------------------------------- + // Settings + + + loadEntitySettings: function(ent) { + + if( !this.selectedEntity ) { + return; + } + var html = + '
x:'+this.selectedEntity.pos.x+'
' + + '
y:'+this.selectedEntity.pos.y+'
'; + + html += this.loadEntitySettingsRecursive( this.selectedEntity._wmSettings ); + this.entityDefinitions.html( html ); + + var className = this.selectedEntity._wmClassName.replace(/^Entity/, ''); + $('#entityClass').text( className ); + + $('.entityDefinition').bind( 'mouseup', this.selectEntitySetting ); + }, + + + loadEntitySettingsRecursive: function( settings, path ) { + path = path || ""; + var html = ""; + for( key in settings ) { + var value = settings[key]; + if( typeof(value) == 'object' ) { + html += this.loadEntitySettingsRecursive( value, path + key + "." ); + } + else { + html += '
'+path+key+':'+value+'
'; + } + } + + return html; + }, + + + setEntitySetting: function( ev ) { + if( ev.which != 13 ) { + return true; + } + var key = $('#entityKey').val(); + var value = $('#entityValue').val(); + var floatVal = parseFloat(value); + if( value == floatVal ) { + value = floatVal; + } + + if( key == 'name' ) { + if( this.selectedEntity.name ) { + delete this.namedEntities[this.selectedEntity.name]; + } + this.namedEntities[ value ] = this.selectedEntity; + } + + if( key == 'x' ) { + this.selectedEntity.pos.x = Math.round(value); + } + else if( key == 'y' ) { + this.selectedEntity.pos.y = Math.round(value); + } + else { + this.writeSettingAtPath( this.selectedEntity._wmSettings, key, value ); + ig.merge( this.selectedEntity, this.selectedEntity._wmSettings ); + } + + ig.game.setModified(); + ig.game.draw(); + + $('#entityKey').val(''); + $('#entityValue').val(''); + $('#entityValue').blur(); + this.loadEntitySettings(); + + $('#entityKey').focus(); + return false; + }, + + + writeSettingAtPath: function( root, path, value ) { + path = path.split('.'); + var cur = root; + for( var i = 0; i < path.length; i++ ) { + var n = path[i]; + if( i < path.length-1 && typeof(cur[n]) != 'object' ) { + cur[n] = {}; + } + + if( i == path.length-1 ) { + cur[n] = value; + } + cur = cur[n]; + } + + this.trimObject( root ); + }, + + + trimObject: function( obj ) { + var isEmpty = true; + for( var i in obj ) { + if( + (obj[i] === "") || + (typeof(obj[i]) == 'object' && this.trimObject(obj[i])) + ) { + delete obj[i]; + } + + if( typeof(obj[i]) != 'undefined' ) { + isEmpty = false; + } + } + + return isEmpty; + }, + + + selectEntitySetting: function( ev ) { + $('#entityKey').val( $(this).children('.key').text() ); + $('#entityValue').val( $(this).children('.value').text() ); + $('#entityValue').select(); + }, + + + + + + + // ------------------------------------------------------------------------- + // UI + + setHotkey: function( hotkey ) { + this.hotkey = hotkey; + this.div.attr('title', 'Select Layer ('+this.hotkey+')' ); + }, + + + showMenu: function( x, y ) { + this.selector.pos = { + x: Math.round( (x + ig.editor.screen.x) / this.gridSize ) * this.gridSize, + y: Math.round( (y + ig.editor.screen.y) / this.gridSize ) * this.gridSize + }; + this.menu.css({top: (y * ig.system.scale + 2), left: (x * ig.system.scale + 2) }); + this.menu.show(); + }, + + + hideMenu: function( x, y ) { + ig.editor.mode = ig.editor.MODE.DEFAULT; + this.menu.hide(); + }, + + + setActive: function( active ) { + this.active = active; + if( active ) { + this.div.addClass( 'layerActive' ); + } else { + this.div.removeClass( 'layerActive' ); + } + }, + + + toggleVisibility: function() { + this.visible ^= 1; + if( this.visible ) { + this.div.children('.visible').addClass('checkedVis'); + } else { + this.div.children('.visible').removeClass('checkedVis'); + } + ig.game.draw(); + }, + + + toggleVisibilityClick: function( ev ) { + if( !this.active ) { + this.ignoreLastClick = true; + } + this.toggleVisibility() + }, + + + click: function() { + if( this.ignoreLastClick ) { + this.ignoreLastClick = false; + return; + } + ig.editor.setActiveLayer( 'entities' ); + }, + + + mousemove: function( x, y ) { + this.selector.pos = { x: x, y: y }; + + if( this.selectedEntity ) { + if( this.selectedEntity._wmScalable && this.selectedEntity.touches(this.selector) ) { + var scale = this.isOnScaleBorder( this.selectedEntity, this.selector ); + if( scale == 'n' || scale == 's' ) { + $('body').css('cursor', 'n-resize'); + return; + } + else if( scale == 'e' || scale == 'w' ) { + $('body').css('cursor', 'e-resize'); + return; + } + } + } + + $('body').css('cursor', 'default'); + }, + + + + + + + // ------------------------------------------------------------------------- + // Drawing + + + draw: function() { + if( this.visible ) { + for( var i = 0; i < this.entities.length; i++ ) { + this.drawEntity( this.entities[i] ); + } + } + }, + + + drawEntity: function( ent ) { + + // entity itself + ent.draw(); + + // box + if( ent._wmDrawBox ) { + ig.system.context.fillStyle = ent._wmBoxColor || 'rgba(128, 128, 128, 0.9)'; + ig.system.context.fillRect( + ig.system.getDrawPos(ent.pos.x - ig.game.screen.x), + ig.system.getDrawPos(ent.pos.y - ig.game.screen.y), + ent.size.x * ig.system.scale, + ent.size.y * ig.system.scale + ); + } + + + if( wm.config.labels.draw ) { + // description + var className = ent._wmClassName.replace(/^Entity/, ''); + var description = className + (ent.name ? ': ' + ent.name : '' ); + + // text-shadow + ig.system.context.fillStyle = 'rgba(0,0,0,0.4)'; + ig.system.context.fillText( + description, + ig.system.getDrawPos(ent.pos.x - ig.game.screen.x), + ig.system.getDrawPos(ent.pos.y - ig.game.screen.y + 0.5) + ); + + // text + ig.system.context.fillStyle = wm.config.colors.primary; + ig.system.context.fillText( + description, + ig.system.getDrawPos(ent.pos.x - ig.game.screen.x), + ig.system.getDrawPos(ent.pos.y - ig.game.screen.y) + ); + } + + + // line to targets + if( typeof(ent.target) == 'object' ) { + for( var t in ent.target ) { + this.drawLineToTarget( ent, ent.target[t] ); + } + } + }, + + + drawLineToTarget: function( ent, target ) { + target = ig.game.getEntityByName( target ); + if( !target ) { + return; + } + + ig.system.context.strokeStyle = '#fff'; + ig.system.context.lineWidth = 1; + + ig.system.context.beginPath(); + ig.system.context.moveTo( + ig.system.getDrawPos(ent.pos.x + ent.size.x/2 - ig.game.screen.x), + ig.system.getDrawPos(ent.pos.y + ent.size.y/2 - ig.game.screen.y) + ); + ig.system.context.lineTo( + ig.system.getDrawPos(target.pos.x + target.size.x/2 - ig.game.screen.x), + ig.system.getDrawPos(target.pos.y + target.size.y/2 - ig.game.screen.y) + ); + ig.system.context.stroke(); + ig.system.context.closePath(); + }, + + + drawCursor: function( x, y ) { + if( this.selectedEntity ) { + ig.system.context.lineWidth = 1; + ig.system.context.strokeStyle = wm.config.colors.highlight; + ig.system.context.strokeRect( + ig.system.getDrawPos(this.selectedEntity.pos.x - ig.editor.screen.x) - 0.5, + ig.system.getDrawPos(this.selectedEntity.pos.y - ig.editor.screen.y) - 0.5, + this.selectedEntity.size.x * ig.system.scale + 1, + this.selectedEntity.size.y * ig.system.scale + 1 + ); + } + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/edit-map.js b/dev/lib/weltmeister/edit-map.js new file mode 100755 index 0000000..704c30b --- /dev/null +++ b/dev/lib/weltmeister/edit-map.js @@ -0,0 +1,383 @@ +ig.module( + 'weltmeister.edit-map' +) +.requires( + 'impact.background-map', + 'weltmeister.tile-select' +) +.defines(function(){ + +wm.EditMap = ig.BackgroundMap.extend({ + name: '', + visible: true, + active: true, + linkWithCollision: false, + + div: null, + currentTile: 0, + brush: [[0]], + oldData: null, + hotkey: -1, + ignoreLastClick: false, + tileSelect: null, + + isSelecting: false, + selectionBegin: null, + + init: function( name, tilesize, tileset, foreground ) { + this.name = name; + this.parent( tilesize, [[0]], tileset || '' ); + this.foreground = foreground; + + this.div = $( '
', { + 'class': 'layer layerActive', + 'id': ('layer_' + name), + 'mouseup': this.click.bind(this) + }); + this.setName( name ); + if( this.foreground ) { + $('#layers').prepend( this.div ); + } + else { + $('#layerEntities').after( this.div ); + } + + this.tileSelect = new wm.TileSelect( this ); + }, + + + getSaveData: function() { + return { + name: this.name, + width: this.width, + height: this.height, + linkWithCollision: this.linkWithCollision, + visible: this.visible, + tilesetName: this.tilesetName, + repeat: this.repeat, + preRender: this.preRender, + distance: this.distance, + tilesize: this.tilesize, + foreground: this.foreground, + data: this.data + }; + }, + + + resize: function( newWidth, newHeight ) { + var newData = new Array( newHeight ); + for( var y = 0; y < newHeight; y++ ) { + newData[y] = new Array( newWidth ); + for( var x = 0; x < newWidth; x++ ) { + newData[y][x] = (x < this.width && y < this.height) ? this.data[y][x] : 0; + } + } + this.data = newData; + this.width = newWidth; + this.height = newHeight; + + this.resetDiv(); + }, + + beginEditing: function() { + this.oldData = ig.copy(this.data); + }, + + getOldTile: function( x, y ) { + var tx = Math.floor( x / this.tilesize ); + var ty = Math.floor( y / this.tilesize ); + if( + (tx >= 0 && tx < this.width) && + (ty >= 0 && ty < this.height) + ) { + return this.oldData[ty][tx]; + } + else { + return 0; + } + }, + + setTileset: function( tileset ) { + if( this.name == 'collision' ) { + this.setCollisionTileset(); + } + else { + this.parent( tileset ); + } + }, + + + setCollisionTileset: function() { + var path = wm.config.collisionTiles.path; + var scale = this.tilesize / wm.config.collisionTiles.tilesize; + this.tiles = new ig.AutoResizedImage( path, scale ); + }, + + + + + + // ------------------------------------------------------------------------- + // UI + + setHotkey: function( hotkey ) { + this.hotkey = hotkey; + this.setName( this.name ); + }, + + + setName: function( name ) { + this.name = name.replace(/[^0-9a-zA-Z]/g, '_'); + this.resetDiv(); + }, + + + resetDiv: function() { + var visClass = this.visible ? ' checkedVis' : ''; + this.div.html( + '' + + '' + this.name + '' + + ' (' + this.width + 'x' + this.height + ')' + ); + this.div.attr('title', 'Select Layer ('+this.hotkey+')' ); + this.div.children('.visible').bind('mousedown', this.toggleVisibilityClick.bind(this) ); + }, + + + setActive: function( active ) { + this.active = active; + if( active ) { + this.div.addClass( 'layerActive' ); + } else { + this.div.removeClass( 'layerActive' ); + } + }, + + + toggleVisibility: function() { + this.visible ^= 1; + this.resetDiv(); + if( this.visible ) { + this.div.children('.visible').addClass('checkedVis'); + } else { + this.div.children('.visible').removeClass('checkedVis'); + } + ig.game.draw(); + }, + + + toggleVisibilityClick: function( event ) { + if( !this.active ) { + this.ignoreLastClick = true; + } + this.toggleVisibility() + }, + + + click: function() { + if( this.ignoreLastClick ) { + this.ignoreLastClick = false; + return; + } + ig.editor.setActiveLayer( this.name ); + }, + + + destroy: function() { + this.div.remove(); + }, + + + + // ------------------------------------------------------------------------- + // Selecting + + beginSelecting: function( x, y ) { + this.isSelecting = true; + this.selectionBegin = {x:x, y:y}; + }, + + + endSelecting: function( x, y ) { + var r = this.getSelectionRect( x, y); + + var brush = []; + for( var ty = r.y; ty < r.y+r.h; ty++ ) { + var row = []; + for( var tx = r.x; tx < r.x+r.w; tx++ ) { + if( tx < 0 || ty < 0 || tx >= this.width || ty >= this.height ) { + row.push( 0 ); + } + else { + row.push( this.data[ty][tx] ); + } + } + brush.push( row ); + } + this.isSelecting = false; + this.selectionBegin = null; + return brush; + }, + + + getSelectionRect: function( x, y ) { + var sx = this.selectionBegin ? this.selectionBegin.x : x, + sy = this.selectionBegin ? this.selectionBegin.y : y; + + var + txb = Math.floor( (sx + this.scroll.x) / this.tilesize ), + tyb = Math.floor( (sy + this.scroll.y) / this.tilesize ), + txe = Math.floor( (x + this.scroll.x) / this.tilesize ), + tye = Math.floor( (y + this.scroll.y) / this.tilesize ); + + return { + x: Math.min( txb, txe ), + y: Math.min( tyb, tye ), + w: Math.abs( txb - txe) + 1, + h: Math.abs( tyb - tye) + 1 + } + }, + + + + + // ------------------------------------------------------------------------- + // Drawing + + draw: function() { + if( this.visible ) { + this.drawTiled(); + } + + // Grid + if( this.active && wm.config.view.grid ) { + + var x = -ig.system.getDrawPos(this.scroll.x % this.tilesize) - 0.5; + var y = -ig.system.getDrawPos(this.scroll.y % this.tilesize) - 0.5; + var step = this.tilesize * ig.system.scale; + + ig.system.context.beginPath(); + for( x; x < ig.system.realWidth; x += step ) { + ig.system.context.moveTo( x, 0 ); + ig.system.context.lineTo( x, ig.system.realHeight ); + } + for( y; y < ig.system.realHeight; y += step ) { + ig.system.context.moveTo( 0, y ); + ig.system.context.lineTo( ig.system.realWidth, y ); + } + ig.system.context.strokeStyle = wm.config.colors.secondary; + ig.system.context.stroke(); + ig.system.context.closePath(); + + // Not calling beginPath() again has some weird performance issues + // in Firefox 5. closePath has no effect. So to make it happy: + ig.system.context.beginPath(); + } + + // Bounds + if( this.active ) { + ig.system.context.lineWidth = 1; + ig.system.context.strokeStyle = wm.config.colors.primary; + ig.system.context.strokeRect( + -ig.system.getDrawPos(this.scroll.x) - 0.5, + -ig.system.getDrawPos(this.scroll.y) - 0.5, + this.width * this.tilesize * ig.system.scale + 1, + this.height * this.tilesize * ig.system.scale + 1 + ); + } + }, + + getCursorOffset: function() { + var w = this.brush[0].length; + var h = this.brush.length; + + //return {x:0, y:0}; + return { + x: (w/2-0.5).toInt() * this.tilesize, + y: (h/2-0.5).toInt() * this.tilesize + } + }, + + drawCursor: function( x, y ) { + if( this.isSelecting ) { + var r = this.getSelectionRect( x, y); + + ig.system.context.lineWidth = 1; + ig.system.context.strokeStyle = wm.config.colors.selection; + ig.system.context.strokeRect( + (r.x * this.tilesize - this.scroll.x) * ig.system.scale - 0.5, + (r.y * this.tilesize - this.scroll.y) * ig.system.scale - 0.5, + r.w * this.tilesize * ig.system.scale + 1, + r.h * this.tilesize * ig.system.scale + 1 + ); + } + else { + var w = this.brush[0].length; + var h = this.brush.length; + + var co = this.getCursorOffset(); + + var cx = Math.floor( (x+this.scroll.x) / this.tilesize ) * this.tilesize - this.scroll.x - co.x; + var cy = Math.floor( (y+this.scroll.y) / this.tilesize ) * this.tilesize - this.scroll.y - co.y; + + ig.system.context.lineWidth = 1; + ig.system.context.strokeStyle = wm.config.colors.primary; + ig.system.context.strokeRect( + ig.system.getDrawPos(cx)-0.5, + ig.system.getDrawPos(cy)-0.5, + w * this.tilesize * ig.system.scale + 1, + h * this.tilesize * ig.system.scale + 1 + ); + + ig.system.context.globalAlpha = 0.5; + for( var ty = 0; ty < h; ty++ ) { + for( var tx = 0; tx < w; tx++ ) { + var t = this.brush[ty][tx]; + if( t ) { + var px = cx + tx * this.tilesize; + var py = cy + ty * this.tilesize; + this.tiles.drawTile( px, py, t-1, this.tilesize ); + } + } + } + ig.system.context.globalAlpha = 1; + } + } +}); + + +ig.AutoResizedImage = ig.Image.extend({ + internalScale: 1, + + staticInstantiate: function() { + return null; // Never cache! + }, + + init: function( path, internalScale ) { + this.internalScale = internalScale; + this.parent( path ); + }, + + onload: function( event ) { + this.width = Math.ceil(this.data.width * this.internalScale); + this.height = Math.ceil(this.data.height * this.internalScale); + + if( this.internalScale != 1 ) { + var scaled = ig.$new('canvas'); + scaled.width = this.width * ig.system.scale; + scaled.height = this.height * ig.system.scale; + var scaledCtx = scaled.getContext('2d'); + + scaledCtx.drawImage( this.data, 0, 0, this.data.width, this.data.height, 0, 0, scaled.width , scaled.height ); + this.data = scaled; + } + + this.loaded = true; + if( this.loadCallback ) { + this.loadCallback( this.path, true ); + } + } +}); + + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/entities.js b/dev/lib/weltmeister/entities.js new file mode 100755 index 0000000..8fbd83b --- /dev/null +++ b/dev/lib/weltmeister/entities.js @@ -0,0 +1,56 @@ +ig.module( + 'weltmeister.entityLoader' +) +.requires( + 'weltmeister.config' +) +.defines(function(){ + +// Load the list of entity files via AJAX PHP glob +var path = wm.config.api.glob + '?', + globs = typeof wm.config.project.entityFiles == 'string' ? + [wm.config.project.entityFiles] : + wm.config.project.entityFiles; + +for (var i = 0; i < globs.length; i++) { + path += 'glob[]=' + encodeURIComponent(globs[i]) + '&'; +} + +path += 'nocache=' + Math.random(); + +var req = $.ajax({ + url: path, + method: 'get', + dataType: 'json', + + // MUST load synchronous, as the engine would otherwise determine that it + // can't resolve dependencies to weltmeister.entities when there are + // no more files to load and weltmeister.entities is still not defined + // because the ajax request hasn't finished yet. + // FIXME FFS! + async: false, + success: function(files) { + + // File names to Module names + var moduleNames = []; + var modules = {}; + for( var i = 0; i < files.length; i++ ) { + var name = files[i].replace(/^lib\/|\.js$/g,'').replace(/\//g, '.'); + moduleNames.push( name ); + modules[name] = files[i]; + } + + // Define a Module that requires all entity Modules + ig.module('weltmeister.entities') + .requires.apply(ig, moduleNames) + .defines(function(){ wm.entityModules = modules; }); + }, + error: function( xhr, status, error ){ + throw( + "Failed to load entity list via glob.php: " + error + "\n" + + xhr.responseText + ); + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/evented-input.js b/dev/lib/weltmeister/evented-input.js new file mode 100755 index 0000000..03d390e --- /dev/null +++ b/dev/lib/weltmeister/evented-input.js @@ -0,0 +1,62 @@ +ig.module( + 'weltmeister.evented-input' +) +.requires( + 'impact.input' +) +.defines(function(){ + +wm.EventedInput = ig.Input.extend({ + mousemoveCallback: null, + keyupCallback: null, + keydownCallback: null, + + delayedKeyup: {push:function(){},length: 0}, + + + keydown: function( event ) { + if( event.target.type == 'text' ) { return; } + + var code = event.type == 'keydown' + ? event.keyCode + : (event.button == 2 ? ig.KEY.MOUSE2 : ig.KEY.MOUSE1); + var action = this.bindings[code]; + if( action ) { + if( !this.actions[action] ) { + this.actions[action] = true; + if( this.keydownCallback ) { + this.keydownCallback( action ); + } + } + event.stopPropagation(); + event.preventDefault(); + } + }, + + + keyup: function( event ) { + if( event.target.type == 'text' ) { return; } + var code = event.type == 'keyup' + ? event.keyCode + : (event.button == 2 ? ig.KEY.MOUSE2 : ig.KEY.MOUSE1); + var action = this.bindings[code]; + if( action ) { + this.actions[action] = false; + if( this.keyupCallback ) { + this.keyupCallback( action ); + } + event.stopPropagation(); + event.preventDefault(); + } + }, + + + mousemove: function( event ) { + this.parent( event ); + if( this.mousemoveCallback ) { + this.mousemoveCallback(); + } + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/jquery-1.4.2.min.js b/dev/lib/weltmeister/jquery-1.4.2.min.js new file mode 100755 index 0000000..bfeac8c --- /dev/null +++ b/dev/lib/weltmeister/jquery-1.4.2.min.js @@ -0,0 +1,405 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); + + + +(function ($) { + var m = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + s = { + 'array': function (x) { + var a = ['['], b, f, i, l = x.length, v; + for (i = 0; i < l; i += 1) { + v = x[i]; + f = s[typeof v]; + if (f) { + v = f(v); + if (typeof v == 'string') { + if (b) { + a[a.length] = ','; + } + a[a.length] = v; + b = true; + } + } + } + a[a.length] = ']'; + return a.join(''); + }, + 'boolean': function (x) { + return String(x); + }, + 'null': function (x) { + return "null"; + }, + 'number': function (x) { + return isFinite(x) ? String(x) : 'null'; + }, + 'object': function (x) { + if (x) { + if (x instanceof Array) { + return s.array(x); + } + var a = ['{'], b, f, i, v; + for (i in x) { + v = x[i]; + f = s[typeof v]; + if (f) { + v = f(v); + if (typeof v == 'string') { + if (b) { + a[a.length] = ','; + } + a.push(s.string(i), ':', v); + b = true; + } + } + } + a[a.length] = '}'; + return a.join(''); + } + return 'null'; + }, + 'string': function (x) { + if (/["\\\x00-\x1f]/.test(x)) { + x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) { + var c = m[b]; + if (c) { + return c; + } + c = b.charCodeAt(); + return '\\u00' + + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + }); + } + return '"' + x + '"'; + } + }; + + $.toJSON = function(v) { + var f = isNaN(v) ? s[typeof v] : s['number']; + if (f) return f(v); + }; + + $.parseJSON = function(v, safe) { + if (safe === undefined) safe = $.parseJSON.safe; + if (safe && !/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(v)) + return undefined; + return eval('('+v+')'); + }; + + $.parseJSON.safe = false; + +})(jQuery); + + +(function(window) { + var p = [], + push = function( m ) { return '\\' + p.push( m ) + '\\'; }; + pop = function( m, i ) { return p[i-1] }; + tabs = function( count ) { return new Array( count + 1 ).join( '\t' ); }; + + window.JSONFormat = function( json ) { + p = []; + var out = "", + indent = 0; + + // Extract backslashes and strings + json = json + .replace( /\\./g, push ) + .replace( /(".*?"|'.*?')/g, push ) + .replace( /\s+/, '' ); + + // Indent and insert newlines + for( var i = 0; i < json.length; i++ ) { + var c = json.charAt(i); + + switch(c) { + case '{': + case '[': + out += c + "\n" + tabs(++indent); + break; + case '}': + case ']': + out += "\n" + tabs(--indent) + c; + break; + case ',': + out += ",\n" + tabs(indent); + break; + case ':': + out += ": "; + break; + default: + out += c; + break; + } + } + + // Strip whitespace from numeric arrays and put backslashes + // and strings back in + out = out + .replace( /\[[\d,\s]+?\]/g, function(m){ return m.replace(/\s/g,''); } ) + .replace( /\\(\d+)\\/g, pop ); + + return out; + }; +})(window); + + + +/** + * Cookie plugin + * + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +/** + * Create a cookie with the given name and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain + * used when the cookie was set. + * + * @param String name The name of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given name. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String name The name of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // CAUTION: Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; diff --git a/dev/lib/weltmeister/jquery-ui-1.8.1.custom.min.js b/dev/lib/weltmeister/jquery-ui-1.8.1.custom.min.js new file mode 100755 index 0000000..e16039c --- /dev/null +++ b/dev/lib/weltmeister/jquery-ui-1.8.1.custom.min.js @@ -0,0 +1,107 @@ +/*! + * jQuery UI 1.8.1 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI + */ +jQuery.ui||function(c){c.ui={version:"1.8.1",plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=0)&&c(a).is(":focusable")}})}(jQuery); +;/*! + * jQuery UI Widget 1.8.1 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Widget + */ +(function(b){var j=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add(this).each(function(){b(this).triggerHandler("remove")});return j.call(b(this),a,c)})};b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend({},c.options);b[e][a].prototype= +b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.substring(0,1)==="_")return h;e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==undefined){h=i;return false}}):this.each(function(){var g= +b.data(this,a);if(g){d&&g.option(d);g._init()}else b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){this.element=b(c).data(this.widgetName,this);this.options=b.extend(true,{},this.options,b.metadata&&b.metadata.get(c)[this.widgetName],a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create(); +this._init()},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a,e=this;if(arguments.length===0)return b.extend({},e.options);if(typeof a==="string"){if(c===undefined)return this.options[a];d={};d[a]=c}b.each(d,function(f, +h){e._setOption(f,h)});return e},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a= +b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery); +;/*! + * jQuery UI Mouse 1.8.1 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function(c){c.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(b){return a._mouseDown(b)}).bind("click."+this.widgetName,function(b){if(a._preventClickEvent){a._preventClickEvent=false;b.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(a){a.originalEvent=a.originalEvent||{};if(!a.originalEvent.mouseHandled){this._mouseStarted&& +this._mouseUp(a);this._mouseDownEvent=a;var b=this,e=a.which==1,f=typeof this.options.cancel=="string"?c(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!e||f||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){b.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault(); +return true}}this._mouseMoveDelegate=function(d){return b._mouseMove(d)};this._mouseUpDelegate=function(d){return b._mouseUp(d)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.browser.safari||a.preventDefault();return a.originalEvent.mouseHandled=true}},_mouseMove:function(a){if(c.browser.msie&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&& +this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=a.target==this._mouseDownEvent.target;this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX- +a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +;/* + * jQuery UI Sortable 1.8.1 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Sortables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.sortable",d.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){this.containerCache={};this.element.addClass("ui-sortable"); +this.refresh();this.floating=this.items.length?/left|right/.test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a==="disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(self, +arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&&!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem= +c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset, +{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment(); +if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start", +a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a);return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute"); +if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0],e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a, +c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset();c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]== +document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp();this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var b=this.containers.length-1;b>=0;b--){this.containers[b]._trigger("deactivate", +null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem): +d(this.domPosition.parent).prepend(this.currentItem);return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});return c.join("&")},toArray:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute||"id")||"")});return c}, +_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+jg&&b+la[this.floating?"width":"height"]?j:g0?"down":"up")},_getDragHorizontalDirection:function(){var a= +this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith();if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h=d.data(f[g],"sortable");if(h&&h!=this&&!h.options.disabled)c.push([d.isFunction(h.options.items)? +h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)});return d(b)},_removeCurrentsFromItems:function(){for(var a=this.currentItem.find(":data(sortable-item)"), +b=0;b=0;f--)for(var g=d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable");if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)? +i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h=0;b--){var c=this.items[b],e=this.options.toleranceElement?d(this.options.toleranceElement, +c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b=this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top=e.top;this.containers[b].containerCache.width=this.containers[b].element.outerWidth();this.containers[b].containerCache.height= +this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f=d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!e)f.style.visibility="hidden";return f},update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()- +parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b=null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0], +this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length===1){this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b= +1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h-f)this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.topthis.containment[3])?g:!(g-this.offset.click.topthis.containment[2])?f:!(f-this.offset.click.left=0;e--)if(d.ui.contains(this.containers[e].element[0],this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive",g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update", +g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this,this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out",g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over=0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity", +this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop",a,this._uiHash());for(e=0;e', {'class':'modalDialogBackground'}); + this.dialogBox = $('
', {'class':'modalDialogBox'}); + this.background.append( this.dialogBox ); + $('body').append( this.background ); + + this.initDialog( text ); + }, + + + initDialog: function() { + this.buttonDiv = $('
', {'class': 'modalDialogButtons'} ); + var okButton = $('', {'type': 'button', 'class':'button', 'value': this.okText}); + var cancelButton = $('', {'type': 'button', 'class':'button', 'value': this.cancelText}); + + okButton.bind( 'click', this.clickOk.bind(this) ); + cancelButton.bind( 'click', this.clickCancel.bind(this) ); + + this.buttonDiv.append( okButton ).append( cancelButton ); + + this.dialogBox.html('
' + this.text + '
' ); + this.dialogBox.append( this.buttonDiv ); + }, + + + clickOk: function() { + if( this.onOk ) { this.onOk(this); } + this.close(); + }, + + + clickCancel: function() { + if( this.onCancel ) { this.onCancel(this); } + this.close(); + }, + + + open: function() { + this.background.fadeIn(100); + }, + + + close: function() { + this.background.fadeOut(100); + } +}); + + + +wm.ModalDialogPathSelect = wm.ModalDialog.extend({ + pathDropdown: null, + pathInput: null, + fileType: '', + + init: function( text, okText, type ) { + this.fileType = type || ''; + this.parent( text, (okText || 'Select') ); + }, + + + setPath: function( path ) { + var dir = path.replace(/\/[^\/]*$/, ''); + this.pathInput.val( path ); + this.pathDropdown.loadDir( dir ); + }, + + + initDialog: function() { + this.parent(); + this.pathInput = $('', {'type': 'text', 'class': 'modalDialogPath'} ); + this.buttonDiv.before( this.pathInput ); + this.pathDropdown = new wm.SelectFileDropdown( this.pathInput, wm.config.api.browse, this.fileType ); + }, + + + clickOk: function() { + if( this.onOk ) { + this.onOk(this, this.pathInput.val()); + } + this.close(); + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/select-file-dropdown.js b/dev/lib/weltmeister/select-file-dropdown.js new file mode 100755 index 0000000..8f9ec3b --- /dev/null +++ b/dev/lib/weltmeister/select-file-dropdown.js @@ -0,0 +1,99 @@ +ig.module( + 'weltmeister.select-file-dropdown' +) +.defines(function(){ + +wm.SelectFileDropdown = ig.Class.extend({ + input: null, + boundShow: null, + div: null, + filelistPHP: '', + filetype: '', + + init: function( elementId, filelistPHP, filetype ) { + this.filetype = filetype || ''; + this.filelistPHP = filelistPHP; + this.input = $(elementId); + this.boundHide = this.hide.bind(this); + this.input.bind('focus', this.show.bind(this) ); + + this.div = $('
', {'class':'selectFileDialog'}); + this.input.after( this.div ); + this.div.bind('mousedown', this.noHide.bind(this) ); + + this.loadDir( '' ); + }, + + + loadDir: function( dir ) { + var path = this.filelistPHP + '?dir=' + encodeURIComponent( dir || '' ) + '&type=' + this.filetype; + var req = $.ajax({ + url:path, + dataType: 'json', + async: false, + success:this.showFiles.bind(this) + }); + }, + + + selectDir: function( event ) { + this.loadDir( $(event.target).attr('href') ); + return false; + }, + + + selectFile: function( event ) { + this.input.val( $(event.target).attr('href') ); + this.input.blur(); + this.hide(); + return false; + }, + + + showFiles: function( data ) { + this.div.empty(); + if( data.parent !== false ) { + var parentDir = $('', {'class':'dir', href:data.parent, html: '…parent directory'}); + parentDir.bind( 'click', this.selectDir.bind(this) ); + this.div.append( parentDir ); + } + for( var i = 0; i < data.dirs.length; i++ ) { + var name = data.dirs[i].match(/[^\/]*$/)[0] + '/'; + var dir = $('', {'class':'dir', href:data.dirs[i], html: name, title: name}); + dir.bind( 'click', this.selectDir.bind(this) ); + this.div.append( dir ); + } + for( var i = 0; i < data.files.length; i++ ) { + var name = data.files[i].match(/[^\/]*$/)[0]; + var file = $('', {'class':'file', href:data.files[i], html: name, title: name}); + file.bind( 'click', this.selectFile.bind(this) ); + this.div.append( file ); + } + }, + + + noHide: function(event) { + event.stopPropagation(); + }, + + + show: function( event ) { + var inputPos = this.input.position();//this.input.getPosition(this.input.getOffsetParent()); + var inputHeight = parseInt(this.input.innerHeight()) + parseInt(this.input.css('margin-top')); + var inputWidth = this.input.innerWidth(); + $(document).bind( 'mousedown', this.boundHide ); + this.div.css({ + 'top': inputPos.top + inputHeight + 1, + 'left': inputPos.left, + 'width': inputWidth + }).slideDown(100); + }, + + + hide: function() { + $(document).unbind( 'mousedown', this.boundHide ); + this.div.slideUp(100); + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/tile-select.js b/dev/lib/weltmeister/tile-select.js new file mode 100755 index 0000000..8ccc042 --- /dev/null +++ b/dev/lib/weltmeister/tile-select.js @@ -0,0 +1,144 @@ +ig.module( + 'weltmeister.tile-select' +) +.defines(function(){ + +wm.TileSelect = ig.Class.extend({ + + pos: {x:0, y:0}, + + layer: null, + selectionBegin: null, + + init: function( layer ) { + this.layer = layer; + }, + + + setPosition: function( x, y ) { + this.selectionBegin = null; + var tile = this.layer.currentTile - 1; + this.pos.x = + Math.floor( x / this.layer.tilesize ) * this.layer.tilesize + - Math.floor( tile * this.layer.tilesize ) % this.layer.tiles.width; + + this.pos.y = + Math.floor( y / this.layer.tilesize ) * this.layer.tilesize + - Math.floor( tile * this.layer.tilesize / this.layer.tiles.width ) * this.layer.tilesize + - (tile == -1 ? this.layer.tilesize : 0); + + this.pos.x = this.pos.x.limit( 0, ig.system.width - this.layer.tiles.width - (ig.system.width % this.layer.tilesize) ); + this.pos.y = this.pos.y.limit( 0, ig.system.height - this.layer.tiles.height - (ig.system.height % this.layer.tilesize) ); + }, + + + beginSelecting: function( x, y ) { + this.selectionBegin = {x:x, y:y}; + }, + + + endSelecting: function( x, y ) { + var r = this.getSelectionRect( x, y); + + var mw = Math.floor( this.layer.tiles.width / this.layer.tilesize ); + var mh = Math.floor( this.layer.tiles.height / this.layer.tilesize ); + + var brush = []; + for( var ty = r.y; ty < r.y+r.h; ty++ ) { + var row = []; + for( var tx = r.x; tx < r.x+r.w; tx++ ) { + if( tx < 0 || ty < 0 || tx >= mw || ty >= mh) { + row.push( 0 ); + } + else { + row.push( ty * Math.floor(this.layer.tiles.width / this.layer.tilesize) + tx + 1 ); + } + } + brush.push( row ); + } + this.selectionBegin = null; + return brush; + }, + + + getSelectionRect: function( x, y ) { + var sx = this.selectionBegin ? this.selectionBegin.x : x, + sy = this.selectionBegin ? this.selectionBegin.y : y; + + var + txb = Math.floor( (sx - this.pos.x) / this.layer.tilesize ), + tyb = Math.floor( (sy - this.pos.y) / this.layer.tilesize ), + txe = Math.floor( (x - this.pos.x) / this.layer.tilesize ), + tye = Math.floor( (y - this.pos.y) / this.layer.tilesize ); + + return { + x: Math.min( txb, txe ), + y: Math.min( tyb, tye ), + w: Math.abs( txb - txe) + 1, + h: Math.abs( tyb - tye) + 1 + } + }, + + + draw: function() { + ig.system.clear( "rgba(0,0,0,0.8)" ); + if( !this.layer.tiles.loaded ) { + return; + } + + // Tileset + ig.system.context.lineWidth = 1; + ig.system.context.strokeStyle = wm.config.colors.secondary; + ig.system.context.fillStyle = wm.config.colors.clear; + ig.system.context.fillRect( + this.pos.x * ig.system.scale, + this.pos.y * ig.system.scale, + this.layer.tiles.width * ig.system.scale, + this.layer.tiles.height * ig.system.scale + ); + ig.system.context.strokeRect( + this.pos.x * ig.system.scale - 0.5, + this.pos.y * ig.system.scale - 0.5, + this.layer.tiles.width * ig.system.scale + 1, + this.layer.tiles.height * ig.system.scale + 1 + ); + + this.layer.tiles.draw( this.pos.x, this.pos.y ); + + // Selected Tile + var tile = this.layer.currentTile - 1; + var tx = Math.floor( tile * this.layer.tilesize ) % this.layer.tiles.width + this.pos.x; + var ty = + Math.floor( tile * this.layer.tilesize / this.layer.tiles.width ) + * this.layer.tilesize + this.pos.y + + (tile == -1 ? this.layer.tilesize : 0); + + ig.system.context.lineWidth = 1; + ig.system.context.strokeStyle = wm.config.colors.highlight; + ig.system.context.strokeRect( + tx * ig.system.scale - 0.5, + ty * ig.system.scale - 0.5, + this.layer.tilesize * ig.system.scale + 1, + this.layer.tilesize * ig.system.scale + 1 + ); + }, + + + drawCursor: function( x, y ) { + var cx = Math.floor( x / this.layer.tilesize ) * this.layer.tilesize; + var cy = Math.floor( y / this.layer.tilesize ) * this.layer.tilesize; + + var r = this.getSelectionRect( x, y); + + ig.system.context.lineWidth = 1; + ig.system.context.strokeStyle = wm.config.colors.selection; + ig.system.context.strokeRect( + (r.x * this.layer.tilesize + this.pos.x) * ig.system.scale - 0.5, + (r.y * this.layer.tilesize + this.pos.y) * ig.system.scale - 0.5, + r.w * this.layer.tilesize * ig.system.scale + 1, + r.h * this.layer.tilesize * ig.system.scale + 1 + ); + } +}); + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/undo.js b/dev/lib/weltmeister/undo.js new file mode 100755 index 0000000..e0dd373 --- /dev/null +++ b/dev/lib/weltmeister/undo.js @@ -0,0 +1,234 @@ +ig.module( + 'weltmeister.undo' +) +.requires( + 'weltmeister.config' +) +.defines(function(){ + + +wm.Undo = ig.Class.extend({ + levels: 10, + chain: [], + rpos: 0, + currentAction: null, + + init: function( levels ) { + this.levels = levels || 10; + }, + + + clear: function() { + this.chain = []; + this.currentAction = null; + }, + + + commit: function( action ) { + if( this.rpos ) { + this.chain.splice( this.chain.length - this.rpos, this.rpos ); + this.rpos = 0; + } + action.activeLayer = ig.game.activeLayer ? ig.game.activeLayer.name : ''; + this.chain.push( action ); + if( this.chain.length > this.levels ) { + this.chain.shift(); + } + }, + + + undo: function() { + var action = this.chain[ this.chain.length - this.rpos - 1 ]; + if( !action ) { + return; + } + this.rpos++; + + + ig.game.setActiveLayer( action.activeLayer ); + + if( action.type == wm.Undo.MAP_DRAW ) { + for( var i = 0; i < action.changes.length; i++ ) { + var change = action.changes[i]; + change.layer.setTile( change.x, change.y, change.old ); + } + } + else if( action.type == wm.Undo.ENTITY_EDIT ) { + action.entity.pos.x = action.old.x; + action.entity.pos.y = action.old.y; + action.entity.size.x = action.old.w; + action.entity.size.y = action.old.h; + ig.game.entities.selectEntity( action.entity ); + ig.game.entities.loadEntitySettings(); + } + else if( action.type == wm.Undo.ENTITY_CREATE ) { + ig.game.entities.removeEntity( action.entity ); + ig.game.entities.selectEntity( null ); + } + else if( action.type == wm.Undo.ENTITY_DELETE ) { + ig.game.entities.entities.push( action.entity ); + if( action.entity.name ) { + this.namedEntities[action.entity.name] = action.entity; + } + ig.game.entities.selectEntity( action.entity ); + } + + ig.game.setModified(); + }, + + + redo: function() { + if( !this.rpos ) { + return; + } + + var action = this.chain[ this.chain.length - this.rpos ]; + if( !action ) { + return; + } + this.rpos--; + + + ig.game.setActiveLayer( action.activeLayer ); + + if( action.type == wm.Undo.MAP_DRAW ) { + for( var i = 0; i < action.changes.length; i++ ) { + var change = action.changes[i]; + change.layer.setTile( change.x, change.y, change.current ); + } + } + else if( action.type == wm.Undo.ENTITY_EDIT ) { + action.entity.pos.x = action.current.x; + action.entity.pos.y = action.current.y; + action.entity.size.x = action.current.w; + action.entity.size.y = action.current.h; + ig.game.entities.selectEntity( action.entity ); + ig.game.entities.loadEntitySettings(); + } + else if( action.type == wm.Undo.ENTITY_CREATE ) { + ig.game.entities.entities.push( action.entity ); + if( action.entity.name ) { + this.namedEntities[action.entity.name] = action.entity; + } + ig.game.entities.selectEntity( action.entity ); + } + else if( action.type == wm.Undo.ENTITY_DELETE ) { + ig.game.entities.removeEntity( action.entity ); + ig.game.entities.selectEntity( null ); + } + + ig.game.setModified(); + }, + + + // ------------------------------------------------------------------------- + // Map changes + + beginMapDraw: function( layer ) { + this.currentAction = { + type: wm.Undo.MAP_DRAW, + time: Date.now(), + changes: [] + }; + }, + + pushMapDraw: function( layer, x, y, oldTile, currentTile ) { + if( !this.currentAction ) { + return; + } + + this.currentAction.changes.push({ + layer: layer, + x: x, + y: y, + old: oldTile, + current: currentTile + }); + }, + + endMapDraw: function() { + if( !this.currentAction || !this.currentAction.changes.length ) { + return; + } + + this.commit( this.currentAction ); + this.currentAction = null; + }, + + + // ------------------------------------------------------------------------- + // Entity changes + + beginEntityEdit: function( entity ) { + this.currentAction = { + type: wm.Undo.ENTITY_EDIT, + time: Date.now(), + entity: entity, + old: { + x: entity.pos.x, + y: entity.pos.y, + w: entity.size.x, + h: entity.size.y + }, + current: { + x: entity.pos.x, + y: entity.pos.y, + w: entity.size.x, + h: entity.size.y + } + }; + }, + + pushEntityEdit: function( entity ) { + if( !this.currentAction ) { + return; + } + + this.currentAction.current = { + x: entity.pos.x, + y: entity.pos.y, + w: entity.size.x, + h: entity.size.y + }; + }, + + + endEntityEdit: function() { + var a = this.currentAction; + + if( !a || ( + a.old.x == a.current.x && a.old.y == a.current.y && + a.old.w == a.current.w && a.old.h == a.current.h + )) { + return; + } + + this.commit( this.currentAction ); + this.currentAction = null; + }, + + + commitEntityCreate: function( entity ) { + this.commit({ + type: wm.Undo.ENTITY_CREATE, + time: Date.now(), + entity: entity + }); + }, + + + commitEntityDelete: function( entity ) { + this.commit({ + type: wm.Undo.ENTITY_DELETE, + time: Date.now(), + entity: entity + }); + } +}); + +wm.Undo.MAP_DRAW = 1; +wm.Undo.ENTITY_EDIT = 2; +wm.Undo.ENTITY_CREATE = 3; +wm.Undo.ENTITY_DELETE = 4; + +}); \ No newline at end of file diff --git a/dev/lib/weltmeister/weltmeister.css b/dev/lib/weltmeister/weltmeister.css new file mode 100755 index 0000000..fd0734c --- /dev/null +++ b/dev/lib/weltmeister/weltmeister.css @@ -0,0 +1,477 @@ +body { + background-color: #000; + color: #fff; + font-family: sans-serif; + font-size: 10pt; + margin: 0px; + overflow: hidden; + text-shadow: 0px 1px 1px rgba(0,0,0,0.5); + -webkit-font-smoothing: antialiased; + -webkit-user-select: none; +} + +h2 { + margin: 0 0 4px 0; + padding: 4px 0 4px 6px; + background-color: #000; + font-size: 100%; + color: #555; + xtext-transform: uppercase; + xborder-bottom: 1px solid #555; +} + +h3 { + margin: 0; + font-size: 100%; + display: block; +} + +dt { + margin: 0; + padding: 4px 0 0 6px; + display:inline; + float:left; + margin-right:5px; +} + +dd { + margin: 0; + padding: 2px 0 8px 6px; +} + +dl { + margin:0; +} + +div.clear { + clear: both; +} + +label { + cursor: pointer; +} + +/* --- Input ------------------------------------------------------------------ */ + +input { + background-color: rgba(0,0,0,0.5); + border: 1px solid rgb(50,50,50); + color: #fff; + margin: 0; + font-family: sans-serif; + -webkit-font-smoothing: antialiased; + font-size: 10pt; + outline: none; + text-shadow: 0px 1px 1px rgba(0,0,0,0.5); + +} + +input:focus{ + border: 1px solid rgb(200,200,200); +} + +input.text { + padding: 1px; + margin: 0; +} + +input.number { + width: 30px; + text-align: right; +} + +input.button { + font-size: 90%; + padding-left: 13px; + padding-right: 13px; + padding-top: 5px; + padding-bottom: 5px; + color: #fff; + font-weight: bold; + background-color: rgba(255,255,255,0.1); + border:none; + border-top: 1px solid rgba(255,255,255,0.1); + border-bottom: 1px solid rgba(0,0,0,0.1); + cursor: pointer; + -webkit-transition: 0.1s linear; +} + +input.button:hover { + background-color: rgba(255,255,255,0.2); +} + +input.button:active { + background-color: rgba(255,255,255,0.3); +} + +input.text#layerName { + width:140px; +} + +input.text#layerTileset { + width:138px; +} + +input#levelSaveAs { margin-right: 10px; } +input#levelLoad { margin-right: 10px; } +input#reloadImages {margin-right: 10px;} + +input:disabled { + background-color: #555; + color: #888; +} + +/* --- Layout ------------------------------------------------------------------ */ + +#editor { + margin: 0px; + position: relative; +} + +#canvas { + image-rendering: optimizeSpeed; + -webkit-interpolation-mode: nearest-neighbor; +} + +#menu { + width: 200px; + float: right; + position:absolute; + top:0px; + right:0px; +} + +/* --- Layers ------------------------------------------------------------------ */ + +#layerContainer { + background-color: rgba(0,0,0,0.95); + padding-right:2px; +} + +#layers { + max-height: 200px; + overflow: auto; +} + +#layerButtons div.button#buttonAddLayer { + position:absolute; + right: 0px; + top:-5px; + cursor: pointer; + padding: 10px; + color:rgba(255,255,255,0.5); + font-weight: bold; + font-size:110%; + margin-left:1px; + -webkit-transition: 0.1s linear; + -webkit-font-smoothing: none; + z-index:10; +} +#layerButtons div.button#buttonAddLayer:hover { color:rgba(255,255,255,1);} +#layerButtons div.button#buttonAddLayer:active { color:rgba(255,255,255,1); text-shadow:none;} + +.layer { + padding: 6px 4px; + cursor: pointer; + -webkit-transition: background-color 0.1s linear; + border-top: 1px solid rgba(255,255,255,0); + border-bottom: 1px solid rgba(255,255,255,0); +} + +.layer:hover { + background-color: rgba(255,255,255,0.1); + border-top: 1px solid rgba(255,255,255,0.1); + border-bottom: 1px solid rgba(255,255,255,0.1); +} + +.layer:active { + background-color: rgba(255,255,255,0.2); + border-top: 1px solid rgba(255,255,255,0.2); + border-bottom: 1px solid rgba(255,255,255,0.2); +} + +.layerActive { + background-color: rgba(255,255,255,0.1); + border-top: 1px solid rgba(255,255,255,0.2); + border-bottom: 1px solid rgba(255,255,255,0.2) !important; +} + + +#layerEntities { border-bottom: 1px solid #000; } + +.layer .visible { + background-color: rgba(255,255,255,0.2); + text-indent: -99999px; + display: inline-block; + width: 10px; + height: 10px; + margin-right: 7px; + margin-left: 4px; + -webkit-transition: 0.1s linear; +} + +.layer .visible.specialVis{ + margin-right: 2px; +} + +.layer .checkedVis{ background-color: rgba(255,255,255,1); } +.layer span.size { font-size: 75%; color: rgba(255,255,255,0.7); } + +#layerSettings { + background-color: rgba(0,0,0,0.95); + padding-top: 5px; + margin-top: 1px; + display: none; +} + +/* --- Entities ------------------------------------------------------------------ */ +h3#entityClass { + border-bottom: 1px solid rgba(255,255,255,0.2); + padding: 5px; + padding-left: 10px; +} + +#entitySettings { + background-color: rgba(0,0,0,0.95); + margin-top: 1px; + display: none; +} + +#entityDefinitions { + max-height: 220px; + overflow: auto; +} + +div.entityDefinition { + color: #aaa; + padding: 2px 0; + border-bottom: 1px solid rgba(255,255,255,0.2); + cursor: pointer; +} + +div.entityDefinition:hover { + background-color: rgba(255,255,255,0.1); +} + +div.entityDefinition .key { + width: 50%; + display: block; + float: left; + margin: 0 0px 0 0; + padding: 0; + text-align: right; + color: #fff; + overflow: hidden; +} + +div.entityDefinition .value { + padding: 0 0 0 2px; + color: #fff; +} + +dl#entityDefinitionInput { + padding: 8px 0; +} + +dl#entityDefinitionInput dt { + width: 40px; + display: block; + float: left; + margin: 0 4px 0 0; + padding: 4px 0 0 0; + text-align: right; +} + +dl#entityDefinitionInput dd { + display: block; + margin: 0; + padding: 2px 0; +} + +#entityKey, #entityValue { +} + +#entityMenu { + background-color: rgba(0,0,0,0.9); + display: none; + position: absolute; + min-width: 100px; + max-height:300px; + overflow-y: scroll; + -webkit-box-shadow:0px 0px 10px rgba(0,0,0,1); + z-index: 1000; +} + +#entityMenu div { + padding: 3px; + padding-left: 8px; + color: #fff; + cursor: pointer; + border-top: 1px solid rgba(255,255,255,0); + border-bottom: 1px solid rgba(255,255,255,0); + -webkit-transition: 0.1s linear; +} + +#entityMenu div:hover { + background-color: rgba(255,255,255,0.2); + border-top: 1px solid rgba(255,255,255,0.2); + border-bottom: 1px solid rgba(255,255,255,0.2); +} + +/* --- Dialogs ------------------------------------------------------------------ */ + +.selectFileDialog { + background-color: rgba(0,0,0,0.9); + border: 1px solid white; + border-top: 1px solid rgba(255,255,255,0.4); + display: none; + position: absolute; + overflow: hidden; + -webkit-box-shadow: 0px 0px 10px rgba(0,0,0,1); + + max-height: 300px; + overflow-y: scroll; +} + +.selectFileDialog a { + padding: 4px; + color: #fff; + display: block; + text-decoration: none; + border-top: 1px solid rgba(255,255,255,0); + border-bottom: 1px solid rgba(255,255,255,0); +} + +.selectFileDialog a:hover { + background-color: rgba(255,255,255,0.2); + border-top: 1px solid rgba(255,255,255,0.2); + border-bottom: 1px solid rgba(255,255,255,0.2); +} + +div.modalDialogBackground { + background-color: rgba(0,0,0,0.7); + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; + display: none; + z-index: 100; +} + +div.modalDialogBox { + width: 300px; + margin-left: -170px; + background-color: rgba(0,0,0,0.9); + border: 1px solid rgb(100,100,100); + -webkit-box-shadow: 0px 0px 10px rgba(0,0,0,1); + position: absolute; + top: 20%; + left: 50%; + padding: 20px; +} + +div.modalDialogText { + font-size: 180%; + font-weight: bold; +} + +div.modalDialogButtons { + margin-top: 20px; + text-align: right; +} + +div.modalDialogButtons input.button { + min-width: 100px; + text-align: center; + margin-left: 10px; +} + +input.modalDialogPath { + margin-top: 20px; + width: 100%; + outline: none; +} + +input.modalDialogPath:focus { + outline: none; + border: 1px solid rgb(100,100,100); +} + +#headerMenu { + position:relative; + z-index:10; + height:47px; + width:100%; + background: #131314; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0,#000000), color-stop(1,#2e3033)); + background: -moz-linear-gradient(center bottom, #000000 0%, #2e3033 100%); + background: -o-linear-gradient(#2e3033, #000000); +} + +#headerMenu span.headerTitle { + display:inline-block; + font-weight:bold; + font-size:200%; + padding-left:20px; + padding-top:7px; + color:rgba(0,0,0,0.1); + text-shadow:0px -1px 0px rgba(255,255,255,0.4); +} + +#headerMenu span.unsavedTitle { + display:inline-block; + font-weight:bold; + font-size:240%; + padding-left:0px; + color:#cc0000; + text-shadow:0px 1px 1px rgba(0,0,0,0.1); +} + +#headerMenu span.headerFloat { + float: right; +} + +input#toggleSidebar { + width: 200px; + height: 47px; + text-indent: -99999px; + background: url(arrow.png) 50% -37px no-repeat; + -webkit-transition: 0s linear; + opacity: 0.25; + padding: 0px; +} + +input#toggleSidebar.active{ + background-position: 50% 10px; +} + +input[type="checkbox"] { + position: relative; + margin: 0; + border: 0; + width: 10px; + height: 10px; + display: inline-block; + -webkit-appearance: none; + -webkit-transition: 0.1s linear; +} +input[type="checkbox"] { + background-color:rgba(255,255,255,0.2); +} +input[type="checkbox"]:checked { + background-color: rgba(255,255,255,1); +} +input[type="checkbox"]:hover { + cursor: pointer; +} +input[type="checkbox"]:disabled { + background-color: rgba(255,255,255,0.1); +} + +::-webkit-scrollbar { width: 2px; } +::-webkit-scrollbar-button:start:decrement, +::-webkit-scrollbar-button:end:increment { display: block; height: 2px; } +::-webkit-scrollbar-button:vertical:increment { background-color: transparent; } +::-webkit-scrollbar-track-piece { background-color: rgba(0,0,0,0); } +::-webkit-scrollbar-thumb:vertical { background-color: rgba(255,255,255,1); } diff --git a/dev/lib/weltmeister/weltmeister.js b/dev/lib/weltmeister/weltmeister.js new file mode 100755 index 0000000..59e93fb --- /dev/null +++ b/dev/lib/weltmeister/weltmeister.js @@ -0,0 +1,880 @@ +var wm = {}; +wm.entityFiles = []; + +ig.module( + 'weltmeister.weltmeister' +) +.requires( + 'dom.ready', + 'impact.game', + 'weltmeister.evented-input', + 'weltmeister.config', + 'weltmeister.edit-map', + 'weltmeister.edit-entities', + 'weltmeister.select-file-dropdown', + 'weltmeister.modal-dialogs', + 'weltmeister.undo' +) +.defines(function() { + +wm.Weltmeister = ig.Class.extend({ + MODE: { + DRAW: 1, + TILESELECT: 2, + ENTITYSELECT: 4 + }, + + layers: [], + entities: null, + activeLayer: null, + collisionLayer: null, + selectedEntity: null, + + screen: {x: 0, y: 0}, + _rscreen: {x: 0, y: 0}, + mouseLast: {x: -1, y: -1}, + waitForModeChange: false, + + tilsetSelectDialog: null, + levelSavePathDialog: null, + rot: 0, + + collisionSolid: 1, + + loadDialog: null, + saveDialog: null, + loseChangesDialog: null, + fileName: 'untitled.js', + filePath: wm.config.project.levelPath + 'untitled.js', + modified: false, + + undo: null, + + init: function() { + ig.game = ig.editor = this; + + ig.system.context.textBaseline = 'top'; + ig.system.context.font = wm.config.labels.font; + + + + // Dialogs + this.loadDialog = new wm.ModalDialogPathSelect( 'Load Level', 'Load', 'scripts' ); + this.loadDialog.onOk = this.load.bind(this); + this.loadDialog.setPath( wm.config.project.levelPath ); + $('#levelLoad').bind( 'click', this.showLoadDialog.bind(this) ); + $('#levelNew').bind( 'click', this.showNewDialog.bind(this) ); + + this.saveDialog = new wm.ModalDialogPathSelect( 'Save Level', 'Save', 'scripts' ); + this.saveDialog.onOk = this.save.bind(this); + this.saveDialog.setPath( wm.config.project.levelPath ); + $('#levelSaveAs').bind( 'click', this.saveDialog.open.bind(this.saveDialog) ); + $('#levelSave').bind( 'click', this.saveQuick.bind(this) ); + + this.loseChangesDialog = new wm.ModalDialog( 'Lose all changes?' ); + + this.deleteLayerDialog = new wm.ModalDialog( 'Delete Layer? NO UNDO!' ); + this.deleteLayerDialog.onOk = this.removeLayer.bind(this); + + this.mode = this.MODE.DEFAULT; + + + this.tilesetSelectDialog = new wm.SelectFileDropdown( '#layerTileset', wm.config.api.browse, 'images' ); + this.entities = new wm.EditEntities( $('#layerEntities') ); + + $('#layers').sortable({ + update: this.reorderLayers.bind(this) + }); + $('#layers').disableSelection(); + this.resetModified(); + + + // Events/Input + for( key in wm.config.binds ) { + ig.input.bind( ig.KEY[key], wm.config.binds[key] ); + } + ig.input.keydownCallback = this.keydown.bind(this); + ig.input.keyupCallback = this.keyup.bind(this); + ig.input.mousemoveCallback = this.mousemove.bind(this); + + $(window).resize( this.resize.bind(this) ); + $(window).bind( 'keydown', this.uikeydown.bind(this) ); + $(window).bind( 'beforeunload', this.confirmClose.bind(this) ); + + $('#buttonAddLayer').bind( 'click', this.addLayer.bind(this) ); + $('#buttonRemoveLayer').bind( 'click', this.deleteLayerDialog.open.bind(this.deleteLayerDialog) ); + $('#buttonSaveLayerSettings').bind( 'click', this.saveLayerSettings.bind(this) ); + $('#reloadImages').bind( 'click', ig.Image.reloadCache ); + $('#layerIsCollision').bind( 'change', this.toggleCollisionLayer.bind(this) ); + + $('input#toggleSidebar').click(function() { + $('div#menu').slideToggle('fast'); + $('input#toggleSidebar').toggleClass('active'); + }); + + + this.undo = new wm.Undo( wm.config.undoLevels ); + + + if( wm.config.loadLastLevel ) { + var path = $.cookie('wmLastLevel'); + if( path ) { + this.load( null, path ) + } + } + }, + + + uikeydown: function( event ) { + if( event.target.type == 'text' ) { + return; + } + + var key = String.fromCharCode(event.which); + if( key.match(/^\d$/) ) { + var index = parseInt(key); + var name = $('#layers div.layer:nth-child('+index+') span.name').text(); + + var layer = name == 'entities' + ? this.entities + : this.getLayerWithName(name); + + if( layer ) { + if( event.shiftKey ) { + layer.toggleVisibility(); + } else { + this.setActiveLayer( layer.name ); + } + } + } + }, + + + showLoadDialog: function() { + if( this.modified ) { + this.loseChangesDialog.onOk = this.loadDialog.open.bind(this.loadDialog); + this.loseChangesDialog.open(); + } else { + this.loadDialog.open(); + } + }, + + showNewDialog: function() { + if( this.modified ) { + this.loseChangesDialog.onOk = this.loadNew.bind(this); + this.loseChangesDialog.open(); + } else { + this.loadNew(); + } + }, + + setModified: function() { + if( !this.modified ) { + this.modified = true; + this.setWindowTitle(); + } + }, + + resetModified: function() { + this.modified = false; + this.setWindowTitle(); + }, + + setWindowTitle: function() { + document.title = this.fileName + (this.modified ? ' * ' : ' - ') + 'Weltmeister'; + $('span.headerTitle').text(this.fileName); + $('span.unsavedTitle').text(this.modified ? '*' : ''); + }, + + + confirmClose: function( event ) { + var rv = undefined; + if( this.modified && wm.config.askBeforeClose ) { + rv = 'There are some unsaved changes. Leave anyway?'; + } + event.returnValue = rv; + return rv; + }, + + + resize: function() { + ig.system.resize( + Math.floor(wm.Weltmeister.getMaxWidth() / wm.config.view.zoom), + Math.floor(wm.Weltmeister.getMaxHeight() / wm.config.view.zoom), + wm.config.view.zoom + ); + ig.system.context.textBaseline = 'top'; + ig.system.context.font = wm.config.labels.font; + this.draw(); + }, + + + + // ------------------------------------------------------------------------- + // Loading + + loadNew: function() { + while( this.layers.length ) { + this.layers[0].destroy(); + this.layers.splice( 0, 1 ); + } + this.screen = {x: 0, y: 0}; + this.entities.clear(); + this.fileName = 'untitled.js'; + this.filePath = wm.config.project.levelPath + 'untitled.js'; + this.saveDialog.setPath( this.filePath ); + this.resetModified(); + this.draw(); + }, + + + load: function( dialog, path ) { + $.cookie( 'wmLastLevel', path ); + this.filePath = path; + this.saveDialog.setPath( path ); + this.fileName = path.replace(/^.*\//,''); + + var req = $.ajax({ + url:( path + '?nocache=' + Math.random() ), + dataType: 'text', + async:false, + success:this.loadResponse.bind(this) + }); + }, + + + loadResponse: function( data ) { + + // extract JSON from a module's JS + var jsonMatch = data.match( /\/\*JSON\[\*\/([\s\S]*?)\/\*\]JSON\*\// ); + data = $.parseJSON( jsonMatch ? jsonMatch[1] : data ); + + while( this.layers.length ) { + this.layers[0].destroy(); + this.layers.splice( 0, 1 ); + } + this.screen = {x: 0, y: 0}; + this.entities.clear(); + + for( var i=0; i < data.entities.length; i++ ) { + var ent = data.entities[i]; + this.entities.spawnEntity( ent.type, ent.x, ent.y, ent.settings ); + } + + for( var i=0; i < data.layer.length; i++ ) { + var ld = data.layer[i]; + var newLayer = new wm.EditMap( ld.name, ld.tilesize, ld.tilesetName, !!ld.foreground ); + newLayer.resize( ld.width, ld.height ); + newLayer.linkWithCollision = ld.linkWithCollision; + newLayer.repeat = ld.repeat; + newLayer.preRender = !!ld.preRender; + newLayer.distance = ld.distance; + newLayer.visible = !ld.visible; + newLayer.data = ld.data; + newLayer.toggleVisibility(); + this.layers.push( newLayer ); + + if( ld.name == 'collision' ) { + this.collisionLayer = newLayer; + } + + this.setActiveLayer( ld.name ); + } + + this.setActiveLayer( 'entities' ); + + this.reorderLayers(); + $('#layers').sortable('refresh'); + + this.resetModified(); + this.undo.clear(); + this.draw(); + }, + + + + // ------------------------------------------------------------------------- + // Saving + + saveQuick: function() { + if( this.fileName == 'untitled.js' ) { + this.saveDialog.open(); + } + else { + this.save( null, this.filePath ); + } + }, + + save: function( dialog, path ) { + this.filePath = path; + this.fileName = path.replace(/^.*\//,''); + var data = { + 'entities': this.entities.getSaveData(), + 'layer': [] + }; + + var resources = []; + for( var i=0; i < this.layers.length; i++ ) { + var layer = this.layers[i]; + data.layer.push( layer.getSaveData() ); + if( layer.name != 'collision' ) { + resources.push( layer.tiles.path ); + } + } + + + var dataString = $.toJSON(data); + if( wm.config.project.prettyPrint ) { + dataString = JSONFormat( dataString ); + } + + // Make it an ig.module instead of plain JSON? + if( wm.config.project.outputFormat == 'module' ) { + var levelModule = path + .replace(wm.config.project.modulePath, '') + .replace(/\.js$/, '') + .replace(/\//g, '.'); + + var levelName = levelModule.replace(/^.*\.(\w)(\w*)$/, function( m, a, b ) { + return a.toUpperCase() + b; + }); + + + var resourcesString = ''; + if( resources.length ) { + resourcesString = "Level" + levelName + "Resources=[new ig.Image('" + + resources.join("'), new ig.Image('") + + "')];\n"; + } + + // Collect all Entity Modules + var requires = ['impact.image']; + for( var i = 0; i < data.entities.length; i++ ) { + requires.push( + this.entities.entityClasses[ data.entities[i].type ] + ); + } + + // include /*JSON[*/ ... /*]JSON*/ markers, so we can easily load + // this level as JSON again + dataString = + "ig.module( '"+levelModule+"' )\n" + + ".requires('"+requires.join("','")+"')\n" + + ".defines(function(){\n"+ + "Level" + levelName + "=" + + "/*JSON[*/" + dataString + "/*]JSON*/" + + ";\n" + + resourcesString + + "});"; + } + + var postString = + 'path=' + encodeURIComponent( path ) + + '&data=' + encodeURIComponent(dataString); + + var req = $.ajax({ + url: wm.config.api.save, + type: 'POST', + dataType: 'json', + async: false, + data: postString, + success:this.saveResponse.bind(this) + }); + }, + + saveResponse: function( data ) { + if( data.error ) { + alert( 'Error: ' + data.msg ); + } else { + this.resetModified(); + } + }, + + + + // ------------------------------------------------------------------------- + // Layers + + addLayer: function() { + var name = 'new_layer_' + this.layers.length; + var newLayer = new wm.EditMap( name, wm.config.layerDefaults.tilesize ); + newLayer.resize( wm.config.layerDefaults.width, wm.config.layerDefaults.height ); + newLayer.setScreenPos( this.screen.x, this.screen.y ); + this.layers.push( newLayer ); + this.setActiveLayer( name ); + this.updateLayerSettings(); + + this.reorderLayers(); + + $('#layers').sortable('refresh'); + }, + + + removeLayer: function() { + var name = this.activeLayer.name; + if( name == 'entities' ) { + return false; + } + this.activeLayer.destroy(); + for( var i = 0; i < this.layers.length; i++ ) { + if( this.layers[i].name == name ) { + this.layers.splice( i, 1 ); + this.reorderLayers(); + $('#layers').sortable('refresh'); + this.setActiveLayer( 'entities' ); + return true; + } + } + return false; + }, + + + getLayerWithName: function( name ) { + for( var i = 0; i < this.layers.length; i++ ) { + if( this.layers[i].name == name ) { + return this.layers[i]; + } + } + return null; + }, + + + reorderLayers: function( dir ) { + var newLayers = []; + var isForegroundLayer = true; + $('#layers div.layer span.name').each((function( newIndex, span ){ + var name = $(span).text(); + + var layer = name == 'entities' + ? this.entities + : this.getLayerWithName(name); + + if( layer ) { + layer.setHotkey( newIndex+1 ); + if( layer.name == 'entities' ) { + // All layers after the entity layer are not foreground + // layers + isForegroundLayer = false; + } + else { + layer.foreground = isForegroundLayer; + newLayers.unshift( layer ); + } + } + }).bind(this)); + this.layers = newLayers; + this.setModified(); + this.draw(); + }, + + + updateLayerSettings: function( ) { + $('#layerName').val( this.activeLayer.name ); + $('#layerTileset').val( this.activeLayer.tilesetName ); + $('#layerTilesize').val( this.activeLayer.tilesize ); + $('#layerWidth').val( this.activeLayer.width ); + $('#layerHeight').val( this.activeLayer.height ); + $('#layerPreRender').attr( 'checked', this.activeLayer.preRender ? 'checked' : '' ); + $('#layerRepeat').attr( 'checked', this.activeLayer.repeat ? 'checked' : '' ); + $('#layerLinkWithCollision').attr( 'checked', this.activeLayer.linkWithCollision ? 'checked' : '' ); + $('#layerDistance').val( this.activeLayer.distance ); + }, + + + saveLayerSettings: function() { + var isCollision = $('#layerIsCollision').attr('checked') ? true : false; + + var newName = $('#layerName').val(); + var newWidth = Math.floor($('#layerWidth').val()); + var newHeight = Math.floor($('#layerHeight').val()); + + if( newWidth != this.activeLayer.width || newHeight != this.activeLayer.height ) { + this.activeLayer.resize( newWidth, newHeight ); + } + this.activeLayer.tilesize = Math.floor($('#layerTilesize').val()); + + if( isCollision ) { + newName = 'collision'; + this.activeLayer.linkWithCollision = false; + this.activeLayer.distance = 1; + this.activeLayer.repeat = false; + this.activeLayer.setCollisionTileset(); + } + else { + var newTilesetName = $('#layerTileset').val(); + if( newTilesetName != this.activeLayer.tilesetName ) { + this.activeLayer.setTileset( newTilesetName ); + } + this.activeLayer.linkWithCollision = $('#layerLinkWithCollision').attr('checked') ? true : false; + this.activeLayer.distance = $('#layerDistance').val(); + this.activeLayer.repeat = $('#layerRepeat').attr('checked') ? true : false; + this.activeLayer.preRender = $('#layerPreRender').attr('checked') ? true : false; + } + + + if( newName == 'collision' ) { + // is collision layer + this.collisionLayer = this.activeLayer; + } + else if( this.activeLayer.name == 'collision' ) { + // was collision layer, but is no more + this.collisionLayer = null; + } + + this.activeLayer.setName( newName ); + this.setModified(); + this.draw(); + }, + + + setActiveLayer: function( name ) { + var previousLayer = this.activeLayer; + this.activeLayer = ( name == 'entities' ? this.entities : this.getLayerWithName(name) ); + if( previousLayer == this.activeLayer ) { + return; // nothing to do here + } + + if( previousLayer ) { + previousLayer.setActive( false ); + } + this.activeLayer.setActive( true ); + this.mode = this.MODE.DEFAULT; + + $('#layerIsCollision').attr('checked', (name == 'collision') ); + + if( name == 'entities' ) { + $('#layerSettings').fadeOut(100); + } + else { + this.entities.selectEntity( null ); + this.toggleCollisionLayer(); + $('#layerSettings') + .fadeOut(100,this.updateLayerSettings.bind(this)) + .fadeIn(100); + } + this.draw(); + }, + + + toggleCollisionLayer: function( ev ) { + var isCollision = $('#layerIsCollision').attr('checked') ? true : false; + $('#layerLinkWithCollision,#layerDistance,#layerPreRender,#layerRepeat,#layerName,#layerTileset') + .attr('disabled', isCollision ); + }, + + + + // ------------------------------------------------------------------------- + // Update + + mousemove: function() { + if( !this.activeLayer ) { + return; + } + + if( this.mode == this.MODE.DEFAULT ) { + + // scroll map + if( ig.input.state('drag') ) { + this.screen.x -= ig.input.mouse.x - this.mouseLast.x; + this.screen.y -= ig.input.mouse.y - this.mouseLast.y; + this._rscreen.x = Math.round(this.screen.x * ig.system.scale)/ig.system.scale; + this._rscreen.y = Math.round(this.screen.y * ig.system.scale)/ig.system.scale; + for( var i = 0; i < this.layers.length; i++ ) { + this.layers[i].setScreenPos( this.screen.x, this.screen.y ); + } + } + + else if( ig.input.state('draw') ) { + + // move/scale entity + if( this.activeLayer == this.entities ) { + var x = ig.input.mouse.x + this.screen.x; + var y = ig.input.mouse.y + this.screen.y; + this.entities.dragOnSelectedEntity( x, y ); + this.setModified(); + } + + // draw on map + else if( !this.activeLayer.isSelecting ) { + this.setTileOnCurrentLayer(); + } + } + else if( this.activeLayer == this.entities ) { + var x = ig.input.mouse.x + this.screen.x; + var y = ig.input.mouse.y + this.screen.y; + this.entities.mousemove( x, y ); + } + } + + this.mouseLast = {x: ig.input.mouse.x, y: ig.input.mouse.y}; + this.draw(); + }, + + + keydown: function( action ) { + if( !this.activeLayer ) { + return; + } + + if( action == 'draw' ) { + if( this.mode == this.MODE.DEFAULT ) { + // select entity + if( this.activeLayer == this.entities ) { + var x = ig.input.mouse.x + this.screen.x; + var y = ig.input.mouse.y + this.screen.y; + var entity = this.entities.selectEntityAt( x, y ); + if( entity ) { + this.undo.beginEntityEdit( entity ); + } + } + else { + if( ig.input.state('select') ) { + this.activeLayer.beginSelecting( ig.input.mouse.x, ig.input.mouse.y ); + } + else { + this.undo.beginMapDraw(); + this.activeLayer.beginEditing(); + if( + this.activeLayer.linkWithCollision && + this.collisionLayer && + this.collisionLayer != this.activeLayer + ) { + this.collisionLayer.beginEditing(); + } + this.setTileOnCurrentLayer(); + } + } + } + else if( this.mode == this.MODE.TILESELECT && ig.input.state('select') ) { + this.activeLayer.tileSelect.beginSelecting( ig.input.mouse.x, ig.input.mouse.y ); + } + } + + this.draw(); + }, + + + keyup: function( action ) { + if( !this.activeLayer ) { + return; + } + + if( action == 'delete' ) { + this.entities.deleteSelectedEntity(); + this.setModified(); + } + + else if( action == 'clone' ) { + this.entities.cloneSelectedEntity(); + this.setModified(); + } + + else if( action == 'grid' ) { + wm.config.view.grid = !wm.config.view.grid; + } + + else if( action == 'menu' ) { + if( this.mode != this.MODE.TILESELECT && this.mode != this.MODE.ENTITYSELECT ) { + if( this.activeLayer == this.entities ) { + this.mode = this.MODE.ENTITYSELECT; + this.entities.showMenu( ig.input.mouse.x, ig.input.mouse.y ); + } + else { + this.mode = this.MODE.TILESELECT; + this.activeLayer.tileSelect.setPosition( ig.input.mouse.x, ig.input.mouse.y ); + } + } else { + this.mode = this.MODE.DEFAULT; + this.entities.hideMenu(); + } + } + + + if( action == 'draw' ) { + // select tile + if( this.mode == this.MODE.TILESELECT ) { + this.activeLayer.brush = this.activeLayer.tileSelect.endSelecting( ig.input.mouse.x, ig.input.mouse.y ); + this.mode = this.MODE.DEFAULT; + } + else if( this.activeLayer == this.entities ) { + this.undo.endEntityEdit(); + } + else { + if( this.activeLayer.isSelecting ) { + this.activeLayer.brush = this.activeLayer.endSelecting( ig.input.mouse.x, ig.input.mouse.y ); + } + else { + this.undo.endMapDraw(); + } + } + } + + if( action == 'undo' ) { + this.undo.undo(); + } + + if( action == 'redo' ) { + this.undo.redo(); + } + + this.draw(); + this.mouseLast = {x: ig.input.mouse.x, y: ig.input.mouse.y}; + }, + + + setTileOnCurrentLayer: function() { + if( !this.activeLayer || !this.activeLayer.scroll ) { + return; + } + + var co = this.activeLayer.getCursorOffset(); + var x = ig.input.mouse.x + this.activeLayer.scroll.x - co.x; + var y = ig.input.mouse.y + this.activeLayer.scroll.y - co.y; + + var brush = this.activeLayer.brush; + for( var by = 0; by < brush.length; by++ ) { + var brushRow = brush[by]; + for( var bx = 0; bx < brushRow.length; bx++ ) { + + var mapx = x + bx * this.activeLayer.tilesize; + var mapy = y + by * this.activeLayer.tilesize; + + var newTile = brushRow[bx]; + var oldTile = this.activeLayer.getOldTile( mapx, mapy ); + + this.activeLayer.setTile( mapx, mapy, newTile ); + this.undo.pushMapDraw( this.activeLayer, mapx, mapy, oldTile, newTile ); + + + if( + this.activeLayer.linkWithCollision && + this.collisionLayer && + this.collisionLayer != this.activeLayer + ) { + var collisionLayerTile = newTile > 0 ? this.collisionSolid : 0; + + var oldCollisionTile = this.collisionLayer.getOldTile(mapx, mapy); + this.collisionLayer.setTile( mapx, mapy, collisionLayerTile ); + this.undo.pushMapDraw( this.collisionLayer, mapx, mapy, oldCollisionTile, collisionLayerTile ); + } + } + } + + this.setModified(); + }, + + + // ------------------------------------------------------------------------- + // Drawing + + draw: function() { + ig.system.clear( wm.config.colors.clear ); + + var entitiesDrawn = false; + for( var i = 0; i < this.layers.length; i++ ) { + layer = this.layers[i]; + + // This layer is a foreground layer? -> Draw entities first! + if( !entitiesDrawn && layer.foreground ) { + entitiesDrawn = true; + this.entities.draw(); + } + layer.draw(); + } + + if( !entitiesDrawn ) { + this.entities.draw(); + } + + + if( this.activeLayer ) { + if( this.mode == this.MODE.TILESELECT ) { + this.activeLayer.tileSelect.draw(); + this.activeLayer.tileSelect.drawCursor( ig.input.mouse.x, ig.input.mouse.y ); + } + + if( this.mode == this.MODE.DEFAULT ) { + this.activeLayer.drawCursor( ig.input.mouse.x, ig.input.mouse.y ); + } + } + + if( wm.config.labels.draw ) { + this.drawLabels( wm.config.labels.step ); + } + }, + + + drawLabels: function( step ) { + ig.system.context.fillStyle = wm.config.colors.primary; + var xlabel = this.screen.x - this.screen.x % step - step; + for( var tx = Math.floor(-this.screen.x % step); tx < ig.system.width; tx += step ) { + xlabel += step; + ig.system.context.fillText( xlabel, tx * ig.system.scale, 0 ); + } + + var ylabel = this.screen.y - this.screen.y % step - step; + for( var ty = Math.floor(-this.screen.y % step); ty < ig.system.height; ty += step ) { + ylabel += step; + ig.system.context.fillText( ylabel, 0, ty * ig.system.scale ); + } + }, + + + getEntityByName: function( name ) { + return this.entities.getEntityByName( name ); + } +}); + + +wm.Weltmeister.getMaxWidth = function() { + return $(window).width(); +}; + +wm.Weltmeister.getMaxHeight = function() { + return $(window).height() - $('#headerMenu').height(); +}; + + + +// Create a custom loader, to skip sound files and the run loop creation +wm.Loader = ig.Loader.extend({ + end: function() { + if( this.done ) { return; } + + clearInterval( this._intervalId ); + this.done = true; + ig.system.clear( wm.config.colors.clear ); + ig.game = new (this.gameClass)(); + }, + + loadResource: function( res ) { + if( res instanceof ig.Sound ) { + this._unloaded.erase( res.path ); + } + else { + this.parent( res ); + } + } +}); + + + +// Init! +ig.system = new ig.System( + '#canvas', 1, + Math.floor(wm.Weltmeister.getMaxWidth() / wm.config.view.zoom), + Math.floor(wm.Weltmeister.getMaxHeight() / wm.config.view.zoom), + wm.config.view.zoom +); + +ig.input = new wm.EventedInput(); +ig.soundManager = new ig.SoundManager(); +ig.ready = true; + +var loader = new wm.Loader( wm.Weltmeister, ig.resources ); +loader.load(); + +}); + + diff --git a/dev/media/04b03.font.png b/dev/media/04b03.font.png new file mode 100755 index 0000000..3e137af Binary files /dev/null and b/dev/media/04b03.font.png differ diff --git a/dev/tools/bake.bat b/dev/tools/bake.bat new file mode 100755 index 0000000..9014090 --- /dev/null +++ b/dev/tools/bake.bat @@ -0,0 +1,21 @@ +@echo off + +:: Path to impact.js and your game's main .js +SET IMPACT_LIBRARY=lib/impact/impact.js +SET GAME=lib/game/main.js + +:: Output file +SET OUTPUT_FILE=game.min.js + + +:: Change CWD to Impact's base dir and bake! +cd ../ +php tools/bake.php %IMPACT_LIBRARY% %GAME% %OUTPUT_FILE% + + +:: If you dont have the php.exe in your PATH uncomment the +:: following line and point it to your php.exe + +::c:/php/php.exe bake.php %IMPACT_LIBRARY% %GAME% %OUTPUT_FILE% + +pause \ No newline at end of file diff --git a/dev/tools/bake.php b/dev/tools/bake.php new file mode 100755 index 0000000..72bafcd --- /dev/null +++ b/dev/tools/bake.php @@ -0,0 +1,157 @@ + \n"; + echo "e.g. bake.php lib/impact/impact.js lib/game/game.js mygame-baked.js\n"; + die; +} + +$inFiles = array_slice( $argv, 1, -1 ); +$outFile = $argv[ count($argv)-1 ]; + +$baker = new Baker( Baker::MINIFIED ); +$baker->bake( $inFiles, $outFile ); + + +class Baker { + const PLAIN = 0; + const MINIFIED = 1; + const GZIPPED = 2; + + protected $base = 'lib/'; + protected $format = 0; + protected $loaded = array(); + protected $currentInput = 'Command Line'; + protected $fileCount = 0, $bytesIn = 0, $bytesOut = 0; + + public function __construct( $format = 0 ) { + $this->format = $format; + if( $this->format & self::MINIFIED ) { + require_once( 'jsmin.php' ); + } + } + + + public function bake( $inFiles, $outFile ) { + $this->fileCount = 0; + $this->bytesIn = 0; + $out = "/*! Built with IMPACT - impactjs.com */\n\n"; + + foreach( $inFiles as $f ) { + $out .= $this->load( $f ); + } + + $bytesOut = strlen($out); + $bytesOutZipped = 0; + + echo "writing $outFile\n"; + @file_put_contents( $outFile, $out ) or + die("ERROR: Couldn't write to $outFile\n"); + + if( $this->format & self::GZIPPED ) { + $gzFile = "$outFile.gz"; + echo "writing $gzFile\n"; + $fh = gzopen( $gzFile, 'w9' ) or + die("ERROR: Couldn't write to $gzFile\n"); + + gzwrite( $fh, $out ); + gzclose( $fh ); + $bytesOutZipped = filesize( $gzFile ); + } + + + echo + "\nbaked {$this->fileCount} files: ". + round($this->bytesIn/1024,1)."kb -> ".round($bytesOut/1024,1)."kb" . + ( $this->format & self::GZIPPED + ? " (".round($bytesOutZipped/1024,1)."kb gzipped)\n" + : "\n" + ); + } + + + protected function load( $path ) { + if( isset($this->loaded[$path]) ) { + return ''; + } + + if( !file_exists($path) ) { + die("ERROR: Couldn't load $path required from {$this->currentInput}\n"); + } + + echo "loading $path \n"; + $this->loaded[$path] = true; + $this->currentInput = $path; + + $code = file_get_contents( $path ); + $this->bytesIn += strlen($code); + $this->fileCount++; + if( $this->format & self::MINIFIED ) { + $code = trim(JSMin::minify($code)); + } + + + // Naively probe the file for 'ig.module().requires().defines()' code; + // the 'requries()' part will be handled by the regexp callback + $this->definesModule = false; + $code = preg_replace_callback( + '/ig\s* + \.\s*module\s*\((.*?)\)\s* + (\.\s*requires\s*\((.*?)\)\s*)? + \.\s*defines\s*\( + /smx', + array($this,'loadCallback'), + $code + ); + + // All files should define a module; maybe we just missed it? Print a + // friendly reminder :) + if( !$this->definesModule ) { + echo "WARNING: file $path seems to define no module!\n"; + } + + return $code; + } + + + protected function loadCallback( $matches ) { + $currentInput = $this->currentInput; + $this->definesModule = true; + + $moduleName = $matches[1]; + $requiredFiles = isset($matches[3]) ? $matches[3] : ''; + $requiredCode = ''; + + if( $requiredFiles ) { + // Explode the module names and map them to file names. Ignore the + // dom.ready module if present + $moduleFiles = array_diff( + explode( + ',', + preg_replace( + '/[\s\'"]|\/\/.*|\/\*.*\*\//', // strip quotes and spaces + '', + str_replace('.', '/', $requiredFiles ) // . to / + ) + ), + array('dom/ready') + ); + + foreach( $moduleFiles as $f ) { + $requiredCode .= $this->load( $this->base . $f.'.js' ); + } + } + + return + $requiredCode . + "\n\n// $currentInput\n" . + 'ig.baked=true;' . + 'ig.module('.$moduleName.')' . + ( $requiredFiles + ? '.requires('.$requiredFiles.')' + : '' + ) . + '.defines('; + } +} + +?> \ No newline at end of file diff --git a/dev/tools/bake.sh b/dev/tools/bake.sh new file mode 100755 index 0000000..e83869a --- /dev/null +++ b/dev/tools/bake.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Path to impact.js and your game's main .js +IMPACT_LIBRARY=lib/impact/impact.js +GAME=lib/game/main.js + +# Output file +OUTPUT_FILE=game.min.js + + +# Change CWD to Impact's base dir and bake! +cd .. +php tools/bake.php $IMPACT_LIBRARY $GAME $OUTPUT_FILE \ No newline at end of file diff --git a/dev/tools/jsmin.php b/dev/tools/jsmin.php new file mode 100755 index 0000000..e45b418 --- /dev/null +++ b/dev/tools/jsmin.php @@ -0,0 +1,291 @@ + + * @copyright 2002 Douglas Crockford (jsmin.c) + * @copyright 2008 Ryan Grove (PHP port) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.1.1 (2008-03-02) + * @link http://code.google.com/p/jsmin-php/ + */ + +class JSMin { + const ORD_LF = 10; + const ORD_SPACE = 32; + + protected $a = ''; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = ''; + + // -- Public Static Methods -------------------------------------------------- + + public static function minify($js) { + $jsmin = new JSMin($js); + return $jsmin->min(); + } + + // -- Public Instance Methods ------------------------------------------------ + + public function __construct($input) { + $this->input = str_replace("\r\n", "\n", $input); + $this->inputLength = strlen($this->input); + } + + // -- Protected Instance Methods --------------------------------------------- + + protected function action($d) { + switch($d) { + case 1: + $this->output .= $this->a; + + case 2: + $this->a = $this->b; + + if ($this->a === "'" || $this->a === '"') { + for (;;) { + $this->output .= $this->a; + $this->a = $this->get(); + + if ($this->a === $this->b) { + break; + } + + if (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated string literal.'); + } + + if ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } + } + } + + case 3: + $this->b = $this->next(); + + if ($this->b === '/' && ( + $this->a === '(' || $this->a === ',' || $this->a === '=' || + $this->a === ':' || $this->a === '[' || $this->a === '!' || + $this->a === '&' || $this->a === '|' || $this->a === '?')) { + + $this->output .= $this->a . $this->b; + + for (;;) { + $this->a = $this->get(); + + if ($this->a === '/') { + break; + } elseif ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } elseif (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated regular expression '. + 'literal.'); + } + + $this->output .= $this->a; + } + + $this->b = $this->next(); + } + } + } + + protected function get() { + $c = $this->lookAhead; + $this->lookAhead = null; + + if ($c === null) { + if ($this->inputIndex < $this->inputLength) { + $c = $this->input[$this->inputIndex]; + $this->inputIndex += 1; + } else { + $c = null; + } + } + + if ($c === "\r") { + return "\n"; + } + + if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { + return $c; + } + + return ' '; + } + + protected function isAlphaNum($c) { + return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; + } + + protected function min() { + $this->a = "\n"; + $this->action(3); + + while ($this->a !== null) { + switch ($this->a) { + case ' ': + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } else { + $this->action(2); + } + break; + + case "\n": + switch ($this->b) { + case '{': + case '[': + case '(': + case '+': + case '-': + $this->action(1); + break; + + case ' ': + $this->action(3); + break; + + default: + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } + else { + $this->action(2); + } + } + break; + + default: + switch ($this->b) { + case ' ': + if ($this->isAlphaNum($this->a)) { + $this->action(1); + break; + } + + $this->action(3); + break; + + case "\n": + switch ($this->a) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case "'": + $this->action(1); + break; + + default: + if ($this->isAlphaNum($this->a)) { + $this->action(1); + } + else { + $this->action(3); + } + } + break; + + default: + $this->action(1); + break; + } + } + } + + return $this->output; + } + + protected function next() { + $c = $this->get(); + + if ($c === '/') { + switch($this->peek()) { + case '/': + for (;;) { + $c = $this->get(); + + if (ord($c) <= self::ORD_LF) { + return $c; + } + } + + case '*': + $this->get(); + + for (;;) { + switch($this->get()) { + case '*': + if ($this->peek() === '/') { + $this->get(); + return ' '; + } + break; + + case null: + throw new JSMinException('Unterminated comment.'); + } + } + + default: + return $c; + } + } + + return $c; + } + + protected function peek() { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } +} + +// -- Exceptions --------------------------------------------------------------- +class JSMinException extends Exception {} +?> \ No newline at end of file diff --git a/dev/weltmeister.html b/dev/weltmeister.html new file mode 100755 index 0000000..53e9605 --- /dev/null +++ b/dev/weltmeister.html @@ -0,0 +1,96 @@ + + + + + Weltmeister + + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + + + + +
+ +