From b8ab13fec8f386a54c1595ec4755c805cb89c1b1 Mon Sep 17 00:00:00 2001 From: Richard Davey Date: Thu, 25 Apr 2013 01:55:56 +0100 Subject: [PATCH] Getting tilemap collision up and running --- Phaser/Collision.ts | 129 +++++++++- Phaser/Game.ts | 3 + Phaser/Phaser.ts | 4 +- Phaser/gameobjects/GameObject.ts | 2 + Phaser/gameobjects/Sprite.ts | 2 +- Phaser/gameobjects/Tilemap.ts | 115 ++++++++- Phaser/geom/Quad.ts | 4 +- Phaser/phaser.js | 4 +- Phaser/system/Camera.ts | 22 +- Phaser/system/QuadTree.ts | 4 +- Phaser/system/Tile.ts | 76 +++--- Phaser/system/TilemapLayer.ts | 140 +++++++++- Phaser/system/input/Input.ts | 1 + Phaser/system/input/Keyboard.ts | 2 +- README.md | 44 ++-- Tests/Tests.csproj | 8 + Tests/assets/maps/desert.json | 39 +++ Tests/assets/maps/desert2.json | 54 ++++ Tests/assets/maps/platform-test-1.json | 39 +++ Tests/assets/tiles/tmw_desert_spacing.png | Bin 0 -> 37830 bytes Tests/phaser.js | 299 ++++++++++++++++------ Tests/tilemap/collision.js | 73 ++++++ Tests/tilemap/collision.ts | 116 +++++++++ Tests/tilemap/get tile.js | 49 ++++ Tests/tilemap/get tile.ts | 78 ++++++ 25 files changed, 1109 insertions(+), 198 deletions(-) create mode 100644 Tests/assets/maps/desert.json create mode 100644 Tests/assets/maps/desert2.json create mode 100644 Tests/assets/maps/platform-test-1.json create mode 100644 Tests/assets/tiles/tmw_desert_spacing.png create mode 100644 Tests/tilemap/collision.js create mode 100644 Tests/tilemap/collision.ts create mode 100644 Tests/tilemap/get tile.js create mode 100644 Tests/tilemap/get tile.ts diff --git a/Phaser/Collision.ts b/Phaser/Collision.ts index aac18b85..55d3d0c7 100644 --- a/Phaser/Collision.ts +++ b/Phaser/Collision.ts @@ -604,7 +604,7 @@ module Phaser { } /** - * The main collision resolution in flixel. + * The main collision resolution. * * @param Object1 Any Sprite. * @param Object2 Any other Sprite. @@ -620,6 +620,120 @@ module Phaser { } + /** + * Collision resolution specifically for GameObjects vs. Tiles. + * + * @param Object1 Any GameObject. + * @param Object2 Any Tile. + * + * @return Whether the objects in fact touched and were separated. + */ + public static separateTile(object:GameObject, tile:Tile): bool { + + //var separatedX: bool = Collision.separateTileX(object, tile); + //var separatedY: bool = Collision.separateTileY(object, tile); + + //return separatedX || separatedY; + + return false; + + } + + /* + public static separateTileX(object:GameObject, tile:Tile): bool { + + //First, get the two object deltas + var overlap: number = 0; + var obj1delta: number = object.x - object.last.x; + var obj2delta: number = tile.x; + + if (obj1delta != obj2delta) + { + //Check if the X hulls actually overlap + var obj1deltaAbs: number = (obj1delta > 0) ? obj1delta : -obj1delta; + var obj2deltaAbs: number = (obj2delta > 0) ? obj2delta : -obj2delta; + //var obj1rect: Rectangle = new Rectangle(Object1.x - ((obj1delta > 0) ? obj1delta : 0), Object1.last.y, Object1.width + ((obj1delta > 0) ? obj1delta : -obj1delta), Object1.height); + //var obj2rect: Rectangle = new Rectangle(Object2.x - ((obj2delta > 0) ? obj2delta : 0), Object2.last.y, Object2.width + ((obj2delta > 0) ? obj2delta : -obj2delta), Object2.height); + + //if ((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height)) + //{ + var maxOverlap: number = obj1deltaAbs + obj2deltaAbs + Collision.OVERLAP_BIAS; + + //If they did overlap (and can), figure out by how much and flip the corresponding flags + if (obj1delta > obj2delta) + { + overlap = Object1.x + Object1.width - Object2.x; + + if ((overlap > maxOverlap) || !(Object1.allowCollisions & Collision.RIGHT) || !(Object2.allowCollisions & Collision.LEFT)) + { + overlap = 0; + } + else + { + Object1.touching |= Collision.RIGHT; + Object2.touching |= Collision.LEFT; + } + } + else if (obj1delta < obj2delta) + { + overlap = Object1.x - Object2.width - Object2.x; + + if ((-overlap > maxOverlap) || !(Object1.allowCollisions & Collision.LEFT) || !(Object2.allowCollisions & Collision.RIGHT)) + { + overlap = 0; + } + else + { + Object1.touching |= Collision.LEFT; + Object2.touching |= Collision.RIGHT; + } + + } + + } + } + + //Then adjust their positions and velocities accordingly (if there was any overlap) + if (overlap != 0) + { + var obj1v: number = Object1.velocity.x; + var obj2v: number = Object2.velocity.x; + + if (!obj1immovable && !obj2immovable) + { + overlap *= 0.5; + Object1.x = Object1.x - overlap; + Object2.x += overlap; + + var obj1velocity: number = Math.sqrt((obj2v * obj2v * Object2.mass) / Object1.mass) * ((obj2v > 0) ? 1 : -1); + var obj2velocity: number = Math.sqrt((obj1v * obj1v * Object1.mass) / Object2.mass) * ((obj1v > 0) ? 1 : -1); + var average: number = (obj1velocity + obj2velocity) * 0.5; + obj1velocity -= average; + obj2velocity -= average; + Object1.velocity.x = average + obj1velocity * Object1.elasticity; + Object2.velocity.x = average + obj2velocity * Object2.elasticity; + } + else if (!obj1immovable) + { + Object1.x = Object1.x - overlap; + Object1.velocity.x = obj2v - obj1v * Object1.elasticity; + } + else if (!obj2immovable) + { + Object2.x += overlap; + Object2.velocity.x = obj1v - obj2v * Object2.elasticity; + } + + return true; + } + else + { + return false; + } + + } + */ + /** * The X-axis component of the object separation process. * @@ -639,19 +753,6 @@ module Phaser { return false; } - //If one of the objects is a tilemap, just pass it off. - /* - if (typeof Object1 === 'Tilemap') - { - return Object1.overlapsWithCallback(Object2, separateX); - } - - if (typeof Object2 === 'Tilemap') - { - return Object2.overlapsWithCallback(Object1, separateX, true); - } - */ - //First, get the two object deltas var overlap: number = 0; var obj1delta: number = Object1.x - Object1.last.x; diff --git a/Phaser/Game.ts b/Phaser/Game.ts index ef780a4f..23218712 100644 --- a/Phaser/Game.ts +++ b/Phaser/Game.ts @@ -35,6 +35,9 @@ * * This is where the magic happens. The Game object is the heart of your game, * providing quick access to common functions and handling the boot process. +* +* "Hell, there are no rules here - we're trying to accomplish something." +* Thomas A. Edison */ module Phaser { diff --git a/Phaser/Phaser.ts b/Phaser/Phaser.ts index 8fca2aee..7434f78c 100644 --- a/Phaser/Phaser.ts +++ b/Phaser/Phaser.ts @@ -1,7 +1,7 @@ /** * Phaser * -* v0.9.3 - April 24th 2013 +* v0.9.4 - April 24th 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -16,6 +16,6 @@ module Phaser { - export var VERSION: string = 'Phaser version 0.9.3'; + export var VERSION: string = 'Phaser version 0.9.4'; } diff --git a/Phaser/gameobjects/GameObject.ts b/Phaser/gameobjects/GameObject.ts index 5b578181..e942cf72 100644 --- a/Phaser/gameobjects/GameObject.ts +++ b/Phaser/gameobjects/GameObject.ts @@ -89,6 +89,8 @@ module Phaser { // rotationOffset to 90 and it would correspond correctly with Phasers rotation system public rotationOffset: number = 0; + public renderRotation: bool = true; + // Physics properties public immovable: bool; diff --git a/Phaser/gameobjects/Sprite.ts b/Phaser/gameobjects/Sprite.ts index ba849c3a..106226b4 100644 --- a/Phaser/gameobjects/Sprite.ts +++ b/Phaser/gameobjects/Sprite.ts @@ -229,7 +229,7 @@ module Phaser { this._game.stage.context.save(); this._game.stage.context.translate(this._dx + (this._dw / 2), this._dy + (this._dh / 2)); - if (this.angle !== 0 || this.rotationOffset !== 0) + if (this.renderRotation == true && (this.angle !== 0 || this.rotationOffset !== 0)) { this._game.stage.context.rotate((this.rotationOffset + this.angle) * (Math.PI / 180)); } diff --git a/Phaser/gameobjects/Tilemap.ts b/Phaser/gameobjects/Tilemap.ts index 526d1aea..09f5c192 100644 --- a/Phaser/gameobjects/Tilemap.ts +++ b/Phaser/gameobjects/Tilemap.ts @@ -1,6 +1,7 @@ /// /// /// +/// /** * Phaser - Tilemap @@ -19,7 +20,8 @@ module Phaser { this.isGroup = false; - this._layers = []; + this.tiles = []; + this.layers = []; this.mapFormat = format; @@ -41,11 +43,11 @@ module Phaser { } - private _layers : TilemapLayer[]; - public static FORMAT_CSV: number = 0; public static FORMAT_TILED_JSON: number = 1; + public tiles : Tile[]; + public layers : TilemapLayer[]; public currentLayer: TilemapLayer; public mapFormat: number; @@ -57,9 +59,9 @@ module Phaser { if (this.cameraBlacklist.indexOf(camera.ID) == -1) { // Loop through the layers - for (var i = 0; i < this._layers.length; i++) + for (var i = 0; i < this.layers.length; i++) { - this._layers[i].render(camera, cameraOffsetX, cameraOffsetY); + this.layers[i].render(camera, cameraOffsetX, cameraOffsetY); } } @@ -67,7 +69,7 @@ module Phaser { private parseCSV(data: string, key: string, tileWidth: number, tileHeight: number) { - var layer: TilemapLayer = new TilemapLayer(this._game, key, Tilemap.FORMAT_CSV, 'TileLayerCSV' + this._layers.length.toString(), tileWidth, tileHeight); + var layer: TilemapLayer = new TilemapLayer(this._game, this, key, Tilemap.FORMAT_CSV, 'TileLayerCSV' + this.layers.length.toString(), tileWidth, tileHeight); // Trim any rogue whitespace from the data data = data.trim(); @@ -85,10 +87,13 @@ module Phaser { } layer.updateBounds(); + var tileQuantity = layer.parseTileOffsets(); this.currentLayer = layer; - this._layers.push(layer); + this.layers.push(layer); + + this.generateTiles(tileQuantity); } @@ -101,10 +106,12 @@ module Phaser { for (var i = 0; i < json.layers.length; i++) { - var layer: TilemapLayer = new TilemapLayer(this._game, key, Tilemap.FORMAT_TILED_JSON, json.layers[i].name, json.tilewidth, json.tileheight); + var layer: TilemapLayer = new TilemapLayer(this._game, this, key, Tilemap.FORMAT_TILED_JSON, json.layers[i].name, json.tilewidth, json.tileheight); layer.alpha = json.layers[i].opacity; layer.visible = json.layers[i].visible; + layer.tileMargin = json.tilesets[0].margin; + layer.tileSpacing = json.tilesets[0].spacing; var c = 0; var row; @@ -129,12 +136,25 @@ module Phaser { layer.updateBounds(); + var tileQuantity = layer.parseTileOffsets(); + this.currentLayer = layer; - this._layers.push(layer); + this.layers.push(layer); } + this.generateTiles(tileQuantity); + + } + + private generateTiles(qty:number) { + + for (var i = 0; i < qty; i++) + { + this.tiles.push(new Tile(this._game, this, i, this.currentLayer.tileWidth, this.currentLayer.tileHeight)); + } + } public get widthInPixels(): number { @@ -145,9 +165,84 @@ module Phaser { return this.currentLayer.heightInPixels; } + // Tile Collision + + public setCollisionRange(start: number, end: number, collision?:number = Collision.ANY) { + + for (var i = start; i < end; i++) + { + this.tiles[i].allowCollisions = collision; + } + + } + + public setCollisionByIndex(values:number[], collision?:number = Collision.ANY) { + + for (var i = 0; i < values.length; i++) + { + this.tiles[values[i]].allowCollisions = collision; + } + + } + + // Tile Management + + public getTile(x: number, y: number, layer?: number = 0):Tile { + + return this.tiles[this.layers[layer].getTileIndex(x, y)]; + + } + + public getTileFromWorldXY(x: number, y: number, layer?: number = 0):Tile { + + return this.tiles[this.layers[layer].getTileFromWorldXY(x, y)]; + + } + + public getTileFromInputXY(layer?: number = 0):Tile { + + return this.tiles[this.layers[layer].getTileFromWorldXY(this._game.input.worldX, this._game.input.worldY)]; + + } + + public getTileOverlaps(object: GameObject) { + + return this.currentLayer.getTileOverlaps(object); + + } + + // COLLIDE + public collide(objectOrGroup = null, callback = null): bool { + + if (objectOrGroup == null) + { + objectOrGroup = this._game.world.group; + } + + // Group? + if (objectOrGroup.isGroup == false) + { + if (objectOrGroup.exists && objectOrGroup.allowCollisions != Collision.NONE) + { + // Get the tiles this object overlaps with (could be any number based on its width/height) + + // Iterate through each tile, checking if it overlaps with the object bounds + + // Yes? then separate, else abort + } + } + else + { + // todo + } + + return true; + + } + + // Set current layer // Set layer order? - // Get tile from x/y // Get block of tiles // Swap tiles around // Delete tiles of certain type diff --git a/Phaser/geom/Quad.ts b/Phaser/geom/Quad.ts index 88adc41d..bfad11db 100644 --- a/Phaser/geom/Quad.ts +++ b/Phaser/geom/Quad.ts @@ -72,11 +72,11 @@ module Phaser { * Determines whether the object specified intersects (overlaps) with this Quad object. * This method checks the x, y, width, and height properties of the specified Quad object to see if it intersects with this Quad object. * @method intersects - * @param {Quad} q The Quad to compare against to see if it intersects with this Quad. + * @param {Object} q The object to check for intersection with this Quad. Must have left/right/top/bottom properties (Rectangle, Quad). * @param {Number} t A tolerance value to allow for an intersection test with padding, default to 0 * @return {Boolean} A value of true if the specified object intersects with this Quad; otherwise false. **/ - public intersects(q: Quad, t?: number = 0): bool { + public intersects(q, t?: number = 0): bool { return !(q.left > this.right + t || q.right < this.left - t || q.top > this.bottom + t || q.bottom < this.top - t); diff --git a/Phaser/phaser.js b/Phaser/phaser.js index 2d2a57e4..b2d7ffdb 100644 --- a/Phaser/phaser.js +++ b/Phaser/phaser.js @@ -1,7 +1,7 @@ /** * Phaser * -* v0.9.3 - April 24th 2013 +* v0.9.4 - April 24th 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -15,5 +15,5 @@ */ var Phaser; (function (Phaser) { - Phaser.VERSION = 'Phaser version 0.9.3'; + Phaser.VERSION = 'Phaser version 0.9.4'; })(Phaser || (Phaser = {})); diff --git a/Phaser/system/Camera.ts b/Phaser/system/Camera.ts index e05e3e71..d43a3ac4 100644 --- a/Phaser/system/Camera.ts +++ b/Phaser/system/Camera.ts @@ -269,23 +269,21 @@ module Phaser { /** * Specify the boundaries of the world or where the camera is allowed to move. * - * @param X The smallest X value of your world (usually 0). - * @param Y The smallest Y value of your world (usually 0). - * @param Width The largest X value of your world (usually the world width). - * @param Height The largest Y value of your world (usually the world height). - * @param UpdateWorld Whether the global quad-tree's dimensions should be updated to match (default: false). + * @param x The smallest X value of your world (usually 0). + * @param y The smallest Y value of your world (usually 0). + * @param width The largest X value of your world (usually the world width). + * @param height The largest Y value of your world (usually the world height). */ - public setBounds(X: number = 0, Y: number = 0, Width: number = 0, Height: number = 0, UpdateWorld: bool = false) { + public setBounds(x: number = 0, y: number = 0, width: number = 0, height: number = 0) { if (this.bounds == null) { this.bounds = new Rectangle(); } - this.bounds.setTo(X, Y, Width, Height); - - //if(UpdateWorld) - // G.worldBounds.copyFrom(bounds); + this.bounds.setTo(x, y, width, height); + this.worldView.setTo(x, y, width, height); + this.scroll.setTo(0, 0); this.update(); } @@ -345,7 +343,7 @@ module Phaser { if (this.scroll.x > this.bounds.right - this.width) { - this.scroll.x = this.bounds.right - this.width; + this.scroll.x = (this.bounds.right - this.width) + 1; } if (this.scroll.y < this.bounds.top) @@ -355,7 +353,7 @@ module Phaser { if (this.scroll.y > this.bounds.bottom - this.height) { - this.scroll.y = this.bounds.bottom - this.height; + this.scroll.y = (this.bounds.bottom - this.height) + 1; } } diff --git a/Phaser/system/QuadTree.ts b/Phaser/system/QuadTree.ts index e157fb8c..307a1101 100644 --- a/Phaser/system/QuadTree.ts +++ b/Phaser/system/QuadTree.ts @@ -632,7 +632,7 @@ module Phaser { */ public execute(): bool { - //console.log('quadtree execute'); + console.log('quadtree execute'); var overlapProcessed: bool = false; var iterator: LinkedList; @@ -704,8 +704,6 @@ module Phaser { */ private overlapNode(): bool { - //console.log('overlapNode'); - //Walk the list and check for overlaps var overlapProcessed: bool = false; var checkObject; diff --git a/Phaser/system/Tile.ts b/Phaser/system/Tile.ts index 5505a6db..ec0c7f87 100644 --- a/Phaser/system/Tile.ts +++ b/Phaser/system/Tile.ts @@ -3,55 +3,35 @@ /** * Phaser - Tile * -* A simple helper object for Tilemap that helps expand collision opportunities and control. +* A Tile is a single representation of a tile within a Tilemap */ module Phaser { - export class Tile extends GameObject { + export class Tile { - /** - * Instantiate this new tile object. This is usually called from Tilemap.loadMap(). - * - * @param Tilemap A reference to the tilemap object creating the tile. - * @param Index The actual core map data index for this tile type. - * @param Width The width of the tile. - * @param Height The height of the tile. - * @param Visible Whether the tile is visible or not. - * @param AllowCollisions The collision flags for the object. By default this value is ANY or NONE depending on the parameters sent to loadMap(). - */ - constructor(game: Game, Tilemap: Tilemap, Index: number, Width: number, Height: number, Visible: bool, AllowCollisions: number) { + constructor(game: Game, tilemap: Tilemap, index: number, width: number, height: number) { - super(game, 0, 0, Width, Height); + this._game = game; + this.tilemap = tilemap; + this.index = index; - this.immovable = true; - this.moves = false; - this.callback = null; - this.filter = null; - - this.tilemap = Tilemap; - this.index = Index; - this.visible = Visible; - this.allowCollisions = AllowCollisions; - - this.mapIndex = 0; + this.width = width; + this.height = height; + this.allowCollisions = Collision.NONE; } - /** - * This function is called whenever an object hits a tile of this type. - * This function should take the form myFunction(Tile:Tile,Object:Object). - * Defaults to null, set through Tilemap.setTileProperties(). - */ - public callback; + private _game: Game; - /** - * Each tile can store its own filter class for their callback functions. - * That is, the callback will only be triggered if an object with a class - * type matching the filter touched it. - * Defaults to null, set through Tilemap.setTileProperties(). - */ - public filter; + // You can give this Tile a friendly name to help with debugging. Never used internally. + public name: string; + + public width: number; + + public height: number; + + public allowCollisions: number; /** * A reference to the tilemap this tile object belongs to. @@ -65,24 +45,26 @@ module Phaser { */ public index: number; - /** - * The current map index of this tile object at this moment. - * You can think of tile objects as moving around the tilemap helping with collisions. - * This value is only reliable and useful if used from the callback function. - */ - public mapIndex: number; - /** * Clean up memory. */ public destroy() { - super.destroy(); - this.callback = null; this.tilemap = null; } + /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + public toString(): string { + + return "[{Tiled (index=" + this.index + " collisions=" + this.allowCollisions + " width=" + this.width + " height=" + this.height + ")}]"; + + } + } } \ No newline at end of file diff --git a/Phaser/system/TilemapLayer.ts b/Phaser/system/TilemapLayer.ts index 4309d028..4f0b3be4 100644 --- a/Phaser/system/TilemapLayer.ts +++ b/Phaser/system/TilemapLayer.ts @@ -10,9 +10,10 @@ module Phaser { export class TilemapLayer { - constructor(game: Game, key: string, mapFormat: number, name: string, tileWidth: number, tileHeight: number) { + constructor(game: Game, parent:Tilemap, key: string, mapFormat: number, name: string, tileWidth: number, tileHeight: number) { this._game = game; + this._parent = parent; this.name = name; this.mapFormat = mapFormat; @@ -24,11 +25,10 @@ module Phaser { this.mapData = []; this._texture = this._game.cache.getImage(key); - this.parseTileOffsets(); - } private _game: Game; + private _parent: Tilemap; private _texture; private _tileOffsets; private _startX: number = 0; @@ -45,6 +45,7 @@ module Phaser { public name: string; public alpha: number = 1; + public exists: bool = true; public visible: bool = true; //public scrollFactor: MicroPoint; public orientation: string; @@ -63,6 +64,119 @@ module Phaser { public widthInPixels: number = 0; public heightInPixels: number = 0; + public tileMargin: number = 0; + public tileSpacing: number = 0; + + public getTileFromWorldXY(x: number, y: number): number { + + x = this._game.math.snapToFloor(x, this.tileWidth) / this.tileWidth; + y = this._game.math.snapToFloor(y, this.tileHeight) / this.tileHeight; + + return this.getTileIndex(x, y); + + } + + public getTileOverlaps(object: GameObject) { + + //var result: bool = false; + //var x: number = object.x; + //var y: number = object.y; + + // What tiles do we need to check against? + var mapX:number = this._game.math.snapToFloor(object.bounds.x, this.tileWidth); + var mapY:number = this._game.math.snapToFloor(object.bounds.y, this.tileHeight); + var mapW:number = this._game.math.snapToCeil(object.bounds.width, this.tileWidth) + this.tileWidth; + var mapH:number = this._game.math.snapToCeil(object.bounds.height, this.tileHeight) + this.tileHeight; + + var tileX = mapX / this.tileWidth; + var tileY = mapY / this.tileHeight; + var tileW = mapW / this.tileWidth; + var tileH = mapH / this.tileHeight; + + if (tileX < 0) + { + tileX = 0; + } + + if (tileY < 0) + { + tileY = 0; + } + + if (tileW > this.widthInTiles) + { + tileW = this.widthInTiles; + } + + if (tileH > this.heightInTiles) + { + tileH = this.heightInTiles; + } + + // Loop through the tiles we've got and check overlaps accordingly + var tiles = this.getTileBlock(tileX, tileY, tileW, tileH); + + var result = []; + var tempBounds = new Quad(); + + for (var r = 0; r < tiles.length; r++) + { + if (tiles[r].tile.allowCollisions != Collision.NONE) + { + tempBounds.setTo(tiles[r].x * this.tileWidth, tiles[r].y * this.tileHeight, this.tileWidth, this.tileHeight); + + if (tempBounds.intersects(object.bounds)) + { + result.push(true); + } + else + { + result.push(false); + } + } + else + { + result.push(false); + } + } + + //return { x: mapX, y: mapY, w: mapW, h: mapH, collision: result }; + return { x: tileX, y: tileY, w: tileW, h: tileH, collision: result }; + + } + + //public checkTileOverlap(object:GameObject, + + public getTileBlock(x: number, y: number, width: number, height: number) { + + var output = []; + + for (var ty = y; ty < y + height; ty++) + { + for (var tx = x; tx < x + width; tx++) + { + output.push({ x: tx, y: ty, tile: this._parent.tiles[this.mapData[ty][tx]] }); + } + } + + return output; + + } + + public getTileIndex(x: number, y: number): number { + + if (y >= 0 && y < this.mapData.length) + { + if (x >= 0 && x < this.mapData[y].length) + { + return this.mapData[y][x]; + } + } + + return null; + + } + public addColumn(column) { var data = []; @@ -89,9 +203,11 @@ module Phaser { this.boundsInTiles.setTo(0, 0, this.widthInTiles, this.heightInTiles); + console.log('layer bounds', this.boundsInTiles); + } - private parseTileOffsets() { + public parseTileOffsets():number { this._tileOffsets = []; @@ -104,15 +220,17 @@ module Phaser { i = 1; } - for (var ty = 0; ty < this._texture.height; ty += this.tileHeight) + for (var ty = this.tileMargin; ty < this._texture.height; ty += (this.tileHeight + this.tileSpacing)) { - for (var tx = 0; tx < this._texture.width; tx += this.tileWidth) + for (var tx = this.tileMargin; tx < this._texture.width; tx += (this.tileWidth + this.tileSpacing)) { this._tileOffsets[i] = { x: tx, y: ty }; i++; } } + return this._tileOffsets.length; + } public renderDebugInfo(x: number, y: number, color?: string = 'rgb(255,255,255)') { @@ -151,6 +269,16 @@ module Phaser { this._startY = 0; } + if (this._maxX > this.widthInTiles) + { + this._maxX = this.widthInTiles; + } + + if (this._maxY > this.heightInTiles) + { + this._maxY = this.heightInTiles; + } + if (this._startX + this._maxX > this.widthInTiles) { this._startX = this.widthInTiles - this._maxX; diff --git a/Phaser/system/input/Input.ts b/Phaser/system/input/Input.ts index f27c4719..bde9380c 100644 --- a/Phaser/system/input/Input.ts +++ b/Phaser/system/input/Input.ts @@ -77,6 +77,7 @@ module Phaser { public renderDebugInfo(x: number, y: number, color?: string = 'rgb(255,255,255)') { + this._game.stage.context.font = '14px Courier'; this._game.stage.context.fillStyle = color; this._game.stage.context.fillText('Input', x, y); this._game.stage.context.fillText('Screen X: ' + this.x + ' Screen Y: ' + this.y, x, y + 14); diff --git a/Phaser/system/input/Keyboard.ts b/Phaser/system/input/Keyboard.ts index 332806f4..0c98191f 100644 --- a/Phaser/system/input/Keyboard.ts +++ b/Phaser/system/input/Keyboard.ts @@ -32,7 +32,7 @@ module Phaser { public addKeyCapture(keycode) { - if (typeof keycode == 'array') + if (typeof keycode === 'object') { for (var code in keycode) { diff --git a/README.md b/README.md index cac21f39..80fe561c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ Phaser ====== -Version 0.9.3 - -24th April 2013 +Version: 0.9.4 Released: XX April 2013 By Richard Davey, [Photon Storm](http://www.photonstorm.com) @@ -20,23 +18,13 @@ Try out the [Phaser Test Suite](http://gametest.mobi/phaser/) Latest Update ------------- -V0.9.3 +V0.9.4 + +* Fixed Tilemap bounds check if map was smaller than game dimensions +* Added Tilemap.getTile, getTileFromWorldXY, getTileFromInputXY +* Added Tilemap.setCollisionByIndex and setCollisionByRange +* Added GameObject.renderRotation boolean to control if the sprite will visually rotate or not (useful when angle needs to change but graphics don't) -* Added the new ScrollZone game object. Endlessly useful but especially for scrolling backdrops. Created 6 example tests. -* Added GameObject.hideFromCamera(cameraID) to stop an object rendering to specific cameras (also showToCamera and clearCameraList) -* Added GameObject.setBounds() to confine a game object to a specific area within the world (useful for stopping them going off the edges) -* Added GameObject.outOfBoundsAction, can be either OUT OF BOUNDS STOP which stops the object moving, or OUT OF BOUNDS KILL which kills it. -* Added GameObject.rotationOffset. Useful if your graphics need to rotate but weren't drawn facing zero degrees (to the right). -* Added shiftSinTable and shiftCosTable to the GameMath class to allow for quick iteration through the data tables. -* Added more robust frame checking into AnimationManager -* Re-built Tilemap handling from scratch to allow for proper layered maps (as exported from Tiled / Mappy) -* Tilemap no longer requires a buffer per Camera (in prep for WebGL support) -* Fixed issues with Group not adding reference to Game to newly created objects (thanks JesseFreeman) -* Fixed a potential race condition issue in Game.boot (thanks Hackmaniac) -* Fixed issue with showing frame zero of a texture atlas before the animation started playing (thanks JesseFreeman) -* Fixed a bug where Camera.visible = false would still render -* Removed the need for DynamicTextures to require a key property and updated test cases. -* You can now pass an array or a single value to Input.Keyboard.addKeyCapture(). Requirements ------------ @@ -171,6 +159,24 @@ Please add them to the [Issue Tracker][1] with as much info as possible. Change Log ---------- +V0.9.3 + +* Added the new ScrollZone game object. Endlessly useful but especially for scrolling backdrops. Created 6 example tests. +* Added GameObject.hideFromCamera(cameraID) to stop an object rendering to specific cameras (also showToCamera and clearCameraList) +* Added GameObject.setBounds() to confine a game object to a specific area within the world (useful for stopping them going off the edges) +* Added GameObject.outOfBoundsAction, can be either OUT OF BOUNDS STOP which stops the object moving, or OUT OF BOUNDS KILL which kills it. +* Added GameObject.rotationOffset. Useful if your graphics need to rotate but weren't drawn facing zero degrees (to the right). +* Added shiftSinTable and shiftCosTable to the GameMath class to allow for quick iteration through the data tables. +* Added more robust frame checking into AnimationManager +* Re-built Tilemap handling from scratch to allow for proper layered maps (as exported from Tiled / Mappy) +* Tilemap no longer requires a buffer per Camera (in prep for WebGL support) +* Fixed issues with Group not adding reference to Game to newly created objects (thanks JesseFreeman) +* Fixed a potential race condition issue in Game.boot (thanks Hackmaniac) +* Fixed issue with showing frame zero of a texture atlas before the animation started playing (thanks JesseFreeman) +* Fixed a bug where Camera.visible = false would still render +* Removed the need for DynamicTextures to require a key property and updated test cases. +* You can now pass an array or a single value to Input.Keyboard.addKeyCapture(). + V0.9.2 * Fixed issue with create not being called if there was an empty init method. diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index fdceaa9e..700bd89b 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -133,6 +133,14 @@ flipped.ts + + + + collision.ts + + + get tile.ts + small map.ts diff --git a/Tests/assets/maps/desert.json b/Tests/assets/maps/desert.json new file mode 100644 index 00000000..f13fc5e6 --- /dev/null +++ b/Tests/assets/maps/desert.json @@ -0,0 +1,39 @@ +{ "height":40, + "layers":[ + { + "data":[30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 14, 15, 16, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 46, 30, 30, 30, 30, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 46, 14, 15, 16, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 14, 15, 16, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 22, 23, 24, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 32, 30, 30, 30, 30, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 1, 3, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 30, 9, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 9, 11, 30, 30, 30, 30, 6, 7, 7, 7, 8, 30, 46, 30, 30, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 30, 33, 34, 36, 42, 37, 34, 34, 34, 34, 34, 34, 34, 35, 30, 9, 11, 30, 30, 30, 30, 14, 15, 15, 15, 16, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 35, 30, 33, 34, 34, 34, 34, 34, 34, 34, 35, 30, 9, 11, 30, 30, 30, 30, 14, 15, 15, 15, 12, 8, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 38, 30, 30, 33, 34, 35, 31, 33, 34, 34, 34, 34, 34, 34, 34, 35, 30, 9, 11, 30, 30, 30, 30, 22, 23, 5, 15, 15, 16, 30, 30, 30, 30, 30, 30, 30, 30, 48, 38, 30, 30, 30, 30, 33, 34, 44, 26, 45, 34, 34, 34, 34, 34, 34, 34, 35, 30, 9, 11, 30, 30, 30, 30, 30, 30, 14, 15, 15, 16, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 40, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 36, 42, 37, 34, 35, 30, 9, 11, 30, 30, 30, 30, 30, 31, 22, 23, 23, 24, 30, 30, 30, 40, 30, 30, 30, 30, 40, 38, 30, 30, 38, 30, 41, 42, 42, 42, 42, 37, 34, 34, 44, 26, 45, 34, 35, 30, 9, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 38, 30, 40, 30, 30, 30, 30, 30, 30, 30, 30, 41, 42, 42, 42, 42, 42, 42, 43, 30, 9, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 9, 11, 30, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 7, 7, 8, 1, 2, 2, 2, 2, 2, 3, 30, 30, 30, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 29, 11, 30, 30, 30, 30, 31, 30, 31, 30, 30, 30, 30, 30, 30, 15, 15, 16, 9, 10, 10, 10, 10, 10, 11, 30, 30, 30, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 30, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 23, 23, 24, 17, 18, 18, 18, 18, 18, 19, 30, 30, 30, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 32, 31, 30, 30, 30, 30, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 46, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 36, 42, 37, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 33, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 47, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 33, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 33, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 44, 26, 45, 35, 30, 30, 30, 30, 30, 30, 39, 30, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 47, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 30, 30, 30, 30, 30, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 30, 30, 30, 38, 30, 38, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 38, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 38, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 6, 7, 7, 7, 7, 8, 30, 30, 30, 30, 30, 30, 30, 40, 30, 40, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 26, 26, 26, 26, 26, 26, 26, 45, 34, 34, 34, 34, 44, 27, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 30, 40, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], + "height":40, + "name":"Ground", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":40, + "x":0, + "y":0 + }], + "orientation":"orthogonal", + "properties": + { + + }, + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "image":"C:\/Program Files (x86)\/Tiled\/examples\/tmw_desert_spacing.png", + "imageheight":199, + "imagewidth":265, + "margin":1, + "name":"Desert", + "properties": + { + + }, + "spacing":1, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "version":1, + "width":40 +} \ No newline at end of file diff --git a/Tests/assets/maps/desert2.json b/Tests/assets/maps/desert2.json new file mode 100644 index 00000000..55664747 --- /dev/null +++ b/Tests/assets/maps/desert2.json @@ -0,0 +1,54 @@ +{ "height":40, + "layers":[ + { + "data":[30, 49, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 14, 15, 16, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 46, 14, 15, 16, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 14, 15, 16, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 22, 23, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 1, 2, 3, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 30, 33, 34, 36, 42, 37, 34, 34, 34, 34, 34, 34, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 35, 30, 33, 34, 34, 34, 34, 34, 34, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 38, 30, 30, 33, 34, 35, 30, 33, 34, 34, 34, 34, 34, 34, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 48, 38, 30, 30, 30, 30, 33, 34, 44, 26, 45, 34, 34, 34, 34, 34, 34, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 40, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 36, 42, 37, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 40, 30, 30, 30, 30, 40, 38, 30, 30, 38, 30, 33, 34, 34, 34, 34, 34, 34, 34, 44, 26, 45, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 38, 30, 40, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 30, 30, 30, 30, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 9, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 7, 7, 8, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 29, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 15, 15, 16, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 23, 23, 24, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 31, 30, 30, 30, 30, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 39, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 46, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 36, 42, 37, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 33, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 47, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 33, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 39, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 33, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 44, 26, 45, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 47, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 48, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 32, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], + "height":40, + "name":"Ground", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":40, + "x":0, + "y":0 + }], + "orientation":"orthogonal", + "properties": + { + + }, + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "image":"C:\/Program Files (x86)\/Tiled\/examples\/tmw_desert_spacing.png", + "imageheight":199, + "imagewidth":265, + "margin":1, + "name":"Desert", + "properties": + { + + }, + "spacing":1, + "tileheight":32, + "tilewidth":32 + }, + { + "firstgid":49, + "image":"..\/..\/..\/..\/kiwi-lite\/Test Suite\/assets\/tiles\/catastrophi_tiles_16.png", + "imageheight":80, + "imagewidth":448, + "margin":0, + "name":"catastrophi_tiles_16", + "properties": + { + + }, + "spacing":0, + "tileheight":16, + "tilewidth":16 + }], + "tilewidth":32, + "version":1, + "width":40 +} \ No newline at end of file diff --git a/Tests/assets/maps/platform-test-1.json b/Tests/assets/maps/platform-test-1.json new file mode 100644 index 00000000..eeb0528a --- /dev/null +++ b/Tests/assets/maps/platform-test-1.json @@ -0,0 +1,39 @@ +{ "height":40, + "layers":[ + { + "data":[21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 108, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 105, 106, 106, 107, 108, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 45, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 45, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 45, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 16, 1, 1, 1, 1, 16, 1, 1, 1, 45, 48, 45, 45, 1, 1, 1, 1, 1, 1, 1, 14, 16, 1, 1, 1, 16, 1, 1, 1, 1, 16, 1, 1, 1, 45, 48, 45, 45, 1, 1, 1, 1, 1, 1, 1, 14, 16, 1, 21, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 3, 1, 1, 1, 2, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 3, 1, 1, 1, 2, 41, 41, 41, 41, 41, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 78, 78, 78, 78, 78, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 78, 78, 78, 78, 78, 79, 79, 79, 79, 79, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78], + "height":40, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":50, + "x":0, + "y":0 + }], + "orientation":"orthogonal", + "properties": + { + + }, + "tileheight":16, + "tilesets":[ + { + "firstgid":1, + "image":"..\/..\/..\/..\/kiwi-lite\/Test Suite\/assets\/tiles\/platformer_tiles.png", + "imageheight":96, + "imagewidth":304, + "margin":0, + "name":"platformer_tiles", + "properties": + { + + }, + "spacing":0, + "tileheight":16, + "tilewidth":16 + }], + "tilewidth":16, + "version":1, + "width":50 +} \ No newline at end of file diff --git a/Tests/assets/tiles/tmw_desert_spacing.png b/Tests/assets/tiles/tmw_desert_spacing.png new file mode 100644 index 0000000000000000000000000000000000000000..4e9995c0d95bc53d3dcc4bca2e7ee0b9aa531550 GIT binary patch literal 37830 zcma&N1yoh-*Y3UP+H`FcX$jfDrn?)F20=hNq+38ha?{=2DM(3ocL@qehopqk8-cTU zp7;Nq^PcgZF}|T9EQf2~YpuEFJ?HPbX1JP)JTCSVY!C>9tEeEO0Rkam0pAZ{q61&C z?@CC3zmQxtSNopo}aCa1g^y z@s%vb0wxuy0B+CtJ#b#oGdEdXH)%(EdkY6QkhF`1iJOHv&HHz5)-+ny7BupTuhdS= zpF4m+G$2J8NiEMGzdAm+QGQG2__F8<-TemK!v8d5pcmWkn%86#=Cp>k6dyv+d zNCN3;b(%$z}cDVnRvj5e39k%-d)sIY{L8-*(56s(B8)sBgeAq66?G1IS$ z%dM;W{^{FP#pjBXSWWkX8SN^E2J=Yw+sfkSN=YU`4WV%Xa8Juu4P3No5Z23wrI#8{Su{U`WdhJ;8UQ=2DsNde*ppE@Su zuC&qBo`b+5srD>p)3CqSj(mlsZDW6qI(#vvXMF_+Y7TFR7wYnpP4|vI#QHUsw!OUd zNkx-(Tto6XWF3P-yl?UHcJDutF~IA@pp@V!KQF|bJ|7sF!D%?h3rCF3K)HzJOyK@}j0fMimAe$g_M75%ZVO<_6ISy5q%d`X}^E z?{nhpaiJF$ZxQbu3H$5M`yzL!c7+3{6bk4$-D^2fI=}4rB#NXfMA)>_fh}-}Ox1ew zLd0#V*c|!&dlPF0c+N4q-HCWu&h5PM&@s~a{kb7md9EK$(<)?SQPjW=Y~ia5f$Y%{ zR@(RzRG}7tK|KQ!BdFN@?=hbG3WPUA1OsM#Uh6j(Cykjkmh**&bq*=#&SBkINI|)c;&rv zc^^auM(~oLfQH|lyS?A|{*?Z_)#&)?8#Tx%qQ(g}EsMMriZrQuKdJ0*;>BDi5GcaVJ1~WF+wLMhy&L$11&^y2 z%yq%`Ywbk9?TTLQL|Jfojq$@)5OMzMmHhAS*Fp_gx9r_F(VYU~-no8De~RvZcG4oc zZa$DB{~Ka*u@|r7)-(b;8A>*b>fK1P2;fFJsK~SkKON4BGF8Och(JlFVrh@0ys8V?wB|<`G+ec#yQaIZ&c`_B^+R z-n<6{q7nn!tl=hOW(NCixjDo*md$&Hs?pISm5f3s50lT1<^P&GJVX)n*)P6 zxs4T=v+d^f%o2z3Xa|{ifk?MyJNxMRDmMdYg5x4`G04jTNMZv6kb{L_B0hNb?r#x# zmulh+oYJko5pgl9f#iXw2v)U0eKi)+tm;ru$Jz%-=7|9|XrcB4DAU0a#DV&|e=3%Z`QJE# z*tprWQK*qJyO05|KifBvey!yi?+gi**lZ~}s!&KQ9R+ot6Az987OK=Yf{F}UOr%P? z>QI7;5^)d#<~$w2XS5TXO#>lguna{In)Fm*to2d{6qHw-F?4B#XM8x%kq|~6aB)?UuV-@W&&wFYBF?me2}*2 zX|p8-nwEA*MVWCsk^~v;h~98HOV}wbC3YNBP!d|*JO2#@E9~FpffpKuitP0Cc{fh3 znWeT*M{4|SAOt4&_(qTPx)E* z7&kel9T7YxxB7jXh#tGpz5U}ChkeL>-AEFqE~wDmf%JD|zzZbONz)RB<4^sO-0mkA z4XM0P4w8U4DlHh3cGE+hq3-#g8uYN=eG(H&Tba56pr$Sd8w)T3)tg+O@5@&v_H5%U z|7X6pwMWO=1iavF?p!2YbRZ)s9)V74AQYa|(eb3uV}rzb0|$TR_72ZnO*jA5{Jb^o z`tU>|8TV@M&KAaXDwclTgw=9&v+Xz5Yy4IM2(lWO6R5ie8mz`kN9z**f)NQGA|D^d zK?-CCI5L@)}}0th43|NT~MGA9t#&GjMd1jcA0 zZn16iCQBWunqaqpX&8^34oNg^G9?PAe~@VgqL&}tXIf@Tgd6{I*j+X%h!}o!hcOZ_ zyF{0Xn}P_uKB7*(Fp)F?NnlcB#50IK3#n9ytAAHywi_R#DnXiF=c zi(^&OTkaDn!ctDI^LfFZb^Py0{+ulh&H|!*0rk?ME`uA()3ic(A*Kt6iD!QsFZ8l-gt<<{ke9#qw5ObbZ?b|jsGKg*>(twolD+Ij{qx&`x zQ)>=XVVT5Y1_DJ7`uLkpn^87iQTOk(3C#epdoF^&^!R%gpTK#oNiQ4Q*Rmx&DBwg| z-#b0KB1_(-UfZ~2a&GiD)A|#au!Z|W8@_iNQk@H`eGS3W?QNcq) zFOX;OT^95-7?Bi-3?&!d73nroWu?NpMj>D@{_1SV(H*t%7+lA0aXR8z8%E8=D(mmW zU35y$D6pRZD>(Yur%0;M!I*ClLB!h;NSUP0Q;fSX?eC!>p#doV#4}*JXfdaZsie2Y zjS&a75ywiLdQ!#8$X&iSxFliL!cf8 zQ7P=_(i#SxUe|w|sJ%uR>T#_cSegNflVr38TI;&F=QE^zcR3<2ts`MS8hy6Zgy2k#BObhCZI%4zEP5+I2 z`L5X26W`n#K45EUIk}xz<3Dct$nyZfj+do{$iZXsoC&+~IJaj*rC?4mOVOhqnJ@{R z@=-hxc1(^y>?MMxNSP?W=~*BWQcp3i0>iAVYGo%%BFJC1LAyRD%NK+3WoM@r*lb2o zTB2N0A)NsN2Xmwf;5Aebr{g2?nYr@9)`KF~DSYt_R&3_&Jx^vGcbmmV)=m<#gM*+c zP1?y(`C7g-kPO;lMnX|v>~E3eyT%~@?QIIXj@!i3C~6lb#5g^=Y&@s?J=8{D*h&;H zIy+T8b*pzkEkg_v8O-)VIsW;u9wBrW4ML^7BxHUi4xU+yx!(Bw4gY z%tIhQZa%y=bSY7FAVXt-u}Y45f@9OeBok2Gd~Tzqg}RRq8hf~$&wA4-wv6CzvaRl< z-R0h~wLROji#p&snYIYWmNv$caN&4O5mOY$eJde6E5mE=pqc3}^E|0}fLZTzJ( zu4`88m~hh}6Z1Vxjgi`R2zK&-9c*ui%aB@WKrKoPhG=|6NGu@YhCZ^u+=~XP zNrRo9p|YWV`y*SKY_l}o&STZP|7z`7^D7ls>emL|yq|&V652nz*mb*Ov*rJKc(F!G zDygi>1oag#;WV=C^eZ{x<&PaoWCh#PM)PPV*k9CL;2~?8q=fjLPcNpncVR!4m)TRe zZ%k?598|E?8S6Ss!p3!|v3nkqQqb@o=V2%7%hL{33 z`H3+5!0>vgbyQ`I-vg8rh%O;G_m*?b?5_G+r>PuRZcmWY;4LN?*x9`}NnYUGF>f$N zN^wGPisREt3BaJ!Wm}eW85jpb!j5PW6_=t|pE5CoTOI%kPc!Ze21xL!HWYfJ_w4* z|Bg?y5T$Gbex)sm10Q<0!PNGvLo=(YWjTxLAwt;-%vaOfgXDZ*3&GGYd0f6hbymOQ zDorg6sBtER;Wu*_cp%TogKZGgw2B+OqK)Hvw#5I(K>CJnP5bHcCAv{O(KC$kdS zdc#q@A+7Scooq^tyIJYR{&a5@TV2gDsb`(pLr%Lw=V5wNIyUXOzCzy-ec(yH?@D8B(f3-O8wQ z_KDT{?ENd$QnAK9;Ii7LRKoOFj%%$c&>@0=v3cvK0}m$RkXECh(xv#Ir85#D{>m$wG{C zyco!){k5W?KItk0xhcBVF`4-1HQ|XMO&r6Cm0 zR2m&-S0HV6*%beBv*WsT1C#O|T-JEtW7zsU& z=zAuR-O9o{tbm8_uT#{)9+vw)#z1a$Rk`S?E8V(#LQeUXxlU(6dL+=KLn04XA#VAh zw^nrBR}wkyx4LS#F!iq{pXwXkJk{omCtL4$#d7+;KL2)pk&yxNwf7Ox)1pS~PUMa? zLHbQwfnSnlohM+r=w9g%mQ ztC6dn(puM<^ZY?39Sb*+JpL#}fhn)ZxJ1m`bdlj27x|svgY5nqyaijri<1-|yO*BJ z6LG0%_*sa_?%5D8Jq<;u473vOWp?qm2X(C#mNk*unbbIKTAc0D2PA*OS%Nw?SiC zi)x5DIFyKXk^j)B>UVaLpEu>wadR8llebnq>x=?o2{W}x-zMpIBa&(k#HOcsDyyST z`1zejl>Ky55^zn@$^&fqn?QMoC95Pf8mLX=8kkSwYn+Ta_!XLAlf|i=^;F8tJS17s zrsAGA7xGy1lVNN@Hh)Yn%rl`Cs<~JEnk`P->pO&_>;DPmPF{ zY)G2o#_77VP0JNR8guT86sDHMI)%JSz+Jdw?y}0O-1QBoIF$v{+v#fFD-%BNgEccF znuvjQw=U57iMKzA4Ch@`e+NPD#S?cOvz37kOzkEb;@BrxuXsZMkn~NR40KvmT`dPx z9s1^+IU8kAPoo-hASI!k;1pO)+UmE1rUvNYdvM?;>=yyt`ae@M$)8R*zF_4dVX?UW zaKaism2-xVFxoeehG~diZ0eo6@oytGF2ae+ zU?d~5WOWQF!6xkIwAApUuF)OTszuxg93*MZy6K?b>n1F4pu8``4Q%y0IS5XoY%_-J zNKr%DKyW!b+BKE(MnR;wS!;c$E+n=3*F!_dHvth5o5h3IueCRU)e(+fBscxD%$%?D zZ@qA`zaUieefO#tnK~A6ZI9)CzeCTM8#5K|D2|@HyXW&51VN2pQkWNX1 zP5A17ZenU(To;>|0{B)KRt?#CL%r9-?t8$7$@V(koK`}Eq|E9Ph15Q{zZd(>-e=Na z@nulgt0bWChuUB6#AWk()0au~WOzm*D&_ zE9gJ>rjW!iCmCoQ4|tMBEfiE@1Fo>wL$JBvb*P?ExY-?>q*w?&6h-x#!V}fTDd=dt zEP!%P4nDl>RjUxw;&>YspHB}|7)qSiC=9=@!*i+YWmv6#^jGMXr)! zV}&6mnDf049zZ^2lS*O4a8rw!rK_9)r$qo8Em7UGdsE*8TrR5W#bBKUn24PRuoJnI zk?vok0_7`w(rOw)Mq4^aV1xK#GMq=I8G{}Gj_!9M>P$=B%;twn@4n`NQyB_UtWr6qZk1elGjlY!oT;@wWddfp4Z#Cq&y~Tah-W%~-@L$=%I@iZc^ zA{INmO{p>`A9-4{Umpg2*Zr6{%gLwoz}n^4ddx54NGMtxVtL@<)a^1r0+u#2m-PdK zo)hj5HUn(>ugltWmPwj3C3d%38T6d(?#JQM%&+aB~c^xwyFLojy654ait?-+P6zF2ePR3Rh_I{tKe~ukQM{) zFe`wr0VFK{@?Pq3VSoT(JZAXJy85TJc=t9$x(n>w;rEez^?jHw3$`G?}0%ugxOq~wtLEU{5G zt}MAr!hm|u3Ei2_wl)|qCr6^F{EeCQJ5-#`zJt#?A_T$Hl_eVb`q?3UdEzq@KB4og zUW;7uM^VE*GAyOC{r4Hqf{15c7`a9)0sJjOzDEK{>KTMeEVNOmFzLyMGU@~cGMUvw zX(2JJx0r7fpdI2~eSb(=ZV);$85Om66hUi_yF6veWPwdj%CEhg0x}XflM3)djQ8$V z`xbnd6up{(3XyMR74#N!6L`ZjUxR@*K;tI|D!^4}+K{(AYkq!Wtvxfs+X}#aSuO_h z_Y8oIH;BRJ7QT9L=eQWaK*SFnETkMC|tj zj(>Ju(SxC(EZx*9wP!Xv$ll&vu!t#p+q~1y)05-`zukdWX|ao+8eMos>A?q@(tYju z^^e~CR&tUAjTUZx1w|l1_vocJX1)yq1r*@KhP?6Sw3BAC809;`Tl|-<;xD)q#lC7x znB8)}DXWQuY9wkZ3=Y94Cnm9gYN#hB;3q?bXzwv$1`}%NrwFFS7A4~IZq#Rw-S&*y-X+Wy; zd}$Vv(Cys*%lodp(pwh=7cybQL!hIB?(qv8ZdosO*ti_EWv_9J(ewj<+g~y|xRiPR z)$gik*5CcdeKCz__0LSjVB?`;PN29nN~AF_XUh!PB2I9u>R8_pT6b(0PNNG&Tm=2I zDNq)5AOU(XfwOo^GOqM*MXO~yl9?G@Ny5BhhN^VJzVhW@?wI^dOOq(-#v}uJu{*cp zL$aT~|6*@kZKz6jj95a8!Z;Jo2ecABV`e>S9xy%13GvIezV{bmEo)q~{eL z|3Gp0PAF_%(B0NG+@$k700jiMpbf4d#EOs9;>3bMMIun=Dp660;!FLXyJPAO)}+~> zY$Ul2E@LYS6}r;8;RPj1YUU0*D4JPjei^hYobHkqpWHTYRoZPaI(+1i%&?zIx&})tfX&+-ukJ z2}QrlZzRhsjl*}i9PQxZ#(G?^P_|}dS^?sMUcS!~y)!|l_RYL}J=t+n;2HVyAE`7W zR~OCJ7RihIalMLh@Pt$h^zwd!ir~zYd-hJ_`y1CpMF?E>X1h76almglBElSsU2WV! z?y6%BN>P^a(9`<cFm6d-GZ4Q2-DHG`?pKKK{)qYc)rZ2ONO4^z^q7Tp|^u-H$e-(nZ`f| z0n~jRqU%%IK98ScX8$b1fQ9d6ndVoD51B0cQTMB-r)Bi~8$iXbP8C9oQIJ%`yK@42 zs4iV>T4KmLzG}_L z$3mI@3(}Jg&e5n*)Pg|%dy6Jn0`ZxubV3;=Ezd(k<$vE%-^X=6jI!z!POw%}*0uAA z*mZ>NS%bw_8@P8@$LAMr-F{4v1oeFNK}BiFoyod2)Y1=G>->xU-EW`l1^AAI&T0gE z((n(iXH1uJTJWOQ=MSUj4Ea_|;e{=4iD8dGzrd{(3N*+PvL1n2pL3qWrOqFz>l5+Cn^-d$I|O0Ut46>A>=b?kE@cXXe0tlvjDaD9A8$0a)Mv-zAo?+{Nz zGS?8=H36ujExRI<;LwqrMcXS##zR*fihp1s(_~?X7$!?1K%4o^#KL{pyhk%sO-l@j zE>9h}G3eGVRK}HOvsprSd3S4j9EgH3_C3Kes)z0iVTf4V??_CTw1Q789L zhE3hx*Us!LyM~DKH@CT&&^p~!9ke_O4I84F*;2I*&sutT`I(ja(vSVHd;(wVU+qz? zKYjto`H_Fry3+#^_{a$Ng7+2E28H;x%6WRR3KCbE| z&eL70pj&~YlEgtelSu5@L$0>JzNbU-Tw4kzLY7Qe6G7Ql8*3uMVChXhTn zf9V>Mpc_&Hk`YZZ<=7|u5Yy=!l%~}$lpVe&8mT$=+jlFOvw#(aI}IlrYEFsW0|21& z$IZ-NJ!Lw$r=6AA5;zEZsz*|RpVeuv*^e4SB_3MbAQEg$T4g1n^MsChjJ!55g|w_j zMQzcV7BsuowpBje)O$2w@+OYbDyKk-AH-x!OX%K2Cxg5XB@PU||hUlsDz>PY6 zlGgd7gZy@meG#hvuKO6#zQ%>;0EirDz6Y~qe(OCg$7VpqsY$1AKX;_kg4i$d! zPgba>zz_zSUXw}3a%!COc0$CA|9GoJ&z2f9`X{KXg?fZaf(2t{-e=O*Z`^tUuE9t& znT&Epg-iurOwqH!6HS>;V;Cl@g3Y9R=`7>`xx(HbtD5O&`=11XR1(_;y@Vz|GhWY_ zdNHpr8Sy)8pBB?DjK@eGnf_sNVi*sBzG4-%L=8py93-P6(2J9RzG%cfC0fJxpQ{;W zVxCNCJ)kVM{iPdi=Z)>o_R`rJMjph>VqNiPI@~0jf7;gEyOO;_!@Vxf+4FC1?DhbR zRnNyB4QR>M5w>I-{El%v*g%^=^n_=~bnNwOGG0KlPg5i--eZvW`P|5c^{5<}ssEqK zLF|7i2LdavnqLX{Z-+Hkp1Ja*D~rVEZ_Mxb5Lq4v3KwDwkPd& z8?dzMZ%oZzb%!4hg(*h!ow|*Oj{3j4wHirsV-n`Nl0`7IaWIM~3|KwKv8L#BA?cMF z(l!q#i{Wik{@*GAl6PzB!rxUrW{u+*Hr7(wmjyWJ^r`*^wGevgkFr4X|0vFO@^aOS zZC|g}@A)D)w9={n{=lwCTe`(J9UIhEP>m)4zhTv5dInnqKueTlqMScE-f^NKXr~(w!tT%T{af{vjg5B_);gWGRZ@vFp~*ygS7wj zxJ}qHM#(@ICAC-hedRSUPHS*H8ACUsn=+^W*-XLWDt>gXF}kQHIlX zHZg^fT1pF_^+__PfG5B99&nOFj&%~buwXuD_pft#5z|;G0<7q+Q2(5RjviMso0m_E zlmkf+zDG0z#`74@P-aH3J#{%6#c$xO==EQpYB#BxeRk6ric=-yHcdm(0}a<2jbotu z{!W_^*q&81ix+6bF!n4<9s`e6?r^D_W7%yobk9k}a~r38L=%`3-=gS<1D@s-JQJyI z+K0%asYKAb9s*YJl~o;3TO!K2X{Igdu`H=f&-wMLo{<}h&yomg`*TPj?!y?0RTfI5 z*_7e;Jx2%~)*zQ7L+mB*xP}a~!)>j=GhR=y16;cF0jQq# zFH}^$>tD`|1m&w&)Dq#iHKwdyC0g zds<^=Sth;b0ku4lB^d^KKFfpoz|-CWE`tk1kSl=0NjfCIOXT*F$-qM#k`h``M!?NN zNhPAfb_MX^fD9eX0BlSV>Gwn-f1WQ(^=I4IZx?E|eVeZ#|5keyAIoZT<=^Z(dE|Fz zh#wcoT#_5#OfJ>@wvf~Ib44?G{sEc!raFD?rxF4n83~OdgE}uRz5#Eq{fo#)laK0U z*5Kp{M(x=mia9G{kN%6mIIwtQ-5drEPVLBpM{}ox^-!auA9}hc4-?mKQ=r^t@-iY| zvpt@Q9~iQy1jLVvP*~0e^znLJEI<8qIWsN1d1WCBrz-}3bXVa?*GP(lt!>YdV{_;B zwkM{&3vkN#iy-V>md_t}zicZ1htGbQWD!Ono5Hir@WkHnvoxO|y0&^ZM&O)++rtsA z7TH)Exh=NYripa}D^M)+_#7}kh)8_>a^?13eXUh+qauB}5D+2mH4AgLrFvmZLi;q4 zu}cCZkw~CNvEol?@gak}^)l{fp(!jAC(HLqEVsu-v3LcAA=hbk6nt<|D_#e&T38y`3c8I?T zyPHOe%xgU#gTZ`VaBEfZ^y527@hCQ!@`ZYTRaElNl3OyRH+VBu?eu56rJV``l+p@8 z$b4Z+sND^$Ib$9dzdW2sTz;EbNC5WuaTSW8`qSXVt!sY84Hxh{G$Y1n0Gd$tHc%oD zS&cMIs(wSKz-$zxNv)UWXjwc1urR#=mUydKAQTQ>ePJUjJA5Vn@zU*m{_Z_5gJ5`t zS}`g)jOA&M@npgm0@!$n$`6a*&Xl31oFrU$0|!M7t3EBs`Yp+szISM#i*Aq9CJN?L z8e-~@ieY02o|JZE<3X96z%Ox;=%Tbtx0310TINYJ+sl7C5Pf};%8om^BhuJL3u0cr zB_4AYlrEieEGoH3eq&IwU8-oR{mnvjpN|O;Y8MM=ZI769UEfI0G9E2o$S9dUlKNp{ zCo3D*%hX7P^HE-SR3!m3_A2)^eu|bmIUZ2aR_4?%?lXuk6a!x9{GTrpp{NQG$QR>& zc?HpNy1J5)o%MlfQ$^e!X8GyN798z~vGnTJU(=-!!>Mj^@7i!jXL3<|2(lC0!ER=1;Z=FlwCkGo-rat#A!7?wehYd+fwBBru3fHP@M`B36h8K71HL?cKXW^b z|7FVWAkYku3f2vM9TkNX|Kr(~DtjWm%WVygjMFs3Q{l8)gF@UBOmpFW)&qiPKFMb3wRJ;n11XhRSbmbsah}GM#IzW~~71)!0U{>A>?dX1eLzw%V~t#q&w6B5dJIahx13x3oMi zjEA;Y^e@cE8T)_3{C#W5U1g@aIc25ooz=MqKC32|mqMDuwl|G4Kr0aGS^Qr#pU2LF zc2Dfrze@`lcIA5v5#?f{&vnyhyn9Efw{Xf1ERP*}E(X>)K$ajpxZtlY;^k{x{uXfg zCgw6RBW5$8+O6QKoL`aAe$w%rcM5|2EL6(e-Jd(z48#@xN-y4_+gFzK%~EXbw&QmW`2z9NIF z&Hsn->CTlpVsZHJ&@MTjK8EnW|FdVSEL-6f%l`fD#GPr#yxK(b*lK>`CONXL5x=U`s5>>eVXP94F@l-fO zMg)(ON|sBEZ;NbM#k-E6SQ*O*9xDGXfr7sv>Bdobaa8$>3!?qL>E7J6SIwIG^A+oJ z0!(e`G8;GzYE50os)jK-2;lXoy$wLyo%Ip}iX&5g<(xm%gDcJQ7(0|PmVl#7aD*=M zac*K|LD2%qe>9K^MFi?iHyP2>|>S=cCIZLAyC9vN;zc0C$x{0SAjtlI(ep}O!&iHfxP&AYJ(lq?}%Y%KX{58k-uOYUM zMz&ZT6BZ??SrkDa5yPcX5vIS*fil6~ zEkN={pv43InhnQS^0Jsq;1qkazj@i!_+lX`MKZY>pBJ*?;UE8Ne`Pj*Cz zq6HV?(_?JdI^3HhtCSnOc278Ro2RN$15DuztsK@It&Xwb$N*DJ=YQALL5{lwh36$! z8DEbGT|;`_Q7X+S;(`8>Hq0p`N1m9~rS0l4de1i8FV2Hk0Nm7ipZB9}n9GYs@m(w}hGyme~{-nXW4G@P8vK60j^Wh0z#~ zBp?C}%2j+9^l}H@8re~Y%u!H038($KY|d3jLH09oi|Ys3EiLPdgj(iyse5I^OYB3C zCGT5TQM`e3d&XC!S@mB~v$LY1d}}QvZ)vSfgf9W0u4EaZ;wcROQT$5bRW=$35Cn9i z!f!Ys!eRQ3QNYeBZBFalWBmDBzEx`mR-eHh1JBiz%COkU;%SXTCasC@ znf^d0_Rbw58r6W>-c>@S{mmUs70UFpky`IpZj-9g2kQB^zCmVBL|qwAeAa-~bMwC8 zo08NO+KNURF-#-XBwLlF9dZ2={hBi}YgQLPdLis-+-#-}S?Mj)BT^O{s?q)%uXl$x z^FOeM9Be$})G{A>9L_+8JLOq}Ep;pa5M-Dh5b;DFEEHhF&Ph^`2nG$ikkvr}V?Czc zb5aal>K~~*bOc`o*4}tL#qa9_ddNMD6ppg<+nUPa#1eN`rIVc$Vhu;!iI>g;#jj^= zt2+?rLq{F@)Q?BTenbmGvKX5(HI_KjZeb4|eR;z~0Q5{?XvGft13JG-t(yOuA$&r& z%%~xKrZM)AnCR^NaIrnkE0ca==R4ar4AAQy6RlxW{y}y;4;nUvi$CvD8{Ja;7K# z*4Ng`r}-C)FV(+Zq_^}C>>mW?CEJG5sUNpxJ!choKf=E*c+X`*WT_dWt& zCh~z820ZMnEsw&w6?Cbyhu0n;HK+4_uP^f$v=#}N!H1_S)oV)r#fvcNMLHvME`mfsjY_2k|j|VednnVOCQ3G1v|AL$!T1J~p{WTl*nRXn2 zA#%_FDc~C8=0`FTKA=&g=*9M3-Tx+k9^1EecYe|SH&V*wO;SbT*iwa3iY!HtZ+wkx z$UZue4mUQZ{bAh715D;4_m9I7O{rW6Fy{r({ybzyJckJb|MIvjvVSHJ1zHW&wOa?KS?_Av zQ?;b!&;b{x+9x4Gm~87lg}Y!3ijf|E?9f&-0yq1KbWscXFk??9*bQLVC=iav`H|l; z=$~FUG(Wk+#+t%2*micZRgpAt5M=vjrVxhkko_Nx>cRdx_CLUZye=YK&|R>UW}1TL z^A<%-g$g7s;IadH_RG}mR3alq@j{x#tPDihs@sLw(c8D^;u z$v=Zc!_M@Xd9;OYk!E#HJG#0^=Cl2tF6S~N@$k3{-xyiVO;GqVrhTWBu-c~AlnFzWx!6lL&*kan3&ia~W6KN`o%{X;JT)fKeTCmS$8 zlZW#Q`rzw4&~t7yrytl>6?eLW5&{I(?Jmrr{Kk$AxacTzViXo3Eqhz*!4u<1>HGKp zJTQnLd+ZSsgv#b8mrKdaGBePYL3cw`BND8I1ru$z*O3VZcY^w{LFz6Xf~cbk=+dvSot)-r~W+@Eug0^b>r>{ z6V+R0E|y>U%zCg&a?CGCSet%?F-J%zI7F9ZTR~v_#R5;=g90RwcJc;}R)T8`3q@p- zBEiG)?+1T^N?<@SpeM&j^2&4J&}L5rYnQ~k76|GAPk|pl4*qxxjtY7G=5(@*Y|f(i zCZ^EH_4ebi=__ppG5}$L5EURfq)!C2hXd*tDT*qR%1pGutjuBNb|iYTvdtxeRu-&g zu)3FroKJVKNE%uc)^3cuY^Rt137v64&ha*MrY(zqrW(;fo5WUt2MU-4>RlN9zpz%- z&i{e6P*%x^yXe^@DAJPr*Z0!Bj2>VmPYUN;C!|vYY`b5Ov>)H*!g|dUrvT!5plDX8-F#_B_NCnDjud*$HVrF0l>E{Yw`cMBJE_1rZX~ z9DAOBCJBUR4GeNIF!s+he3l0=oJzHD{D%((jmmtpP>qxV+$ULPGo$nD+w@H`J*|zH15uSVrEL z6?kQul+J~&zn5m|296G$ps1y;GDQwV`#)pvl??QEj>5mo8ri`gn~@@IK0(F3e;|Ma zr?QYxAQ5itk&qSwv$XjqCGXMEhQ480G~M$-FE&nQ12RjQ%sB=2EaS~JLDm!Be`Rw!UsZO;ixM4%|4OP3Bo^Tr^RidHG?<&A&& zh^v5+vWZs>=R*f8&COeJ_?b6vfqy9(@XQk7`{#qpHn?<)_0$D{S@ep0hN&RJC$B!? z=%tg8LPWUXKPQ5tWpP=&g}C9x{H3LP--+%1{0CBWG)DErhwLTsE-f#IX+ZZD&})%J$)n{-X;Ta*1XflWPhB}=lA_al0KH2* zBP%x&{3jddUU@m8a%^^RT0~*Tcc+M=l0t=IBDr^*laAmVwxg6;&|bLU7=+=qRI=$_az@Mw>feM+ja zCR8KUayoff2y(}N(4T=swN-Ql{!NMcW~4lzXtmEQKnv~?!9oS=$p2^P{QO5yL=y6# z7%Ev6dJ)M=9c-*iN)eb zOEvNx`0e*_fiu53_$hOTr0-(w4!43lgkXoHrk$MkoAzm))6!`ED;UsTd4QOc#&?igNcwF765qlezkF5IBo-ud6u^SM31*rtmL z?7D+0SdTCPJNO#=dXWb59R+SeV>?qoN}8;oVtJa*Di+GWir~#S%;<^D=`IWHBY*JV zzr4TFx9XQj!N$k=vH~u{CK1Hp2AQ+_6I4_NRb1wj|qu~>Owbk@#nmf zfzZyQ=3n+8*tvC~i}E!%H#zCL!(MT`BXoAae({lGlIseB(_gCzx@YyW*TZozXv6|-nt*uZ!=YnS!G|+Bs9_^=z7sA<*}5*?%QvY(CKNn# zJnM76k)TzMvP#PIgt;=such(^V;k1)E}|d;-tgk$UR|eE8i=DmtbQB%wf2l)Ls~$l z^03nhCGJ&Fby>)~QGjYNSm=LI^^M_mL|xk__6Zvsjcv2BZL6`HhK+4BX5*xBlQgy( z+ji2}`liqG>HT?Tu5-=owb#Cv?zN_r0tq=!$8~jF*aCULycXv$A3>KbFFh0JLei(E zIIn0f-P!b5t)ZSB&h)elIO$!-xhH?@x6E4cC+_lxanLtoK->4&!8ac4+`eo_7ijh% z`K?(M%)=VEi<4P<-|wsKeem5fWNX>JwdO|^_6JdLtv2FT4as-I4y^OeDiMu(=_2Xe zF*!aKw;OTKS0M(SI>H=GsQ6hAc?Egn1kzr$?#+Jj8-Z{9A^Jec+2ei!#KnsNj^jaMq@N3THd8t4J zlGSPF3cjL~k7@wL2nt$Xj7{gE*-+4lnG9b6$K0#B9k+m>Hsiu zPMWbcZ>fvn(cj={p{Eup@#Qz_C&u~y?>o*9Ji|1nyQ*(lPf8EjT)MJ9`(NFofYp@ASAdAW&MArw?!61Oey z2Au^5U!xNq9Sy*cYl+1(y-nG_j((VfTZqVDJHDTSY0#!)8dQlN<+6T-7K1zDTZ9q-#s^Y z3t*QEz3hK~uGx8iWPAHO!QXtlC8+IukQ5#(bNJkAe>lLb?A=ShI4ukY@ig4vYR0w& z>c)TC9Dm2|3IY-P+M|_&|?8HPwOaeg6V> zzqKFwvF&+Y6?aiqA#FS#DTfnukeN4v7^+ATMn{j!Unr(g?|E`-q?-Ec2a7RjZ?7c; z$lGW&3}{6e2qY`PE?qb%WIc#Z9h02bWE#TzQCxEPcsQrEK3b7w+v$dsC%yP=`cv=l zxBG(WdFOf2dux>`PTiTWGf9LujSJ98tPs0$3MfDMn8HVjOcNbpUZz6(T9Uc~a=fS> zdLGP@o~^w8E8!g`Q!r~Xz^7s~zJn3#JRtNNf{YKr>;3Ipy_Hd>$WW2pN0i$Met%nb zbarclVt(nAnI$I2UGU4C-4M8#h3DhrV@V&{%#iE&_P5pu)Jbiy?!RO6GLwTWR>IbN zcHcReK)-+$Eqdt$kI3p|hjz=z$n(Vp7m$vz0G;9UYZ&O;gr6S_tT29G3#QI;fA0H( zoZR>HMH+z)8;nsCj~2Ez7^wf`U{Z$VXBgA7K3q<Y3T_3gUDb0xsVb$g2)kNv7c6Y2OjYHzB^1hH8R z4CG?Et_{umlPL&Tjl2(nUfJvLDaN8)1Rwx*_9am-h)FY3;I_uS&^>@gGxmTQQiCx_X~be%bx}mPe^i~sM_X& zIUkvYn+Xz>w*V-DDc=qVyCprW5Hk?`IzNaZ^IHVem0rk7sm5AEjBPBS?S7#|-Pql3DByPvqMH+9@;k6ZmY%Maso?XWU7Mhgmp z(r$7fGW0s4dh+Vp+~D@erdVj_6w8U162WsGzdZm;+^jjjaQS$1&z8K`8Y5>x z)3hPdGKyY2qQO70WJ_NdWO4f^{oy{kg-P*z1R+-ka9kX+cvwpKk)#wGd4`!;V#>`%NpAR%2rogAVf* zR(-}^I{fUV;B?^s_@+NM_)A$?!Od6cg>`pB2;3;g?O2^5$pM!PX#QOc2oN20Fy}&w zedKo|1Vre0RP-G_R+!|Arfdv5D$6{EEx!L|;}^qU=0#5zo3wc%nSC8buYTC0>9s+8 z=wq9QEB+U57E+?uy;L{l}<`y53=HrCuJ#C_XAh z>0`lGE5Yns8oOKxNKT-beWstf7d`_j^7jj5nAF5^sIi3oIPWttxPtuA`vd*bKaw8S zNbd3*^AXO@a{Iyt*Tw0GM{cdsSe$tMRQ}(jigqU(w9@EZ4a{fCFPRUDNzG~d^9Sm;4Ho^bz)R=5-XkE?!xe(;^&d1$(YpM-ol4BRR6S580V`wocyFG zZGShWfIa&$U|P%$+HR$G4MP?Say_0k9L|eBs-4)qun~VQ{zg03ol_Q2) zuL{Es;+W(TzkH-WNKdXUlVvVEu-;CQMFes_T=1xX;1kx<$Fl0ODbNBGV zKZEcuER285%Y|1*?}o$aW`81X{yoR54@nJGBnaUkkUg59t*)mr?Ri6#mX+kU`atlz zmW{-g!;20W`(WCT9LZlfyx-ujvT_%$eN(^f5b1v1$h8gZb5MNd&JD(4`H%<_~ z-w{0N5Iy^#AxiQQ6L0nM@#K&^pZk?&d+URAohnNx^Bm@ye=fzShUy<(4~)BF8a9E* zzz_67A-!Q6)ZT*mh!KByI$RCf_v#n?f6Sh_4l{-n`yb5 z=1Y=742PdP-Om+W?1b@SpeztAFD&OOm^P^^VXGcK7>7Vr#4bs`W-KD2p;7wdEvrD})_YRC_l#_tH%h9O4U5#1%`w{;`PyM_6YBb|ccr5SYq{j=`(B4wP=H8rJUp2d}0Y$jVuFKK?mO%f@ z3@409^JVJ09k1#!KYY{t>Tiq^4g!fEdzhv73*-<93!O#5Ga~kqOs+mC+f9 z-az93UDIj9pdk@}P4Wv2NoxklB8^2G)@VIQ9~6Z)K!SoFVH$~Dnl zB+Lat;(eJ)r2(Ikw|!m-XC%58pL5AIdoRRwSKX)J>v}v@ju+KcDg} zto!XmzQ3AP#_V|Tr%b1(U|BAA&T-~ZZL}q z=5^2Rr>g0ef7n#%+Lwiu-)dGw=rV6y3VaYe@NBw!#%G@EMW4j}@c9!=;xG{gB^hP< zA9Dw9fB)0=`O0>y^K`+M8*9{)ub$Fu-sdk@KO6m?+pF#^6k-Z=<_@1ssllOCLtJ?- zl2je66v%%rASXMZZ_s(LgG6SzX!!!6h{9BKX?l)^OCo5=-Aifm(dos{FKitgWXND# zh?ahw!p=Er7FeijJgRZo8W^LdNhCz({bxPJvUQ`s9c~tSEuKIAY{g^IJJ>1E^1-vQvNbV)wRRC`E`P3 zI1R(mO8rcE2}yqjnrt#84aU~ywv+2AIurC zahfpBjo%>8z59oXcn%f9byJ4O^G}1b>^0BzTJgQSWTQ+Mgf(@Pjns(47P8cEz$iro zyod|sx?wn5sQ%uvqBdUEGe|S>nA3MkyYy9S8Kcnji0)8^l$eUOi>ULK z!kEb7Bw1_V3T?1>137z-i7!T_QRs`n9`Uw`QaZBPIseMlgmn#iaKa=_k9hmxF<)g% zaRohRgAKN`anzj9+iY*>Lj1u|ZvMOA)AMbJ(-L8Pettr7;kKD>Qk}$JDf2dzqQrAP znR=?c#?M+798266_UlV_&=|$wW^Q`i#``1bat}%V6TwI*GyN1c#Q3{0^aIJ`;n`4c zkrQN!4VERedejg$RD$^JaEZxS^lI`;PTku*z}5BgpsDLT$lhIEygQsr-OHP__X#G} zE20DMSd4{|{^9Z}uG#MJ&EDi8@XUcS7S~xKlqmc*W*IaF3va`JGo#4=!N6cG-hvb# zMzbm-CCu#L@a4iyTb#UsgxQ7G_p?R_c2p0)wTzznJv9D;cl!YH241fj|X$YGbpoB$f3v#3dTB*w`IX_((*z9t0%g z`lZRKso45)T?o+Op*5Uw(3qvT-kN=iX{I}r>F@9(BWf}o(+a^Jr_vt=;wuC$rp0 z$~`*gZk58+VSUIE>dp62av_EHNPSqWjwqHmktvNM8qL^+=?~4(;^gN<6^j&)q^iq^ za>MQTy3Vc2(snsw`)~jFR`f?w|Fqo!?lXQ+e8>oh0nMB$$NSfUkmpmF+n5Nl2dCok4GiR zWiu2nuS1saovagKTx=}ZDGLP#1`!h*YnL1i-uU$i32RW*mj3E0(R^|qTg1qSnT}q@ zG7Y&abbcPxW{5*a?^{}am0v{lZLReSErXs8g6c>`BWE6iPc@QNu5WCC35;&HGZ#nM&S53ILrU71%%b33QsG;;(bs2$-;u_)T5Ou@#47x#Cf7x(@MH%0OAQJrv5QR znr9>Zh~QhB}@{hgx3I3kLd$aHEU zq@lYqyWG}oyI=QyxajTdI;aR-IDs6b5}Z&hA@XSbcEA3N`rzZoLpTh@6ClEFW3%6L zexKjMoA=nV#?*CiJu^WF=HDOBmJ@~pUDHZF#2jI6qK%+@E6Z=f^gPx zrl<@{fMXPJzzdwRU%x<}hL0UpjNgurc}vU6G`X#=4hRPgCuSywI)O_!ExT|3?)=OV z_lFyFrj^)pf9%NIC6eo>Ftw!j&MX*I}Oy!B_MN9FQXbN=CM6X70m zWR`$VwZ3sF#UuEYBkbF*OwA+4QwGMat}kQg<$5(q`mt8>ccmEtsoV+H_2+t>q+R^= zoWeBRS~BT~X4N#wR~+KM^#VBWXt<#APaN%&!I+BoYJro{9JtUP=+xv=vq&E_2L4{z zj33Ie5=VF@57^9l!Rt-#Vm5SZh1H+SX?AxZ{KQv%E&|px$Z(`K^v<2HLVqm z!G#CIGEVdhL&CmVdx`4A8$qioF=d);f=GELqpW$tfAvzRr#Le$!^h%~$*~nAu-rAe z(a2j|NM$Z>gD?;xFvMg{qdv2#M&XOy*!IL*LU$*2dk$!`)R-qa@VG|J5+jcsA}nt) z(uh(nerGr>M?$4jL5};VH)S1^Nu?ii>t~PEU-&gvh@QD?$o za@G8k5$ah=Qb#H3=3SaZqXwx_jKa14JmV`F9_p^EIR6TG2sdZcpMQSGkSVd)JKaw1 zX|{B|jQbYAqvqQkHx(sdx8V!=M(+CS!JZF@-#w8c6{uyM_Ir-TWq_##1|(fWvaLla3i`8(STVdUki;~gQpn91n&Nn_gOs&Fyq=bg(>t^}qD=zZiAxF}#! zuR{unUeREn!wm~xlwsEKE7O|_OS}+?91^TEAhgxr?A3l8C-xs^on1c&GIe8&%GNqw3@x#Rz zRqk(-!1QuYJj2dcK7X9AGz#N9nKuvVhAk9_8XUZI_8}yxR1#ai4j&#Qq+CQpAP!)l z>T-t6dZZE&2p+h>}u z#o>`tv|!;uPof+OTZeNQN!dkNFU#pjLb3O2N7%gA>OOKK!$u0+;TfGmDeO;W@-opx zq1Ut8(`JdkIKH@G0TpR~HZl^17M1XSSCLOI$kM3^LlqmjCgWH|o%*XTo9ap)A6^D0 zzgyi%_z^!Sh4bsS!Lp*&^WV-Ohv=zI&}pNV7~cgXsTV0rX8*B%^k*ZM%sPiwycQM8p4W?U{Q^kLIkRRAZ#~&XRP{fbL<0Yq1Uo)M_S+x_Oif6y=NHkx&q9NTCxL#wu7us3WQ=0Lt&vn$i$A(Cqj|!#u^WADUG|impFbc!@y#cW9pQiV zLOyMNKM#TsInQjGu8z`rCj?!*p)4qc6COI;?^YP9cZZBD!NiLh1-gz)BZ^skl!4U+ z-)1u5w@Zp9mTH$2sTjb&k|HT~#@~e4$Or}Lyanqtm=&;Sr$FgBX}Eb#x^o1`x)c4l z@qfV|F=1oT)=6S^^ydm;(bV@~yIb>a%^1Ul;UVU1S`9HZBKu|F?yl*YaMn?plM>c8 zQ!OQT?T7X7;oxl(4h1wgW0i>Uh8eMyl08NntkU*Ge6!x9LlL%}*_3A^%Il%;Tx5Yf zd^j-WJItn&08xY>Ek;X9H!R{=1K0a%5FQ#-N`u8#9%=3jc=6DXjBczozi9P3#!vk) zd?zbvC4mw;7fUNCHAavIFU|U4jYmEUMbTULf;>1WkgPWY`rG zazi~U_|DBCC^cO0u+OLt?&o%d0G)bnuH4wt<0krRB2H@t|7O?t-$BKSZT~ufvEHmR=jFk8}3IMhUs=W zbN(<(?GQY_jr{um;R5i)O1Zf~a}nI5QiUOOcU8*#Zh<&`EFpm^92Wc1{tD;37Sgmi zweWWiBgDoJ4aID%m|gjlUSdq?Z`Z+>4Lf@#N^aK*U&K>JHF~@Ur9q#g0BMuzjyyAd%N1 zIS3HD<#&32qiesmhd%5lUXZ@et!2pU+g*B~_m&UVJ|7y9OpY&I#!teIn5BL#YjU3{o8IX+ z{sjJ(s4GW@+~SI~HO?4(c(Hj=%D;zu`JY9LHho;GJ=+law{4RzcKsIAJ9}pnrodj( z=u|eJn#Y)_?I<^~;qW1Q1sW?v^&=hZY*%m>zD*a=XwiSsdOXx~;%l+^9H`%T~gYPXrtQ$+zktO+xhMnuUXB$7X;4~W-z zZ~mbTp27%U)vjEoNpt#aVI`GKAw}EVdNNPotaFM`#o8{{&&twv98~t z9-%$vLy#euQm{2YpKMQPid~_spGGd!V{<_V>;=fzY%GHl@X5(W*}#NDvVCjseev{5 z@bJh64z%jGR)iR^`p5xNR@*5-6$qnyCscnt$OGhHEi525TwL^3xLlpR9}$*33|L@Z z{t+*rubdXwT8zpZnPc2plEZ=eGyFa*d&>aym?r~tI*LIr7JNbx+X*xvO6CdE;*F=| zAp;YCTiXjELf;d3Iixu)SgC7)7|L+7_UNwNsLzxqQEa&KUVKInwO9sWMu@&^auT+w zl@YtS87-J%i0k|?y+QnGtHE*|B4KipVqBVinSp1P=y zQBNW*Mt-yk?zXJYzwh?ukjO*zKZa`weIQ$`-3f5i`+Jc8(QO+D3Nw@Q8EpqjKkqH|NS))x!>x?gWt9Omq=OI&_MWncQ&}*p4)p~T@Vr~ot^83i@ZR|Zn*x&kn^-KcroC(Q6dEY z>IrNuryo4%5QUP+w|2;Bxy6_j`L7qDi=8@xMfo!%$Z+Eq+=(W`{CNKF%ZM^j>EPGO zEh~aD88P@kAIV0f`u5>yEe|fUQOCDvL1u%1&|-s7e*k@4@r9v54L-{bI$%gpvkPKb zlDS(mYT#PF`WIr2Y-nSbCDx+kZZ_Ya^L{{a*}d7fp+@$4ze%@q8QJI;FG z2b)=n8T~_6tg0uxr>2d#2Wzx{2NR_H4u#ib^o16iIV(=k{}~y_s69Oq!Zs4^iZla( zEDSi=YMn5%LxJgphMr-AR23fil=oQWjI-74gv@Q%=d>=QiesrtD^u-s0Z)zJ3;n80 ze`zO(%Q7ps_KL>kxY#(SL(fXYOF(aJiH?zz-8C(Jq-Cz|D)@wC+=LYR^-VHL_nd}6 zi4neC%NH=|2|+Z=5R@k#9z+UxKEII=WF)OiQ9ViyY?x}4QfFPwZPx(SN$i7yR1ys#Fl$X$O- zN(S~H)HS;&JY{EhHBY_V6TP(8{Q6t&avM_*E78-B`k2`u@`*opo`+Ru%=EJ7@!!o_ z=e06V-xcBp5@JySe6EzNQEm`kkykRyQZIsR3_8BH8KB(!XP1WF%!vGdnj| zSX6|!x3_2g`Evjb;>~Gj3Y{utLuwhBV{rkuBwWHr3@F-fqa!9~%Z=97))Hz-zjV3y zrRRxCFr-Hch9HAX@S9n4gf>~T$1s`6n1=WQ-T5&#n*yC}^g9+>^ z$wsQ@9JaxH3WsAAhmSZErm#eqqO@^nkxMF~5gTN7OIw9F01s6MsZOemq2pD+kzD!qm?AHjlu$_ub>z7~nsg2tDzNV9L)lMY>!O{+O73O~iqs&Rcf7dFPF?bRJ2j zZITbtRvh-$^{n=#^*_gT(?x!5DjFCF9-k0gWNxf}OHBo>I#}&*N|MJe=hB&s3kqxf ziR|z6k;fc1&Q7-F$`0W6dlq8SeKUxO3$#WH7BKXAqe5}6Tym3CMVcB5nd&fpMFS)u zjSw)0uU#g^m!Z2 z9=A|aeq01ilcnY@*ll8RwD%N*O|WPDUb|&TabQ`O?wUOiwxz{fRaqGu|R@BT^Q=5(wr!5*VWYYu$n3X9b5d|HkgX5-gd)cudf(MW{d z(?dz4Spd&eNhV+p96q0SFM8-o1K$FucnXHZ{Uprqt)5_)^UY}Qu{C(rdT%e3dAA0L%>G;UXOC}2$+ z4shR(_~{d#1?r>twkg3di^|&s2}CG&P4_+7{J#3Q85tQIKe}vGEMwwh#L{;Se?2v+ zP?mHUR%FjEETr=!B<+U+wFNyrdVMB-&zuSQo8t^D81&j^E-&Y)tMhHZ%T*oQ+?M0U zwZQ^{nak~K=B?jIj`>zRH$TMw;JoVNo*^Y3a@6ejeZzF_d0Ihnr3&{Ce0@7wIGAxI z%&?t=tcaQw%SN_h4G&e7qZo(Z1Zpys7H+8Ag^8il!zO8M`SmXp)Q_>Ew0wnaE#-Q5FJJ&};V98H1mfxf<;#*3p~~C-466 z365(q+mq($*`;v}IUELLRaSZlTCHRIyv}fh@o0(y2W+7e?TPm7a|Oo~e4#n3;Bf5T zp^udnb+P=goZdjZkW|yhjn_BHKdom%SkH)@WXg&3-C!}?zCYUaN61;7Pl3rBa|DOI z9p8o6a*;lWch`~FUXd@>P}^#@b=af+>?qL3+*g0o>d7Kl$S!AP)!u-W8S%PPdRF0j z<*nq&pH>E+`yR0Uc{!?9Zp_R2s1A|Wf^kY3Ecz$mKAM|00@z1w zF-dZPWV@^GWs^%Mo3EV3qNvXvX5LEiWHM|+k1_HVqkp(SnH?2cN@MFkEp1LE%#0N> z++6bVsk)5W@&t>8VUZ<2qRJK6(f6dLmSbTC$E2mD@ zB%X>>#Oi0?tTCTn;EySk5%zOzHA&IqC^k+c&ZkH*YMaXlhb}KHI-jk2^&|eF*{(>3 z|5I+tWU1fk>!cvwUY<3^CAyb+ayd`*!df(GC9+Pcg1F&u|56&)0a=ojLFXhJF9$V# zP!U-_T3jeP3aYpp9f|?(dIH88E-EZVQW#puyEfdv1mpG;%lH-6W`>%7d|#Jm{RxD= zYbYxD%pW|f@j3m%qn3i7VFUKwjOn?W2V$Kcm;Oit8(VC>0Khee&|M8iVw)S3Lifjf z#TeKre>5DU-w^-s9B_Y-l&S@hh~qHx^GOwrQYdqJX1Gl*g2FBqM@eirV7G0J>lt(k ztUdioGTv*{TwT;jFpB*Bul-1g2WU%jVu6ACVltOl7X@_w*@TP#`=P?W9?A7v_bfnN z_6rlmw2PFp!?Az!4VurMmj3wwSQ~9H)mg28sswK1PNx)NW;N)*IU5fYn!tN}cd48$ zr45g;@$Nw=DK$4~z*XIOw(Nke|1)eyfszzKmWhr62Z1h9Ns@>KCvmO?+iPA4R}gWi z6p%3n6BRHPJ)8Mqr4|LJx&zQt%J+ZE2b3BzCO0r9IsPR)M&UieYTO9n%TFAVwtAGP zacRwBztcTbT0FD^^WJOzvageVp3t}7*`}a0f;>%6Z1G);v(vnig0jwe_^<}{H(v2j z9r-XXA-_caTW!0}CFF?nj+cE}k~LzSeFr>2=fvl%WV;{1yHY+Uap&8&?gu}H$RfI$ zh-)WuL+qO|T^x8x1+xnVul(> z2cyUe358UBFap<#ET^5(1Qn$dL`9TGA=uhU1|tE>PD3Vn_NvJm?JoF(yA^AuzNjd@ z*rH@i__As+Qg7aRx?JW45;RAQPvVUWY_+5B}TiWr(SdH;?GUPNiZj)z$n`3B7+*#d#@Z$S>%+BI2)o zX0gfk8jB>5U!-xmK8VRmBxiV@uZi1hcoZh?;?2|lz1A>!d%yeXp~66mw)1EPmK5dELofrp3ffozTJXR*2-ng8vMYq(`xduVcBBLcICh7H1>=*+ zPkO2>nSqsiqexf3t2QpSX~D|J94aJ?N8@2Ljtt6g6-+z`FuY8EvJ=99F$vvFn?WLI zCyMeOenuXHPyhEJtFOx6Gv4EGdS~;qih)<&3VX@%>-L5Uyw^4lo=8F=T~$lyb;sI;cmlY9qqA6IP#@fRr%j znVO8Ee_|>0NggGBUzDRz2^riSe*3WK_!mMXsf*5aPt*zj6T9fxXmYFwv2o{Ow7&QD zYrUZ{4Y9{lE5(FfGvRh<)k7LS?^sjGDJ(m$3r4K{qlNn_Z4!)1>U9+Eaig!Q6(^y^sSe)L}ryt z+0yp-4k>Ug9Vx}IG!biJVZ=Z3#s`-lTEma868`2D7nef&ZoaFSo`SK2iv&w=qFsSI z@SB(|-GD0-v#P2}ySYYl#$Jf13?143<*tb)bmZjk4?mbh_)jlL{T*T`5p54IaSI4^H30dI|wLWUpg6aNF5_FFFny;W?Zqn z-xAL0ga$n!J-%ZGy$*I=3amQ7c@9Z;!AcM7rp#O$r14eYI?D{m;O%c$A3B$dibgwfKqyLdmX0GC#b9&3)&wR+B$wwK~HV=rx z1Z_l}_#rqsVp-KZwl|A@#OPSb1Zf(AxsS`NpOo3ec#DrShG9w?L{c?gbUmla>$Vdx;0v5pe1Tn4E9lv?W9#P8IpLaH)k zMbEI2Dc7kCZ?n`T*>i#I?d|b7?HLHP&OPOE_E^XxFBdP;|D{-jNIFdCIr_w3H+KEz=soMtCJHBkuJMYm0do zG*FkGvK^mFAUh5u0S3ijrh@v}{ysJb2c<1Wc!9{ui_TKCq#$AR@@UZsD{t!Lp*Cxc z5Gn=l6Mk}Ep$4daRg$EP|xHg8k;5Vut5dxPcjOOlA+HmE_P^OZ{kS$s{v-X z^;l+bLtgn0k$!X-Zme&~Cu%)^0tpJxV1=>LYFdo^A@(y9n~Ap5bj0E6v)0fflRPthfbo}n?+w2TQ?O@F7^Mu586_OdFD-zK&{mgG z##p`B#p+2?<8gKg4^tKrY!Lacy%?)qT^tUE_LlQeqOILVLO4LBq*( zFJOAb3WvZY5hz6}hv1Y2vhqVfL(RWz_iY%2e1@e4(#%)74?acV5iTlw^5Y$z!aX^kM5DM_R@{5Z< z_Cs47m@?pkmM$2OjiGqZ{T)_=t?nob5#t2sc(z=If*}&Od16DusO_0P;zMzAOSZm4 zS`C*W!%~|jLO)JEizv2iYwTqoM<_3GXnl*$&o?_f`=G2GZ>8g_*mOCQ?ZY9*H!`oo zi;ZkxkX>4p9k;b*0+Ll#Ff!_#H-s@pB?(SG5Vvk7DOf8@W;l=_MOPCSxk_V2%MC^k zeS0g^F-wcS1;wl7wWJk?I^=cW>AQwBN}}!x?r-DXqy4o4BW7u=&(evGTeY za~)VaeKY-b7|><6D3cw9Fd(Ak(xVp&)Pb|sAshVgx}#TVFt+?Wt(au^)OLgM>(G(8 zvGq)bj@-WOcmj+sA)}|H?~(*_r}$T}oX5)9Oiu|%HwT_9Ka!w`)NHz($yXnF!$Y3X z@df1Ban2zj3e87UkwegSbW$mWgSeFSdImsyx1nW<7&5OvuKb( zEG#VFdc(ZS%8j&nJ^+cz+0{HYbz9ePFzWBvkIH2~gOcCkbW8!VTBa%KzT_|5k6wZR zV<`=nE0b|Y-#kcE_^1A#t#rXeGTs`Kqli@T+e(2mS9L3@W^-qpX+@K|ebWL~N+SrV zdx#k&<&Ie7Z#yaKrWTT)BtpG_V1w*8qOuANDB$LpYH+E`nF|CIb;mY&m@3AnpS)1K zI9C|b>D0(FA_gXBXM+O+1Ecptw`0Jz)G|0yB&e|T`;vg3dJ3YM)+~J!Aq9zUvQK=% z#sy;V-!V4eR@7l_gMNafAP4zU!dZ0UzfjOP`b>&ndy$xc83tH#eu6r#tlhYY2&kW^ zX=9Q0ive1XHA8@P4JrV7ygty1O{?~aViWe|9O5Ux1XFsx=#{#npUj6e+r9ykJsVxu>`=j(X2MKw`XE7 zUA$(NJ>o6kjrR{G^&m;)e4k;JaruYQ5r)nB#1^hDZQBPO=^lQ zgAlXcUv&BkWFu>=zZi3PA{Wdg$pg$&$S;d$ zCUh;#Jc~!|@wHYaQCz>S`{;(8@C75#8_~2|$SEG=_+fbkwa`bmZjIudvkM3;xrk|m zapI|@BX|n7fE0T;P@x~86*Zu-Xzt|SA)Lq&Bmi^@9$I&5m9VBk^wy?ChXSAN2F75} zOcyZwbBNkMT981^|6d_gPaMsxJN~nIj0wiPxSSmO$cSZORTU8l^i=Zx1E zLDpu6Hh+0sECq6Oi{*z)v{PaykETST)NnJmLpg~pk)O*yCld6KZGwyO&d&a&B`q6) zJeJRqjRtf&^&&>YGLCSu8eq%_6SjGv$$*522=J)pbfs6}dw&i( z)>QJM#ROQRf>^4RC20^j5sfpkcz3h>3ir_y;)wI@S&8KQ!iS_#l7w(#Pq=J}$%Mb6 z_8YezYYr)`XMcc&07=H)0u*E(jz5Jil1z5sIG3c&P)|2*suQ0Y5D4Q;MnY8mL4X7e zY|4QP0gaP2{-@RVd;ry((TSbe`2jx94Gj&*YIT$?UJmZ29~^O1jt>1eEF=Psg~awAQv@G`I7e6- z5@(FD_gF@?vyhRWns_U^|wXwoKeVChMCoqK-mgT}_n+S2~)Ii2;A&&Nu3Z|JJ z3n7DbKO95UG$S>F>aymLp$sM@{`w+HNr_iGm5-1VWF{j>73dDKOis2P0>+;cRd)i6 zVoqu4tKjjFv+@-hbp5)KYFfwptDx^CH;Y5CK+W`Lme?^R9?{<*<1aLG*-{!(-`C25 z<#%yCV}r1W6m=%e>tqB2D5Shp)Hy!b+gG7MgZ=zDeNXU7T|%!xqZ;(1yo`H#JZA3?*(M=cFrXLrcg1-?bz z7Fto=h9-Xp6{BQT~lqlB1AB0*)IzL}t)&!lh%ae~#|Gvxx8`r#Fd{?*iyqJq~ z-g!#0hM`k+=>)2Z#mWvS4+@>iSxYfZR(+(0f+HSbK{?(B`jNt95Ger|=)qxdjI^l$ zjeKp5D2=@Y)^-2Eq<)D_Q)9Y>GOkIcS|!+wjrH&q;Z(4|8ZQ2Z=F9)AB3g;<)*QZv4KYzRV`9o}lpJbZdt z|Hlf>Pc{GOj3wS=tobVatO8E7EA1w>B>;{+z<2H_wqk>3{aubvFsV${`9GY7>2}8T zQ9GV$i-iZL_c#|@d~>OgMdRUw_ z(bm`-#4$P*cX#L9|8rU5SzpDsQh5W>IPEtfy^|)594VV^5Qm(2IZy_T5f?fbLS{Dm zvC~fMCnp76yQVSa#`H9sx~3*Mnsw1!-PnGQwL(S+XUtFl6+Eaki<9$H(g+7m`W+0O zgxNP6n@YX(7vATke|t5jWf)GK^VPnGwMM~9)4N6o6U;EbQ zN^A%Olc*3?IrT_12?1*voRSD9f?ee(7>wL+9uv8p@tN(kw7OdEQEz56pVhuunzohqP!-Dl(aaedYb$6@Ha`Yma_>&zCXLX^}eYA)mwVb;=UNBceS;_Hz{ z)(&e6od4M&Ye_15`=eRpB>IbKb|M^VJSix$g_gY?C;xbC?ppaG^nmr%`z> zp-_-3{ZW`|zvU7}_;;;-sN*e(+}^s=a^j6~<*Af$(!Ww_dw*vT@#VH>mm-nF>PzQR z!Hv#x`b-^kr@hy}I6uuJIg_n4H38Ey9*s<`27a0tdLJ?Al)bsT!%ZZh=kMiA4=EI^D!?SAS$49)8tNPpk*TCxOOF}LlKr=8H+}6$Qh2jQ%P029Z zHs^6IQ41v9M=bNPw*b~uBOwwV5D#s2EN3x5+Lhi(RR$Nujx$n+5CJIxcyx z5R!~6NENHViq1|-D1fDEe!5+9H$z%_vD2S~5ZrpCUsF8k-%e<*uKA?OflHxQy=$w5yL ztaCGT>`+!1g(%FFlc`!y7D1%0uYY?f)lY&KjhpB#3Ccu-`)$i`qnesaC+yZBuPjl5(tN*a5(I= zw7(fim_5fx4#`sdO{RopRSw!=Lt9$_4u>5)&t-1ztXq?hJpLeEladV0nlu_CN4sEm z<ncQ?Mcsc?HnJ#*-bSDA_Kk6pGYJSiA1Tq$%d=1uS;>R zrt=4xnH=WVHKoXbkOnIUmXQ4ZDpXh3A{aadhr>Zh!rasu|C)5?6{Q{~Q{Tu|=0cAF zNM=&%{Sai+@9Wx*n%OzH`FqP#OcOmp(2)fOS(fn9uP@isX<;K3%BFt~OPAN+;XC{( zriq>)tY8p`DIrBiaG>KbN()6eJ^8ekfJmJg(8N@U7NPZI*Te5u-atnFfV#SR&7vdy zGWBmAqYa|3cto1du$qkDUxmiT_Jv}|h%k!iy zsZXCbhBc`lh6EwS08Z4*#V61F1P+%2^G-ZTI~O_(`oZQu-1ocfc;cDYsCL07QV|n` z{7=RWcWqU^NVgtRFvuwcK?vYScQn%+2-dVH$vuT8S&{$Axah{`GGed+kfVE^LL%6k zY~!X2hoqEO<3u!W+lz*VCY2Pli^53FH#&?(7FB(HBN`g&(!RyaROyMtP*=A$s~ik7 ze+#=20ibT>3CiL$@~|^S z`bIqZ=+Cfs@83{bT8e`Q4}#-tkY%OH*sNKz(A}-{)jaLAQ*rR%AxM%0o6Uyq?rxNn zlt7XsNRo_rJdT4058})-mm?G!LT;`XE|&v>z|#$E6pgB{Z$!3hQeVDq_aPaNJpQ0Y z&h3Z8s4SVPnTs!W?}m-DAr^^Z!RgC1bD_uJSW-{@3}o}`H3hxKpbv){X%aWwwKaL3 zL$@CE7;IPFM*R$A^XoMQy~dyqhZ<=T7v1<=@;rwQgQ7{qN9d7Js^Tyn&J+(TfK8{B zV$*3?<3ML0DvQ0^&jmLiMd5TR@$h&wjI%rixaQO!fEVm3O>64`TyusGB3sSr%(du^#f+0RSG@%%N{k_#%47!l7(dLei4gsf?8elk!Vjw5%;?Xdo;bDkQPZER9lT0?Q zSru^hEy?pdJRTQ%dipRj5=L8F8_LSc(B6Is2M>1Rth3HUdwU0D8L-=xH(Y0D4+Oyj zS(dSI;S!MZR)-GtVabwH)N5Lhm*>UM&;W9BTqrBkCRZIh<^*8Flqq=_9v-A`oV>g_ z=;-J~Sy?F(2^n2o9az8qf8g-pUgYI@sV~Cp;9rug8ppII^@A2};Y2{-F*Dyr=i=k{ zw!`IhLy~0h;IQD-(^Df&It-DI9)V-Y53x@5L9pb&k_J22Xoqs9Ly2hgbhqQa-)#o~ z{OR5e`0SHUF#m*;GGdUQya%?cZp9_5{OBIqg-cc?FJ(!C9c;8iIn(hmc5dH?OI9VQ z_VM1OYfLc+0CXDzw8;qTz>G)35bQ2`tdtiNdu3kL?9`aE6o(W`m`|JtAl9#RG!m7K zbsU2?C5&q1pI|dcd7e`@oI*XYu0mNFr2EBlAVjc{pBx^V{tf~c0@yAWmoVC+|+tD7uGssYJl1 zXb##mZN)rcotc?1BoX-5Blh{Tunl)Y&MgOIiH44N(Vq0O#ubK%$3}H8)*RS)J2*)> zRmSGp3w9R-y9)pZ(c^>^83Y9Nt;_bnJdPnPLFgTrWW#YBhtAG6xLkJl{eJj-J_G`- zc<;S!%Ae$`Qk`BvAfOhYaoVwZ^=kEJ%J1-a6vvgz+8zAfRHf!SDBLOpJ7)-LcMvek3H0&c*)SyD%CI zL52*c$BlSG!s2D8DjJGleLZ;3xJ47y(367R2JS)nychz0=wQ{B$A7?LCrcopD8$ay|$uaVG^l!$>J=>oVy zNQnrz>PvvnpJmw1jc9Zfd}J`C%2GPX&jFFvnU*= zLR2JRHZ6iowXKm#%}EQRKnM z;Yse#bPnXJg2C!s7>&*^_5vKIbm~^Sn3qBbl76rKoRGuE8!xnk zfs%xjh#?U8NK}bqq-{9*KA~IZ=*$fRn&WO07MrrVvL@Xb{oS5S+O=bTUFi zM-Z1}FnB?L zzjpqFuZPI)-{OO}--g4ZsO9r&{rG%uGX#Oh$jB&oo`c=xM0h9&UL~nSERI=4c{q4@ z5Q4y=sK5h`=ON27rk0f`A>Ax6$p7d5NyoqeKyaWJ(QpuLhexPN=UlW=0U^nVM#9+F z(m}n&c9$3KDP?fFbKn#?hz<{2i+=!)7d8KSf_6A_i`3LvRs9Ou;VmdZB-ocM4sDN&d#zAu&L;8(tRO-zv@ybkvAJ1W~zc+?#=i=uN{Z=<8`1`Df%9+|Q>_P@Z z;8iSJxm<$*g8OTQfdc?(N-8spJOEEx705%?9G`ArZ89^*Eg3;grqK7_0 zG#o@|=m5Jcwbn!?Lin%5 zaC|*{bugs#qD#rH)Ugd4vo4>WEFNNac>&iV1+aQLFBTa_)Hw^z6z1Z9!2mtkcN{~- znBVGF`kw0V(Puc0)in5fVK}}X{%bL4dz>F1$H(z;eCWpi2S_}f3_h)b1^@s607*qo IM6N<$f^W5A%>V!Z literal 0 HcmV?d00001 diff --git a/Tests/phaser.js b/Tests/phaser.js index 305a170f..9d434036 100644 --- a/Tests/phaser.js +++ b/Tests/phaser.js @@ -472,6 +472,7 @@ var Phaser; // For example if you had a sprite drawn facing straight up then you could set // rotationOffset to 90 and it would correspond correctly with Phasers rotation system this.rotationOffset = 0; + this.renderRotation = true; this.moves = true; // Input this.inputEnabled = false; @@ -1125,24 +1126,22 @@ var Phaser; Camera.prototype.setBounds = /** * Specify the boundaries of the world or where the camera is allowed to move. * - * @param X The smallest X value of your world (usually 0). - * @param Y The smallest Y value of your world (usually 0). - * @param Width The largest X value of your world (usually the world width). - * @param Height The largest Y value of your world (usually the world height). - * @param UpdateWorld Whether the global quad-tree's dimensions should be updated to match (default: false). + * @param x The smallest X value of your world (usually 0). + * @param y The smallest Y value of your world (usually 0). + * @param width The largest X value of your world (usually the world width). + * @param height The largest Y value of your world (usually the world height). */ - function (X, Y, Width, Height, UpdateWorld) { - if (typeof X === "undefined") { X = 0; } - if (typeof Y === "undefined") { Y = 0; } - if (typeof Width === "undefined") { Width = 0; } - if (typeof Height === "undefined") { Height = 0; } - if (typeof UpdateWorld === "undefined") { UpdateWorld = false; } + function (x, y, width, height) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + if (typeof width === "undefined") { width = 0; } + if (typeof height === "undefined") { height = 0; } if(this.bounds == null) { this.bounds = new Phaser.Rectangle(); } - this.bounds.setTo(X, Y, Width, Height); - //if(UpdateWorld) - // G.worldBounds.copyFrom(bounds); + this.bounds.setTo(x, y, width, height); + this.worldView.setTo(x, y, width, height); + this.scroll.setTo(0, 0); this.update(); }; Camera.prototype.update = function () { @@ -1177,13 +1176,13 @@ var Phaser; this.scroll.x = this.bounds.left; } if(this.scroll.x > this.bounds.right - this.width) { - this.scroll.x = this.bounds.right - this.width; + this.scroll.x = (this.bounds.right - this.width) + 1; } if(this.scroll.y < this.bounds.top) { this.scroll.y = this.bounds.top; } if(this.scroll.y > this.bounds.bottom - this.height) { - this.scroll.y = this.bounds.bottom - this.height; + this.scroll.y = (this.bounds.bottom - this.height) + 1; } } this.worldView.x = this.scroll.x; @@ -1595,7 +1594,7 @@ var Phaser; if(this.angle !== 0 || this.rotationOffset !== 0 || this.flipped == true) { this._game.stage.context.save(); this._game.stage.context.translate(this._dx + (this._dw / 2), this._dy + (this._dh / 2)); - if(this.angle !== 0 || this.rotationOffset !== 0) { + if(this.renderRotation == true && (this.angle !== 0 || this.rotationOffset !== 0)) { this._game.stage.context.rotate((this.rotationOffset + this.angle) * (Math.PI / 180)); } this._dx = -(this._dw / 2); @@ -4369,7 +4368,7 @@ var Phaser; * @return Whether or not any overlaps were found. */ function () { - //console.log('quadtree execute'); + console.log('quadtree execute'); var overlapProcessed = false; var iterator; if(this._headA.object != null) { @@ -4415,7 +4414,6 @@ var Phaser; * @return Whether or not any overlaps were found. */ function () { - //console.log('overlapNode'); //Walk the list and check for overlaps var overlapProcessed = false; var checkObject; @@ -7465,7 +7463,7 @@ var Phaser; /** * Phaser * -* v0.9.3 - April 24th 2013 +* v0.9.4 - April 24th 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -7479,7 +7477,7 @@ var Phaser; */ var Phaser; (function (Phaser) { - Phaser.VERSION = 'Phaser version 0.9.3'; + Phaser.VERSION = 'Phaser version 0.9.4'; })(Phaser || (Phaser = {})); /// /** @@ -9491,6 +9489,7 @@ var Phaser; }; Input.prototype.renderDebugInfo = function (x, y, color) { if (typeof color === "undefined") { color = 'rgb(255,255,255)'; } + this._game.stage.context.font = '14px Courier'; this._game.stage.context.fillStyle = color; this._game.stage.context.fillText('Input', x, y); this._game.stage.context.fillText('Screen X: ' + this.x + ' Screen Y: ' + this.y, x, y + 14); @@ -9530,7 +9529,7 @@ var Phaser; }, false); }; Keyboard.prototype.addKeyCapture = function (keycode) { - if(typeof keycode == 'array') { + if(typeof keycode === 'object') { for(var code in keycode) { this._capture[code] = true; } @@ -10991,7 +10990,7 @@ var Phaser; var Phaser; (function (Phaser) { var TilemapLayer = (function () { - function TilemapLayer(game, key, mapFormat, name, tileWidth, tileHeight) { + function TilemapLayer(game, parent, key, mapFormat, name, tileWidth, tileHeight) { this._startX = 0; this._startY = 0; this._maxX = 0; @@ -11003,12 +11002,16 @@ var Phaser; this._oldCameraX = 0; this._oldCameraY = 0; this.alpha = 1; + this.exists = true; this.visible = true; this.widthInTiles = 0; this.heightInTiles = 0; this.widthInPixels = 0; this.heightInPixels = 0; + this.tileMargin = 0; + this.tileSpacing = 0; this._game = game; + this._parent = parent; this.name = name; this.mapFormat = mapFormat; this.tileWidth = tileWidth; @@ -11017,8 +11020,84 @@ var Phaser; //this.scrollFactor = new MicroPoint(1, 1); this.mapData = []; this._texture = this._game.cache.getImage(key); - this.parseTileOffsets(); } + TilemapLayer.prototype.getTileFromWorldXY = function (x, y) { + x = this._game.math.snapToFloor(x, this.tileWidth) / this.tileWidth; + y = this._game.math.snapToFloor(y, this.tileHeight) / this.tileHeight; + return this.getTileIndex(x, y); + }; + TilemapLayer.prototype.getTileOverlaps = function (object) { + //var result: bool = false; + //var x: number = object.x; + //var y: number = object.y; + // What tiles do we need to check against? + var mapX = this._game.math.snapToFloor(object.bounds.x, this.tileWidth); + var mapY = this._game.math.snapToFloor(object.bounds.y, this.tileHeight); + var mapW = this._game.math.snapToCeil(object.bounds.width, this.tileWidth) + this.tileWidth; + var mapH = this._game.math.snapToCeil(object.bounds.height, this.tileHeight) + this.tileHeight; + var tileX = mapX / this.tileWidth; + var tileY = mapY / this.tileHeight; + var tileW = mapW / this.tileWidth; + var tileH = mapH / this.tileHeight; + if(tileX < 0) { + tileX = 0; + } + if(tileY < 0) { + tileY = 0; + } + if(tileW > this.widthInTiles) { + tileW = this.widthInTiles; + } + if(tileH > this.heightInTiles) { + tileH = this.heightInTiles; + } + // Loop through the tiles we've got and check overlaps accordingly + var tiles = this.getTileBlock(tileX, tileY, tileW, tileH); + var result = []; + var tempBounds = new Phaser.Quad(); + for(var r = 0; r < tiles.length; r++) { + if(tiles[r].tile.allowCollisions != Phaser.Collision.NONE) { + tempBounds.setTo(tiles[r].x * this.tileWidth, tiles[r].y * this.tileHeight, this.tileWidth, this.tileHeight); + if(tempBounds.intersects(object.bounds)) { + result.push(true); + } else { + result.push(false); + } + } else { + result.push(false); + } + } + //return { x: mapX, y: mapY, w: mapW, h: mapH, collision: result }; + return { + x: tileX, + y: tileY, + w: tileW, + h: tileH, + collision: result + }; + }; + TilemapLayer.prototype.getTileBlock = //public checkTileOverlap(object:GameObject, + function (x, y, width, height) { + var output = []; + for(var ty = y; ty < y + height; ty++) { + for(var tx = x; tx < x + width; tx++) { + output.push({ + x: tx, + y: ty, + tile: this._parent.tiles[this.mapData[ty][tx]] + }); + } + } + return output; + }; + TilemapLayer.prototype.getTileIndex = function (x, y) { + if(y >= 0 && y < this.mapData.length) { + if(x >= 0 && x < this.mapData[y].length) { + return this.mapData[y][x]; + } + } + return null; + }; TilemapLayer.prototype.addColumn = function (column) { var data = []; for(var c = 0; c < column.length; c++) { @@ -11034,6 +11113,7 @@ var Phaser; }; TilemapLayer.prototype.updateBounds = function () { this.boundsInTiles.setTo(0, 0, this.widthInTiles, this.heightInTiles); + console.log('layer bounds', this.boundsInTiles); }; TilemapLayer.prototype.parseTileOffsets = function () { this._tileOffsets = []; @@ -11043,8 +11123,8 @@ var Phaser; this._tileOffsets[0] = null; i = 1; } - for(var ty = 0; ty < this._texture.height; ty += this.tileHeight) { - for(var tx = 0; tx < this._texture.width; tx += this.tileWidth) { + for(var ty = this.tileMargin; ty < this._texture.height; ty += (this.tileHeight + this.tileSpacing)) { + for(var tx = this.tileMargin; tx < this._texture.width; tx += (this.tileWidth + this.tileSpacing)) { this._tileOffsets[i] = { x: tx, y: ty @@ -11052,6 +11132,7 @@ var Phaser; i++; } } + return this._tileOffsets.length; }; TilemapLayer.prototype.renderDebugInfo = function (x, y, color) { if (typeof color === "undefined") { color = 'rgb(255,255,255)'; } @@ -11078,6 +11159,12 @@ var Phaser; if(this._startY < 0) { this._startY = 0; } + if(this._maxX > this.widthInTiles) { + this._maxX = this.widthInTiles; + } + if(this._maxY > this.heightInTiles) { + this._maxY = this.heightInTiles; + } if(this._startX + this._maxX > this.widthInTiles) { this._startX = this.widthInTiles - this._maxX; } @@ -11134,8 +11221,44 @@ var Phaser; Phaser.TilemapLayer = TilemapLayer; })(Phaser || (Phaser = {})); /// +/** +* Phaser - Tile +* +* A Tile is a single representation of a tile within a Tilemap +*/ +var Phaser; +(function (Phaser) { + var Tile = (function () { + function Tile(game, tilemap, index, width, height) { + this._game = game; + this.tilemap = tilemap; + this.index = index; + this.width = width; + this.height = height; + this.allowCollisions = Phaser.Collision.NONE; + } + Tile.prototype.destroy = /** + * Clean up memory. + */ + function () { + this.tilemap = null; + }; + Tile.prototype.toString = /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + function () { + return "[{Tiled (index=" + this.index + " collisions=" + this.allowCollisions + " width=" + this.width + " height=" + this.height + ")}]"; + }; + return Tile; + })(); + Phaser.Tile = Tile; +})(Phaser || (Phaser = {})); +/// /// /// +/// /** * Phaser - Tilemap * @@ -11152,7 +11275,8 @@ var Phaser; if (typeof tileHeight === "undefined") { tileHeight = 0; } _super.call(this, game); this.isGroup = false; - this._layers = []; + this.tiles = []; + this.layers = []; this.mapFormat = format; switch(format) { case Tilemap.FORMAT_CSV: @@ -11173,13 +11297,13 @@ var Phaser; Tilemap.prototype.render = function (camera, cameraOffsetX, cameraOffsetY) { if(this.cameraBlacklist.indexOf(camera.ID) == -1) { // Loop through the layers - for(var i = 0; i < this._layers.length; i++) { - this._layers[i].render(camera, cameraOffsetX, cameraOffsetY); + for(var i = 0; i < this.layers.length; i++) { + this.layers[i].render(camera, cameraOffsetX, cameraOffsetY); } } }; Tilemap.prototype.parseCSV = function (data, key, tileWidth, tileHeight) { - var layer = new Phaser.TilemapLayer(this._game, key, Tilemap.FORMAT_CSV, 'TileLayerCSV' + this._layers.length.toString(), tileWidth, tileHeight); + var layer = new Phaser.TilemapLayer(this._game, this, key, Tilemap.FORMAT_CSV, 'TileLayerCSV' + this.layers.length.toString(), tileWidth, tileHeight); // Trim any rogue whitespace from the data data = data.trim(); var rows = data.split("\n"); @@ -11190,17 +11314,21 @@ var Phaser; } } layer.updateBounds(); + var tileQuantity = layer.parseTileOffsets(); this.currentLayer = layer; - this._layers.push(layer); + this.layers.push(layer); + this.generateTiles(tileQuantity); }; Tilemap.prototype.parseTiledJSON = function (data, key) { // Trim any rogue whitespace from the data data = data.trim(); var json = JSON.parse(data); for(var i = 0; i < json.layers.length; i++) { - var layer = new Phaser.TilemapLayer(this._game, key, Tilemap.FORMAT_TILED_JSON, json.layers[i].name, json.tilewidth, json.tileheight); + var layer = new Phaser.TilemapLayer(this._game, this, key, Tilemap.FORMAT_TILED_JSON, json.layers[i].name, json.tilewidth, json.tileheight); layer.alpha = json.layers[i].opacity; layer.visible = json.layers[i].visible; + layer.tileMargin = json.tilesets[0].margin; + layer.tileSpacing = json.tilesets[0].spacing; var c = 0; var row; for(var t = 0; t < json.layers[i].data.length; t++) { @@ -11215,8 +11343,15 @@ var Phaser; } } layer.updateBounds(); + var tileQuantity = layer.parseTileOffsets(); this.currentLayer = layer; - this._layers.push(layer); + this.layers.push(layer); + } + this.generateTiles(tileQuantity); + }; + Tilemap.prototype.generateTiles = function (qty) { + for(var i = 0; i < qty; i++) { + this.tiles.push(new Phaser.Tile(this._game, this, i, this.currentLayer.tileWidth, this.currentLayer.tileHeight)); } }; Object.defineProperty(Tilemap.prototype, "widthInPixels", { @@ -11233,12 +11368,59 @@ var Phaser; enumerable: true, configurable: true }); + Tilemap.prototype.setCollisionRange = // Tile Collision + function (start, end, collision) { + if (typeof collision === "undefined") { collision = Phaser.Collision.ANY; } + for(var i = start; i < end; i++) { + this.tiles[i].allowCollisions = collision; + } + }; + Tilemap.prototype.setCollisionByIndex = function (values, collision) { + if (typeof collision === "undefined") { collision = Phaser.Collision.ANY; } + for(var i = 0; i < values.length; i++) { + this.tiles[values[i]].allowCollisions = collision; + } + }; + Tilemap.prototype.getTile = // Tile Management + function (x, y, layer) { + if (typeof layer === "undefined") { layer = 0; } + return this.tiles[this.layers[layer].getTileIndex(x, y)]; + }; + Tilemap.prototype.getTileFromWorldXY = function (x, y, layer) { + if (typeof layer === "undefined") { layer = 0; } + return this.tiles[this.layers[layer].getTileFromWorldXY(x, y)]; + }; + Tilemap.prototype.getTileFromInputXY = function (layer) { + if (typeof layer === "undefined") { layer = 0; } + return this.tiles[this.layers[layer].getTileFromWorldXY(this._game.input.worldX, this._game.input.worldY)]; + }; + Tilemap.prototype.getTileOverlaps = function (object) { + return this.currentLayer.getTileOverlaps(object); + }; + Tilemap.prototype.collide = // COLLIDE + function (objectOrGroup, callback) { + if (typeof objectOrGroup === "undefined") { objectOrGroup = null; } + if (typeof callback === "undefined") { callback = null; } + if(objectOrGroup == null) { + objectOrGroup = this._game.world.group; + } + // Group? + if(objectOrGroup.isGroup == false) { + if(objectOrGroup.exists && objectOrGroup.allowCollisions != Phaser.Collision.NONE) { + // Get the tiles this object overlaps with (could be any number based on its width/height) + // Iterate through each tile, checking if it overlaps with the object bounds + // Yes? then separate, else abort + } + } else { + // todo + } + return true; + }; return Tilemap; })(Phaser.GameObject); Phaser.Tilemap = Tilemap; // Set current layer // Set layer order? - // Get tile from x/y // Get block of tiles // Swap tiles around // Delete tiles of certain type @@ -11280,6 +11462,9 @@ var Phaser; * * This is where the magic happens. The Game object is the heart of your game, * providing quick access to common functions and handling the boot process. +* +* "Hell, there are no rules here - we're trying to accomplish something." +* Thomas A. Edison */ var Phaser; (function (Phaser) { @@ -11649,7 +11834,7 @@ var Phaser; * Determines whether the object specified intersects (overlaps) with this Quad object. * This method checks the x, y, width, and height properties of the specified Quad object to see if it intersects with this Quad object. * @method intersects - * @param {Quad} q The Quad to compare against to see if it intersects with this Quad. + * @param {Object} q The object to check for intersection with this Quad. Must have left/right/top/bottom properties (Rectangle, Quad). * @param {Number} t A tolerance value to allow for an intersection test with padding, default to 0 * @return {Boolean} A value of true if the specified object intersects with this Quad; otherwise false. **/ @@ -11917,50 +12102,6 @@ var Phaser; })(Phaser.GameObject); Phaser.ScrollZone = ScrollZone; })(Phaser || (Phaser = {})); -/// -/** -* Phaser - Tile -* -* A simple helper object for Tilemap that helps expand collision opportunities and control. -*/ -var Phaser; -(function (Phaser) { - var Tile = (function (_super) { - __extends(Tile, _super); - /** - * Instantiate this new tile object. This is usually called from Tilemap.loadMap(). - * - * @param Tilemap A reference to the tilemap object creating the tile. - * @param Index The actual core map data index for this tile type. - * @param Width The width of the tile. - * @param Height The height of the tile. - * @param Visible Whether the tile is visible or not. - * @param AllowCollisions The collision flags for the object. By default this value is ANY or NONE depending on the parameters sent to loadMap(). - */ - function Tile(game, Tilemap, Index, Width, Height, Visible, AllowCollisions) { - _super.call(this, game, 0, 0, Width, Height); - this.immovable = true; - this.moves = false; - this.callback = null; - this.filter = null; - this.tilemap = Tilemap; - this.index = Index; - this.visible = Visible; - this.allowCollisions = AllowCollisions; - this.mapIndex = 0; - } - Tile.prototype.destroy = /** - * Clean up memory. - */ - function () { - _super.prototype.destroy.call(this); - this.callback = null; - this.tilemap = null; - }; - return Tile; - })(Phaser.GameObject); - Phaser.Tile = Tile; -})(Phaser || (Phaser = {})); /// /** * Phaser - State diff --git a/Tests/tilemap/collision.js b/Tests/tilemap/collision.js new file mode 100644 index 00000000..081f0217 --- /dev/null +++ b/Tests/tilemap/collision.js @@ -0,0 +1,73 @@ +/// +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update); + function init() { + myGame.loader.addTextFile('platform', 'assets/maps/platform-test-1.json'); + myGame.loader.addImageFile('tiles', 'assets/tiles/platformer_tiles.png'); + myGame.loader.addImageFile('ufo', 'assets/sprites/ufo.png'); + myGame.loader.addImageFile('ilkke', 'assets/sprites/ilkke.png'); + myGame.loader.addImageFile('chunk', 'assets/sprites/chunk.png'); + myGame.loader.addImageFile('healthbar', 'assets/sprites/healthbar.png'); + myGame.loader.load(); + } + var map; + var car; + var marker; + var tile; + function create() { + map = myGame.createTilemap('tiles', 'platform', Phaser.Tilemap.FORMAT_TILED_JSON); + map.setCollisionRange(21, 53); + map.setCollisionRange(105, 109); + myGame.camera.backgroundColor = 'rgb(47,154,204)'; + car = myGame.createSprite(250, 0, 'ufo'); + car.renderRotation = false; + car.renderDebug = true; + car.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); + //car.velocity.y = 10; + marker = myGame.createGeomSprite(0, 0); + marker.createRectangle(16, 16); + marker.renderFill = false; + myGame.onRenderCallback = render; + } + function update() { + marker.x = myGame.math.snapToFloor(myGame.input.worldX, 16); + marker.y = myGame.math.snapToFloor(myGame.input.worldY, 16); + //myGame.collide(car, map.currentLayer); + car.velocity.x = 0; + car.velocity.y = 0; + if(myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { + car.velocity.x = -100; + } else if(myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { + car.velocity.x = 100; + } + if(myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) { + car.velocity.y = -100; + } else if(myGame.input.keyboard.isDown(Phaser.Keyboard.DOWN)) { + car.velocity.y = 100; + } + } + function render() { + tile = map.getTileFromInputXY(); + var b = map.getTileOverlaps(car); + myGame.stage.context.font = '18px Arial'; + myGame.stage.context.fillStyle = 'rgb(255,255,255)'; + myGame.stage.context.fillText(tile.toString(), 32, 32); + myGame.input.renderDebugInfo(32, 64, 'rgb(255,255,255)'); + myGame.stage.context.fillStyle = 'rgb(255,255,255)'; + myGame.stage.context.fillText(b.x + ' ' + b.y + ' ' + b.w + ' ' + b.h, 32, 200); + myGame.stage.context.fillText(car.bounds.x + ' ' + car.bounds.y + ' ' + car.bounds.width + ' ' + car.bounds.height, 32, 232); + var i = 0; + for(var y = b.y; y < b.y + b.h; y++) { + for(var x = b.x; x < b.x + b.w; x++) { + if(b.collision[i] == true) { + myGame.stage.context.fillStyle = 'rgba(255,0,0,0.5)'; + } else { + myGame.stage.context.fillStyle = 'rgba(0,255,0,0.5)'; + } + myGame.stage.context.fillRect(x * 16, y * 16, 16, 16); + i++; + } + } + } +})(); diff --git a/Tests/tilemap/collision.ts b/Tests/tilemap/collision.ts new file mode 100644 index 00000000..c2eb0496 --- /dev/null +++ b/Tests/tilemap/collision.ts @@ -0,0 +1,116 @@ +/// +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update); + + function init() { + + myGame.loader.addTextFile('platform', 'assets/maps/platform-test-1.json'); + myGame.loader.addImageFile('tiles', 'assets/tiles/platformer_tiles.png'); + myGame.loader.addImageFile('ufo', 'assets/sprites/ufo.png'); + myGame.loader.addImageFile('ilkke', 'assets/sprites/ilkke.png'); + myGame.loader.addImageFile('chunk', 'assets/sprites/chunk.png'); + myGame.loader.addImageFile('healthbar', 'assets/sprites/healthbar.png'); + + myGame.loader.load(); + + } + + var map: Phaser.Tilemap; + var car: Phaser.Sprite; + var marker: Phaser.GeomSprite; + var tile: Phaser.Tile; + + function create() { + + map = myGame.createTilemap('tiles', 'platform', Phaser.Tilemap.FORMAT_TILED_JSON); + map.setCollisionRange(21,53); + map.setCollisionRange(105,109); + + myGame.camera.backgroundColor = 'rgb(47,154,204)'; + + car = myGame.createSprite(250, 0, 'ufo'); + car.renderRotation = false; + car.renderDebug = true; + + car.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); + //car.velocity.y = 10; + + marker = myGame.createGeomSprite(0, 0); + marker.createRectangle(16, 16); + marker.renderFill = false; + + myGame.onRenderCallback = render; + + } + + function update() { + + marker.x = myGame.math.snapToFloor(myGame.input.worldX, 16); + marker.y = myGame.math.snapToFloor(myGame.input.worldY, 16); + + //myGame.collide(car, map.currentLayer); + + car.velocity.x = 0; + car.velocity.y = 0; + + if (myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) + { + car.velocity.x = -100; + } + else if (myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) + { + car.velocity.x = 100; + } + + if (myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) + { + car.velocity.y = -100; + } + else if (myGame.input.keyboard.isDown(Phaser.Keyboard.DOWN)) + { + car.velocity.y = 100; + } + + } + + function render { + + tile = map.getTileFromInputXY(); + + var b = map.getTileOverlaps(car); + + myGame.stage.context.font = '18px Arial'; + myGame.stage.context.fillStyle = 'rgb(255,255,255)'; + myGame.stage.context.fillText(tile.toString(), 32, 32); + myGame.input.renderDebugInfo(32, 64, 'rgb(255,255,255)'); + myGame.stage.context.fillStyle = 'rgb(255,255,255)'; + myGame.stage.context.fillText(b.x + ' ' + b.y + ' ' + b.w + ' ' + b.h, 32, 200); + myGame.stage.context.fillText(car.bounds.x + ' ' + car.bounds.y + ' ' + car.bounds.width + ' ' + car.bounds.height, 32, 232); + + + var i = 0; + + for (var y = b.y; y < b.y + b.h; y++) + { + for (var x = b.x; x < b.x + b.w; x++) + { + if (b.collision[i] == true) + { + myGame.stage.context.fillStyle = 'rgba(255,0,0,0.5)'; + } + else + { + myGame.stage.context.fillStyle = 'rgba(0,255,0,0.5)'; + } + + myGame.stage.context.fillRect(x * 16, y * 16, 16, 16); + i++; + } + } + + } + +})(); diff --git a/Tests/tilemap/get tile.js b/Tests/tilemap/get tile.js new file mode 100644 index 00000000..ff0a3c65 --- /dev/null +++ b/Tests/tilemap/get tile.js @@ -0,0 +1,49 @@ +/// +/// +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update); + function init() { + myGame.loader.addTextFile('desert', 'assets/maps/desert.json'); + myGame.loader.addImageFile('tiles', 'assets/tiles/tmw_desert_spacing.png'); + myGame.loader.addImageFile('car', 'assets/sprites/car90.png'); + myGame.loader.load(); + } + var map; + var car; + var marker; + var tile; + function create() { + map = myGame.createTilemap('tiles', 'desert', Phaser.Tilemap.FORMAT_TILED_JSON); + car = myGame.createSprite(250, 200, 'car'); + car.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); + marker = myGame.createGeomSprite(0, 0); + marker.createRectangle(32, 32); + marker.renderFill = false; + marker.lineColor = 'rgb(0,0,0)'; + myGame.camera.follow(car); + myGame.onRenderCallback = render; + } + function update() { + marker.x = myGame.math.snapToFloor(myGame.input.worldX, 32); + marker.y = myGame.math.snapToFloor(myGame.input.worldY, 32); + car.velocity.x = 0; + car.velocity.y = 0; + car.angularVelocity = 0; + if(myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { + car.angularVelocity = -200; + } else if(myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { + car.angularVelocity = 200; + } + if(myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) { + car.velocity.copyFrom(myGame.motion.velocityFromAngle(car.angle, 300)); + } + } + function render() { + tile = map.getTileFromInputXY(); + myGame.stage.context.font = '18px Arial'; + myGame.stage.context.fillStyle = 'rgb(0,0,0)'; + myGame.stage.context.fillText(tile.toString(), 32, 32); + myGame.input.renderDebugInfo(32, 64, 'rgb(0,0,0)'); + } +})(); diff --git a/Tests/tilemap/get tile.ts b/Tests/tilemap/get tile.ts new file mode 100644 index 00000000..462118cc --- /dev/null +++ b/Tests/tilemap/get tile.ts @@ -0,0 +1,78 @@ +/// +/// +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update); + + function init() { + + myGame.loader.addTextFile('desert', 'assets/maps/desert.json'); + myGame.loader.addImageFile('tiles', 'assets/tiles/tmw_desert_spacing.png'); + myGame.loader.addImageFile('car', 'assets/sprites/car90.png'); + + myGame.loader.load(); + + } + + var map: Phaser.Tilemap; + var car: Phaser.Sprite; + var marker: Phaser.GeomSprite; + var tile: Phaser.Tile; + + function create() { + + map = myGame.createTilemap('tiles', 'desert', Phaser.Tilemap.FORMAT_TILED_JSON); + + car = myGame.createSprite(250, 200, 'car'); + car.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); + + marker = myGame.createGeomSprite(0, 0); + marker.createRectangle(32, 32); + marker.renderFill = false; + marker.lineColor = 'rgb(0,0,0)'; + + myGame.camera.follow(car); + myGame.onRenderCallback = render; + + } + + function update() { + + marker.x = myGame.math.snapToFloor(myGame.input.worldX, 32); + marker.y = myGame.math.snapToFloor(myGame.input.worldY, 32); + + car.velocity.x = 0; + car.velocity.y = 0; + car.angularVelocity = 0; + + if (myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) + { + car.angularVelocity = -200; + } + else if (myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) + { + car.angularVelocity = 200; + } + + if (myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) + { + car.velocity.copyFrom(myGame.motion.velocityFromAngle(car.angle, 300)); + } + + } + + function render { + + tile = map.getTileFromInputXY(); + + myGame.stage.context.font = '18px Arial'; + myGame.stage.context.fillStyle = 'rgb(0,0,0)'; + myGame.stage.context.fillText(tile.toString(), 32, 32); + + myGame.input.renderDebugInfo(32, 64, 'rgb(0,0,0)'); + + } + +})();