Mercurial > vanillian
changeset 2:0c46edeabad6
scrolling map
| author | Radomir Dopieralski <sheep-devel@sheep.art.pl> |
|---|---|
| date | Sat, 15 Dec 2012 13:10:41 +0100 |
| parents | 71b36e588242 |
| children | f5de6c75c4d0 |
| files | AdvTiledLoader/Base64.lua AdvTiledLoader/Grid.lua AdvTiledLoader/Loader.lua AdvTiledLoader/Map.lua AdvTiledLoader/Object.lua AdvTiledLoader/ObjectLayer.lua AdvTiledLoader/README.md AdvTiledLoader/Tile.lua AdvTiledLoader/TileLayer.lua AdvTiledLoader/TileSet.lua AdvTiledLoader/VERSION.txt AdvTiledLoader/external/crc32lua.lua AdvTiledLoader/external/deflatelua.lua AdvTiledLoader/external/gunziplua.lua AdvTiledLoader/external/numberlua.lua AdvTiledLoader/external/optparse.lua AdvTiledLoader/external/xml.lua AdvTiledLoader/init.lua level01.tmx main.lua shadow.png sprite.lua tiles.png villain.png |
| diffstat | 24 files changed, 4286 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/Base64.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,203 @@ +--------------------------------------------------------------------------------------------------- +-- -= Base64 =- +--------------------------------------------------------------------------------------------------- + +-- Make some functions easier to write +local floor = math.floor +local sub = string.sub +local gsub = string.gsub +local rem = table.remove + +--------------------------------------------------------------------------------------------------- +-- Our base64 value table +local base64 = { ['A']=0,['B']=1,['C']=2,['D']=3,['E']=4,['F']=5,['G']=6,['H']=7,['I']=8, + ['J']=9,['K']=10,['L']=11,['M']=12,['N']=13,['O']=14,['P']=15,['Q']=16, + ['R']=17,['S']=18,['T']=19,['U']=20,['V']=21,['W']=22,['X']=23,['Y']=24, + ['Z']=25,['a']=26,['b']=27,['c']=28,['d']=29,['e']=30,['f']=31,['g']=32, + ['h']=33,['i']=34,['j']=35,['k']=36,['l']=37,['m']=38,['n']=39,['o']=40, + ['p']=41,['q']=42,['r']=43,['s']=44,['t']=45,['u']=46,['v']=47,['w']=48, + ['x']=49,['y']=50,['z']=51,['0']=52,['1']=53,['2']=54,['3']=55,['4']=56, + ['5']=57,['6']=58,['7']=59,['8']=60,['9']=61,['+']=62,['/']=63,['=']=nil} + +--------------------------------------------------------------------------------------------------- +-- Decimal values for binary digits +local bin ={} +local mult = 1 +for i = 1,40 do + bin[i] = mult + mult = mult*2 +end + +--------------------------------------------------------------------------------------------------- +-- A buffer we will use to process the bits +local buffer = 0 +local pos = 0 +local function clearBuffer() + buffer = 0 + pos = 1 +end + +--------------------------------------------------------------------------------------------------- +-- Shift all of the bits up in the buffer and put the base64 number on the bottom +local function pushBase64(n) + if base64[n] == nil then return end + buffer = buffer * bin[7] + base64[n] + pos = pos + 6 +end + +--------------------------------------------------------------------------------------------------- +-- Get an int out of the buffer. This is tricky. The byte order is in little endian so we're going +-- to have to isolate and cut the bytes out and then move them around. +local function getInt() + -- If our buffer isn't filled all the way then fill it with zeros + while pos < 33 do + buffer = buffer * bin[2] + pos = pos + 1 + end + -- Move the buffer position to just below the integer. + pos = pos - 32 + + -- Swap the first and forth byte and then the second and third. + local tmp = floor((buffer%bin[33+pos-1])/bin[25+pos-1]) + + floor((buffer%bin[25+pos-1])/bin[17+pos-1])*bin[9] + + floor((buffer%bin[17+pos-1])/bin[9+pos-1])*bin[17] + + floor((buffer%bin[9+pos-1])/bin[pos])*bin[25] + + -- We've got our integer so let's cut that portion out of the buffer + buffer = buffer % bin[pos] + -- Return the int + return tmp +end + +--------------------------------------------------------------------------------------------------- +-- Get a byte out of the buffer +local function getByte() + -- If our buffer isn't filled all the way then fill it with zeros + while pos < 9 do + buffer = buffer * bin[2] + pos = pos + 1 + end + -- Move the buffer position to just below the byte. + pos = pos - 8 + -- Cut out the byte + local tmp = floor((buffer%bin[9+pos-1])/bin[pos]) + -- Delete the byte from the buffer + buffer = buffer % bin[pos] + -- Return the byte + return tmp +end + +--------------------------------------------------------------------------------------------------- +-- Glues together an integer from four bytes. Little endian +local function glueInt(b1, b2, b3, b4) + return b1%bin[9] + b2%bin[9]*bin[9] + b3%bin[9]*bin[17] + b4%bin[9]*bin[25] +end + +--------------------------------------------------------------------------------------------------- +-- A Lua set that will filter out characters that aren't in the base64 table +local set = "[^%a%d%+%/%=]" + +--------------------------------------------------------------------------------------------------- +-- Decodes a base64 string into the given type +local function decode(mode, raw) + + -- Make sure the mode is supported + assert(mode=="string" or mode=="int" or mode=="byte", "Base64 decode - Invalid mode: " .. mode) + + -- Clear the buffer + clearBuffer() + + -- Filters undefined characters out of the string + raw = gsub(raw, set, "") + + local size = 0 -- Size of the returned type in bits + local val = {} -- A table containing the data to be returned + local raw_pos = 1 -- The position of the progress through the raw base64 string + local raw_size = #raw -- The size of the base64 string + local char = "" -- The current base64 character to be processed + + -- If we're expected to return an int then the bit size is 32, otherwise it's 8 + if mode == "int" then size = 32 else size = 8 end + + -- While we still have input + while raw_pos <= raw_size do + -- Fill the buffer until we have enough bits + while pos <= size and raw_pos <= raw_size do + char = sub(raw,raw_pos,raw_pos) + pushBase64( char ) + raw_pos = raw_pos + 1 + end + -- If a nil character is encountered the end the loop + if char == "=" then break end + -- Get data from the buffer depending on the type + if mode == "string" then val[#val+1] = string.char( getByte() ) end + if mode == "byte" then val[#val+1] = getByte() end + if mode == "int" then val[#val+1] = getInt() end + end + + if mode == "string" then return table.concat(val) end + return val +end + +--------------------------------------------------------------------------------------------------- +-- Encodes a table of ints into a base64 string. +local function encode(ints) + + -- Clear the buffer + clearBuffer() + + local size = 0 -- Size of the returned type in bits + local val = {} -- A table containing the data to be returned + local char = "" -- The current base64 character to be processed + + -- If we're expected to return an int then the bit size is 32, otherwise it's 8 + if mode == "int" then size = 32 else size = 8 end + + -- While we still have input + while raw_pos <= raw_size do + -- Fill the buffer until we have enough bits + while pos <= size and raw_pos <= raw_size do + char = sub(raw,raw_pos,raw_pos) + pushBase64( char ) + raw_pos = raw_pos + 1 + end + -- If a nil character is encountered the end the loop + if char == "=" then break end + -- Get data from the buffer depending on the type + if mode == "string" then val[#val+1] = string.char( getByte() ) end + if mode == "byte" then val[#val+1] = getByte() end + if mode == "int" then val[#val+1] = getInt() end + end + + if mode == "string" then return table.concat(val) end + return val +end + +--------------------------------------------------------------------------------------------------- +-- Returns the functions +return {decode = decode, glueInt = glueInt, base64 = base64} + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/Grid.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,172 @@ +--------------------------------------------------------------------------------------------------- +-- -= Grid =- +--------------------------------------------------------------------------------------------------- +local Grid = {class = "Grid"} +Grid.__index = Grid + +--------------------------------------------------------------------------------------------------- +-- Creates and returns a new grid +function Grid:new() + local grid = {} + grid.cells = {} + return setmetatable(grid, Grid) +end + +--------------------------------------------------------------------------------------------------- +-- Gets the value of a single cell +function Grid:get(x,y) + return self.cells[x] and self.cells[x][y] +end + +--------------------------------------------------------------------------------------------------- +-- Sets the value of a single cell +function Grid:set(x,y,value) + if not self.cells[x] then self.cells[x] = {} end + self.cells[x][y] = value +end + +--------------------------------------------------------------------------------------------------- +-- Iterate over all values +local x, y, row, val +function Grid:iterate() + x, y, row, val = nil, nil, nil, nil + return function() + repeat + if not y then + x, row = next(self.cells, x) + if not row then return end + end + y, val = next(row, y) + until y + return x, y, val + end +end + +--------------------------------------------------------------------------------------------------- +-- Iterate over a rectangle shape +function Grid:rectangle(startX, startY, width, height, includeNil) + local x, y = startX, startY + return function() + while y <= startY + height do + while x <= startX + width do + x = x+1 + if self(x-1,y) ~= nil or includeNil then + return x-1, y, self(x-1,y) + end + end + x = startX + y = y+1 + end + return nil + end +end + +--------------------------------------------------------------------------------------------------- +-- Iterate over a line. Set noDiag to true to keep from traversing diagonally. +function Grid:line(startX, startY, endX, endY, noDiag, includeNil) + local dx = math.abs(endX - startX) + local dy = math.abs(endY - startY) + local x = startX + local y = startY + local incrX = endX > startX and 1 or -1 + local incrY = endY > startY and 1 or -1 + local err = dx - dy + local err2 = err*2 + local i = 1+dx+dy + local rx,ry,rv + local checkX = false + return function() + while i>0 do + rx,ry,rv = x,y,self(x,y) + err2 = err*2 + while true do + checkX = not checkX + if checkX == true or not noDiag then + if err2 > -dy then + err = err - dy + x = x + incrX + i = i-1 + if noDiag then break end + end + end + if checkX == false or not noDiag then + if err2 < dx then + err = err + dx + y = y + incrY + i = i-1 + if noDiag then break end + end + end + if not noDiag then break end + end + if rx == endX and ry == endY then i = 0 end + if rv ~= nil or includeNil then return rx,ry,rv end + end + return nil + end +end + +--------------------------------------------------------------------------------------------------- +-- Iterates over a circle of cells +function Grid:circle(cx, cy, r, includeNil) + local x,y + x = x or cx-r + return function() + repeat + y = y == nil and cy or y <= cy and y-1 or y+1 + while ((cx-x)*(cx-x)+(cy-y)*(cy-y)) >= r*r do + if x > cx+r then return nil end + x = x + (y < cy and 0 or 1) + y = cy + (y < cy and 1 or 0) + end + until self(x,y) ~= nil or includeNil + return x,y,self(x,y) + end +end + +--------------------------------------------------------------------------------------------------- +-- Cleans the grid of empty rows. +function Grid:clean() + for key,row in pairs(self.cells) do + if not next(row) then self.cells[key] = nil end + end +end + +--------------------------------------------------------------------------------------------------- +-- Clears the grid +function Grid:clear() + self.cells = {} +end + +--------------------------------------------------------------------------------------------------- +-- This makes calling the grid as a function act like Grid.get. +Grid.__call = Grid.get + +--------------------------------------------------------------------------------------------------- +-- Returns the grid class +return Grid + +-------------------------------------------------------------------------------------- +-- Copyright (c) 2011-2012 Casey Baxter + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. + +-- Except as contained in this notice, the name(s) of the above copyright holders +-- shall not be used in advertising or otherwise to promote the sale, use or +-- other dealings in this Software without prior written authorization. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/Loader.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,767 @@ +--------------------------------------------------------------------------------------------------- +-- -= Loader =- +--------------------------------------------------------------------------------------------------- + +-- Define path so lua knows where to look for files. +TILED_LOADER_PATH = TILED_LOADER_PATH or ({...})[1]:gsub("[%.\\/][Ll]oader$", "") .. '.' + +-- A cache to store tileset images so we don't load them multiple times. Filepaths are keys. +local cache = setmetatable({}, {__mode = "v"}) + +-- This stores cached images' original dimensions. Images are weak keys. +local cache_imagesize = setmetatable({}, {__mode="k"}) + +-- Decompresses gzip and zlib +local decompress = require(TILED_LOADER_PATH .. "external/deflatelua") + +-- XML parser +local xml = require(TILED_LOADER_PATH .. "external/xml") + +-- Base64 parser, Turns base64 strings into other data formats +local base64 = require(TILED_LOADER_PATH .. "Base64") + +-- Get the map classes +local Map = require(TILED_LOADER_PATH .. "Map") +local TileSet = require(TILED_LOADER_PATH .. "TileSet") +local TileLayer = require(TILED_LOADER_PATH .. "TileLayer") +local Tile = require(TILED_LOADER_PATH .. "Tile") +local Object = require(TILED_LOADER_PATH .. "Object") +local ObjectLayer = require(TILED_LOADER_PATH .. "ObjectLayer") + +local Loader = { + path = "", -- The path to tmx files. + filterMin = "nearest", -- The default min filter for image:setFilter() + filterMag = "nearest", -- The default mag filter for image:setFilter() + useSpriteBatch = true, -- The default setting for map.useSpriteBatch + drawObjects = true, -- The default setting for map.drawObjects. + saveDirectory = "Saved Maps", -- The directory to use for Loader.save() +} + +local filename -- The name of the tmx file +local fullpath -- The full path to the tmx file minus the name + +---------------------------------------------------------------------------------------------------- +-- Loads a Map from a tmx file and returns it. +function Loader.load(tofile) + + -- Get the raw path + fullpath = Loader.path .. tofile + + -- Process directory up + while string.find(fullpath, "[/\\][^/^\\]+[/\\]%.%.[/\\]") do + fullpath = string.gsub(fullpath, "[/\\][^/^\\]+[/\\]%.%.[/\\]", "/", 1) + end + + -- Get the file name + filename = string.match(fullpath, "[^/^\\]+$") + + -- Get the path to the file + fullpath = string.gsub(fullpath, "[^/^\\]+$", "") + + -- Find out if the file is in the game or save directory + if love.filesystem.exists(fullpath .. filename) then + elseif love.filesystem.exists(fullpath .. filename .. ".tmx") then + filename = filename .. ".tmx" + elseif love.filesystem.exists(Loader.saveDirectory.."/"..filename) then + fullpath = Loader.saveDirectory .. "/" + elseif love.filesystem.exists(Loader.saveDirectory .. "/" .. filename .. ".tmx") then + fullpath = Loader.saveDirectory .. "/" + filename = filename .. ".tmx" + else + error("Loader.load() Could not find find the file in these locations:\n" .. + "\nGame Directory: " .. fullpath .. filename .. + "\nGame Directory: " .. fullpath .. filename .. ".tmx" .. + "\nSave Directory: " .. Loader.saveDirectory.."/"..filename .. + "\nSave Directory: " .. Loader.saveDirectory.."/"..filename .. ".tmx") + end + + -- Read the file and parse it into a table + local t = love.filesystem.read(fullpath .. filename) + t = xml.string_to_table(t) + + -- Get the map and expand it + for _, v in pairs(t) do + if v.label == "map" then + return Loader._expandMap(fullpath .. filename, v) + end + end + + -- If we made this this far then there wasn't a map tag + error("Loader.load - No map found in file " .. fullpath .. filename) +end + +---------------------------------------------------------------------------------------------------- +-- Saves a map to a file. +function Loader.save(map, filename) + local mapx = Loader._compactMap(map) + if not love.filesystem.exists(Loader.saveDirectory) then + love.filesystem.mkdir(Loader.saveDirectory) + end + love.filesystem.write( Loader.saveDirectory .. "/" .. filename, xml.table_to_string(mapx) ) +end + +---------------------------------------------------------------------------------------------------- +-- Checks to see if a saved map exists +function Loader.savedMapExists(filename) + if love.filesystem.exists(Loader.saveDirectory.."/"..filename) then + return true + elseif love.filesystem.exists(Loader.saveDirectory .. "/" .. filename .. ".tmx") then + return true + end + return false +end + +---------------------------------------------------------------------------------------------------- +-- Private +---------------------------------------------------------------------------------------------------- + +-- Returns a new image from the filename. +function Loader._newImage(source) + return love.graphics.newImage(source), source:getWidth(), source:getHeight() +end + +---------------------------------------------------------------------------------------------------- +-- Checks to see if the table is a valid XML table +function Loader._checkXML(t) + assert(type(t) == "table", "Loader._checkXML - Passed value is not a table") + assert(t ~= Loader, "Loader._checkXML - Passed table is the Loader class. " .. + "You probably used a : instead of a .") + assert(t.label, "Loader._checkXML - Table does not contain a label value") + assert(t.xarg, "Loader._checkXML - Table does not contain an xarg table") +end + +---------------------------------------------------------------------------------------------------- +-- This is used to eliminate naming conflicts. It checks to see if the string is inside the table and +-- continues to rename it until there isn't a conflict. +function Loader._checkName(t, str) + while t[str] do + if string.find(str, "%(%d+%)$") == nil then str = str .. "(1)" end + str = string.gsub(str, "%(%d+%)$", function(a) return "(" .. + tonumber( string.sub(a, string.find(a, "%d+")) ) + 1 .. ")" end) + end + return str +end + +---------------------------------------------------------------------------------------------------- +-- Processes a properties table and returns it +function Loader._expandProperties(t) + + -- Do some checking + Loader._checkXML(t) + if t.label ~= "properties" then + error("Loader._expandProperties - Passed value is not a properties table") + end + + -- Create a properties table and populate it. Will attempt to convert the property to + -- the appropriate type. + local prop = {} + for _,v in pairs(t) do + Loader._checkXML(t) + if v.label == "property" then + if v.xarg.value == "true" then + prop[v.xarg.name] = true + elseif v.xarg.value == "false" then + prop[v.xarg.name] = false + else + prop[v.xarg.name] = tonumber(v.xarg.value) or v.xarg.value + end + end + end + + -- Return the properties + return prop +end + +---------------------------------------------------------------------------------------------------- +-- Process Map data from xml table +function Loader._expandMap(name, t) + + -- Do some checking + Loader._checkXML(t) + assert(t.label == "map", "Loader._expandMap - Passed table is not a map") + assert(t.xarg.width, t.xarg.height, t.xarg.tilewidth, t.xarg.tileheight, + "Loader._expandMap - Map data is corrupt") + + -- We'll use these for temporary storage + local map, tileset, tilelayer, objectlayer, props + local props = {} + + -- Get the properties + for _, v in ipairs(t) do + if v.label == "properties" then + props = Loader._expandProperties(v) + end + end + + -- Create the map from the settings + local map = Map:new(name, tonumber(t.xarg.width),tonumber(t.xarg.height), + tonumber(t.xarg.tilewidth), tonumber(t.xarg.tileheight), + t.xarg.orientation, props.atl_directory or fullpath, props) + + -- Apply the loader settings if atl_useSpriteBatch or atl_drawObjects was not set + map.useSpriteBatch = map.useSpriteBatch == nil and Loader.useSpriteBatch or map.useSpriteBatch + map.drawObjects = map.drawObjects == nil and Loader.drawObjects or map.drawObjects + + -- Now we fill it with the content + for _, v in ipairs(t) do + + -- Process TileSet + if v.label == "tileset" then + tileset = Loader._expandTileSet(v, map) + map.tilesets[tileset.name] = tileset + map:updateTiles() + end + + -- Process TileLayer + if v.label == "layer" then + tilelayer = Loader._expandTileLayer(v, map) + map.layers[tilelayer.name] = tilelayer + map.layerOrder[#map.layerOrder + 1] = tilelayer + end + + -- Process ObjectLayer + if v.label == "objectgroup" then + objectlayer = Loader._expandObjectLayer(v, map) + map.layers[objectlayer.name] = objectlayer + map.layerOrder[#map.layerOrder + 1] = objectlayer + end + + -- Process CustomLayer + if v.label == "customlayer" then + map:newCustomLayer(v.xarg.name) + for _, v2 in ipairs(v) do + if v2.label == "data" then + map(v.xarg.name).data = v2[1] + end + end + end + + end + + -- Return our map + return map +end + +---------------------------------------------------------------------------------------------------- +-- Process TileSet from xml table +function Loader._expandTileSet(t, map) + + -- Do some checking + Loader._checkXML(t) + assert(t.label == "tileset", "Loader._expandTileSet - Passed table is not a tileset") + + -- If the tileset is an external one then replace it as the tileset. The firstgid is + -- stored in the tileset tag in the original file while the rest of the tileset information + -- is stored in the external file. + if t.xarg.source then + local gid = t.xarg.firstgid + t = love.filesystem.read(map._directory .. t.xarg.source) + for _,v in pairs(xml.string_to_table(t)) do if v.label == "tileset" then t = v end end + t.xarg.firstgid = gid + end + + if not t.xarg.name or not t.xarg.tilewidth or not t.xarg.tileheight or not t.xarg.firstgid then + error("Loader._expandTileSet - Tileset data is corrupt") + end + + + -- Temporary storage + local image, imagePath, imageWidth, imageHeight, path, prop, tileSetProperties, trans + local tileProperties = {} + local offsetX, offsetY = 0, 0 + + -- Process elements + for _, v in ipairs(t) do + -- Process image + if v.label == "image" then + imagePath = v.xarg.source + path = map._directory .. v.xarg.source + -- Process directory up + while string.find(path, "[^/^\\]+[/\\]%.%.[/\\]") do + path = string.gsub(path, "[^/^\\]+[/\\]%.%.[/\\]", "", 1) + end + -- If the image is in the cache then load it + if cache[path] then + image = cache[path] + imageWidth = cache_imagesize[image].width + imageHeight = cache_imagesize[image].height + -- Else load it and store in the cache + else + image = love.image.newImageData(path) + -- transparent color + if v.xarg.trans then + trans = { tonumber( "0x" .. v.xarg.trans:sub(1,2) ), + tonumber( "0x" .. v.xarg.trans:sub(3,4) ), + tonumber( "0x" .. v.xarg.trans:sub(5,6) )} + image:mapPixel( function(x,y,r,g,b,a) + return r,g,b, (trans[1] == r and trans[2] == g and trans[3] ==b and 0) or a end + ) + end + -- Set the image information + image, imageWidth, imageHeight = Loader._newImage(image) + image:setFilter(Loader.filterMin, Loader.filterMag) + -- Cache the created image + cache[path] = image + cache_imagesize[image] = {width = imageWidth, height = imageHeight} + end + end + + -- Process tile properties + if v.label == "tile" then + for _, v2 in ipairs(v) do + if v2.label == "properties" then + -- Store the property. We must increase the id the starting gid + if not v.xarg.id then error(v.xarg.id) end + tileProperties[v.xarg.id+t.xarg.firstgid] = Loader._expandProperties(v2) + end + end + end + + -- Process tile set properties + if v.label == "properties" then + tileSetProperties = Loader._expandProperties(v) + end + + -- Get the tile offset if there is one. + if v.label == "tileoffset" then + offsetX, offsetY = tonumber(v.xarg.x or 0), tonumber(v.xarg.y or 0) + end + + end + + -- Make sure that an image was loaded + assert(image, "Loader._expandTileSet - Tileset did not contain an image") + + -- Return the TileSet + local tileset = TileSet:new(image, imagePath, Loader._checkName(map.tilesets, t.xarg.name), + tonumber(t.xarg.tilewidth), tonumber(t.xarg.tileheight), + tonumber(imageWidth), tonumber(imageHeight), + tonumber(t.xarg.firstgid), tonumber(t.xarg.spacing), tonumber(t.xarg.margin), + offsetX, offsetY, trans, tileProperties, tileSetProperties) + return tileset +end + +---------------------------------------------------------------------------------------------------- +-- Process TileLayer from xml table +function Loader._expandTileLayer(t, map) + + -- Do some checking + Loader._checkXML(t) + assert(t.label == "layer", "Loader._expandTileLayer - Passed table is not a tileset") + + -- Process elements + local data, properties + for _, v in ipairs(t) do + Loader._checkXML(t) + + -- Process data + if v.label == "data" then + data = Loader._expandTileLayerData(v) + end + + -- Process TileLayer properties + if v.label == "properties" then + properties = Loader._expandProperties(v) + end + end + + -- Create the new layer + local layer = TileLayer:new(map, t.xarg.name, t.xarg.opacity, properties) + + -- Set the visibility + layer.visible = not (t.xarg.visible == "0") + + -- Populate the tiles and return the layer + layer:_populate(data) + return layer +end + +---------------------------------------------------------------------------------------------------- +-- Process TileLayer data from xml table +function Loader._expandTileLayerData(t) + + -- Do some checking + Loader._checkXML(t) + assert(t.label == "data", "Loader._expandTileLayerData - Passed table is not TileLayer data") + + local data = {} + + -- If encoded by comma seperated value (csv) then cut each value out and put it into a table. + if t.xarg.encoding == "csv" then + string.gsub(t[1], "[%-%d]+", function(a) data[#data+1] = tonumber(a) or 0 end) + end + + -- Base64 encoding. See base64.lua for more details. + if t.xarg.encoding == "base64" then + + -- If a compression method is used + if t.xarg.compression == "gzip" or t.xarg.compression == "zlib" then + -- Select the appropriate function + local decomp = t.xarg.compression == "gzip" and decompress.gunzip or decompress.inflate_zlib + -- Decompress the string into bytes + local bytes = {} + decomp({input = base64.decode("string", t[1]), output = function(b) bytes[#bytes+1] = b end}) + -- Glue the bytes into ints + for i=1,#bytes,4 do + data[#data+1] = base64.glueInt(bytes[i],bytes[i+1],bytes[i+2],bytes[i+3]) + end + -- If there is no compression then just convert to ints + else + data = base64.decode("int", t[1]) + end + end + + -- If there is no encoding then the file is probably saved as XML + if t.xarg.encoding == nil then + for k,v in ipairs(t) do + if v.label == "tile" then + data[#data+1] = tonumber(v.xarg.gid) + end + end + end + + -- Return the data + return data +end + +---------------------------------------------------------------------------------------------------- +-- Process ObjectLayer from xml table +function Loader._expandObjectLayer(t, map) + + -- Do some checking + Loader._checkXML(t) + if t.label ~= "objectgroup" then + error("Loader._expandObjectLayer - Passed table is not ObjectLayer data") + end + + -- Tiled stores colors in hexidecimal format that looks like "#FFFFFF" + -- We need go convert them into base 10 RGB format + if t.xarg.color == nil then t.xarg.color = "#000000" end + local color = { tonumber( "0x" .. t.xarg.color:sub(2,3) ), + tonumber( "0x" .. t.xarg.color:sub(4,5) ), + tonumber( "0x" .. t.xarg.color:sub(6,7) )} + + -- Create a new layer + local layer = ObjectLayer:new(map, Loader._checkName(map.layers, t.xarg.name), color, + t.xarg.opacity) + + -- Process elements + local objects = {} + local prop, obj, poly + for _, v in ipairs(t) do + + -- Process objects + local obj + if v.label == "object" then + obj = Object:new(layer, v.xarg.name, v.xarg.type, tonumber(v.xarg.x), + tonumber(v.xarg.y), tonumber(v.xarg.width), + tonumber(v.xarg.height), tonumber(v.xarg.gid) ) + objects[#objects+1] = obj + for _, v2 in ipairs(v) do + + -- Process object properties + if v2.label == "properties" then + obj.properties = Loader._expandProperties(v2) + end + + -- Process polyline objects + local polylineFunct = function(a) + obj.polyline[#obj.polyline+1] = tonumber(a) or 0 + end + if v2.label == "polyline" then + obj.polyline = {} + string.gsub(v2.xarg.points, "[%-%d]+", polylineFunct) + end + + -- Process polyline objects + local polygonFunct = function(a) + obj.polygon[#obj.polygon+1] = tonumber(a) or 0 + end + if v2.label == "polygon" then + obj.polygon = {} + string.gsub(v2.xarg.points, "[%-%d]+", polygonFunct) + end + + end + obj:updateDrawInfo() + end + + -- Process properties + if v.label == "properties" then + prop = Loader._expandProperties(v) + end + + end + + -- Set the properties and object tables + layer.properties = prop or {} + layer.objects = objects + + -- Set the visibility + layer.visible = not (t.xarg.visible == "0") + + -- Return the layer + return layer +end + +---------------------------------------------------------------------------------------------------- +-- Compact a Map +function Loader._compactMap(map) + + -- Set the ATL properties + map.properties.atl_directory = map._directory + map.properties.atl_useSpritebatch = map.useSpriteBatch + map.properties.atl_viewX = map.viewX + map.properties.atl_viewY = map.viewY + map.properties.atl_viewW = map.viewW + map.properties.atl_viewH = map.viewH + map.properties.atl_viewScaling = map.viewScaling + map.properties.atl_viewPadding = map.viewPadding + map.properties.atl_drawObjects = map.drawObjects + + -- <map> + local mapx = {label="map", xarg = { + version = "1.0", + orientation = map.orientation, + width = map.width, + height = map.height, + tilewidth = map.tileWidth, + tileheight = map.tileHeight, + }} + + -- <tileset> + for k,tileset in pairs(map.tilesets) do + mapx[#mapx+1] = Loader._compactTileSet(tileset) + end + + local layer, layerx + for i = 1,#map.layerOrder do + layer = map.layerOrder[i] + + -- <layer> + if layer.class == "TileLayer" then + mapx[#mapx+1] = Loader._compactTileLayer(layer) + + -- <objectgroup> + elseif layer.class == "ObjectLayer" then + mapx[#mapx+1] = Loader._compactObjectLayer(layer) + + -- <custom> + elseif layer.encode then + local layerx = {label="customlayer", xarg={name=layer.name}} + layerx[1] = {label="data", xarg={}} + layerx[1][1] = layer:encode() + mapx[#mapx+1] = layerx + end + + end + + -- <properties> + mapx[#mapx+1] = Loader._compactProperties(map.properties) + return mapx +end + +---------------------------------------------------------------------------------------------------- +-- Compact a TileSet +function Loader._compactTileSet(tileset) + + -- <Tileset> + local tilesetx = {label="tileset", xarg = { + firstgid = tileset.firstgid, + name = tileset.name, + tilewidth = tileset.tileWidth, + tileheight = tileset.tileHeight, + spacing = tileset.spacing, + margin = tileset.margin + }} + + -- <image> + tilesetx[1] = {label="image", xarg = { + source = tileset.imagePath, + trans = tileset.trans, + }} + + -- <tileoffset> + if tileset.offsetX ~= 0 or tileset.offsetY ~= 0 then + tilesetx[2] = {label="tileoffset", xarg={x = tileset.offsetX, y = tileset.offsetY}} + end + + -- <properties> + if next(tileset.properties) then + tilesetx[#tilesetx+1] = Loader._compactProperties(tileset.properties) + end + + -- <tile> + if next(tileset.tileProperties) then + local tilex + for k,props in pairs(tileset.tileProperties) do + tilex = {label = "tile", xarg = {id = k - tileset.firstgid}} + tilex[1] = Loader._compactProperties(props) + tilesetx[#tilesetx+1] = tilex + end + end + + return tilesetx +end + +---------------------------------------------------------------------------------------------------- +-- Compact a TileLayer +function Loader._compactTileLayer(layer) + + -- Set the ATL properties + layer.properties.atl_useSpriteBatch = layer.useSpriteBatch + layer.properties.atl_parallaxX = layer.parallaxX + layer.properties.atl_parallaxY = layer.parallaxY + layer.properties.atl_offsetX = layer.offsetX + layer.properties.atl_offsetY = layer.offsetY + + -- <layer> + local layerx = {label="layer", xarg = { + name = layer.name, + opacity = layer.opacity, + visible = layer.visible and 1 or 0, + width = layer.map.width, + height = layer.map.height, + }} + + -- <data> + local tiles = {} + local flipBits = 2^29 + local flipValue + for x, y, tile in layer:rectangle(0, 0, layer.map.width-1, layer.map.height-1, true) do + if tile then + flipValue = layer._flippedTiles(x,y) and layer._flippedTiles(x,y) * flipBits or 0 + tiles[#tiles+1] = tile.id + flipValue + else + tiles[#tiles+1] = 0 + end + end + layerx[1] = {label ="data", xarg = {encoding="csv"}, [1] = table.concat(tiles,",")} + + -- <properties> + if next(layer.properties) then + layerx[2] = Loader._compactProperties(layer.properties) + end + + return layerx +end + +---------------------------------------------------------------------------------------------------- +-- Compact an ObjectLayer +function Loader._compactObjectLayer(layer) + + -- <objectgroup> + local layerx = {label = "objectgroup", xarg = { + name = layer.name, + color = string.format("#%x%x%x", layer.color[1], layer.color[2], layer.color[3]), + opacity = layer.opacity, + visible = layer.visible and 1 or 0, + }} + + -- <object> + for i = 1,#layer.objects do + object = layer.objects[i] + layerx[#layerx+1] = Loader._compactObject(layer.objects[i]) + end + + -- <properties> + if next(layer.properties) then + layerx[#layerx+1] = Loader._compactProperties(layer.properties) + end + + return layerx +end + +---------------------------------------------------------------------------------------------------- +-- Compact an Object +function Loader._compactObject(object) + + -- <object> + local objectx = {label="object", xarg = { + name = object.name, + type = object.type, + x = object.x, + y = object.y, + width = object.width, + height = object.height, + gid = object.gid, + visible = object.visible, + }} + + -- <polyline> + if object.polyline then + local polylinex = {label="polyline", xarg = {}} + local points = {} + for i = 1,#object.polyline,2 do + points[#points+1] = object.polyline[i] .. "," .. object.polyline[i+1] + end + polylinex.xarg.points = table.concat(points, " ") + objectx[#objectx+1] = polylinex + end + + -- <polygon> + if object.polygon then + local polygonx = {label="polygon", xarg = {}} + local points = {} + for i = 1,#object.polygon,2 do + points[#points+1] = object.polygon[i] .. "," .. object.polygon[i+1] + end + polygonx.xarg.points = table.concat(points, " ") + objectx[#objectx+1] = polygonx + end + + -- <properties> + if next(object.properties) then + objectx[#objectx+1] = Loader.compactProperties(object.properties) + end + + return objectx +end + +---------------------------------------------------------------------------------------------------- +-- Compact Properties +function Loader._compactProperties(prop) + + if not prop then return end + + -- <properties> + local propx = {label="properties", xarg={}} + + -- <property> + for k,v in pairs(prop) do + propx[#propx+1] = {label="property", xarg={name=k, value=v}} + end + + return propx +end + +---------------------------------------------------------------------------------------------------- +-- Return the loader +return Loader + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]] + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/Map.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,357 @@ +--------------------------------------------------------------------------------------------------- +-- -= Map =- +--------------------------------------------------------------------------------------------------- +-- Setup + +-- Import the other classes +TILED_LOADER_PATH = TILED_LOADER_PATH or ({...})[1]:gsub("[%.\\/][Mm]ap$", "") .. '.' +local Tile = require( TILED_LOADER_PATH .. "Tile") +local TileSet = require( TILED_LOADER_PATH .. "TileSet") +local TileLayer = require( TILED_LOADER_PATH .. "TileLayer") +local Object = require( TILED_LOADER_PATH .. "Object") +local ObjectLayer = require( TILED_LOADER_PATH .. "ObjectLayer") + +-- Localize some functions so they are faster +local love = love +local table = table +local ceil = math.ceil +local floor = math.floor + +-- Make our map class +local Map = {class = "Map"} +Map.__index = Map + +--------------------------------------------------------------------------------------------------- +-- Returns a new map +function Map:new(name, width, height, tileWidth, tileHeight, orientation, path, prop) + + -- Our map + local map = setmetatable({}, Map) + prop = prop or {} + + -- Public: + map.name = name or "Unnamed Map" -- Name of the map + map.width = width or 0 -- Width of the map in tiles + map.height = height or 0 -- Height of the map in tiles + map.tileWidth = tileWidth or 0 -- Width in pixels of each tile + map.tileHeight = tileHeight or 0 -- Height in pixels of each tile + map.orientation = orientation or "orthogonal" -- Type of map. orthogonal or isometric + map.properties = prop or {} -- Properties of the map set by Tiled + map.useSpriteBatch = prop.atl_useSpriteBatch -- True = TileLayers use sprite batches + map.visible = true -- False = the map will not be drawn + map.drawObjects = prop.atl_drawObjects -- True = object layers will be drawn + + map.viewX = prop.atl_viewX or 0 -- X coord of the viewing screen. + map.viewY = prop.atl_viewY or 0 -- Y coord of the viewing screen. + map.viewW = prop.atl_viewW or love.graphics.getWidth() -- The width of the viewing screen + map.viewH = prop.atl_viewH or love.graphics.getHeight() -- The height of the viewing screen + map.viewScaling = 1 -- The game scaling + map.viewPadding = 10 -- Padding for the view + + map.offsetX = prop.atl_offsetX or 0 -- Drawing offset X + map.offsetY = prop.atl_offsetY or 0 -- Drawing offset Y + + map.layers = {} -- Layers of the map indexed by name + map.tilesets = {} -- Tilesets indexed by name + map.tiles = {} -- Tiles indexed by id + map.layerOrder = {} -- The order of the layers. Callbacks are called in this order. + + -- Private: + map._widestTile = 0 -- The widest tile on the map. + map._highestTile = 0 -- The tallest tile on the map. + map._forceRedraw = false -- If true then the next redraw is forced + map._previousUseSpriteBatch = false -- The previous useSpiteBatch. + map._tileClipboard = nil -- The value that stored for tile copying and pasting. + map._directory = path -- The directory the map is in + + -- Return the new map + return map +end + +--------------------------------------------------------------------------------------------------- +-- Creates a new tileset and adds it to the map. The map will then auto-update its tiles. +function Map:newTileSet(...) + if not name then + error("Map:newTileSet - The name parameter is invalid") + end + self.tilesets[name] = TileSet:new(...) + self:updateTiles() + return self.tilesets[name] +end + +--------------------------------------------------------------------------------------------------- +-- Creates a new TileLayer and adds it to the map. The position parameter is the position to insert +-- the layer into the layerOrder. +function Map:newTileLayer(position, ...) + if self.layers[name] then + error( string.format("Map:newTileLayer - The layer name \"%s\" already exists.", name) ) + end + self.layers[name] = TileLayer:new(self, ...) + table.insert(self.layerOrder, position or #self.layerOrder + 1, self.layers[name]) + return self.layers[name] +end + +--------------------------------------------------------------------------------------------------- +-- Creates a new ObjectLayer and inserts it into the map +function Map:newObjectLayer(position, ...) + if self.layers[name] then + error( string.format("Map:newObjectLayer - The layer name \"%s\" already exists.", name) ) + end + self.layers[name] = ObjectLayer:new(self, ...) + table.insert(self.layerOrder, position or #self.layerOrder + 1, self.layers[name]) + return self.layers[name] +end + +--------------------------------------------------------------------------------------------------- +-- Add a custom layer to the map. You can include a predefined layer table or one will be created. +function Map:newCustomLayer(name, position, layer) + if self.layers[name] then + error( string.format("Map:newCustomLayer - The layer name \"%s\" already exists.", name) ) + end + self.layers[name] = layer or {name=name} + self.layers[name].class = "CustomLayer" + table.insert(self.layerOrder, position or #self.layerOrder + 1, self.layers[name]) + return self.layers[name] +end + +--------------------------------------------------------------------------------------------------- +-- Cuts tiles out of tilesets and stores them in the tiles tables under their id +-- Call this after the tilesets are set up +function Map:updateTiles() + self.tiles = {} + self._widestTile = 0 + self._highestTile = 0 + for _, ts in pairs(self.tilesets) do + if ts.tileWidth > self._widestTile then self._widestTile = ts.tileWidth end + if ts.tileHeight > self._highestTile then self._highestTile = ts.tileHeight end + for id, val in pairs(ts:getTiles()) do + self.tiles[id] = val + end + end +end + +--------------------------------------------------------------------------------------------------- +-- Forces the map to redraw the sprite batches. +function Map:forceRedraw() + self._forceRedraw = true +end + +--------------------------------------------------------------------------------------------------- +-- Performs a callback on all map layers. +local layer +function Map:callback(cb, ...) + if cb == "draw" then self:_updateTileRange() end + for i = 1, #self.layerOrder do + layer = self.layerOrder[i] + if layer[cb] then layer[cb](layer, ...) end + end +end + +--------------------------------------------------------------------------------------------------- +-- Draw the map. +function Map:draw() + if self.visible then self:callback("draw") end +end + +--------------------------------------------------------------------------------------------------- +-- Returns the position of the layer inside the map's layerOrder. Can be the layer name or table. +function Map:layerPosition(layer) + if type(layer) == "string" then layer = self.layers[layer] end + for i = 1,#self.layerOrder do + if self.layerOrder[i] == layer then return i end + end +end + +--------------------------------------------------------------------------------------------------- +-- Returns the position of the layer inside the map's layerOrder. The passed layers can be the +-- layer name, the layer tables themselves, or the layer positions. +function Map:swapLayers(layer1, layer2) + if type(layer1) ~= "number" then layer1 = self:layerPosition(layer1) end + if type(layer2) ~= "number" then layer2 = self:layerPosition(layer2) end + local bubble = self.layers[layer1] + self.layers[layer1] = self.layers[layer2] + self.layers[layer2] = bubble +end + +--------------------------------------------------------------------------------------------------- +-- Removes a layer from the map +function Map:removeLayer(layer) + if type(layer) ~= "number" then layer = self:layerPosition(layer) end + layer = table.remove(self.layerOrder, layer) + self.layers[layer.name] = nil + return layer +end + +--------------------------------------------------------------------------------------------------- +-- Turns an isometric location into a world location. The unit length for isometric tiles is always +-- the map's tileHeight. This is both for width and height. +local h, tw, th +function Map:fromIso(x, y) + h, tw, th = self.height, self.tileWidth, self.tileHeight + return ((x-y)/th + h - 1)*tw/2, (x+y)/2 +end + +--------------------------------------------------------------------------------------------------- +-- Turns a world location into an isometric location +local ix, iy +function Map:toIso(a, b) + a, b = a or 0, b or 0 + h, tw, th = self.height, self.tileWidth, self.tileHeight + ix = b - (h-1)*th/2 + (a*th)/tw + iy = 2*b - ix + return ix, iy +end + +--------------------------------------------------------------------------------------------------- +-- Sets the draw range +function Map:setDrawRange(x,y,w,h) + self.viewX, self.viewY, self.viewW, self.viewH = x, y, w, h + self.viewPadding = 0 + self.viewScaling = 1 +end + +--------------------------------------------------------------------------------------------------- +-- Automatically sets the draw range to fit the display +function Map:autoDrawRange(tx, ty, scale, pad) + tx, ty, scale, pad = tx or 0, ty or 0, scale or 1, pad or 0 + self.viewX = -tx + self.viewY = -ty + self.viewW = love.graphics.getWidth() + self.viewH = love.graphics.getHeight() + self.viewScaling = scale > 0.001 and scale or 0.001 + self.viewPadding = pad +end + +--------------------------------------------------------------------------------------------------- +-- Returns the normal draw range +function Map:getDrawRange() + return self.viewX - self.viewPadding, self.viewY - self.viewPadding, + self.viewW/self.viewScaling + self.viewPadding*2, + self.viewH/self.viewScaling + self.viewPadding*2 +end + +---------------------------------------------------------------------------------------------------- +-- Private Functions +---------------------------------------------------------------------------------------------------- +-- This is an internal function used to update the map's _tileRange, _previousTileRange, and +-- _specialRedraw +local x1, y1, x2, y2, highOffset, widthOffset, tr, ptr, layer +function Map:_updateTileRange() + + -- Offset to make sure we can always draw the highest and widest tile + heightOffset = self._highestTile - self.tileHeight + widthOffset = self._widestTile - self.tileWidth + + -- Go through each layer + for i = 1,#self.layerOrder do + layer = self.layerOrder[i] + + -- If the layer is a TileLayer + if layer.class == "TileLayer" then + + -- Get the draw range. + x1 = self.viewX * layer.parallaxX - self.viewPadding + layer.offsetX + y1 = self.viewY * layer.parallaxY - self.viewPadding + layer.offsetY + x2 = self.viewW/self.viewScaling + self.viewPadding*2 + y2 = self.viewH/self.viewScaling + self.viewPadding*2 + + -- Apply the offset + x1 = x1 - self.offsetX - layer.offsetX + y1 = y1 - self.offsetY - layer.offsetY + + -- Calculate the _tileRange for orthogonal tiles + if self.orientation == "orthogonal" then + + -- Limit the drawing range. We must make sure we can draw the tiles that are bigger + -- than the self's tileWidth and tileHeight. + if x1 and y1 and x2 and y2 then + x2 = ceil(x2/self.tileWidth) + y2 = ceil((y2+heightOffset)/self.tileHeight) + x1 = floor((x1-widthOffset)/self.tileWidth) + y1 = floor(y1/self.tileHeight) + + -- Make sure that we stay within the boundry of the map + x1 = x1 > 0 and x1 or 0 + y1 = y1 > 0 and y1 or 0 + x2 = x2 < self.width and x2 or self.width - 1 + y2 = y2 < self.height and y2 or self.height - 1 + + else + -- If the drawing range isn't defined then we draw all the tiles + x1, y1, x2, y2 = 0, 0, self.width-1, self.height-1 + end + + -- Calculate the _tileRange for isometric tiles. + else + -- If the drawRange is set + if x1 and y1 and x2 and y2 then + x1, y1 = self:toIso(x1-self._widestTile,y1) + x1, y1 = ceil(x1/self.tileHeight), ceil(y1/self.tileHeight)-1 + x2 = ceil((x2+self._widestTile)/self.tileWidth) + y2 = ceil((y2+heightOffset)/self.tileHeight) + -- else draw everything + else + x1 = 0 + y1 = 0 + x2 = self.width - 1 + y2 = self.height - 1 + end + end + + -- Assign the new values to the tile range + tr, ptr = layer._tileRange, layer._previousTileRange + ptr[1], ptr[2], ptr[3], ptr[4] = tr[1], tr[2], tr[3], tr[4] + tr[1], tr[2], tr[3], tr[4] = x1, y1, x2, y2 + + -- If the tile range or useSpriteBatch is different than the last frame then we need to + -- update its sprite batches. + layer._redraw = self.useSpriteBatch ~= self._previousUseSpriteBatch or + self._forceRedraw or + tr[1] ~= ptr[1] or + tr[2] ~= ptr[2] or + tr[3] ~= ptr[3] or + tr[4] ~= ptr[4] + end + end + + -- Set the previous useSpritebatch + self._previousUseSpriteBatch = self.useSpriteBatch + + -- Reset the forced special redraw + self._forceRedraw = false +end + +--------------------------------------------------------------------------------------------------- +-- Calling the map as a function will return the layer +function Map:__call(layerName) + return self.layers[layerName] +end + +--------------------------------------------------------------------------------------------------- +-- Returns the Map class +return Map + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/Object.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,239 @@ +--------------------------------------------------------------------------------------------------- +-- -= Object =- +--------------------------------------------------------------------------------------------------- +-- Setup +local Object = {class = "Object"} +Object.__index = Object + +--------------------------------------------------------------------------------------------------- + +-- Returns a new Object +function Object:new(layer, name, type, x, y, width, height, gid, prop) + + -- Public: + local obj = setmetatable({}, Object) + obj.layer = layer -- The layer that the object belongs to + obj.name = name or "" -- Name of the object + obj.type = type or "" -- Type of the object + obj.x = x or 0 -- X location on the map + obj.y = y or 0 -- Y location on the map + obj.width = width or 0 -- Object width in tiles + obj.height = height or 0 -- Object height in tiles + obj.gid = gid -- The object's associated tile. If false an outline will be drawn. + obj.visible = true -- If false then the object will not be drawn + obj.properties = prop or {} -- Properties set by tiled. + obj.polygon = nil -- Polygon points. Set by the Loader if defined in Tiled. + obj.polyline = nil -- Polyline points. Set by the Loader if defined in Tiled. + + -- drawInfo stores values needed to actually draw the object. You can either set these yourself + -- or use updateDrawInfo to do it automatically. + obj.drawInfo = { + + -- x and y are the drawing location of the object. This is different than the object's x and + -- y value which is the object's placement on the map. + x = 0, -- The x draw location + y = 0, -- The y draw location + + -- These limit the drawing of the object. If the object falls out of the bounds of + -- the map's drawRange then the object will not be drawn. + left = 0, -- The leftmost point on the object + right = 0, -- The rightmost point on the object + top = 0, -- The highest point on the object + bottom = 0, -- The lowest point on the object + + -- The order to draw the object in relation to other objects. Usually equal to bottom. + order = 0, + + -- In addition to this, other drawing information can be stored in the numerical + -- indicies which is context sensitive to the map's orientation, if the object has a gid, or + -- of the object is a polygon or polyline object. + } + + -- Update the draw info + Object.updateDrawInfo(obj) + + -- Return our object + return obj +end + +--------------------------------------------------------------------------------------------------- +-- Updates the draw information. Call this every time the object moves or changes size. +function Object:updateDrawInfo() + local di = self.drawInfo + local map = self.layer.map + + if self.polygon or self.polyline then + -- Reset the draw info + self.drawInfo = {} + di = self.drawInfo + -- Set the box to the first vertex + local vertexes = self.polygon or self.polyline + -- Create the draw information for each vertex + for k,v in ipairs(vertexes) do + if k%2 == 1 then + if map.orientation == "isometric" then + di[k], di[k+1] = map:fromIso(self.x+vertexes[k], self.y+vertexes[k+1]) + else + di[k], di[k+1] = self.x+vertexes[k], self.y+vertexes[k+1] + end + di[k], di[k+1] = di[k] - map.offsetX, di[k+1] - map.offsetY + end + end + -- Set the start draw location + di.x, di.y = di[1], di[2] + -- Prime the bounds + di.left, di.right, di.top, di.bottom = di.x, di.x, di.y, di.y + -- Go through each vertex and find the highest and lowest values for the bounds. + for k,v in ipairs(di) do + -- if it's odd then it's an x value + if k%2 == 1 then + if v < di.left then di.left = v end + if v > di.right then di.right = v end + -- if it's even it's a y value + else + if v < di.top then di.top = v end + if v > di.bottom then di.bottom = v end + end + end + di.order = di.bottom + + -- Isometric map + elseif map.orientation == "isometric" then + -- Is a tile object + if self.gid then + local t = map.tiles[self.gid] + local tw, th = t.width, t.height + di.x, di.y = map:fromIso(self.x, self.y) + di.x, di.y = di.x - map.offsetX, di.y - map.offsetY + di.order = di.y + di.x, di.y = di.x - map.tileWidth/2, di.y - th + di.left, di.right, di.top, di.bottom = di.x, di.x+tw, di.y , di.y +th + -- Is not a tile object + else + di[1], di[2] = map:fromIso(self.x, self.y) + di[3], di[4] = map:fromIso(self.x + self.width, self.y) + di[5], di[6] = map:fromIso(self.x + self.width, self.y + self.height) + di[7], di[8] = map:fromIso(self.x, self.y + self.height) + for i =1,7,2 do + di[i] = di[i] - map.offsetX + di[i+1] = di[i+1] - map.offsetY + end + di.left, di.right, di.top, di.bottom = di[7], di[3], di[2], di[6] + di.order = 1 + end + + -- Orthogonal map + else + -- Is a tile object + if self.gid then + local t = map.tiles[self.gid] + local tw, th = t.width, t.height + di.x, di.y = self.x - map.offsetX, self.y - map.offsetY + di.order = di.y + di.y = di.y - th + di.left, di.top, di.right, di.bottom = di.x, di.y, di.x+tw, di.y+th + -- Is not a tile object + else + di.x, di.y = self.x - map.offsetX, self.y - map.offsetY + di[1], di[2] = di.x, di.y + di[3], di[4] = self.width > 20 and self.width or 20, self.height > 20 and self.height or 20 + di.left, di.top, di.right, di.bottom = di.x, di.y , di.x+di[3], di.y +di[4] + di.order = 1 + end + end +end + +--------------------------------------------------------------------------------------------------- +-- Draw the object. The passed color is the color of the object layer the object belongs to. +function Object:draw(x, y, r, g, b, a) + + if not self.visible then return end + + local di = self.drawInfo + love.graphics.setLineWidth(2) + + -- The object is a polyline. + if self.polyline then + love.graphics.push() + love.graphics.translate(0,1) + love.graphics.setColor(0, 0, 0, a) + love.graphics.line( unpack(di) ) + love.graphics.pop() + love.graphics.setColor(r, g, b, a) + love.graphics.line( unpack(di) ) + + -- The object is a polygon. + elseif self.polygon then + love.graphics.push() + love.graphics.translate(0,1) + love.graphics.setColor(0, 0, 0, a) + love.graphics.polygon( "line", unpack(di) ) + love.graphics.pop() + love.graphics.setColor(r,g,b,a) + love.graphics.polygon( "line", unpack(di) ) + + -- The object is a tile object. Draw the tile. + elseif self.gid then + local tile = self.layer.map.tiles[self.gid] + tile:draw(di.x, di.y - tile.height) + + -- Map is isometric. Draw a parallelogram. + elseif self.layer.map.orientation == "isometric" then + love.graphics.setColor(r, g, b, a/5) + love.graphics.polygon("fill", unpack(di)) + + love.graphics.push() + love.graphics.translate(0,1) + love.graphics.setColor(0, 0, 0, a) + love.graphics.polygon("line", unpack(di)) + love.graphics.pop() + + love.graphics.setColor(r,g,b,a) + love.graphics.polygon("line", unpack(di)) + + -- Map is orthogonal. Draw a rectangle. + else + love.graphics.setColor(r, g, b, a/5) + love.graphics.rectangle("fill", unpack(di)) + + love.graphics.setColor(0, 0, 0, a) + love.graphics.push() + love.graphics.translate(1,1) + love.graphics.rectangle("line", unpack(di)) + love.graphics.print(self.name, di.x, di.y-20) + love.graphics.pop() + + love.graphics.setColor(r,g,b,a) + love.graphics.rectangle("line", unpack(di)) + love.graphics.print(self.name, di.x, di.y-20) + end +end + +--------------------------------------------------------------------------------------------------- +-- Returns the Object class +return Object + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/ObjectLayer.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,141 @@ +--------------------------------------------------------------------------------------------------- +-- -= ObjectLayer =- +--------------------------------------------------------------------------------------------------- +-- Setup +TILED_LOADER_PATH = TILED_LOADER_PATH or ({...})[1]:gsub("[%.\\/][Oo]bject[Ll]ayer$", "") .. '.' +local love = love +local unpack = unpack +local pairs = pairs +local ipairs = ipairs +local Object = require( TILED_LOADER_PATH .. "Object") +local ObjectLayer = {class = "ObjectLayer"} +local grey = {128,128,128,255} +ObjectLayer.__index = ObjectLayer + +--------------------------------------------------------------------------------------------------- +-- Creates and returns a new ObjectLayer +function ObjectLayer:new(map, name, color, opacity, prop) + + -- Create a new table for our object layer and do some error checking. + local layer = setmetatable({}, ObjectLayer) + + layer.map = map -- The map this layer belongs to + layer.name = name or "Unnamed ObjectLayer" -- The name of this layer + layer.color = color or grey -- The color theme + layer.opacity = opacity or 1 -- The opacity + layer.objects = {} -- The layer's objects indexed numerically + layer.properties = prop or {} -- Properties set by Tiled. + layer.visible = true -- If false then the layer will not be drawn + + -- Return the new object layer + return layer +end + +--------------------------------------------------------------------------------------------------- +-- Creates a new object, automatically inserts it into the layer, and then returns it +function ObjectLayer:newObject(name, type, x, y, width, height, gid, prop) + local obj = Object:new(self, name, type, x, y, width, height, gid, prop) + self.objects[#self.objects+1] = obj + return obj +end + +--------------------------------------------------------------------------------------------------- +-- Sorting function for objects. We'll use this below in ObjectLayer:draw() +local function drawSort(o1, o2) + return o1.drawInfo.order < o2.drawInfo.order +end + +--------------------------------------------------------------------------------------------------- +-- Draws the object layer. The way the objects are drawn depends on the map orientation and +-- if the object has an associated tile. It tries to draw the objects as closely to the way +-- Tiled does it as possible. +local di, dr, drawList, r, g, b, a, line, obj, offsetX, offsetY +function ObjectLayer:draw() + + -- Early exit if the layer is not visible. + if not self.visible then return end + + -- Exit if objects are not suppose to be drawn + if not self.map.drawObjects then return end + + di = nil -- The draw info + dr = {self.map:getDrawRange()} -- The drawing range. [1-4] = x, y, width, height + drawList = {} -- A list of the objects to be drawn + r,g,b,a = 255, 255, 255, 255 ---love.graphics.getColor() -- Save the color so we can set it back at the end + line = love.graphics.getLineWidth() -- Save the line width too + self.color[4] = 255 * self.opacity -- Set the opacity + + -- Put only objects that are on the screen in the draw list. If the screen range isn't defined + -- add all objects + for i = 1, #self.objects do + obj = self.objects[i] + obj:updateDrawInfo() + di = obj.drawInfo + if dr[1] and dr[2] and dr[3] and dr[4] then + if di.right > dr[1]-20 and + di.bottom > dr[2]-20 and + di.left < dr[1]+dr[3]+20 and + di.top < dr[2]+dr[4]+20 then + drawList[#drawList+1] = obj + end + else + drawList[#drawList+1] = obj + end + end + + -- Sort the draw list by the object's draw order + table.sort(drawList, drawSort) + + -- Draw all the objects in the draw list. + offsetX, offsetY = self.map.offsetX, self.map.offsetY + for i = 1, #drawList do + obj = drawList[i] + love.graphics.setColor(r,b,g,a) + drawList[i]:draw(di.x, di.y, unpack(self.color or neutralColor)) + end + + -- Reset the color and line width + love.graphics.setColor(r,b,g,a) + love.graphics.setLineWidth(line) +end + +--------------------------------------------------------------------------------------------------- +-- Changes an object layer into a custom layer. A function can be passed to convert objects. +function ObjectLayer:toCustomLayer(convert) + if convert then + for i = 1, #self.objects do + self.objects[i] = convert(self.objects[i]) + end + end + self.class = "CustomLayer" + return setmetatable(self, nil) +end + +--------------------------------------------------------------------------------------------------- +-- Return the ObjectLayer class +return ObjectLayer + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/README.md Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,83 @@ +#Advanced Tiled Loader +======== +Advanced Tiled Loader (ATL) loads and renders [Tiled](http://www.mapeditor.org/) maps inside of the [Löve2D](http://love2d.org) game framework. + +Supported features include: +* Multiple Layers +* All object types (regular, polygon, and tile) +* Properties +* Transparent colors +* Margins and spacing +* External tilesets +* zlib/gzip compression +* Isometric maps +* Flipped and rotated tiles +* Saving loaded maps + +Example files can be found at https://github.com/Kadoba/Advanced-Tiled-Loader-Examples + +---------------------------------------------------------------------------------------------------- +# Quick Example + +```lua + +-- Gets the loader +loader = require("AdvTiledLoader/loader.lua") + + -- Path to the tmx files. The file structure must be similar to how they are saved in Tiled +loader.path = "maps/" + + -- Loads the map file and returns it +map = loader.load("desert.tmx") + +-- Draws the map +map:draw() + +-- Limits the drawing range of the map. Important for performance +map:setDrawRange(0,0,love.graphics.getWidth(), love.graphics.getHeight()) + +-- Automatically sets the drawing range to the size of the screen. +map:autoDrawRange(tx, ty, scale, padding) + +-- Accessing individual layers +map.layers["layer name"] + +-- A shortcut for accessing specific layers +map("layer name") + +-- Finding a specific tile +map.layers["layer name"]:get(5,5) + +-- A shortcut for finding a specific tile +map("layer name")(5,5) + +-- Iterating over all tiles in a layer +for x, y, tile in map("layer name"):iterate() do + print( string.format("Tile at (%d,%d) has an id of %d", x, y, tile.id) ) +end + +-- Iterating over all objects in a layer +for i, obj in pairs( map("object layer").objects ) do + print( "Hi, my name is " .. obj.name ) +end + +-- Find all objects of a specific type in all layers +for _, layer in pairs(map.layers) do + if layer.class == "ObjectLayer" then + for _, obj in pairs(player.objects) do + if obj.type == "enemy" then print(obj.name) end + end + end +end + +-- draw the tile with the id 4 at (100,100) +map.tiles[4]:draw(100,100) + +-- Access the tile's properties set by Tiled +map.tiles[4].properties + +-- Turns off drawing of non-tiled objects. +map.drawObjects = false + +``` +---------------------------------------------------------------------------------------------------- \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/Tile.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,57 @@ +--------------------------------------------------------------------------------------------------- +-- -= Tile =- +--------------------------------------------------------------------------------------------------- + +-- Setup +local assert = assert +local Tile = {class = "Tile"} +Tile.__index = Tile + +-- Creates a new tile and returns it. +function Tile:new(id, tileset, quad, width, height, prop) + if not id or not tileset or not quad then + error("Tile:new - Needs at least 3 parameters for id, tileset and quad.") + end + local tile = setmetatable({}, Tile) + tile.id = id -- The id of the tile + tile.tileset = tileset -- The tileset this tile belongs to + tile.quad = quad -- The of the tileset that defines the tile + tile.width = width or 0 -- The width of the tile in pixels + tile.height = height or 0 -- The height of the tile in pixels + tile.properties = prop or {} -- The properties of the tile set in Tiled + return tile +end + +-- Draws the tile at the given location +function Tile:draw(x, y, rotation, scaleX, scaleY, offsetX, offsetY) + love.graphics.drawq(self.tileset.image, self.quad, self.tileset.offsetX + x, + self.tileset.offsetY + y, rotation, scaleX, scaleY, offsetX, offsetY) +end + +-- Return the Tile class +return Tile + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]] \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/TileLayer.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,448 @@ +--------------------------------------------------------------------------------------------------- +-- -= TileLayer =- +--------------------------------------------------------------------------------------------------- +-- Setup +TILED_LOADER_PATH = TILED_LOADER_PATH or ({...})[1]:gsub("[%.\\/][Tt]ile[Ll]ayer$", "") .. '.' +local math = math +local type = type +local love = love +local Grid = require(TILED_LOADER_PATH .. "Grid") +local TileLayer = {class = "TileLayer"} +TileLayer.__index = function(t, i) return TileLayer[i] or Grid[i] end +TileLayer.__call = Grid.__call + +---------------------------------------------------------------------------------------------------- +-- Returns a new TileLayer +function TileLayer:new(map, name, opacity, prop) + if not map or not name then + error("TileLayer:new - Needs at least 2 parameters for the map and name.") + end + + local tl = setmetatable(Grid:new(), TileLayer) + + prop = prop or {} + + -- Public: + tl.name = name -- The name of the tile layer + tl.map = map -- The map that this layer belongs to + tl.opacity = opacity or 1 -- The opacity to draw the tiles (0-1) + tl.properties = prop -- Properties set by Tiled + tl.visible = true -- If false then the layer will not be drawn + + tl.useSpriteBatch = prop.alt_useSpriteBatch -- If true then the layer is rendered with sprite + -- batches. If false then the layer will not use + -- sprite batches. If nil then map.useSpriteBatch + -- will be used. + + tl.parallaxX = prop.atl_parallaxX or 1 -- The horizontal speed of the parallax. 1 is normal + tl.parallaxY = prop.atl_parallaxX or 1 -- The vertical speed of the parallax. 1 is normal + tl.offsetX = prop.atl_offsetX or 0 -- Drawing offset X + tl.offsetY = prop.atl_offsetY or 0 -- Drawing offset Y + + -- Private: + tl._redraw = true -- If true then the layer needs to redraw tis sprite batches. + tl._tileRange = {0,0,0,0} -- Keeps the drawn tile range for the layer + tl._previousTileRange = {0,0,0,0} -- Previous _tileRange + tl._batches = {} -- Keeps track of the sprite batches for each tileset + tl._flippedTiles = Grid:new() -- Stores the flipped tile locations. + -- 1 = flipped X, 2 = flipped Y, 3 = both + tl._afterTileFunction = nil -- A function that handles drawing things after individual + -- tiles. This will not work with SpriteBatches. + tl._afterTileArgs = nil -- Starting arguments for the after tile function + tl._previousUseSpriteBatch = false -- The previous useSpriteBatch. If this is different then we + -- need to force a special redraw + return tl +end + +---------------------------------------------------------------------------------------------------- +-- Clears the tile layer of its after tile function. +function TileLayer:clearAfterTileFunction() + self._afterTileFunction = nil + self._afterTileArgs = nil +end + +---------------------------------------------------------------------------------------------------- +-- Sets a function that will be called after every tile. funct(layer, x, y, drawX, drawY ...) +function TileLayer:setAfterTileFunction(funct, ...) + self._afterTileFunction = funct + local args = {...} + if #args > 0 then self._afterTileArgs = args end +end + +---------------------------------------------------------------------------------------------------- +-- These are used in TileLayer:draw() but since that function is called so often we'll define them +-- outside to prevent them from being created and destroyed all the time. +local map, tile, tiles, postDraw, useSpriteBatch, tile, width, height +local at, drawX, drawY, flipX, flipY, r, g, b, a, halfW, halfH +local x1, y1, x2, y2 +-- Draws the TileLayer. +function TileLayer:draw() + + -- Early exit of the layer is not visible + if not self.visible then return end + + -- We access these a lot so we'll shorted them a bit. + map, tiles = self.map, self.map.tiles + postDraw = self.postDraw + + -- If useSpriteBatch was changed then we need to force the sprite batches to redraw. + if self.useSpriteBatch ~= self._previousUseSpriteBatch then map:forceRedraw() end + + -- Set the previous useSpriteBatch + self._previousUseSpriteBatch = self.useSpriteBatch + + -- If useSpriteBatch is set for this layer then use that, otherwise use the map's setting. + useSpriteBatch = self.useSpriteBatch ~= nil and self.useSpriteBatch or map.useSpriteBatch + + -- We'll blend the set alpha in with the current alpha + r,g,b,a = love.graphics.getColor() + love.graphics.setColor(r,g,b, a*self.opacity) + + -- Clear sprite batches if the screen has changed. + if self._redraw and useSpriteBatch then + for k,v in pairs(self._batches) do + v:clear() + end + end + + -- Get the tile range + x1, y1, x2, y2 = self._tileRange[1], self._tileRange[2], self._tileRange[3], self._tileRange[4] + + -- Translate for the parallax + if self.parallaxX ~= 1 or self.parallaxY ~= 1 then + love.graphics.push() + love.graphics.translate(math.floor(map.viewX - map.viewX*self.parallaxX), + math.floor(map.viewY - map.viewY*self.parallaxY)) + end + + -- Only draw if we're not using sprite batches or we need to update the sprite batches. + if self._redraw or not useSpriteBatch then + + -- Bind the sprite batches + if useSpriteBatch then + for k, batch in pairs(self._batches) do + batch:bind() + end + end + + -- Orthogonal tiles + if map.orientation == "orthogonal" then + + -- Go through each tile + for x,y,tile in self:rectangle(x1,y1,x2,y2) do + + -- Get the half-width and half-height + halfW, halfH = tile.width*0.5, tile.height*0.5 + + -- Draw the tile from the bottom left corner + drawX, drawY = (x)*map.tileWidth, (y+1)*map.tileHeight + + -- Apply the offset + drawX = drawX - map.offsetX - self.offsetX + drawY = drawY - map.offsetY - self.offsetY + + -- Get the flipped tiles + if self._flippedTiles(x,y) then + rot = (self._flippedTiles(x,y) % 2) == 1 and true or false + flipY = (self._flippedTiles(x,y) % 4) >= 2 and -1 or 1 + flipX = self._flippedTiles(x,y) >= 4 and -1 or 1 + if rot then flipX, flipY = -flipY, flipX end + else + rot, flipX, flipY = false, 1, 1 + end + + -- If we are using spritebatches + if useSpriteBatch then + -- If we dont have a spritebatch for the current tile's tileset then make one + if not self._batches[tile.tileset] then + self._batches[tile.tileset] = love.graphics.newSpriteBatch( + tile.tileset.image, map.width * map.height) + self._batches[tile.tileset]:bind() + end + -- Add the quad to the spritebatch + self._batches[tile.tileset]:addq(tile.quad, drawX + halfW, + drawY - halfH, + rot and math.pi*1.5 or 0, + flipX, flipY, halfW, halfH) + + -- If we are not using spritebatches + else + -- Draw the tile + tile:draw(drawX + halfW, + drawY - halfH, + rot and math.pi*1.5 or 0, + flipX, flipY, halfW, halfH) + + -- Call the after tile function + if self._afterTileFunction then + if self._afterTileArgs then + self._afterTileFunction(self, x, y, drawX, drawY, + unpack(self._afterTileArgs)) + else + self._afterTileFunction(self, x, y, drawX, drawY) + end + end + end + end + end + + -- Isometric tiles + if map.orientation == "isometric" then + local x,y + + -- Get the starting x drawing location + draw_start = map.height * map.tileWidth/2 + + -- Draw each tile starting from the top left tile. Make sure we have enough + -- room to draw the widest and tallest tile in the map. + for down=0,y2 do + for layer=0,1 do + for right=0,x2 do + x = x1 + right + down + layer - 1 + y = y1 - right + down - 1 + + -- If there is a tile row + if self(x,y) then + + -- Check and see if the tile is flipped + if self._flippedTiles(x,y) then + rot = (self._flippedTiles(x,y) % 2) == 1 and true or false + flipY = (self._flippedTiles(x,y) % 4) >= 2 and -1 or 1 + flipX = self._flippedTiles(x,y) >= 4 and -1 or 1 + if rot then flipX, flipY = -flipY, flipX end + else + rot, flipX, flipY = false, 1, 1 + end + + -- Get the tile + tile = self(x,y) + + -- If the tile exists then draw the tile + if tile then + + -- Get the half-width and half-height + halfW, halfH = tile.width*0.5, tile.height*0.5 + + -- Get the tile draw location + drawX = math.floor(draw_start + map.tileWidth/2 * (x - y-2)) + drawY = math.floor(map.tileHeight/2 * (x + y+2)) + + -- Apply the offset + drawX = drawX - map.offsetX - self.offsetX + drawY = drawY - map.offsetY - self.offsetY + + -- Using sprite batches + if useSpriteBatch then + -- If we dont have a spritebatch for the current tile's tileset + -- then make one + if not self._batches[tile.tileset] then + self._batches[tile.tileset] = love.graphics.newSpriteBatch( + tile.tileset.image, + map.width * map.height) + end + -- Add the tile to the sprite batch. + self._batches[tile.tileset]:addq(tile.quad, drawX + halfW + + (rot and halfW or 0), + drawY-halfH+(rot and halfW or 0), + rot and math.pi*1.5 or 0, + flipX, flipY, halfW, halfH) + -- Bind the sprite batch + self._batches[tile.tileset]:bind() + + -- Not using sprite batches + else + tile:draw(drawX + halfW + (rot and halfW or 0), + drawY - halfH + (rot and halfW or 0), + rot and math.pi*1.5 or 0, + flipX, flipY, halfW, halfH) + + -- Call the after tile function + if self._afterTileFunction then + if self._afterTileArgs then + self._afterTileFunction(self, x, y, drawX, drawY, + unpack(self._afterTileArgs)) + else + self._afterTileFunction(self, x, y, drawX, drawY) + end + end + + end + end + end + end + end + end + end + + -- Unbind the sprite batches + if useSpriteBatch then + for k, batch in pairs(self._batches) do + batch:unbind() + end + end + + end + + -- We finished redrawing + self._redraw = false + + -- If sprite batches are turned on then render them + if useSpriteBatch then + for k, batch in pairs(self._batches) do + love.graphics.draw(batch) + end + end + + -- If we applied a translation for our parallax then remove it + if self.parallaxX ~= 1 or self.parallaxY ~= 1 then + love.graphics.pop() + end + + -- Change the color back + love.graphics.setColor(r,g,b,a) +end + +---------------------------------------------------------------------------------------------------- +-- This copies a tile so that you can paste it in another spot. The pasted tile will keep the +-- rotation and flipped status. You can copy and paste between layers. +local flippedVal = 2^29 +function TileLayer:tileCopy(x,y) + if not self(x,y) then + self.map._tileClipboard = 0 + else + self.map._tileClipboard = self(x,y).id + (self._flippedTiles(x,y) or 0) * flippedVal + end +end + +---------------------------------------------------------------------------------------------------- +-- Paste a copied tile. +function TileLayer:tilePaste(x,y) + self._redraw = true + if not self.map._tileClipboard then + error("TileLayer:tilePaste() - A tile must be copied with tileCopy() before pasting") + end + local clip = self.map._tileClipboard + if clip / flippedVal > 0 then + self._flippedTiles:set(x, y, math.floor(clip / flippedVal)) + end + self:set(x, y, self.map.tiles[clip % flippedVal]) +end + +---------------------------------------------------------------------------------------------------- +-- Flip the tile's X. If doFlip is not specified then the flip is toggled. +function TileLayer:tileFlipX(x, y, doFlip) + self._redraw = true + local flip = self._flippedTiles(x,y) or 0 + if doFlip ~= false and flip < 4 then + flip = flip + 4 + elseif doFlip ~= true and flip >= 4 then + flip = flip - 4 + end + self._flippedTiles:set(x, y, flip ~= 0 and flip or nil) +end + +---------------------------------------------------------------------------------------------------- +-- Flip the tile's Y. If doFlip is not specified then the flip is toggled. +function TileLayer:tileFlipY(x, y, doFlip) + self.redraw = true + local flip = self._flippedTiles(x,y) or 0 + if doFlip ~= false and flip % 4 < 2 then + flip = flip + 2 + elseif doFlip ~= true and flip % 4 >= 2 then + flip = flip - 2 + end + self._flippedTiles:set(x, y, flip ~= 0 and flip or nil) +end + +---------------------------------------------------------------------------------------------------- +-- Rotate the tile. +function TileLayer:tileRotate(x, y, rot) + local flip = self._flippedTiles(x,y) or 0 + if rot then flip = rot % 8 + elseif flip == 0 then flip = 5 + elseif flip == 1 then flip = 4 + elseif flip == 2 then flip = 1 + elseif flip == 3 then flip = 0 + elseif flip == 4 then flip = 7 + elseif flip == 5 then flip = 6 + elseif flip == 6 then flip = 3 + elseif flip == 7 then flip = 2 + end + self._flippedTiles:set(x, y, flip ~= 0 and flip or nil) +end + +---------------------------------------------------------------------------------------------------- +-- Private +---------------------------------------------------------------------------------------------------- + +---------------------------------------------------------------------------------------------------- +-- Creates the tile data from a table containing each tile id in sequential order +-- from left-to-right, top-to-bottom. +str = "" +function TileLayer:_populate(t) + + str = "" + local lasty = 0 + for i=1,#t do + str = str .. t[i] + if lasty ~= math.floor(i/self.map.width) then + str = str .. "\n" + lasty = math.floor(i/self.map.width) + end + end + + -- Some temporary storage + local width, height = self.map.width, self.map.height + local tileID + + -- The values that indicate flipped tiles are in the last three binary digits. We need + -- to seperate those. + local flipped = 2^29 + + -- Clear the tiles + self:clear() + + -- Go through every tile + for x,y,v in self:rectangle(0,0,width-1,height-1,true) do + tileID = t[width*y+x+1] or 0 + + -- If the tile has a value in the last three binary digits then we seperate them + if tileID >= flipped then + self._flippedTiles:set(x, y, math.floor(tileID / flipped)) + tileID = tileID % flipped + end + + -- Set the tile + self:set(x, y, self.map.tiles[tileID]) + end +end + +---------------------------------------------------------------------------------------------------- +-- Return the TileLayer class +return TileLayer + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]] +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/TileSet.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,102 @@ +--------------------------------------------------------------------------------------------------- +-- -= TileSet =- +--------------------------------------------------------------------------------------------------- +-- Setup +TILED_LOADER_PATH = TILED_LOADER_PATH or ({...})[1]:gsub("[%.\\/][Tt]ile[Ss]et", "") .. '.' +local ceil = math.ceil +local Tile = require( TILED_LOADER_PATH .. "Tile") +local TileSet = {class = "TileSet"} +TileSet.__index = TileSet + +---------------------------------------------------------------------------------------------------- +-- Creates a new tileset. +function TileSet:new(img, imgPath, name, tw, th, w, h, gid, space, marg, offx, offy, trans, tprop, prop) + local ts = {} + + -- Public: + ts.image = img -- The image of the tileset + ts.imagePath = imgPath -- The path to the image file + ts.name = name -- Name of the tilseset + ts.tileWidth = tw -- The width of each tile in pixels + ts.tileHeight = th -- The height of each tile in pixels + ts.width = w -- The width of the tileset image in pixels + ts.height = h -- The height of the tileset image in pixels + ts.firstgid = gid -- The id of the first tile + ts.spacing = space or 0 -- The spacing in pixels between each tile + ts.margin = marg or 0 -- The margin in pixels surrounding the entire tile set. + ts.trans = trans -- The transparency value. Only used when saving maps. + ts.offsetX = offx -- The X offset. + ts.offsetY = offy -- The Y offset. + ts.tileProperties = tprop or {} -- Properties of contained tiles indexed by the tile's gid + ts.properties = prop or {} -- The properties of the tileset + + return setmetatable(ts, TileSet) +end + +---------------------------------------------------------------------------------------------------- +-- Returns the width in tiles +function TileSet:tilesWide() + return ceil( (self.width - self.margin*2 - self.spacing) / + (self.tileWidth + self.spacing) ) +end + +---------------------------------------------------------------------------------------------------- +-- Returns the height in tiles +function TileSet:tilesHigh() + return ceil( (self.height - self.margin*2 - self.spacing) / + (self.tileHeight + self.spacing) ) +end + +---------------------------------------------------------------------------------------------------- +-- Produces tiles from the settings and returns them in a table indexed by their id. +-- These are cut out left-to-right, top-to-bottom. +function TileSet:getTiles() + local x,y = self.margin, self.margin + local tiles = {} + local quad = false + local id = self.firstgid + local imageWidth, imageHeight = self.image:getWidth(), self.image:getHeight() + + for i = 1, self:tilesHigh() do + for j = 1, self:tilesWide() do + quad = love.graphics.newQuad(x, y, self.tileWidth, self.tileHeight, imageWidth, imageHeight) + tiles[id] = Tile:new(id, self, quad, self.tileWidth, self.tileHeight, self.tileProperties[id]) + x = x + self.tileWidth + self.spacing + id = id + 1 + end + x = self.margin + y = y + self.tileHeight + self.spacing + end + + return tiles +end + +---------------------------------------------------------------------------------------------------- +-- Return the TileSet class +return TileSet + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]] +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/VERSION.txt Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,141 @@ +Advanced Tiled Loader +-------------------------------------------------------------------------------------- + +CHANGES: +0.12.1 (10/31/12) +- TileLayer:drawAfterTile() has been reworked into TileLayer:newAfterTileFunction() +- Arguments for the Map functions that create and add map components directly to the + map have changed. (Map:newTileLayer(), Map:newObjectLayer(), etc) + +0.12.0 (10/17/12) +- Maps can now be saved with Loader.save() +- Introduced ATL properties + +0.11.2 (10/08/12) +- TileLayers will now calculate their own viewable tile range. +- Added parallax support for TileLayers. +- Added offset support for Maps and TileLayers. +- You can now load .tmx files without including the extension in the file name. +- Map.drawRange has been removed and replaced with viewX, viewY, viewScaling, and viewPadding. + Map:autoDrawRange() and other such interface functions have not changed. + + +0.11.1 (09/26/12) +- Added a 'visible' value to Map, ObjectLayer, TileLayer, and Object +- Created ObjectLayer:toCustomLayer() +- Created Map:removeLayer() + +0.11.0 (09/04/12) +- Renamed Map.drawList to Map.layerOrder. +- Renamed Map.drawPosition to Map.layerPosition. +- Combined Map.tileLayers and Map.objectLayers into one table - Map.layers. +- The functionality of tileData is now combined with TileLayer. +- Changed Map.__call to return layers by their name. +- Created Map.callback() to forward love callbacks to layers. +- Created Map.swapLayers() +- Created Map.newCustomLayers() which is a new interface for creating custom layers. +- Classes now remember their class name. +- Removed object movement functions to deter the use of objects other than reading data from. +- Removed the fixPO2 option for the loader. +- Generally cleaned up the code and made it easier to read. +- Properties are now automatically converted into proper types (numbers, boolean, string) when loaded. +- Readme file changed. There is now a new VERSION.txt file for version information. + + +0.10.2 (04/26/12) +- Relative paths now fully work. Before, ATL did not like to travel backwards in + a directory from a map file to reach a tileset. + +0.10.1 (04/20/12) +- Added quit compatibility for love 0.8.0 in the example files +- AdvTiledLoader can now be correctly required from any subfolder +- Fixed a bug with map.drawObjects not working +- Undefined object colors will now appear grey instead of black +- Added Loader.drawObjects and Loader.useSpriteBatch values. These values will be + applied to any new map that is loaded. +- Fixed a bug where the map would be drawn incorrectly if the map's width and height + were different + +0.10.0 (04/11/12) +- 2d arrays are replaced with a much easier to use grid class. +- Tiles are now directly inserted into TileLayer.tileData. You no longer have to + look them up via their ID in Map.tiles. +- Tile indexes are now the same as their coordinants as shown inside the Tiled. +- Added support for Tiled version 0.8.0. This includes tile rotation, polygon + objects, polyline objects, tilemap offset, and tilemap propterties. +- Moved the object drawing code from object layers to the objects themselves. +- Added functions tileCopy(), tilePaste(), tileRotate(), tileFlipX(), tileFlipY() + to TileLayers. + +0.9.0 (8/22/11) +- Advanced Tile Loader now has a wiki page and a github repository! +- Cleaned up the code quite a bit. +- Classes are now broken up into individual files rather than grouping similar + ones into one file. +- Many identifiers have been changed to be less confusing and more consistent. Now + camelCase is used to separate words instead of underscores. Internal + functions and data are now prefixed with an underscore. +- Sprite batch mode can now be set through the map or through individual tile + layers. Tile layer settings take precedence. +- A bug with layer transparency has been fixed. +- Tile:draw() now accepts parameters for scaling, rotation, and offset. +- TileLayer:drawAfterTile() now works with multiple functions +- Forcing a redraw is now automatic when you switch sprite batch modes. +- Added support for flipped tiles that were introduced in Tiled version 0.7.1 + +0.8.2 (5/9/11): +- Tileset images are now cached between maps. +- Added an option to automatically pad images for PO2. To do this set + Loader.fix_po2 to true. +- Changed Map.tl and Map.ol back to their old names. Map.tl and Map.ol remain as + aliases. +- Tile layers can now render using sprite batches by setting TileLayer.use_batch + to true. +- Added an init.lua file. +- Removed hardcoded require() paths. Added in global TILED_LOADER_PATH to point + to the library path. +- Renamed the TileSet functions getWidth() and getHeight() to tilesWide() and + tilesHigh(). + +0.8.1 (3/10/11): +- Renamed Map.tilelayers and Map.objectlayers to Map.tl and Map.ol respectively. +- Added function Tile.drawAfterTile() +- You may now define a draw() function for objects which overrides the default + drawing routine + +0.8.0 (2/28/11): +- Initial release + + +For more information see here: +http://love2d.org/forums/viewtopic.php?f=5&t=2567 + +-------------------------------------------------------------------------------------- + + +All images and code is subject to the following license unless otherwise stated: + + +Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/external/crc32lua.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,134 @@ +--[[ +dmlib.crc32 +CRC-32 checksum implemented entirely in Lua. This is similar to [1-2]. + +References + [1] http://www.axlradius.com/freestuff/CRC32.java + [2] http://www.gamedev.net/reference/articles/article1941.asp + [3] http://java.sun.com/j2se/1.5.0/docs/api/java/util/zip/CRC32.html + [4] http://www.dsource.org/projects/tango/docs/current/tango.io.digest.Crc32.html + [5] http://pydoc.org/1.5.2/zlib.html#-crc32 + [6] http://www.python.org/doc/2.5.2/lib/module-binascii.html + +(c) 2008 David Manura. Licensed under the same terms as Lua (MIT). +--]] + + +local M = {} + +local type = type +local require = require +local setmetatable = setmetatable +local _G = _G + +local bxor = require ( TILED_LOADER_PATH .. "external.numberlua" ) . bxor + +--[[NATIVE_BITOPS +local bxor = bit.bxor +local rshift = bit.rshift +local bnot = bit.bnot +--]] + +-- CRC-32-IEEE 802.3 (V.42) +local POLY = 0xEDB88320 + + +local function memoize(f) + local mt = {} + local t = setmetatable({}, mt) + function mt:__index(k) + local v = f(k); t[k] = v + return v + end + return t +end + + +local function get(i) + local crc = i + for j=1,8 do + local b = crc % 2 + crc = (crc - b) / 2 + if b == 1 then crc = bxor(crc, POLY) end + end + return crc +end +local crc_table = memoize(get) + + +local function crc32_byte(byte, crc) + crc = 0xffffffff - (crc or 0) + local v1 = (crc - crc % 256) / 256 + local v2 = crc_table[bxor(crc % 256, byte)] + return 0xffffffff - bxor(v1, v2) +end +--[[NATIVE_BITOPS +local function crc32_byte(byte, crc) + crc = bnot(crc or 0) + local v1 = rshift(crc, 8) + local v2 = crc_table[bxor(crc % 256, byte)] + return bnot(bxor(v1, v2)) +end +--]] +M.crc32_byte = crc32_byte + + +local function crc32_string(s, crc) + crc = crc or 0 + for i=1,#s do + crc = crc32_byte(s:byte(i), crc) + end + return crc +end +M.crc32_string = crc32_string + + +local function crc32(s, crc) + if type(s) == 'string' then + return crc32_string(s, crc) + else + return crc32_byte(s, crc) + end +end +M.crc32 = crc32 + + +--DEBUG: +--local s = "test123" +--local crc = 0xffffffff +--for i=1,#s do +-- local byte = s:sub(i,i):byte() +-- crc = crc32(crc, byte) +--end +--crc = 0xffffffff - crc +--print(string.format("%08X", crc)) + + +return M + + +--[[ +LICENSE + +Copyright (C) 2008, David Manura. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +(end license) +--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/external/deflatelua.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,768 @@ +-- dmlib.deflate +-- deflate (and gunzip) implemented in Lua. +-- +-- Note: only supports decompression. +-- Compression not implemented. +-- +-- References +-- [1] DEFLATE Compressed Data Format Specification version 1.3 +-- http://tools.ietf.org/html/rfc1951 +-- [2] GZIP file format specification version 4.3 +-- http://tools.ietf.org/html/rfc1952 +-- [3] http://en.wikipedia.org/wiki/DEFLATE +-- [4] pyflate, by Paul Sladen +-- http://www.paul.sladen.org/projects/pyflate/ +-- [5] Compress::Zlib::Perl - partial pure Perl implementation of +-- Compress::Zlib +-- http://search.cpan.org/~nwclark/Compress-Zlib-Perl/Perl.pm +-- +-- (c) 2008 David Manura. Licensed under the same terms as Lua (MIT). + +local assert = assert +local error = error +local ipairs = ipairs +local pairs = pairs +local print = print +local require = require +local tostring = tostring +local type = type +local setmetatable = setmetatable +local io = io +local math = math +local table_sort = table.sort +local math_max = math.max +local string_char = string.char +local io_open = io.open +local _G = _G + +local crc32 = require( TILED_LOADER_PATH .. "external.crc32lua" ) . crc32_byte + +local DEBUG = false + +--[[NATIVE_BITOPS +local band = bit.band +local lshift = bit.lshift +local rshift = bit.rshift +--]] + +local M = {} + + +local function warn(s) + io.stderr:write(s, '\n') +end + + +local function debug(...) + print('DEBUG', ...) +end + + +local function runtime_error(s, level) + level = level or 1 + error({s}, level+1) +end + + +local function make_outstate(outbs) + local outstate = {} + outstate.outbs = outbs + outstate.window = {} + outstate.window_pos = 1 + return outstate +end + + +local function output(outstate, byte) + -- debug('OUTPUT:', s) + local window_pos = outstate.window_pos + outstate.outbs(byte) + outstate.window[window_pos] = byte + outstate.window_pos = window_pos % 32768 + 1 -- 32K +end + + +local function noeof(val) + return assert(val, 'unexpected end of file') +end + + +local function hasbit(bits, bit) + return bits % (bit + bit) >= bit +end + + +local function memoize(f) + local mt = {} + local t = setmetatable({}, mt) + function mt:__index(k) + local v = f(k) + t[k] = v + return v + end + return t +end + + +-- small optimization (lookup table for powers of 2) +local pow2 = memoize(function(n) return 2^n end) + +--local tbits = memoize( +-- function(bits) +-- return memoize( function(bit) return getbit(bits, bit) end ) +-- end ) + + +-- weak metatable marking objects as bitstream type +local is_bitstream = setmetatable({}, {__mode='k'}) + + +-- DEBUG +-- prints LSB first +local function bits_tostring(bits, nbits) + local s = '' + local tmp = bits + local function f() + local b = tmp % 2 == 1 and 1 or 0 + s = s .. b + tmp = (tmp - b) / 2 + end + if nbits then + for i=1,nbits do f() end + else + while tmp ~= 0 do f() end + end + + return s +end + + +local function bytestream_from_file(fh) + local o = {} + function o:read() + local sb = fh:read(1) + if sb then return sb:byte() end + end + return o +end + + +local function bytestream_from_string(s) + local i = 1 + local o = {} + function o:read() + local by + if i <= #s then + by = s:byte(i) + i = i + 1 + end + return by + end + return o +end + + +local function bytestream_from_function(f) + local i = 0 + local buffer = '' + local o = {} + function o:read() + i = i + 1 + if i > #buffer then + buffer = f() + if not buffer then return end + i = 1 + end + return buffer:byte(i,i) + end + return o +end + + +local left +local function bitstream_from_bytestream(bys) + local buf_byte = 0 + local buf_nbit = 0 + local o = {} + + function o:nbits_left_in_byte() + return buf_nbit + end + + function o:read(nbits) + nbits = nbits or 1 + while buf_nbit < nbits do + local byte = bys:read() + if not byte then return end -- note: more calls also return nil + buf_byte = buf_byte + pow2[buf_nbit] * byte + buf_nbit = buf_nbit + 8 + end + local m = pow2[nbits] + local bits = buf_byte % m + buf_byte = (buf_byte - bits) / m + buf_nbit = buf_nbit - nbits + return bits + end + --[[NATIVE_BITOPS + function o:read(nbits) + nbits = nbits or 1 + while buf_nbit < nbits do + local byte = bys:read() + if not byte then return end -- note: more calls also return nil + buf_byte = buf_byte + lshift(byte, buf_nbit) + buf_nbit = buf_nbit + 8 + end + local bits + if nbits == 0 then + bits = 0 + elseif nbits == 32 then + bits = buf_byte + buf_byte = 0 + else + bits = band(buf_byte, rshift(0xffffffff, 32 - nbits)) + buf_byte = rshift(buf_byte, nbits) + end + buf_nbit = buf_nbit - nbits + return bits + end + --]] + + is_bitstream[o] = true + + return o +end + + +local function get_bitstream(o) + local bs + if is_bitstream[o] then + return o + elseif io.type(o) == 'file' then + bs = bitstream_from_bytestream(bytestream_from_file(o)) + elseif type(o) == 'string' then + bs = bitstream_from_bytestream(bytestream_from_string(o)) + elseif type(o) == 'function' then + bs = bitstream_from_bytestream(bytestream_from_function(o)) + else + runtime_error 'unrecognized type' + end + return bs +end + + +local function get_obytestream(o) + local bs + if io.type(o) == 'file' then + bs = function(sbyte) o:write(string_char(sbyte)) end + elseif type(o) == 'function' then + bs = o + else + runtime_error('unrecognized type: ' .. tostring(o)) + end + return bs +end + + +local function HuffmanTable(init, is_full) + local t = {} + if is_full then + for val,nbits in pairs(init) do + if nbits ~= 0 then + t[#t+1] = {val=val, nbits=nbits} + --debug('*',val,nbits) + end + end + else + for i=1,#init-2,2 do + local firstval, nbits, nextval = init[i], init[i+1], init[i+2] + --debug(val, nextval, nbits) + if nbits ~= 0 then + for val=firstval,nextval-1 do + t[#t+1] = {val=val, nbits=nbits} + end + end + end + end + table_sort(t, function(a,b) + return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits + end) + + -- assign codes + local code = 1 -- leading 1 marker + local nbits = 0 + for i,s in ipairs(t) do + if s.nbits ~= nbits then + code = code * pow2[s.nbits - nbits] + nbits = s.nbits + end + s.code = code + --debug('huffman code:', i, s.nbits, s.val, code, bits_tostring(code)) + code = code + 1 + end + + local minbits = math.huge + local look = {} + for i,s in ipairs(t) do + minbits = math.min(minbits, s.nbits) + look[s.code] = s.val + end + + --for _,o in ipairs(t) do + -- debug(':', o.nbits, o.val) + --end + + -- function t:lookup(bits) return look[bits] end + + local function msb(bits, nbits) + local res = 0 + for i=1,nbits do + local b = bits % 2 + bits = (bits - b) / 2 + res = res * 2 + b + end + return res + end + --[[NATIVE_BITOPS + local function msb(bits, nbits) + local res = 0 + for i=1,nbits do + res = lshift(res, 1) + band(bits, 1) + bits = rshift(bits, 1) + end + return res + end + --]] + + local tfirstcode = memoize( + function(bits) return pow2[minbits] + msb(bits, minbits) end) + + function t:read(bs) + local code = 1 -- leading 1 marker + local nbits = 0 + while 1 do + if nbits == 0 then -- small optimization (optional) + code = tfirstcode[noeof(bs:read(minbits))] + nbits = nbits + minbits + else + local b = noeof(bs:read()) + nbits = nbits + 1 + code = code * 2 + b -- MSB first + --[[NATIVE_BITOPS + code = lshift(code, 1) + b -- MSB first + --]] + end + --debug('code?', code, bits_tostring(code)) + local val = look[code] + if val then + --debug('FOUND', val) + return val + end + end + end + + return t +end + + +local function parse_gzip_header(bs) + -- local FLG_FTEXT = 2^0 + local FLG_FHCRC = 2^1 + local FLG_FEXTRA = 2^2 + local FLG_FNAME = 2^3 + local FLG_FCOMMENT = 2^4 + + local id1 = bs:read(8) + local id2 = bs:read(8) + if id1 ~= 31 or id2 ~= 139 then + runtime_error 'not in gzip format' + end + local cm = bs:read(8) -- compression method + local flg = bs:read(8) -- FLaGs + local mtime = bs:read(32) -- Modification TIME + local xfl = bs:read(8) -- eXtra FLags + local os = bs:read(8) -- Operating System + + if DEBUG then + debug("CM=", cm) + debug("FLG=", flg) + debug("MTIME=", mtime) + -- debug("MTIME_str=",os.date("%Y-%m-%d %H:%M:%S",mtime)) -- non-portable + debug("XFL=", xfl) + debug("OS=", os) + end + + if not os then runtime_error 'invalid header' end + + if hasbit(flg, FLG_FEXTRA) then + local xlen = bs:read(16) + local extra = 0 + for i=1,xlen do + extra = bs:read(8) + end + if not extra then runtime_error 'invalid header' end + end + + local function parse_zstring(bs) + repeat + local by = bs:read(8) + if not by then runtime_error 'invalid header' end + until by == 0 + end + + if hasbit(flg, FLG_FNAME) then + parse_zstring(bs) + end + + if hasbit(flg, FLG_FCOMMENT) then + parse_zstring(bs) + end + + if hasbit(flg, FLG_FHCRC) then + local crc16 = bs:read(16) + if not crc16 then runtime_error 'invalid header' end + -- IMPROVE: check CRC. where is an example .gz file that + -- has this set? + if DEBUG then + debug("CRC16=", crc16) + end + end +end + +local function parse_zlib_header(bs) + local cm = bs:read(4) -- Compression Method + local cinfo = bs:read(4) -- Compression info + local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG) + local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary) + local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level) + local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags) + local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs + + if cm ~= 8 then -- not "deflate" + runtime_error("unrecognized zlib compression method: " + cm) + end + local window_size + if cinfo > 7 then + runtime_error("invalid zlib window size: cinfo=" + cinfo) + end + local window_size = 2^(cinfo + 8) + + if (cmf*256 + flg) % 31 ~= 0 then + runtime_error("invalid zlib header (bad fcheck sum)") + end + + if fdict == 1 then + runtime_error("FIX:TODO - FDICT not currently implemented") + local dictid = bs:read(32) + end + + return window_size +end + +local function parse_huffmantables(bs) + local hlit = bs:read(5) -- # of literal/length codes - 257 + local hdist = bs:read(5) -- # of distance codes - 1 + local hclen = noeof(bs:read(4)) -- # of code length codes - 4 + + local ncodelen_codes = hclen + 4 + local codelen_init = {} + local codelen_vals = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15} + for i=1,ncodelen_codes do + local nbits = bs:read(3) + local val = codelen_vals[i] + codelen_init[val] = nbits + end + local codelentable = HuffmanTable(codelen_init, true) + + local function decode(ncodes) + local init = {} + local nbits + local val = 0 + while val < ncodes do + local codelen = codelentable:read(bs) + --FIX:check nil? + local nrepeat + if codelen <= 15 then + nrepeat = 1 + nbits = codelen + --debug('w', nbits) + elseif codelen == 16 then + nrepeat = 3 + noeof(bs:read(2)) + -- nbits unchanged + elseif codelen == 17 then + nrepeat = 3 + noeof(bs:read(3)) + nbits = 0 + elseif codelen == 18 then + nrepeat = 11 + noeof(bs:read(7)) + nbits = 0 + else + error 'ASSERT' + end + for i=1,nrepeat do + init[val] = nbits + val = val + 1 + end + end + local huffmantable = HuffmanTable(init, true) + return huffmantable + end + + local nlit_codes = hlit + 257 + local ndist_codes = hdist + 1 + + local littable = decode(nlit_codes) + local disttable = decode(ndist_codes) + + return littable, disttable +end + + +local tdecode_len_base +local tdecode_len_nextrabits +local tdecode_dist_base +local tdecode_dist_nextrabits +local function parse_compressed_item(bs, outstate, littable, disttable) + local val = littable:read(bs) + --debug(val, val < 256 and string_char(val)) + if val < 256 then -- literal + output(outstate, val) + elseif val == 256 then -- end of block + return true + else + if not tdecode_len_base then + local t = {[257]=3} + local skip = 1 + for i=258,285,4 do + for j=i,i+3 do t[j] = t[j-1] + skip end + if i ~= 258 then skip = skip * 2 end + end + t[285] = 258 + tdecode_len_base = t + --for i=257,285 do debug('T1',i,t[i]) end + end + if not tdecode_len_nextrabits then + local t = {} + for i=257,285 do + local j = math_max(i - 261, 0) + t[i] = (j - (j % 4)) / 4 + --[[NATIVE_BITOPS + t[i] = rshift(j, 2) + --]] + end + t[285] = 0 + tdecode_len_nextrabits = t + --for i=257,285 do debug('T2',i,t[i]) end + end + local len_base = tdecode_len_base[val] + local nextrabits = tdecode_len_nextrabits[val] + local extrabits = bs:read(nextrabits) + local len = len_base + extrabits + + if not tdecode_dist_base then + local t = {[0]=1} + local skip = 1 + for i=1,29,2 do + for j=i,i+1 do t[j] = t[j-1] + skip end + if i ~= 1 then skip = skip * 2 end + end + tdecode_dist_base = t + --for i=0,29 do debug('T3',i,t[i]) end + end + if not tdecode_dist_nextrabits then + local t = {} + for i=0,29 do + local j = math_max(i - 2, 0) + t[i] = (j - (j % 2)) / 2 + --[[NATIVE_BITOPS + t[i] = rshift(j, 1) + --]] + end + tdecode_dist_nextrabits = t + --for i=0,29 do debug('T4',i,t[i]) end + end + local dist_val = disttable:read(bs) + local dist_base = tdecode_dist_base[dist_val] + local dist_nextrabits = tdecode_dist_nextrabits[dist_val] + local dist_extrabits = bs:read(dist_nextrabits) + local dist = dist_base + dist_extrabits + + --debug('BACK', len, dist) + for i=1,len do + local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K + output(outstate, assert(outstate.window[pos], 'invalid distance')) + end + end + return false +end + + +local function parse_block(bs, outstate) + local bfinal = bs:read(1) + local btype = bs:read(2) + + local BTYPE_NO_COMPRESSION = 0 + local BTYPE_FIXED_HUFFMAN = 1 + local BTYPE_DYNAMIC_HUFFMAN = 2 + local BTYPE_RESERVED = 3 + + if DEBUG then + debug('bfinal=', bfinal) + debug('btype=', btype) + end + + if btype == BTYPE_NO_COMPRESSION then + bs:read(bs:nbits_left_in_byte()) + local len = bs:read(16) + local nlen = noeof(bs:read(16)) + + for i=1,len do + local by = noeof(bs:read(8)) + output(outstate, by) + end + elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then + local littable, disttable + if btype == BTYPE_DYNAMIC_HUFFMAN then + littable, disttable = parse_huffmantables(bs) + else + littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil} + disttable = HuffmanTable {0,5, 32,nil} + end + + repeat + local is_done = parse_compressed_item( + bs, outstate, littable, disttable) + until is_done + else + runtime_error 'unrecognized compression type' + end + + return bfinal ~= 0 +end + + +local function deflate(t) + local bs = get_bitstream(t.input) + local outbs = get_obytestream(t.output) + local outstate = make_outstate(outbs) + + repeat + local is_final = parse_block(bs, outstate) + until is_final +end +M.deflate = deflate + +-- http://tools.ietf.org/html/rfc1952 +local function gunzip(t) + local bs = get_bitstream(t.input) + local outbs = get_obytestream(t.output) + local disable_crc = t.disable_crc + if disable_crc == nil then disable_crc = false end + + parse_gzip_header(bs) + + local data_crc32 = 0 + + deflate{input=bs, output= + disable_crc and outbs or + function(byte) + data_crc32 = crc32(byte, data_crc32) + outbs(byte) + end + } + + bs:read(bs:nbits_left_in_byte()) + + local expected_crc32 = bs:read(32) + local isize = bs:read(32) -- ignored + if DEBUG then + debug('crc32=', expected_crc32) + debug('isize=', isize) + end + if not disable_crc and data_crc32 then + if data_crc32 ~= expected_crc32 then + runtime_error('invalid compressed data--crc error') + end + end + if bs:read() then + warn 'trailing garbage ignored' + end +end +M.gunzip = gunzip + +-- adler32 checksum +-- see ADLER32 in http://tools.ietf.org/html/rfc1950 . +local function adler32(byte, crc) + local s1 = crc % 65536 + local s2 = (crc - s1) / 65536 + s1 = (s1 + byte) % 65521 + s2 = (s2 + s1) % 65521 + return s2*65536 + s1 +end -- 65521 is the largest prime smaller than 2^16 + +-- http://tools.ietf.org/html/rfc1950 +local function inflate_zlib(t) + local bs = get_bitstream(t.input) + local outbs = get_obytestream(t.output) + local disable_crc = t.disable_crc + if disable_crc == nil then disable_crc = false end + + local window_size = parse_zlib_header(bs) + + local data_adler32 = 1 + + deflate{input=bs, output= + disable_crc and outbs or + function(byte) + data_adler32 = adler32(byte, data_adler32) + outbs(byte) + end + } + + bs:read(bs:nbits_left_in_byte()) + + local b3 = bs:read(8) + local b2 = bs:read(8) + local b1 = bs:read(8) + local b0 = bs:read(8) + local expected_adler32 = ((b3*256 + b2)*256 + b1)*256 + b0 + if DEBUG then + debug('alder32=', expected_adler32) + end + if not disable_crc then + if data_adler32 ~= expected_adler32 then + runtime_error('invalid compressed data--crc error') + end + end + if bs:read() then + warn 'trailing garbage ignored' + end +end +M.inflate_zlib = inflate_zlib + +return M + +--[[ +LICENSE + +Copyright (C) 2008, David Manura. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +(end license) +--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/external/gunziplua.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,161 @@ +-- bin.gunzip +-- gunzip command partially reimplemented in Lua. +-- +-- Note: this does not implement all of the GNU +-- gunzip[1] command-line options and might have +-- slightly different behavior. +-- +-- This is designed to be called from a shell script: +-- +-- #!/bin/env lua +-- package.path = '?.lua;' .. package.path +-- require 'dmlib.command_gunzip' (...) +-- +-- References +-- +-- [1] http://www.gnu.org/software/gzip/ +-- +-- (c) 2008 David Manura. Licensed under the same terms as Lua (MIT). + +local assert = assert +local error = error +local ipairs = ipairs +local require = require +local xpcall = xpcall +local type = type +local io = io +local os = os +local string = string +local debug = require "debug" +local debug_traceback = debug.traceback +local _G = _G + +local DEFLATE = require( TILED_LOADER_PATH .. "external.deflatelua" ) + +local OptionParser = require( TILED_LOADER_PATH .. "external.optparse" ) . OptionParser + +local version = '0.1' + + +local function runtime_assert(val, msg) + if not val then error({msg}, val) end + return val +end + + +local function runtime_error(s, level) + level = level or 1 + error({s}, level+1) +end + + +local function file_exists(filename) + local fh = io.open(filename) + if fh then fh:close(); return true end + return false +end + + +-- Run gunzip command, given command-line arguments. +local function call(...) + local opt = OptionParser{usage="%prog [options] [gzip-file...]", + version=string.format("gunzip %s", version), + add_help_option=false} + opt.add_option{"-h", "--help", action="store_true", dest="help", + help="give this help"} + opt.add_option{ + "-c", "--stdout", dest="stdout", action="store_true", + help="write on standard output, keep original files unchanged"} + opt.add_option{ + "-f", "--force", dest="force", action="store_true", + help="force overwrite of output file"} + opt.add_option{ + "--disable-crc", dest="disable_crc", action="store_true", + help="skip CRC check (faster performance)"} + + + local options, args = opt.parse_args() + + local gzipfiles = args + + if options.help then + opt.print_help() + os.exit() + end + + local ok, err = xpcall(function() + local outfile_of = {} + local out_of = {} + + for _,gzipfile in ipairs(gzipfiles) do + local base = gzipfile:match('(.+)%.[gG][zZ]$') + if not base then + runtime_error(gzipfile .. ': unknown suffix') + end + outfile_of[gzipfile] = base + + out_of[gzipfile] = + (options.stdout or not gzipfile) and assert(io.stdout) + or outfile_of[gzipfile] + + if type(out_of[gzipfile]) == 'string' then + if file_exists(out_of[gzipfile]) then + io.stderr:write(out_of[gzipfile] .. + ' already exists; do you wish to overwrite(y or n)? ') + if not io.stdin:read'*l':match'^[yY]' then + runtime_error 'not overwritten' + end + end + end + end + + for _,gzipfile in ipairs(gzipfiles) do + local fh = gzipfile and runtime_assert(io.open(gzipfile, 'rb')) + or assert(io.stdin) + local ofh = type(out_of[gzipfile]) == 'string' and + runtime_assert(io.open(out_of[gzipfile], 'wb')) + or out_of[gzipfile] + + DEFLATE.gunzip {input=fh, output=ofh, + disable_crc=options.disable_crc} + end + + for _,gzipfile in ipairs(gzipfiles) do + assert(os.remove(gzipfile)) + end + + end, debug_traceback) + if not ok then + if type(err) == 'table' then err = err[1] end + io.stderr:write('error: ' .. err, '\n') + end +end + + +return call + +--[[ +LICENSE + +Copyright (C) 2008, David Manura. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +(end license) +--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/external/numberlua.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,91 @@ +-- dmlib.bit +-- Bitwise operations implemented entirely in Lua. +-- Represents bit arrays as non-negative Lua numbers.[1] +-- Can represent 32-bit bit arrays, when Lua is compiled +-- with lua_Number being double-precision IEEE 754 floating point. +-- +-- Based partly on Roberto Ierusalimschy's post +-- in http://lua-users.org/lists/lua-l/2002-09/msg00134.html . +-- +-- References +-- +-- [1] http://lua-users.org/wiki/FloatingPoint +-- +-- (c) 2008 David Manura. Licensed under the same terms as Lua (MIT). + + +local function memoize(f) + local mt = {} + local t = setmetatable({}, mt) + function mt:__index(k) + local v = f(k); t[k] = v + return v + end + return t +end + +local function make_bitop_uncached(t, m) + local function bitop(a, b) + local res,p = 0,1 + while a ~= 0 and b ~= 0 do + local am, bm = a%m, b%m + res = res + t[am][bm]*p + a = (a - am) / m + b = (b - bm) / m + p = p*m + end + res = res + (a+b)*p + return res + end + return bitop +end + +local function make_bitop(t) + local op1 = make_bitop_uncached(t,2^1) + local op2 = memoize(function(a) + return memoize(function(b) + return op1(a, b) + end) + end) + return make_bitop_uncached(op2, 2^(t.n or 1)) +end + +local bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4} +local F8 = 2^32 - 1 +local function bnot(a) return F8 - a end +local function band(a,b) return ((a+b) - bxor(a,b))/2 end +local function bor(a,b) return F8 - band(F8 - a, F8 - b) end + +local M = {} +M.bxor = bxor +M.bnot = bnot +M.band = band +M.bor = bor + +return M + +--[[ +LICENSE + +Copyright (C) 2008, David Manura. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +(end license) +--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/external/optparse.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,158 @@ +-- lua-optparse [1] +-- Lua-based partial reimplementation of Python's optparse[2-3] command-line +-- parsing module. +-- +-- Note: Python also supports getopt[4]. +-- +-- References +-- [1] http://lua-users.org/wiki/CommandLineParsing +-- [2] http://docs.python.org/lib/optparse-defining-options.html +-- [3] http://blog.doughellmann.com/2007/08/pymotw-optparse.html +-- [4] http://docs.python.org/lib/module-getopt.html +-- +-- (c) 2008 David Manura. Licensed under the same terms as Lua (MIT). + +local ipairs = ipairs +local unpack = unpack +local io = io +local table = table +local os = os +local arg = arg +local _G = _G + + +local function OptionParser(t) + local usage = t.usage + local version = t.version + + local o = {} + local option_descriptions = {} + local option_of = {} + + function o.fail(s) -- extension + io.stderr:write(s .. '\n') + os.exit(1) + end + + function o.add_option(optdesc) + option_descriptions[#option_descriptions+1] = optdesc + for _,v in ipairs(optdesc) do + option_of[v] = optdesc + end + end + function o.parse_args() + -- expand options (e.g. "--input=file" -> "--input", "file") + local arg = {unpack(arg)} + for i=#arg,1,-1 do local v = arg[i] + local flag, val = v:match('^(%-%-%w+)=(.*)') + if flag then + arg[i] = flag + table.insert(arg, i+1, val) + end + end + + local options = {} + local args = {} + local i = 1 + while i <= #arg do local v = arg[i] + local optdesc = option_of[v] + if optdesc then + local action = optdesc.action + local val + if action == 'store' or action == nil then + i = i + 1 + val = arg[i] + if not val then o.fail('option requires an argument ' .. v) end + elseif action == 'store_true' then + val = true + elseif action == 'store_false' then + val = false + end + options[optdesc.dest] = val + else + if v:match('^%-') then o.fail('invalid option ' .. v) end + args[#args+1] = v + end + i = i + 1 + end + if options.help then + o.print_help() + os.exit() + end + if options.version then + io.stdout:write(t.version .. "\n") + os.exit() + end + return options, args + end + + local function flags_str(optdesc) + local sflags = {} + local action = optdesc.action + for _,flag in ipairs(optdesc) do + local sflagend + if action == nil or action == 'store' then + local metavar = optdesc.metavar or optdesc.dest:upper() + sflagend = #flag == 2 and ' ' .. metavar + or '=' .. metavar + else + sflagend = '' + end + sflags[#sflags+1] = flag .. sflagend + end + return table.concat(sflags, ', ') + end + + function o.print_help() + io.stdout:write("Usage: " .. usage:gsub('%%prog', arg[0]) .. "\n") + io.stdout:write("\n") + io.stdout:write("Options:\n") + for _,optdesc in ipairs(option_descriptions) do + io.stdout:write(" " .. flags_str(optdesc) .. + " " .. optdesc.help .. "\n") + end + end + if t.add_help_option == nil or t.add_help_option == true then + o.add_option{"--help", action="store_true", dest="help", + help="show this help message and exit"} + end + if t.version then + o.add_option{"--version", action="store_true", dest="version", + help="output version info."} + end + return o +end + + +local t = {} +t.OptionParser = OptionParser + + +return t + + +--[[ +LICENSE + +Copyright (C) 2008, David Manura. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +(end license) +--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/external/xml.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,129 @@ +-- XML parser from http://lua-users.org/wiki/LuaXml + +local escaped = { + ['"'] = '"', + ['&'] = '&', + ['''] = "'", + ['<'] = '<', + ['>'] = '>' +} + +local xml = {} + +--------------------------------------------------------------------------------------------------- +local function args_to_table(s) + local arg = {} + string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a) + for f, t in pairs(escaped) do + a = string.gsub(a, f, t) + end + arg[w] = a + end) + return arg +end + +--------------------------------------------------------------------------------------------------- +function xml.string_to_table(s) + local stack = {} + local top = {} + table.insert(stack, top) + local ni,c,label,xarg, empty + local i, j = 1, 1 + while true do + ni,j,c,label,xarg, empty = string.find(s, "<(%/?)([%w:]+)(.-)(%/?)>", i) + if not ni then break end + local text = string.sub(s, i, ni-1) + if not string.find(text, "^%s*$") then + table.insert(top, text) + end + if empty == "/" then -- empty element tag + table.insert(top, {label=label, xarg=args_to_table(xarg), empty=1}) + elseif c == "" then -- start tag + top = {label=label, xarg=args_to_table(xarg)} + table.insert(stack, top) -- new level + else -- end tag + local toclose = table.remove(stack) -- remove top + top = stack[#stack] + if #stack < 1 then + error("nothing to close with "..label) + end + if toclose.label ~= label then + error("trying to close "..toclose.label.." with "..label) + end + table.insert(top, toclose) + end + i = j+1 + end + local text = string.sub(s, i) + if not string.find(text, "^%s*$") then + table.insert(stack[#stack], text) + end + if #stack > 1 then + error("unclosed "..stack[#stack].label) + end + return stack[1] +end + +--------------------------------------------------------------------------------------------------- +local function args_to_string(args) + if not args then return "" end + local str = "" + for k,v in pairs(args) do + str = str .. string.format(' %s="%s"', k, tostring(v)) + end + return str +end + +--------------------------------------------------------------------------------------------------- +local function expand(t, indent) + indent = indent .. " " + local str = {} + for i = 1,#t do + if type(t[i]) == "table" then + str[#str+1] = xml.table_to_string(t[i], indent) + else + str[#str+1] = indent + str[#str+1] = t[i] + str[#str+1] = "\n" + end + end + return str +end + +--------------------------------------------------------------------------------------------------- +local function flatten (t) + local n = { } + local function flatten_aux(t) + for k, v in ipairs(t) do + if type(v) == "table" then + flatten_aux(v) + else + n[#n+1] = v + end + end + end + flatten_aux(t) + return n +end + +--------------------------------------------------------------------------------------------------- +function xml.table_to_string(t, indent) + if not indent then indent = "" end + local label = t.label + local xarg = args_to_string(t.xarg) + local str = {} + if #t > 0 then + local body = expand(t,indent) + str = {indent, "<", label, xarg, ">\n", body, indent, "</", label, ">\n"} + else + str = {indent, "<", label, xarg, "/>\n"} + end + if indent == "" then + return table.concat(flatten(str)) + else + return str + end +end + +return xml +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AdvTiledLoader/init.lua Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,38 @@ +TILED_LOADER_PATH = TILED_LOADER_PATH or ({...})[1]:gsub("[%.\\/]init$", "") .. '.' + +-- Return the classes in a table +return { + Map = require(TILED_LOADER_PATH .. "Map"), + TileLayer = require(TILED_LOADER_PATH .. "TileLayer"), + Tile = require(TILED_LOADER_PATH .. "Tile"), + TileSet = require(TILED_LOADER_PATH .. "TileSet"), + Object = require(TILED_LOADER_PATH .. "Object"), + ObjectLayer = require(TILED_LOADER_PATH .. "ObjectLayer"), + Loader = require(TILED_LOADER_PATH .. "Loader"), + Grid = require(TILED_LOADER_PATH .. "Grid"), +} + + +--[[Copyright (c) 2011-2012 Casey Baxter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.--]]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/level01.tmx Sat Dec 15 13:10:41 2012 +0100 @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<map version="1.0" orientation="orthogonal" width="21" height="16" tilewidth="48" tileheight="48"> + <tileset firstgid="1" name="tiles" tilewidth="48" tileheight="48"> + <image source="tiles.png" width="128" height="128"/> + </tileset> + <layer name="Tile Layer 1" width="21" height="16"> + <data encoding="base64" compression="zlib"> + eJxjZGBgYByCmAkPJqQOXZ4YMwmZQ6zbyDWTkjCidbgTspOc8BqMZpKjn1pmkptWSDWT3LRKLT245PHpI8VMamMAsJQB1A== + </data> + </layer> +</map>
--- a/main.lua Sat Dec 15 10:23:56 2012 +0100 +++ b/main.lua Sat Dec 15 13:10:41 2012 +0100 @@ -1,22 +1,59 @@ +local ATL = require("AdvTiledLoader") local anim8 = require("anim8") local sprite = require("sprite") local sprites = {} +local shadows = {} +local map +local tx, ty = 0, 0 +local DX = {0, 1, 0, -1} +local DY = {-1, 0, 1, 0} +local villain + +function newVillain(x, y, image) + local grid = anim8.newGrid(64, 64, 128, 128) + local villain = sprite.newSprite( + x, y, + image, + anim8.newAnimation('loop', grid('1-2,1'), 0.25) + ) + function villain:walk(dt, dir) + self.x = self.x + DX[dir] * dt * 250 + self.y = self.y + DY[dir] * dt * 250 + end + return villain +end + +function newCivilian(x, y, image) + local grid = anim8.newGrid(64, 64, 128, 128) + local civilian = sprite.newSprite( + x, y, + image, + anim8.newAnimation('loop', grid('1-2,1'), 0.25) + ) + return civilian +end function love.load() - local VILLAIN_IMAGE = love.graphics.newImage('villain.png') - local CIVILIAN_IMAGE = love.graphics.newImage('civilian.png') - local grid = anim8.newGrid(64, 64, 128, 128) - table.insert(sprites, sprite.newSprite( - 255, - 255, - VILLAIN_IMAGE, - anim8.newAnimation('loop', grid('1-2,1'), 0.25) - )) + local shadow_image = love.graphics.newImage('shadow.png') + local civilian_image = love.graphics.newImage('civilian.png') + local villain_image = love.graphics.newImage('villain.png') + villain = newVillain(48, 48, villain_image) + sprites[1] = villain + shadows[1] = sprite.newShadow(shadow_image, villain) + sprites[2] = newCivilian(128, 64, civilian_image) + shadows[2] = sprite.newShadow(shadow_image, sprites[2]) + map = ATL.Loader.load('level01.tmx') end function love.draw() + love.graphics.translate(math.floor(tx), math.floor(ty)) + map:autoDrawRange(math.floor(tx), math.floor(ty), 1, 0) + map:draw() + for k, shadow in pairs(shadows) do + shadow:draw() + end for k, sprite in pairs(sprites) do sprite:draw() end @@ -24,6 +61,26 @@ function love.update(dt) + local scroll_speed = 250 + local scroll_margin = 64 + if love.keyboard.isDown("up") then villain:walk(dt, 1) end + if love.keyboard.isDown("right") then villain:walk(dt, 2) end + if love.keyboard.isDown("down") then villain:walk(dt, 3) end + if love.keyboard.isDown("left") then villain:walk(dt, 4) end + if villain.x + tx < scroll_margin then + tx = tx + dt * scroll_speed + else + if villain.x + tx > 800 - 64 - scroll_margin then + tx = tx - dt * scroll_speed + end + end + if villain.y + ty < scroll_margin then + ty = ty + dt * scroll_speed + else + if villain.y + ty > 600 - 64 - scroll_margin then + ty = ty - dt * scroll_speed + end + end for k, sprite in pairs(sprites) do sprite:update(dt) end
--- a/sprite.lua Sat Dec 15 10:23:56 2012 +0100 +++ b/sprite.lua Sat Dec 15 13:10:41 2012 +0100 @@ -1,5 +1,4 @@ local Sprite = {} -local Sprite_mt = { __index = Sprite } local function newSprite(x, y, image, animation) local sprite = setmetatable({ @@ -7,7 +6,7 @@ y = y, image = image, animation = animation, - }, Sprite_mt) + }, { __index = Sprite }) return sprite end @@ -19,8 +18,27 @@ self.animation:update(dt) end + +local Shadow = {} + +function newShadow(image, sprite) + return setmetatable({ + image = image, + sprite = sprite, + }, { __index = Shadow }) +end + +function Shadow:draw() + love.graphics.draw(self.image, self.sprite.x, self.sprite.y + 8) +end + +function Shadow:update(dt) +end + + local sprite = { newSprite = newSprite, + newShadow = newShadow, } return sprite
