summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Base64/Base64.lua178
-rw-r--r--bump/.gitignore1
-rw-r--r--bump/MIT-LICENSE.txt20
-rw-r--r--bump/README.md14
-rw-r--r--bump/bump.lua773
-rw-r--r--bump/bump_debug.lua31
-rw-r--r--bump/config.lua6
-rw-r--r--bump/main.lua163
-rw-r--r--json/README.md0
-rw-r--r--json/json.lua400
-rw-r--r--log/log.lua (renamed from log/init.lua)2
-rw-r--r--main.lua94
-rw-r--r--tiled/atlas.pngbin0 -> 10939 bytes
-rw-r--r--tiled/eventset.pngbin0 -> 548 bytes
-rw-r--r--tiled/map1.tmx69
-rw-r--r--tiled/map2.tmx83
-rw-r--r--tiled/map3.tmx74
-rw-r--r--tiled/map4.tmx105
-rw-r--r--tiled/map5.tmx110
-rw-r--r--tiled/map6.tmx120
-rw-r--r--tiled/signs.pngbin0 -> 348 bytes
-rw-r--r--tiled/signs_OLD.pngbin0 -> 271 bytes
-rw-r--r--tiled/tiled.lua244
-rw-r--r--tiled/tileset.pngbin0 -> 6340 bytes
24 files changed, 2391 insertions, 96 deletions
diff --git a/Base64/Base64.lua b/Base64/Base64.lua
new file mode 100644
index 0000000..33e78bd
--- /dev/null
+++ b/Base64/Base64.lua
@@ -0,0 +1,178 @@
+---------------------------------------------------------------------------------------------------
+-- -= 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} \ No newline at end of file
diff --git a/bump/.gitignore b/bump/.gitignore
new file mode 100644
index 0000000..3159fbe
--- /dev/null
+++ b/bump/.gitignore
@@ -0,0 +1 @@
+*.love
diff --git a/bump/MIT-LICENSE.txt b/bump/MIT-LICENSE.txt
new file mode 100644
index 0000000..2e015c6
--- /dev/null
+++ b/bump/MIT-LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2012 Enrique García Cota
+
+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.
diff --git a/bump/README.md b/bump/README.md
new file mode 100644
index 0000000..17521c7
--- /dev/null
+++ b/bump/README.md
@@ -0,0 +1,14 @@
+# bump.lua simple demo
+
+This is the simple demo of [bump.lua](http://github.com/kikito/bump.lua)
+
+It requires [LÖVE](http://love2d.org) to work properly.
+
+This simple demo creates a world populated by random boxes, and allows to move a player around using the keyboard arrow keys. The player has constant velocity
+and can will "slide" over the boxes without penetrating them, similarly to some classic games such as Zelda.
+
+Pressing TAB will display some debug information.
+
+![simpledemo](https://kikito.github.io/bump.lua/img/bump-simpledemo.gif)
+
+
diff --git a/bump/bump.lua b/bump/bump.lua
new file mode 100644
index 0000000..6dabca7
--- /dev/null
+++ b/bump/bump.lua
@@ -0,0 +1,773 @@
+local bump = {
+ _VERSION = 'bump v3.1.7',
+ _URL = 'https://github.com/kikito/bump.lua',
+ _DESCRIPTION = 'A collision detection library for Lua',
+ _LICENSE = [[
+ MIT LICENSE
+
+ Copyright (c) 2014 Enrique García Cota
+
+ 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.
+ ]]
+}
+
+------------------------------------------
+-- Auxiliary functions
+------------------------------------------
+local DELTA = 1e-10 -- floating-point margin of error
+
+local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max
+
+local function sign(x)
+ if x > 0 then return 1 end
+ if x == 0 then return 0 end
+ return -1
+end
+
+local function nearest(x, a, b)
+ if abs(a - x) < abs(b - x) then return a else return b end
+end
+
+local function assertType(desiredType, value, name)
+ if type(value) ~= desiredType then
+ error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')')
+ end
+end
+
+local function assertIsPositiveNumber(value, name)
+ if type(value) ~= 'number' or value <= 0 then
+ error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')')
+ end
+end
+
+local function assertIsRect(x,y,w,h)
+ assertType('number', x, 'x')
+ assertType('number', y, 'y')
+ assertIsPositiveNumber(w, 'w')
+ assertIsPositiveNumber(h, 'h')
+end
+
+local defaultFilter = function()
+ return 'slide'
+end
+
+------------------------------------------
+-- Rectangle functions
+------------------------------------------
+
+local function rect_getNearestCorner(x,y,w,h, px, py)
+ return nearest(px, x, x+w), nearest(py, y, y+h)
+end
+
+-- This is a generalized implementation of the liang-barsky algorithm, which also returns
+-- the normals of the sides where the segment intersects.
+-- Returns nil if the segment never touches the rect
+-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
+local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2)
+ ti1, ti2 = ti1 or 0, ti2 or 1
+ local dx, dy = x2-x1, y2-y1
+ local nx, ny
+ local nx1, ny1, nx2, ny2 = 0,0,0,0
+ local p, q, r
+
+ for side = 1,4 do
+ if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left
+ elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right
+ elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top
+ else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom
+ end
+
+ if p == 0 then
+ if q <= 0 then return nil end
+ else
+ r = q / p
+ if p < 0 then
+ if r > ti2 then return nil
+ elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny
+ end
+ else -- p > 0
+ if r < ti1 then return nil
+ elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny
+ end
+ end
+ end
+ end
+
+ return ti1,ti2, nx1,ny1, nx2,ny2
+end
+
+-- Calculates the minkowsky difference between 2 rects, which is another rect
+local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
+ return x2 - x1 - w1,
+ y2 - y1 - h1,
+ w1 + w2,
+ h1 + h2
+end
+
+local function rect_containsPoint(x,y,w,h, px,py)
+ return px - x > DELTA and py - y > DELTA and
+ x + w - px > DELTA and y + h - py > DELTA
+end
+
+local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2)
+ return x1 < x2+w2 and x2 < x1+w1 and
+ y1 < y2+h2 and y2 < y1+h1
+end
+
+local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2)
+ local dx = x1 - x2 + (w1 - w2)/2
+ local dy = y1 - y2 + (h1 - h2)/2
+ return dx*dx + dy*dy
+end
+
+local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY)
+ goalX = goalX or x1
+ goalY = goalY or y1
+
+ local dx, dy = goalX - x1, goalY - y1
+ local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
+
+ local overlaps, ti, nx, ny
+
+ if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other
+ local px, py = rect_getNearestCorner(x,y,w,h, 0, 0)
+ local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection
+ ti = -wi * hi -- ti is the negative area of intersection
+ overlaps = true
+ else
+ local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge)
+
+ -- item tunnels into other
+ if ti1
+ and ti1 < 1
+ and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner
+ and (0 < ti1 + DELTA
+ or 0 == ti1 and ti2 > 0)
+ then
+ ti, nx, ny = ti1, nx1, ny1
+ overlaps = false
+ end
+ end
+
+ if not ti then return end
+
+ local tx, ty
+
+ if overlaps then
+ if dx == 0 and dy == 0 then
+ -- intersecting and not moving - use minimum displacement vector
+ local px, py = rect_getNearestCorner(x,y,w,h, 0,0)
+ if abs(px) < abs(py) then py = 0 else px = 0 end
+ nx, ny = sign(px), sign(py)
+ tx, ty = x1 + px, y1 + py
+ else
+ -- intersecting and moving - move in the opposite direction
+ local ti1, _
+ ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1)
+ if not ti1 then return end
+ tx, ty = x1 + dx * ti1, y1 + dy * ti1
+ end
+ else -- tunnel
+ tx, ty = x1 + dx * ti, y1 + dy * ti
+ end
+
+ return {
+ overlaps = overlaps,
+ ti = ti,
+ move = {x = dx, y = dy},
+ normal = {x = nx, y = ny},
+ touch = {x = tx, y = ty},
+ itemRect = {x = x1, y = y1, w = w1, h = h1},
+ otherRect = {x = x2, y = y2, w = w2, h = h2}
+ }
+end
+
+------------------------------------------
+-- Grid functions
+------------------------------------------
+
+local function grid_toWorld(cellSize, cx, cy)
+ return (cx - 1)*cellSize, (cy-1)*cellSize
+end
+
+local function grid_toCell(cellSize, x, y)
+ return floor(x / cellSize) + 1, floor(y / cellSize) + 1
+end
+
+-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing",
+-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf
+-- It has been modified to include both cells when the ray "touches a grid corner",
+-- and with a different exit condition
+
+local function grid_traverse_initStep(cellSize, ct, t1, t2)
+ local v = t2 - t1
+ if v > 0 then
+ return 1, cellSize / v, ((ct + v) * cellSize - t1) / v
+ elseif v < 0 then
+ return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v
+ else
+ return 0, math.huge, math.huge
+ end
+end
+
+local function grid_traverse(cellSize, x1,y1,x2,y2, f)
+ local cx1,cy1 = grid_toCell(cellSize, x1,y1)
+ local cx2,cy2 = grid_toCell(cellSize, x2,y2)
+ local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
+ local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
+ local cx,cy = cx1,cy1
+
+ f(cx, cy)
+
+ -- The default implementation had an infinite loop problem when
+ -- approaching the last cell in some occassions. We finish iterating
+ -- when we are *next* to the last cell
+ while abs(cx - cx2) + abs(cy - cy2) > 1 do
+ if tx < ty then
+ tx, cx = tx + dx, cx + stepX
+ f(cx, cy)
+ else
+ -- Addition: include both cells when going through corners
+ if tx == ty then f(cx + stepX, cy) end
+ ty, cy = ty + dy, cy + stepY
+ f(cx, cy)
+ end
+ end
+
+ -- If we have not arrived to the last cell, use it
+ if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end
+
+end
+
+local function grid_toCellRect(cellSize, x,y,w,h)
+ local cx,cy = grid_toCell(cellSize, x, y)
+ local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize)
+ return cx, cy, cr - cx + 1, cb - cy + 1
+end
+
+------------------------------------------
+-- Responses
+------------------------------------------
+
+local touch = function(world, col, x,y,w,h, goalX, goalY, filter)
+ return col.touch.x, col.touch.y, {}, 0
+end
+
+local cross = function(world, col, x,y,w,h, goalX, goalY, filter)
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+local slide = function(world, col, x,y,w,h, goalX, goalY, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+
+ local tch, move = col.touch, col.move
+ if move.x ~= 0 or move.y ~= 0 then
+ if col.normal.x ~= 0 then
+ goalX = tch.x
+ else
+ goalY = tch.y
+ end
+ end
+
+ col.slide = {x = goalX, y = goalY}
+
+ x,y = tch.x, tch.y
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+local bounce = function(world, col, x,y,w,h, goalX, goalY, filter)
+ goalX = goalX or x
+ goalY = goalY or y
+
+ local tch, move = col.touch, col.move
+ local tx, ty = tch.x, tch.y
+
+ local bx, by = tx, ty
+
+ if move.x ~= 0 or move.y ~= 0 then
+ local bnx, bny = goalX - tx, goalY - ty
+ if col.normal.x == 0 then bny = -bny else bnx = -bnx end
+ bx, by = tx + bnx, ty + bny
+ end
+
+ col.bounce = {x = bx, y = by}
+ x,y = tch.x, tch.y
+ goalX, goalY = bx, by
+
+ local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
+ return goalX, goalY, cols, len
+end
+
+------------------------------------------
+-- World
+------------------------------------------
+
+local World = {}
+local World_mt = {__index = World}
+
+-- Private functions and methods
+
+local function sortByWeight(a,b) return a.weight < b.weight end
+
+local function sortByTiAndDistance(a,b)
+ if a.ti == b.ti then
+ local ir, ar, br = a.itemRect, a.otherRect, b.otherRect
+ local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h)
+ local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h)
+ return ad < bd
+ end
+ return a.ti < b.ti
+end
+
+local function addItemToCell(self, item, cx, cy)
+ self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'})
+ local row = self.rows[cy]
+ row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})}
+ local cell = row[cx]
+ self.nonEmptyCells[cell] = true
+ if not cell.items[item] then
+ cell.items[item] = true
+ cell.itemCount = cell.itemCount + 1
+ end
+end
+
+local function removeItemFromCell(self, item, cx, cy)
+ local row = self.rows[cy]
+ if not row or not row[cx] or not row[cx].items[item] then return false end
+
+ local cell = row[cx]
+ cell.items[item] = nil
+ cell.itemCount = cell.itemCount - 1
+ if cell.itemCount == 0 then
+ self.nonEmptyCells[cell] = nil
+ end
+ return true
+end
+
+local function getDictItemsInCellRect(self, cl,ct,cw,ch)
+ local items_dict = {}
+ for cy=ct,ct+ch-1 do
+ local row = self.rows[cy]
+ if row then
+ for cx=cl,cl+cw-1 do
+ local cell = row[cx]
+ if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling
+ for item,_ in pairs(cell.items) do
+ items_dict[item] = true
+ end
+ end
+ end
+ end
+ end
+
+ return items_dict
+end
+
+local function getCellsTouchedBySegment(self, x1,y1,x2,y2)
+
+ local cells, cellsLen, visited = {}, 0, {}
+
+ grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy)
+ local row = self.rows[cy]
+ if not row then return end
+ local cell = row[cx]
+ if not cell or visited[cell] then return end
+
+ visited[cell] = true
+ cellsLen = cellsLen + 1
+ cells[cellsLen] = cell
+ end)
+
+ return cells, cellsLen
+end
+
+local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter)
+ local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2)
+ local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1
+ local visited, itemInfo, itemInfoLen = {},{},0
+ for i=1,len do
+ cell = cells[i]
+ for item in pairs(cell.items) do
+ if not visited[item] then
+ visited[item] = true
+ if (not filter or filter(item)) then
+ rect = self.rects[item]
+ l,t,w,h = rect.x,rect.y,rect.w,rect.h
+
+ ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1)
+ if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then
+ -- the sorting is according to the t of an infinite line, not the segment
+ tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge)
+ itemInfoLen = itemInfoLen + 1
+ itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)}
+ end
+ end
+ end
+ end
+ end
+ table.sort(itemInfo, sortByWeight)
+ return itemInfo, itemInfoLen
+end
+
+local function getResponseByName(self, name)
+ local response = self.responses[name]
+ if not response then
+ error(('Unknown collision type: %s (%s)'):format(name, type(name)))
+ end
+ return response
+end
+
+
+-- Misc Public Methods
+
+function World:addResponse(name, response)
+ self.responses[name] = response
+end
+
+function World:project(item, x,y,w,h, goalX, goalY, filter)
+ assertIsRect(x,y,w,h)
+
+ goalX = goalX or x
+ goalY = goalY or y
+ filter = filter or defaultFilter
+
+ local collisions, len = {}, 0
+
+ local visited = {}
+ if item ~= nil then visited[item] = true end
+
+ -- This could probably be done with less cells using a polygon raster over the cells instead of a
+ -- bounding rect of the whole movement. Conditional to building a queryPolygon method
+ local tl, tt = min(goalX, x), min(goalY, y)
+ local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h)
+ local tw, th = tr-tl, tb-tt
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th)
+
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
+
+ for other,_ in pairs(dictItemsInCellRect) do
+ if not visited[other] then
+ visited[other] = true
+
+ local responseName = filter(item, other)
+ if responseName then
+ local ox,oy,ow,oh = self:getRect(other)
+ local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY)
+
+ if col then
+ col.other = other
+ col.item = item
+ col.type = responseName
+
+ len = len + 1
+ collisions[len] = col
+ end
+ end
+ end
+ end
+
+ table.sort(collisions, sortByTiAndDistance)
+
+ return collisions, len
+end
+
+function World:countCells()
+ local count = 0
+ for _,row in pairs(self.rows) do
+ for _,_ in pairs(row) do
+ count = count + 1
+ end
+ end
+ return count
+end
+
+function World:hasItem(item)
+ return not not self.rects[item]
+end
+
+function World:getItems()
+ local items, len = {}, 0
+ for item,_ in pairs(self.rects) do
+ len = len + 1
+ items[len] = item
+ end
+ return items, len
+end
+
+function World:countItems()
+ local len = 0
+ for _ in pairs(self.rects) do len = len + 1 end
+ return len
+end
+
+function World:getRect(item)
+ local rect = self.rects[item]
+ if not rect then
+ error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.')
+ end
+ return rect.x, rect.y, rect.w, rect.h
+end
+
+function World:toWorld(cx, cy)
+ return grid_toWorld(self.cellSize, cx, cy)
+end
+
+function World:toCell(x,y)
+ return grid_toCell(self.cellSize, x, y)
+end
+
+
+--- Query methods
+
+function World:queryRect(x,y,w,h, filter)
+
+ assertIsRect(x,y,w,h)
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
+
+ local items, len = {}, 0
+
+ local rect
+ for item,_ in pairs(dictItemsInCellRect) do
+ rect = self.rects[item]
+ if (not filter or filter(item))
+ and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:queryPoint(x,y, filter)
+ local cx,cy = self:toCell(x,y)
+ local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1)
+
+ local items, len = {}, 0
+
+ local rect
+ for item,_ in pairs(dictItemsInCellRect) do
+ rect = self.rects[item]
+ if (not filter or filter(item))
+ and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y)
+ then
+ len = len + 1
+ items[len] = item
+ end
+ end
+
+ return items, len
+end
+
+function World:querySegment(x1, y1, x2, y2, filter)
+ local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
+ local items = {}
+ for i=1, len do
+ items[i] = itemInfo[i].item
+ end
+ return items, len
+end
+
+function World:querySegmentWithCoords(x1, y1, x2, y2, filter)
+ local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
+ local dx, dy = x2-x1, y2-y1
+ local info, ti1, ti2
+ for i=1, len do
+ info = itemInfo[i]
+ ti1 = info.ti1
+ ti2 = info.ti2
+
+ info.weight = nil
+ info.x1 = x1 + dx * ti1
+ info.y1 = y1 + dy * ti1
+ info.x2 = x1 + dx * ti2
+ info.y2 = y1 + dy * ti2
+ end
+ return itemInfo, len
+end
+
+
+--- Main methods
+
+function World:add(item, x,y,w,h)
+ local rect = self.rects[item]
+ if rect then
+ error('Item ' .. tostring(item) .. ' added to the world twice.')
+ end
+ assertIsRect(x,y,w,h)
+
+ self.rects[item] = {x=x,y=y,w=w,h=h}
+
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ for cy = ct, ct+ch-1 do
+ for cx = cl, cl+cw-1 do
+ addItemToCell(self, item, cx, cy)
+ end
+ end
+
+ return item
+end
+
+function World:remove(item)
+ local x,y,w,h = self:getRect(item)
+
+ self.rects[item] = nil
+ local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
+ for cy = ct, ct+ch-1 do
+ for cx = cl, cl+cw-1 do
+ removeItemFromCell(self, item, cx, cy)
+ end
+ end
+end
+
+function World:update(item, x2,y2,w2,h2)
+ local x1,y1,w1,h1 = self:getRect(item)
+ w2,h2 = w2 or w1, h2 or h1
+ assertIsRect(x2,y2,w2,h2)
+
+ if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then
+
+ local cellSize = self.cellSize
+ local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1)
+ local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2)
+
+ if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then
+
+ local cr1, cb1 = cl1+cw1-1, ct1+ch1-1
+ local cr2, cb2 = cl2+cw2-1, ct2+ch2-1
+ local cyOut
+
+ for cy = ct1, cb1 do
+ cyOut = cy < ct2 or cy > cb2
+ for cx = cl1, cr1 do
+ if cyOut or cx < cl2 or cx > cr2 then
+ removeItemFromCell(self, item, cx, cy)
+ end
+ end
+ end
+
+ for cy = ct2, cb2 do
+ cyOut = cy < ct1 or cy > cb1
+ for cx = cl2, cr2 do
+ if cyOut or cx < cl1 or cx > cr1 then
+ addItemToCell(self, item, cx, cy)
+ end
+ end
+ end
+
+ end
+
+ local rect = self.rects[item]
+ rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2
+
+ end
+end
+
+function World:move(item, goalX, goalY, filter)
+ local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter)
+
+ self:update(item, actualX, actualY)
+
+ return actualX, actualY, cols, len
+end
+
+function World:check(item, goalX, goalY, filter)
+ filter = filter or defaultFilter
+
+ local visited = {[item] = true}
+ local visitedFilter = function(itm, other)
+ if visited[other] then return false end
+ return filter(itm, other)
+ end
+
+ local cols, len = {}, 0
+
+ local x,y,w,h = self:getRect(item)
+
+ local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter)
+
+ while projected_len > 0 do
+ local col = projected_cols[1]
+ len = len + 1
+ cols[len] = col
+
+ visited[col.other] = true
+
+ local response = getResponseByName(self, col.type)
+
+ goalX, goalY, projected_cols, projected_len = response(
+ self,
+ col,
+ x, y, w, h,
+ goalX, goalY,
+ visitedFilter
+ )
+ end
+
+ return goalX, goalY, cols, len
+end
+
+
+-- Public library functions
+
+bump.newWorld = function(cellSize)
+ cellSize = cellSize or 64
+ assertIsPositiveNumber(cellSize, 'cellSize')
+ local world = setmetatable({
+ cellSize = cellSize,
+ rects = {},
+ rows = {},
+ nonEmptyCells = {},
+ responses = {}
+ }, World_mt)
+
+ world:addResponse('touch', touch)
+ world:addResponse('cross', cross)
+ world:addResponse('slide', slide)
+ world:addResponse('bounce', bounce)
+
+ return world
+end
+
+bump.rect = {
+ getNearestCorner = rect_getNearestCorner,
+ getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices,
+ getDiff = rect_getDiff,
+ containsPoint = rect_containsPoint,
+ isIntersecting = rect_isIntersecting,
+ getSquareDistance = rect_getSquareDistance,
+ detectCollision = rect_detectCollision
+}
+
+bump.responses = {
+ touch = touch,
+ cross = cross,
+ slide = slide,
+ bounce = bounce
+}
+
+return bump
diff --git a/bump/bump_debug.lua b/bump/bump_debug.lua
new file mode 100644
index 0000000..308608f
--- /dev/null
+++ b/bump/bump_debug.lua
@@ -0,0 +1,31 @@
+
+local bump = require 'bump'
+
+local bump_debug = {}
+
+local function getCellRect(world, cx,cy)
+ local cellSize = world.cellSize
+ local l,t = world:toWorld(cx,cy)
+ return l,t,cellSize,cellSize
+end
+
+function bump_debug.draw(world)
+ local cellSize = world.cellSize
+ -- local font = love.graphics.getFont()
+ local fontHeight = 20
+ local topOffset = (cellSize - fontHeight) / 2
+ for cy, row in pairs(world.rows) do
+ for cx, cell in pairs(row) do
+ local l,t,w,h = getCellRect(world, cx,cy)
+ local intensity = cell.itemCount * 12 + 16
+ jin.graphics.setColor(255,255,255,intensity)
+ jin.graphics.rect('fill', l,t,w,h)
+ jin.graphics.setColor(255,255,255, 64)
+ -- jin.graphics.printf(cell.itemCount, l, t+topOffset, cellSize, 'center')
+ jin.graphics.setColor(255,255,255,10)
+ jin.graphics.rect('line', l,t,w,h)
+ end
+ end
+end
+
+return bump_debug
diff --git a/bump/config.lua b/bump/config.lua
new file mode 100644
index 0000000..d3b3d68
--- /dev/null
+++ b/bump/config.lua
@@ -0,0 +1,6 @@
+return
+{
+ width = 800,
+ height = 600
+
+} \ No newline at end of file
diff --git a/bump/main.lua b/bump/main.lua
new file mode 100644
index 0000000..973cd61
--- /dev/null
+++ b/bump/main.lua
@@ -0,0 +1,163 @@
+local bump = require 'bump'
+local bump_debug = require 'bump_debug'
+io.stdout:setvbuf("no")
+local instructions = [[
+ bump.lua simple demo
+
+ arrows: move
+ tab: toggle debug info
+ delete: run garbage collector
+]]
+
+local cols_len = 0 -- how many collisions are happening
+
+-- World creation
+local world = bump.newWorld()
+
+
+-- Message/debug functions
+local function drawMessage()
+ local msg = instructions:format(tostring(shouldDrawDebug))
+ jin.graphics.setColor(255, 255, 255)
+ -- jin.graphics.print(msg, 550, 10)
+end
+
+local function drawDebug()
+ bump_debug.draw(world)
+
+ local statistics = ("fps: %d, mem: %dKB, collisions: %d, items: %d"):format(love.timer.getFPS(), collectgarbage("count"), cols_len, world:countItems())
+ jin.graphics.setColor(255, 255, 255)
+ -- jin.graphics.printf(statistics, 0, 580, 790, 'right')
+end
+
+local consoleBuffer = {}
+local consoleBufferSize = 15
+for i=1,consoleBufferSize do consoleBuffer[i] = "" end
+local function consolePrint(msg)
+ table.remove(consoleBuffer,1)
+ consoleBuffer[consoleBufferSize] = msg
+end
+
+local function drawConsole()
+ local str = table.concat(consoleBuffer, "\n")
+ for i=1,consoleBufferSize do
+ jin.graphics.setColor(255,255,255, i*255/consoleBufferSize)
+ -- love.graphics.printf(consoleBuffer[i], 10, 580-(consoleBufferSize - i)*12, 790, "left")
+ end
+end
+
+-- helper function
+local function drawBox(box, r,g,b)
+ jin.graphics.setColor(r,g,b,70)
+ jin.graphics.rect("fill", box.x, box.y, box.w, box.h)
+ jin.graphics.setColor(r,g,b)
+ jin.graphics.rect("line", box.x, box.y, box.w, box.h)
+end
+
+
+
+-- Player functions
+local player = { x=50,y=50,w=20,h=20, speed = 80, onGround = false}
+vx = 0
+vy = 0
+local function updatePlayer(dt)
+ local speed = player.speed
+ if jin.keyboard.isDown('Right') then
+ vx = speed
+ elseif jin.keyboard.isDown('Left') then
+ vx = -speed
+ end
+ if jin.keyboard.isDown('Down') then
+ vy = vy
+ elseif jin.keyboard.isDown('Up') then
+ vy = vy
+ elseif jin.keyboard.isDown('Space') and player.onGround then
+ vy = vy - 12000 * dt
+ print("jump")
+ print("on sky")
+ player.onGround = false
+ end
+ if not player.onGround then
+ vy = vy + 200 * dt
+ end
+ if vx ~= 0 or vy ~= 0 then
+ local cols
+ local py = player.y
+ player.x, player.y, cols, cols_len = world:move(player, player.x + vx * dt, player.y + vy * dt)
+ if py == player.y then
+ player.onGround = true
+ vy = 0
+ print("on ground")
+ end
+ for i=1, cols_len do
+ local col = cols[i]
+ consolePrint(("col.other = %s, col.type = %s, col.normal = %d,%d"):format(col.other, col.type, col.normal.x, col.normal.y))
+ end
+ end
+ vx = 0
+end
+
+local function drawPlayer()
+ drawBox(player, 0, 255, 0)
+end
+
+-- Block functions
+
+local blocks = {}
+
+local function addBlock(x,y,w,h)
+ local block = {x=x,y=y,w=w,h=h}
+ blocks[#blocks+1] = block
+ world:add(block, x,y,w,h)
+end
+
+local function drawBlocks()
+ for _,block in ipairs(blocks) do
+ drawBox(block, 255,0,0)
+ end
+end
+
+function jin.core.onEvent(e)
+ if e.type == "quit" then
+ jin.core.stop()
+ end
+ if e.type == "keydown" then
+ if e.key == "Escape" then
+ jin.core.stop()
+ end
+ end
+end
+
+-- Main LÖVE functions
+
+function jin.core.onLoad()
+ world:add(player, player.x, player.y, player.w, player.h)
+
+ addBlock(0, 0, 800, 32)
+ addBlock(0, 32, 32, 600-32*2)
+ addBlock(800-32, 32, 32, 600-32*2)
+ addBlock(0, 600-32, 800, 32)
+
+ for i=1,30 do
+ addBlock( math.random(100, 600),
+ math.random(100, 400),
+ math.random(10, 100),
+ math.random(10, 100)
+ )
+ end
+end
+
+function jin.core.onUpdate(dt)
+ cols_len = 0
+ updatePlayer(dt)
+end
+
+function jin.core.onDraw()
+ drawBlocks()
+ drawPlayer()
+ if shouldDrawDebug then
+ drawDebug()
+ drawConsole()
+ end
+ drawMessage()
+end
diff --git a/json/README.md b/json/README.md
deleted file mode 100644
index e69de29..0000000
--- a/json/README.md
+++ /dev/null
diff --git a/json/json.lua b/json/json.lua
new file mode 100644
index 0000000..a3d7530
--- /dev/null
+++ b/json/json.lua
@@ -0,0 +1,400 @@
+--
+-- json.lua
+--
+-- Copyright (c) 2018 rxi
+--
+-- 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.
+--
+
+local json = { _version = "0.1.1" }
+
+-------------------------------------------------------------------------------
+-- Encode
+-------------------------------------------------------------------------------
+
+local encode
+
+local escape_char_map = {
+ [ "\\" ] = "\\\\",
+ [ "\"" ] = "\\\"",
+ [ "\b" ] = "\\b",
+ [ "\f" ] = "\\f",
+ [ "\n" ] = "\\n",
+ [ "\r" ] = "\\r",
+ [ "\t" ] = "\\t",
+}
+
+local escape_char_map_inv = { [ "\\/" ] = "/" }
+for k, v in pairs(escape_char_map) do
+ escape_char_map_inv[v] = k
+end
+
+
+local function escape_char(c)
+ return escape_char_map[c] or string.format("\\u%04x", c:byte())
+end
+
+
+local function encode_nil(val)
+ return "null"
+end
+
+
+local function encode_table(val, stack)
+ local res = {}
+ stack = stack or {}
+
+ -- Circular reference?
+ if stack[val] then error("circular reference") end
+
+ stack[val] = true
+
+ if val[1] ~= nil or next(val) == nil then
+ -- Treat as array -- check keys are valid and it is not sparse
+ local n = 0
+ for k in pairs(val) do
+ if type(k) ~= "number" then
+ error("invalid table: mixed or invalid key types")
+ end
+ n = n + 1
+ end
+ if n ~= #val then
+ error("invalid table: sparse array")
+ end
+ -- Encode
+ for i, v in ipairs(val) do
+ table.insert(res, encode(v, stack))
+ end
+ stack[val] = nil
+ return "[" .. table.concat(res, ",") .. "]"
+
+ else
+ -- Treat as an object
+ for k, v in pairs(val) do
+ if type(k) ~= "string" then
+ error("invalid table: mixed or invalid key types")
+ end
+ table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
+ end
+ stack[val] = nil
+ return "{" .. table.concat(res, ",") .. "}"
+ end
+end
+
+
+local function encode_string(val)
+ return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
+end
+
+
+local function encode_number(val)
+ -- Check for NaN, -inf and inf
+ if val ~= val or val <= -math.huge or val >= math.huge then
+ error("unexpected number value '" .. tostring(val) .. "'")
+ end
+ return string.format("%.14g", val)
+end
+
+
+local type_func_map = {
+ [ "nil" ] = encode_nil,
+ [ "table" ] = encode_table,
+ [ "string" ] = encode_string,
+ [ "number" ] = encode_number,
+ [ "boolean" ] = tostring,
+}
+
+
+encode = function(val, stack)
+ local t = type(val)
+ local f = type_func_map[t]
+ if f then
+ return f(val, stack)
+ end
+ error("unexpected type '" .. t .. "'")
+end
+
+
+function json.encode(val)
+ return ( encode(val) )
+end
+
+
+-------------------------------------------------------------------------------
+-- Decode
+-------------------------------------------------------------------------------
+
+local parse
+
+local function create_set(...)
+ local res = {}
+ for i = 1, select("#", ...) do
+ res[ select(i, ...) ] = true
+ end
+ return res
+end
+
+local space_chars = create_set(" ", "\t", "\r", "\n")
+local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
+local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
+local literals = create_set("true", "false", "null")
+
+local literal_map = {
+ [ "true" ] = true,
+ [ "false" ] = false,
+ [ "null" ] = nil,
+}
+
+
+local function next_char(str, idx, set, negate)
+ for i = idx, #str do
+ if set[str:sub(i, i)] ~= negate then
+ return i
+ end
+ end
+ return #str + 1
+end
+
+
+local function decode_error(str, idx, msg)
+ local line_count = 1
+ local col_count = 1
+ for i = 1, idx - 1 do
+ col_count = col_count + 1
+ if str:sub(i, i) == "\n" then
+ line_count = line_count + 1
+ col_count = 1
+ end
+ end
+ error( string.format("%s at line %d col %d", msg, line_count, col_count) )
+end
+
+
+local function codepoint_to_utf8(n)
+ -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
+ local f = math.floor
+ if n <= 0x7f then
+ return string.char(n)
+ elseif n <= 0x7ff then
+ return string.char(f(n / 64) + 192, n % 64 + 128)
+ elseif n <= 0xffff then
+ return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
+ elseif n <= 0x10ffff then
+ return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
+ f(n % 4096 / 64) + 128, n % 64 + 128)
+ end
+ error( string.format("invalid unicode codepoint '%x'", n) )
+end
+
+
+local function parse_unicode_escape(s)
+ local n1 = tonumber( s:sub(3, 6), 16 )
+ local n2 = tonumber( s:sub(9, 12), 16 )
+ -- Surrogate pair?
+ if n2 then
+ return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
+ else
+ return codepoint_to_utf8(n1)
+ end
+end
+
+
+local function parse_string(str, i)
+ local has_unicode_escape = false
+ local has_surrogate_escape = false
+ local has_escape = false
+ local last
+ for j = i + 1, #str do
+ local x = str:byte(j)
+
+ if x < 32 then
+ decode_error(str, j, "control character in string")
+ end
+
+ if last == 92 then -- "\\" (escape char)
+ if x == 117 then -- "u" (unicode escape sequence)
+ local hex = str:sub(j + 1, j + 5)
+ if not hex:find("%x%x%x%x") then
+ decode_error(str, j, "invalid unicode escape in string")
+ end
+ if hex:find("^[dD][89aAbB]") then
+ has_surrogate_escape = true
+ else
+ has_unicode_escape = true
+ end
+ else
+ local c = string.char(x)
+ if not escape_chars[c] then
+ decode_error(str, j, "invalid escape char '" .. c .. "' in string")
+ end
+ has_escape = true
+ end
+ last = nil
+
+ elseif x == 34 then -- '"' (end of string)
+ local s = str:sub(i + 1, j - 1)
+ if has_surrogate_escape then
+ s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
+ end
+ if has_unicode_escape then
+ s = s:gsub("\\u....", parse_unicode_escape)
+ end
+ if has_escape then
+ s = s:gsub("\\.", escape_char_map_inv)
+ end
+ return s, j + 1
+
+ else
+ last = x
+ end
+ end
+ decode_error(str, i, "expected closing quote for string")
+end
+
+
+local function parse_number(str, i)
+ local x = next_char(str, i, delim_chars)
+ local s = str:sub(i, x - 1)
+ local n = tonumber(s)
+ if not n then
+ decode_error(str, i, "invalid number '" .. s .. "'")
+ end
+ return n, x
+end
+
+
+local function parse_literal(str, i)
+ local x = next_char(str, i, delim_chars)
+ local word = str:sub(i, x - 1)
+ if not literals[word] then
+ decode_error(str, i, "invalid literal '" .. word .. "'")
+ end
+ return literal_map[word], x
+end
+
+
+local function parse_array(str, i)
+ local res = {}
+ local n = 1
+ i = i + 1
+ while 1 do
+ local x
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of array?
+ if str:sub(i, i) == "]" then
+ i = i + 1
+ break
+ end
+ -- Read token
+ x, i = parse(str, i)
+ res[n] = x
+ n = n + 1
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "]" then break end
+ if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
+ end
+ return res, i
+end
+
+
+local function parse_object(str, i)
+ local res = {}
+ i = i + 1
+ while 1 do
+ local key, val
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of object?
+ if str:sub(i, i) == "}" then
+ i = i + 1
+ break
+ end
+ -- Read key
+ if str:sub(i, i) ~= '"' then
+ decode_error(str, i, "expected string for key")
+ end
+ key, i = parse(str, i)
+ -- Read ':' delimiter
+ i = next_char(str, i, space_chars, true)
+ if str:sub(i, i) ~= ":" then
+ decode_error(str, i, "expected ':' after key")
+ end
+ i = next_char(str, i + 1, space_chars, true)
+ -- Read value
+ val, i = parse(str, i)
+ -- Set
+ res[key] = val
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "}" then break end
+ if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
+ end
+ return res, i
+end
+
+
+local char_func_map = {
+ [ '"' ] = parse_string,
+ [ "0" ] = parse_number,
+ [ "1" ] = parse_number,
+ [ "2" ] = parse_number,
+ [ "3" ] = parse_number,
+ [ "4" ] = parse_number,
+ [ "5" ] = parse_number,
+ [ "6" ] = parse_number,
+ [ "7" ] = parse_number,
+ [ "8" ] = parse_number,
+ [ "9" ] = parse_number,
+ [ "-" ] = parse_number,
+ [ "t" ] = parse_literal,
+ [ "f" ] = parse_literal,
+ [ "n" ] = parse_literal,
+ [ "[" ] = parse_array,
+ [ "{" ] = parse_object,
+}
+
+
+parse = function(str, idx)
+ local chr = str:sub(idx, idx)
+ local f = char_func_map[chr]
+ if f then
+ return f(str, idx)
+ end
+ decode_error(str, idx, "unexpected character '" .. chr .. "'")
+end
+
+
+function json.decode(str)
+ if type(str) ~= "string" then
+ error("expected argument of type string, got " .. type(str))
+ end
+ local res, idx = parse(str, next_char(str, 1, space_chars, true))
+ idx = next_char(str, idx, space_chars, true)
+ if idx <= #str then
+ decode_error(str, idx, "trailing garbage")
+ end
+ return res
+end
+
+
+return json
diff --git a/log/init.lua b/log/log.lua
index bfa2e4e..0c1d3bc 100644
--- a/log/init.lua
+++ b/log/log.lua
@@ -1,5 +1,3 @@
--- 不能使用 debug 命名模块,会冲突,
--- 要使用其余名字比如 loghelper
local log = {}
io.stdout:setvbuf("no")
diff --git a/main.lua b/main.lua
index 5e1f26a..e69de29 100644
--- a/main.lua
+++ b/main.lua
@@ -1,94 +0,0 @@
--- _require = require
--- require = function(name)
--- return _require("jin-modules." .. name)
--- end
-local jnet = require("jnet")
-local log = require("log")
-log.dateFormat("")
-log.strict(log.LEVEL.INFO)
-local EventMsgCenter = require("EventMsgCenter.EventMsgCenter")
-local Events = require("EventMsgCenter.Events")
-local timer = require("timer.timer")
-require("gameloop")
-_G["frame"] = 0
-
-local thread = nil
-local socket = nil
-jin.core.onLoad = function()
----- 网络测试
- jin.net.init()
- socket = jin.net.newSocket("TCP", 8807)
- local Skill = {
- id = jnet.DataType.INT,
- damage = jnet.DataType.FLOAT,
- name = jnet.DataType.STRING,
- description = jnet.DataType.STRING,
- -- bonus = {
- -- damage = FLOAT,
- -- range = FLOAT,
- -- cd = BOOL
- -- }
- cd = jnet.DataType.BOOL
- }
- local buf = jin.net.newBuffer(0)
- local mySkill = {
- id = 12,
- name = "Hell fire!!!",
- damage = 3.4,
- description = "1234",
- -- bonus = {
- -- damage = FLOAT,
- -- range = FLOAT,
- -- cd = BOOL
- -- }
- cd = true
- }
- local len = buf:append("Skill")
- jnet.serialize(mySkill, buf)
- local msgName = buf:grabString(0)
- log.error(msgName)
- local msg = jnet.deserialize(Skill, buf, len)
- log.info(msg.id)
-
- thread = jin.thread.newThread("Test", [[
- local thread = jin.thread.getThread()
- local socket = thread:demand(1)
- while true do
- local client = socket:accept()
- local buf = client:receive()
- local str = buf:grabString(0)
- print(str)
- end
- ]])
- thread:start()
- thread:send(1, socket)
- EventMsgCenter.registerMsg(Events.Player_Move, function(msg)
- -- print(msg)
- end)
- timer.every(1.0, function()
- log.info(_G["frame"] .. "fps")
- -- EventMsgCenter.sendMsg(Events.Player_Move, _G["frame"])
- _G["frame"] = 0
- end)
- timer.after(4.0, function()
- EventMsgCenter.unregisterAllMsgByEvent(Events.Player_Move)
- end)
-end
-
-jin.core.onEvent = function(e)
- if e.type == "quit" then
- jin.core.stop() elseif e.type == "keydown" then
- if e.key == "Escape" then
- jin.core.stop()
- end
- end
-end
-
-jin.core.onUpdate = function(dt)
- _G["frame"] = _G["frame"] + 1
- timer.update(dt)
-end
-
-jin.core.onDraw = function()
-
-end \ No newline at end of file
diff --git a/tiled/atlas.png b/tiled/atlas.png
new file mode 100644
index 0000000..e32d664
--- /dev/null
+++ b/tiled/atlas.png
Binary files differ
diff --git a/tiled/eventset.png b/tiled/eventset.png
new file mode 100644
index 0000000..929c3da
--- /dev/null
+++ b/tiled/eventset.png
Binary files differ
diff --git a/tiled/map1.tmx b/tiled/map1.tmx
new file mode 100644
index 0000000..376c292
--- /dev/null
+++ b/tiled/map1.tmx
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="70" height="40" tilewidth="16" tileheight="16" nextobjectid="1">
+ <tileset firstgid="1" name="eventset" tilewidth="16" tileheight="16" tilecount="16" columns="4">
+ <image source="eventset.png" width="64" height="64"/>
+ <tile id="1">
+ <properties>
+ <property name="portal" value="1"/>
+ <property name="target" value="2-1"/>
+ </properties>
+ </tile>
+ <tile id="2">
+ <properties>
+ <property name="start" value="player2"/>
+ </properties>
+ </tile>
+ <tile id="3">
+ <properties>
+ <property name="floor" value="true"/>
+ </properties>
+ </tile>
+ <tile id="4">
+ <properties>
+ <property name="lowest" value="true"/>
+ </properties>
+ </tile>
+ <tile id="5">
+ <properties>
+ <property name="id" value="3"/>
+ <property name="portal" value="true"/>
+ </properties>
+ </tile>
+ <tile id="7">
+ <properties>
+ <property name="block" value="true"/>
+ </properties>
+ </tile>
+ </tileset>
+ <tileset firstgid="17" name="atlas" tilewidth="16" tileheight="16" tilecount="1024" columns="32">
+ <image source="atlas.png" width="512" height="512"/>
+ </tileset>
+ <tileset firstgid="1041" name="signs" tilewidth="16" tileheight="16" tilecount="4" columns="4">
+ <image source="signs.png" width="64" height="16"/>
+ </tileset>
+ <layer name="bg" width="70" height="40">
+ <data encoding="base64" compression="zlib">
+ eJztzkVylGEYhdE/BPdi3nHreDEnuOsGsAT3DQQI7rABXINvAHdnAzjBbQPImbGFrq/eqnumT91clmUllFJGORVUUkU1NdRSR556GmikiWZaaCWXSG8UbYxmDGMZx3gmMJFJTGYKU5nGdGYwk1nMZk5CvbnMYz4LWEg7HSxiMUtYyjKWs4KVrGI1a1jLuoR6naxnAxvpYhOb2cJWtrGdHexkF7vZw172sZ8DCfUOcojDHOEoxzjOCU5yitOcoZuznOM8F7jIJS4n1LvCVa5xnRvc5Ba3ucNd7nGfBzzkEY95wlOe8Tyh3gte8orXvOEt73hPDx/4yCc+84WvfOM7P/jJr4R6v/nDX7IioxfF9KYPfelHfwYwkEEMZghDGcbwonR6OUoopYxyKqikimpqqKWOPPU00EgTzbTQmlAvH0IIIRtRAB8K0cgC+BBCCCGEEEIIIYQQQgghhBBC+O8fgFh8kg==
+ </data>
+ </layer>
+ <layer name="trees" width="70" height="40">
+ <data encoding="base64" compression="zlib">
+ eJzt0MkNwlAQRMGRHQVkAmmy5gHGzoM1HDoBFokT+lXSu/VlpgoAAH6z6qrWadN93m6z2aX9F9t/d8iNxzSkWV81719vT9mMaWrgL+fceEnXtMhPlm/+csvmnh4N/AUAAAAAAACAdjwBV0ANmg==
+ </data>
+ </layer>
+ <layer name="main" width="70" height="40">
+ <data encoding="base64" compression="zlib">
+ eJzt0DEKwjAYhuE/R6i2k6cQwcGeQkc9hrviGTyKJ9H2IrWDQxc/sdAQdHBJJbwPvCQpHZLfDAAAAADi2Duzq33upipVf9l3Y1w4krPm0mi9q1YddD4G3xobzic3fJ+5kS4dwUVvK1Sm5n15f371sPeae+eJ1qkqE55L5c1j4e3DlsF/q8Tn0upt6x/bqK3aJTwXAAAAAAAAAAAA/Kcn5RgnOg==
+ </data>
+ </layer>
+ <layer name="signs" width="70" height="40">
+ <data encoding="base64" compression="zlib">
+ eJztybEJADAIADAHH7D/H9sPdBOEZE0EAADAvsr+3/AAAAAAAAAAcNUHZv8ALA==
+ </data>
+ </layer>
+ <layer name="events" width="70" height="40">
+ <data encoding="base64" compression="zlib">
+ eJzt0MEJACAQA8FDtP+WbcCXDy/IDOQftgoAAHhpdB8g0rzc73Q50wUA8q3uAwAAAABAtA3ReQCw
+ </data>
+ </layer>
+</map>
diff --git a/tiled/map2.tmx b/tiled/map2.tmx
new file mode 100644
index 0000000..0fba89a
--- /dev/null
+++ b/tiled/map2.tmx
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="70" height="45" tilewidth="16" tileheight="16" nextobjectid="1">
+ <tileset firstgid="1" name="atlas" tilewidth="16" tileheight="16" tilecount="1024" columns="32">
+ <image source="atlas.png" width="512" height="512"/>
+ </tileset>
+ <tileset firstgid="1025" name="eventset" tilewidth="16" tileheight="16" tilecount="16" columns="4">
+ <image source="eventset.png" width="64" height="64"/>
+ <tile id="1">
+ <properties>
+ <property name="portal" value="1"/>
+ <property name="target" value="1-1"/>
+ </properties>
+ </tile>
+ <tile id="2">
+ <properties>
+ <property name="portal" value="2"/>
+ <property name="target" value="3-1"/>
+ </properties>
+ </tile>
+ <tile id="3">
+ <properties>
+ <property name="floor" value="true"/>
+ </properties>
+ </tile>
+ <tile id="4">
+ <properties>
+ <property name="lowest" value="true"/>
+ </properties>
+ </tile>
+ <tile id="5">
+ <properties>
+ <property name="portal" value="true"/>
+ </properties>
+ </tile>
+ <tile id="7">
+ <properties>
+ <property name="block" value="true"/>
+ </properties>
+ </tile>
+ <tile id="12">
+ <properties>
+ <property name="checkpoint" value="true"/>
+ </properties>
+ </tile>
+ </tileset>
+ <tileset firstgid="1041" name="signs" tilewidth="16" tileheight="16" tilecount="4" columns="4">
+ <image source="signs.png" width="64" height="16"/>
+ </tileset>
+ <layer name="bg" width="70" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt1XdSU1EUx/GXBSSUAAkQygbsBQuoG7Bhw+4GbNiwoG7A3rsbsKCIvWzAhiA21A3YRaWp3wxk5s0Vk5ec6/Aczp35/PfOL+eeeS8n5DhOFrKRg1yEkYd8FCCCKApRhGLEUIJSlKEcQYQEedJ6sx/JGY4RGIlRGI0xGItxGI8KTMBETMJkVKIKUzAV0zBMmCetN/uRnBmYiVmYjWrMwVzMw3wsQA0WYhEWYwmWYhmWYwWmC/Ok9WY/krMKq7EGa1GLdViPDdiITajDZmzBVmxDPbZjB3ZipTBPWm/2Izm7sQd7sQ/7cQAHcQiHcQRHcQzHcQIncQqncQZnsUuYJ603+5Gc87iAi2jAJVxGI66gCVdxDddxAzdxC7dxB3dxD+eEedJ6sx/JeYCHeITHaMYTtKAVT9GGZ3iOF3iJV2jHa7zBW9wX5knrzX4ke+09PuAjPuEzvuArOvAN3/EDnehCN3rQi5/4FR9wwHHeCfOk9WY/kr0Woj6cpmLEUIJSlKEcwUBfXhaykYPc/po85CfJjHmsL0AEURSiKEk/kr2W7kz+F9K9Ntj9/yvSvTbY/dtkay/G95r7e7TRW4ulWTc7f+a1Jcm2uRfb++eS4LXnVHvD/WyqveHl9/62hwZ61sZe7DXmEnTlJ9sHqfZGOJDe3rC9h2zlJXidc9cQYL4vXuYcr3PP2UYffsvLZC4RS737WSbfkfl/ZYPf8jJ9XxLvqa17+C3PPZdoBmzew0950rnYuovf8kIBpZRSSimllFJKKaWUUkqpoek3zxlDIw==
+ </data>
+ </layer>
+ <layer name="plants" width="70" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt17kNwkAQheGRnQBtcNMVdMJVCFcAVMEVAE3xEgcIJ6yNvIz/T3rhSjtPWmnHDAAAAACA740Ts0lS9S3iM1UnM3r5sFIn64K9zHV+oSwd9XvSLOeC82x0fqvsHPVShov6uCo3ekHkGqlZU2mlVd8kLgP1MVRG9AIAqBmPe08Z2Hvy1XHv2WvWg3J0PHM/4A98Vx8P5RlRL6F7Tsj8/yTbc7zPiXfs/fmy99BWOkpX6dETAAAAfugFJZkXWQ==
+ </data>
+ </layer>
+ <layer name="treetrunks" width="70" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt2MkNwjAQBVCTnFi64MAS2gAhUQV0AgfWKuDAWgUgsZYRCuEfyIEoRCEkdoz/k+aI8HxbY4MQRERERERERJQFFVuIqq16FfLl0XMBVUSVDOz/kxqyqKMcVIO50B8ZWkKMUGNL9UqyZYk8Vqi1Qbl48z/MCXmcURcNc2nm4n3Om/8mmmCfp6iZhvudpg3y2KJ2zOXNFXncUHfmYqQod4iJTL5D4nBi3tVxyXgHlyXufxd99BLoRYd38DdnpY8+Bgn0ovM7OMgcfSx8vXiz3OTZtUcmB18uWcqDv9+D6TC3VFAxt1qS7/IwR0Vraav52sjcVy5p5+P/D6aTobMRxg1Y5+PHtct+4+pClzNBRETpewJlgiX+
+ </data>
+ </layer>
+ <layer name="main" width="70" height="45">
+ <data encoding="base64" compression="zlib">
+ eJztWFluE0EUnA6OYwchzgAhzsItICuERVwhiEVivwIICHxAuEJI2MRyBXASNgG5QuI4bBJnoErTT/Pc9NgxHiWTqEsq9awev+pXr99MFAUEBAQEBAQEBAQEBAQEBGSFiy3Om5R7boN3wLvgDHgPvJ/ye3Iu75g2cUwzLa5jjKKL1mceXACfgE/BZ+Bz8EWT32p2Li+4aeJ4foJv2+Q7cAlcBt+DH8CP4Cfwc5NnuvmVR8xBFwOuYnstSsZak/2aOl4HN8Dv4I8o1vdXC+r8WsGzX4GvwTc+o24TqvgvB8AesGQ1Kln2pezvsWOX3S6CA/YaatZrYq5G/pHsBr/ZY+tRvL2SI23mrC6MuaJGOebb7/Ocpy6D4JDVmBxWdPe7LY3aFm3ykDuiy2AH1LpUFIdTxorSwqX4k/qcaVObbrAI9oAlsNyBLjU8e6JDjoHjdnsSPLYJVlN0MTYHr4M3rC5TJqnzVXAR/O2JpR+sgAPgIDjUgS7bBc7HUXAEHPVoOws+Mo19gNR/+m2vJ5dGwTFwHJwAJ7csmq0FNdF9AOt80frxcIrHzoHnwQtR6z41T2DvIp7Q3uD6Ld5gr3nWNGpStJqUbA1P02UngvGKJ1Yd9ipvsNe8ZWLfsDcSTcjd5g/miWjCWKX/kVF7g2vkY9Ooh2iyG0AtxCfURDyh13jRRXujapJaIv3kbtFE1hFqQZ+wp3U1cdei41YXrlXrYB3csOu6nMsT5D1P18m0mklMOzUzrRfcybVzyiTzznfEayYmc2DN7uuaSUjdZB1J65GzyIGXpvV7ucwpe0HOYTt9MGM/YRr7SXnOJRy/bGPkO2IBXLaePwT2e/oJ1of9XfE1vjpSyChXvuA3vprku48vr6XWy3y20wdL7FdN3E+yFvB7Ab2hva49r3tVd+7lHjk/4tSSZrkisUke6LkSv0p/sw/a/zHJ9w3Ja+mLJb/pZfq23T6Y6+KS4oJJvlX53kOyhmhBDRhb2XqTcUmMnCvxq/iUxxkz1yy5TnK4YMeKXcseYHzYpnc5v3yvYO5rfUQbt65mBeqhc71s45XvOgVnW/xKny6q2GXsz0ALF6KN1oXzUDaN3zl1XpObqXmnbf06CZ4y8T5H7X2pXz4dGNus8p/49Ijxv3t2qoVPG3mGq4+sQTUbh3jX5zfxv8R9xd6vPSF1sGh1qHhiHMs4vixQt/9RtJk3ic9kPvm9pM+kr9OSBxwPmsY1TPoXrcNOQt3571Xz7/c2d52W95uCk3dLKify2sP+L+g1V6tx41+nfdcFBAQEBAQI/gKKMkyD
+ </data>
+ </layer>
+ <layer name="signs" width="70" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt1sEJACAMBME80oBi/7WmASE/hTBTwb2OjQAAAACAuU7+XgDQ274KAGC8F823dCUAAABcFc8tAFo=
+ </data>
+ </layer>
+ <layer name="events" width="70" height="45">
+ <properties>
+ <property name="portal" value="2"/>
+ <property name="target" value="3-1"/>
+ </properties>
+ <data encoding="base64" compression="zlib">
+ eJzt1k0OAiEMhmFUXHosvYm38e/IkqjJ7CwDtNPyPkkTF0bSbzrFlAAAAAAAAHQdsvy7OX8qktZ+fplEzKVX1Z65ZT1z0chUi3UekmxOBrlZZ+FhdjScS11S/2y853stdduldC/1KPUs9fp+XtaS535H8T4HI8y0f/71OuNOlvRLJvJcopnt2UvMsCdqe7TOZK9wxpr/7Vq5SH6v5XyvMz76LtvS+996l/fuwzKb2h6195f2nvR2X/WaU81517S2p2g5SLTkAsDGMcD7F6EHQOINSDISEA==
+ </data>
+ </layer>
+</map>
diff --git a/tiled/map3.tmx b/tiled/map3.tmx
new file mode 100644
index 0000000..991b7b1
--- /dev/null
+++ b/tiled/map3.tmx
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="100" height="50" tilewidth="16" tileheight="16" nextobjectid="1">
+ <tileset firstgid="1" name="atlas" tilewidth="16" tileheight="16" tilecount="1024" columns="32">
+ <image source="atlas.png" width="512" height="512"/>
+ </tileset>
+ <tileset firstgid="1025" name="eventset" tilewidth="16" tileheight="16" tilecount="16" columns="4">
+ <image source="eventset.png" width="64" height="64"/>
+ <tile id="1">
+ <properties>
+ <property name="portal" value="1"/>
+ <property name="target" value="2-2"/>
+ </properties>
+ </tile>
+ <tile id="2">
+ <properties>
+ <property name="portal" value="2"/>
+ <property name="target" value="4-1"/>
+ </properties>
+ </tile>
+ <tile id="3">
+ <properties>
+ <property name="floor" value="true"/>
+ </properties>
+ </tile>
+ <tile id="4">
+ <properties>
+ <property name="lowest" value="true"/>
+ </properties>
+ </tile>
+ <tile id="5">
+ <properties>
+ <property name="portal" value="true"/>
+ </properties>
+ </tile>
+ <tile id="7">
+ <properties>
+ <property name="block" value="true"/>
+ </properties>
+ </tile>
+ <tile id="12">
+ <properties>
+ <property name="checkpoint" value="true"/>
+ </properties>
+ </tile>
+ </tileset>
+ <tileset firstgid="1041" name="signs" tilewidth="16" tileheight="16" tilecount="4" columns="4">
+ <image source="signs.png" width="64" height="16"/>
+ </tileset>
+ <layer name="bg" width="100" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt2XVuG0EYxuH1AYJOYoecXKDM3AuUmXuBpsx4gTJzL1Bm7gXKzL1ACimn+IuykarKY83OzG6n0ffHo0gTz/vO5svG8iYdBEEFKlGFDLKoRg1qUYd65NCARhShGCUoRRnKkS6w7rpHJ892v+l5THt7oTf6oC/6oT8GYCAGYTCGYCiGYTi6oCu6oTt6oGeYp1p33aOTZ7vf9DymveMwHhMwEZMwGVMwFdMwHTMwE7MwGyMwEqMwGmMwNsxTrbvu0cmz3W96HtPehViExViCpViG5ViBlViF1ViDtViHOWjCXMzDfCwI81Trrnt08mz3m57HtHcrtmE7dmAndmE39mAv9mE/DuAgDmE9NmAjNmEztoR5qnXXPTp5tvtNz2PaexKncBpncBbncB4XcBGXcBlXcBXXcBhHcBTHcBwnwjzVuusenTyd/W9iOI/pue/iHu7jAR7iER7jCZ7iGZ7jBV7iFa7jBm7iFm7jTpinWnfdo5Nnu9/0PKa97/EBH/EJn/EFX9GKb/iOH/iJXwhSQdDMl9dB++/WW7xDS5inWnfdo5Nnu9/0PKa9aToqUIkqZJBFNWpQizrUI4cGNKIIxShBKcpQnmrPU6277tHJs91veh7T3uZACKHSIoQQQgghRCdm+5k/l+cZQi6Vf911j6tnFXFmR33+YvuZP5fndTnFuuseV88q4syO+vyl0IxbNbi4P0x7bPKSyo6q0Izbvv/njFXXYnt/mPbY5CWV7XIeGcNriToP0x6bvKSyo4rynqR7r0f9e2XaY5OXVLbr+6Pj3oz7/jDpsclLKjuqbEpPoWv5+7Ud89DNNu2xyUsqO655qPpdzyNKj6ufW5zZUbXdo0IIIYQQQnRWrv+f8q+v538n8/CLzMMvMg+/yDz8IvPwi8zDLzIPv8g8/CLz8IvMwy8yD7/IPPwi8/CLzMMvMg+//Ab3FJwp
+ </data>
+ </layer>
+ <layer name="trees" width="100" height="50">
+ <data encoding="base64" compression="zlib">
+ eJztlmtu00AURscJQQQk9hAglRoEiyBNeFRQttBKDfCDQpfAo5TAKoDSJCAoW8ijCQ/x2AJtCaJIsAWOlUSZUDsQJNc2+o50ZOvaM76eO7auMUIIIYQQQgghhBBCCCGEEEIIIYQQIq68cIx5iRv4ygk7m+AoJYy5glcTYWcynvfU4AN+xFo/ViPnZ/j8L3Kf556FiL+jyz1yXMX7HrmWiT3AhyG+xx2efRdXcI1a/MRPWOfaG2Jv8R2W/zDPLe65HYN6rJNjBaseuU6y/4LiiVsHfIo/qMMhXMIbeBOXsWqG34wfjxj/OAb1aJNjB1975GrvvyBI4UHT2+sNvMTazuFlZ3je5Nkt3MSjmHX2msNv1jxNbOFFa546Yxu4S/x7MK8Te7I4hZ9xy4zue/fcjbvnXzieYi1P+9TDNd0f547f5v4dvP7bt1QhdpjjkRD7Afv/GzUKWESH9UlgEg9gCrvGf+399JrHvt4xvW/pZIj1sP+/UWMRS5hhfY7hcTzRX7tBLDNhTcb51YRfD/v/GzXaZnTPnsE8zmABi3gWz+F5vOATt8cMzvM+c7lzzIZYj0nY7x692z/GaY32k6B79GvBTR170kl6jeRoLC49+v/INLXIYdaqyaBHF0IIQY8adgJiBPW1QgghhBBCCCGEEEL8G78A/dZihQ==
+ </data>
+ </layer>
+ <layer name="main" width="100" height="50">
+ <data encoding="base64" compression="zlib">
+ eJztmGlOFEEYhqtQNo3eAZUBNXoIERjcUK/gSqKIXgHccPcKgEuM2xGUAX8YtyPggEaN2w18v3RVpqboRpjunm5m3id50j3f9NR0V1XX8ilFCCGEEEIIIYQQQgghhBBCVksrbIPtsAN21hgnydANC7AH9sKdNcZJfDTshwNwEBbhUI1xEh9bj6fhGXgWjtQQHwkpOyo+CW8kcO+Nit+33XrXq4g/iSg3Kv6/70g83q4xnjZP0VmewefwBQfSzHmHNngPP8DHWd9Mhky2ZH0HAdIe0/A3/AhfIfYazsJr8Hq2t9d0/EQb/IIdcBRegGPwInyk1v87cwjPcRgegcO60teGQ2Li8RTGbLsHtX3d/u9RXX0ubsF7uhV26+Xugt9U9TtTUtXPJ2VcRew7/JH8o8TmHO7vvOljbl8bM/FRL3Yphfawe9AF+FlV93s5l7icL+G4B22xN6I9xE7nWcq4flFVnsOW+RCxTThuzuF64Cbu6Ra8De/Au/CeOQ+L30/hGeweVKPsFrgBboSt8IuKrvsow8pxv3+jgndpdw7bIwlO1fi7y/CKCsaWkoltQx1thztM3XXpINa1xjZZya8q+/bIY55Q5q4HOM6rYGyRfrtPB/bB/bAfDsBBWIRD8EBE3P2NPe+LKEvKOJhhe+QxT2jnLhmTpM826vgRRliesF6MwwkVjE8u7tyVdX+tNzLG+3nCeuG2h8wXsg7N29iZNXbfIfXRrqrrJul6moLTcEYF80Uex86ssWN3WN0kVU8nUP5JXdmXzalg7rbrnCzGzrzijt12XingvAf26mTqaRzlTDj7MtnP+fuJeo+decH2VZunfmnmTplXymY96K4fhbj1NKWD3N+MrGvNvqzNaYtizPLXM7avSp5a8qKf6rCWmcV/lOAcnDdtUDBHu65oVmxflTz1H/hXV3Jufm7NzzFKDu6YXp6n8z+710resWz2YEshe7xmx/ZVF8ndydju59bsXO/nsxec38h84H92r00j79hISF9djMhB+HuxsBzjSvkL9zzNvCMhhBBCCCGEEJIm/wAtkMkQ
+ </data>
+ </layer>
+ <layer name="signs" width="100" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt100Kg0AMBtBY3PdHz9k7Wo9Su+8RGvEAaqs4lPfgYzbDEBgISQQAAAAAAADfaOpt7wEAAPB/boXshG0hdQAwuerLAIuYY+eNO0eX5yPTH10M8cwMmdfRhQBQpHe139vmpnXOp4hL5r7jnwAAAAD86gMFzQct
+ </data>
+ </layer>
+ <layer name="events" width="100" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt2EEOgyAQBVBjMXHT+1+32yYqLS3tDPpewsYNMB9x4jQBAAAAAAAjm0v0CiBOKdvxyXMAgJ70Gbns5bHKp7v7mzXd68lr78xNVl95ddZb8xhNbX//2G/L/D0HdfLI5ap5ZOtFMtbo17Kej2zrAXiW9e6MFtGDyCHWKD3nVcjiWEQdZHFMPXJpuTvO8i8CANhafKsBgAE8ADCABsw=
+ </data>
+ </layer>
+</map>
diff --git a/tiled/map4.tmx b/tiled/map4.tmx
new file mode 100644
index 0000000..ae383e9
--- /dev/null
+++ b/tiled/map4.tmx
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="80" height="50" tilewidth="16" tileheight="16" nextobjectid="1">
+ <tileset firstgid="1" name="atlas" tilewidth="16" tileheight="16" tilecount="1024" columns="32">
+ <image source="atlas.png" width="512" height="512"/>
+ </tileset>
+ <tileset firstgid="1025" name="eventset" tilewidth="16" tileheight="16" tilecount="16" columns="4">
+ <image source="eventset.png" width="64" height="64"/>
+ <tile id="1">
+ <properties>
+ <property name="portal" value="1"/>
+ <property name="target" value="3-2"/>
+ </properties>
+ </tile>
+ <tile id="2">
+ <properties>
+ <property name="portal" value="2"/>
+ <property name="target" value="5-1"/>
+ </properties>
+ </tile>
+ <tile id="3">
+ <properties>
+ <property name="floor" value="true"/>
+ </properties>
+ </tile>
+ <tile id="4">
+ <properties>
+ <property name="lowest" value="true"/>
+ </properties>
+ </tile>
+ <tile id="5">
+ <properties>
+ <property name="portal" value="3"/>
+ <property name="target" value="6-1"/>
+ </properties>
+ </tile>
+ <tile id="7">
+ <properties>
+ <property name="block" value="true"/>
+ </properties>
+ </tile>
+ <tile id="8">
+ <properties>
+ <property name="spikes" value="true"/>
+ </properties>
+ </tile>
+ <tile id="9">
+ <properties>
+ <property name="turnaround" value="true"/>
+ </properties>
+ </tile>
+ <tile id="10">
+ <properties>
+ <property name="movingplatform" value="up"/>
+ </properties>
+ </tile>
+ <tile id="12">
+ <properties>
+ <property name="checkpoint" value="true"/>
+ </properties>
+ </tile>
+ <tile id="15">
+ <properties>
+ <property name="stop" value="true"/>
+ </properties>
+ </tile>
+ </tileset>
+ <tileset firstgid="1041" name="signs" tilewidth="16" tileheight="16" tilecount="4" columns="4">
+ <image source="signs.png" width="64" height="16"/>
+ </tileset>
+ <layer name="bg" width="80" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt1klSFEEYxfHsA9CMDa0L8QI4yy2cGFyiF3AeluoFUFBxCVwAEQeWygUQ56VyAcB5Wgh/FhnRC1Iyk8qsXrzFLyoq4ov3KjKzKmrEGHMGZ9GCKlrRhnZ0oBNdqKHbc87mjSTOd83l6r2Bm7iFPuzDfhzAQRzCYRzBUfR7ztm81PmuuVy9k5jCNI7hOE7gJE5hAIMYwjBOe87ZvNT5rrlcvc/xAgs4h/O4gIu4hMu4gqu4huueczYvdb5rLlfvR3zCMkZxG3cwhnHcxT3cxwQeeM7ZvNT5rrlcvf+wDlMxZobLQ8ziEebwGE/wFM8wb/zmbF7qfNdcrt493PdiLxa5f4klvMJrvMFbvMN7fDB+czYvdb5rLlfvZo+1wv0q1vAZX/AV3/AdP/DT+M3ZvNT5rrlcvY09LaiiFW1oRwc60YUauj3nbF7qfNdcrt6YffrrYSfnLyS/yPMX0xuzT5s5da67sLvi7ok9fyH5RZ6/mN6Ynh5HdlHrF5Jf5PrF9Mac81/4jT+J3t+Q/CLf35je2H2y5zzV+fPNL/r8hfY29tQj/K+nniHfJVfvTntcXUWt33b5qdbPt7es71NoXmOuz/OWsX45v0+heVutXzM8b1nfp9C8rdavGZ63rO9T7Bo22/NWRUREJLtm+x/w/QdMlR2q7P0TEREREREREREREREREbE2AFiabOs=
+ </data>
+ </layer>
+ <layer name="plants" width="80" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt11kOgjAUheGGnWrisA3naRXO0yoUZzfledfYpiUQ6P8l502sHCxwjQEAAAAAAACA35qJMS2lnRT9S8ppqN5Gypj+vGzU21bZ0Z+Xm3q7Kw/6A0qjiGduTevVuU9466i7bsX6y/P9b641FgHr9HRsXxlU7Bq4Oum8zwHnvtSxK2UdaX+hUvV2Ua4l7C/mfZMF9k2YLPfNRN8zVWZcDy979XZQjvT3peHwmad6eylv+gNQEOb5MLZ5PvZ508Y2zzNv/meb513mTd6DkaeU/xoAAAAQrQ//nypV
+ </data>
+ </layer>
+ <layer name="treetrunks" width="80" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt1sEJwkAQBdAhOcU2VIIpTO3DWIlaiaYv5+LJgAEhIex78G9zGD4LsxEAAAAAAKzFpYroM9dq6U3W6Za93TOPgvrb1hG7+vdcO2Hmmb29MkNB/QEwnyZv0WbCPWLcIbvr9DfqmDllzksvMoPP32/vLUBR3ND/uKEAAAAAAPDtDd7dCNw=
+ </data>
+ </layer>
+ <layer name="main" width="80" height="50">
+ <data encoding="base64" compression="zlib">
+ eJztmFtu00AYhccR964CIUKhq+DS0pBy3UJJaKWWAFsA+sR9C9C+AksAWl6hS+DSRqIPLTvomWaMfiYztvENOzmf9HXkS+zJkRsfWylCCCGEEEIIIYQQQggheTDR+N8zqDdTzC8Ri571Mr+5QKmr8Bq8Dm/Am2JZrrf3uRX4zz2PbbdhB+7k+q2KJeIr/UHnt2j2XcKfZXgX9uA9eF8sy/X2Pg8iTvYQ2x7Bx3AiyaRqhLz+nuC7PYXP4HP4Ar4Uy3K9vc+riFxeY9sbuAqnRji/ogjzO5Qiv6rf38rM71yK/Kp+f2N+2Shjfp+Q2Skrv8PwCPwAP6rB/fwoxmPweMnzy0IZ8/uBbK4Y2ya/07AJv8HvanAfP4NxEp5NML+o/F19K65jpSVJ/yvivNNwBgY4dgM24WUsz8KWZ34SO/+eyN/Vt+I6VlraYn6+/uc6byfjefXnu/Ck+d/W2U1iXFB/d31ffnb+fTU4Rt9jXMfKA1//0+ct6to8j89fgLOw5TiWLz9f/q7sqsC/XJt5Evf7HJd/VYi6NjXhM659jfqWXet/qeHn46rff/MifMa1r1Hfsmv9iWD4+Xhc8pMdOYt2vx6X/HRHXmd+qdEd+Sec0fdC8Xt+CU6LMdzuWi/7dci45FcUzC8bdchPvl/vmtFl13SMtxjfwfcl9K065Cffr6+Y0eWK6Rhf4Fe4yfwOkO/X18zocs3cI3fhHvzt6cHj1p/D7rEBP5vRpatvJO3Laftzuwb5hd1jC26b0WX4Dk+/98ja85L2v1FEv/e4o5hfVi4G/n6cV38mhBBC6sA+f57HuQ==
+ </data>
+ </layer>
+ <layer name="signs" width="80" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt1jENAAAIwDAODIB/sUjgJCStgp2LAAAAAAAAAAAAADad1wUAAPBTeWkAAHhlADEZACw=
+ </data>
+ </layer>
+ <layer name="spikes" width="80" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt1sEJACAMBMEY7b9mEfFnChBn4B6BFLARAAAAAAAAAAAAQM96AADwm5Z71b06eVxa+fyllgYAgGdNLVUASg==
+ </data>
+ </layer>
+ <layer name="events" width="80" height="50">
+ <data encoding="base64" compression="zlib">
+ eJzt2NEKwiAUBuBRBkUEvf/TduFVQq151JV9H3ixXcj4ncezLQsAAAAAzOOe9n4CYISjvU5nKdWNT+fdyvmW1eZHJr8Y+cXIL+Zdfme5bhY9z8lqc+vRY9W4FvP+2ruwV26Mt7a2N2vd3CW9Hjyr/XZWqzL5xcgPvsvBnmIAdT6m9qwse+G16/L+LL10i96jR/9S/t+Yxaj8ZiY/gLZO6iB/6gFmoAhP
+ </data>
+ </layer>
+</map>
diff --git a/tiled/map5.tmx b/tiled/map5.tmx
new file mode 100644
index 0000000..e3091e3
--- /dev/null
+++ b/tiled/map5.tmx
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="100" height="45" tilewidth="16" tileheight="16" nextobjectid="1">
+ <tileset firstgid="1" name="eventset" tilewidth="16" tileheight="16" tilecount="16" columns="4">
+ <image source="eventset.png" width="64" height="64"/>
+ <tile id="1">
+ <properties>
+ <property name="portal" value="1"/>
+ <property name="target" value="4-2"/>
+ </properties>
+ </tile>
+ <tile id="2">
+ <properties>
+ <property name="portal" value="2"/>
+ <property name="target" value="6-1"/>
+ </properties>
+ </tile>
+ <tile id="3">
+ <properties>
+ <property name="floor" value="true"/>
+ </properties>
+ </tile>
+ <tile id="4">
+ <properties>
+ <property name="lowest" value="true"/>
+ </properties>
+ </tile>
+ <tile id="5">
+ <properties>
+ <property name="portal" value="3"/>
+ <property name="target" value="6-1"/>
+ </properties>
+ </tile>
+ <tile id="7">
+ <properties>
+ <property name="block" value="true"/>
+ </properties>
+ </tile>
+ <tile id="8">
+ <properties>
+ <property name="spikes" value="true"/>
+ </properties>
+ </tile>
+ <tile id="9">
+ <properties>
+ <property name="movingplatform" value="down"/>
+ </properties>
+ </tile>
+ <tile id="10">
+ <properties>
+ <property name="movingplatform" value="up"/>
+ </properties>
+ </tile>
+ <tile id="12">
+ <properties>
+ <property name="checkpoint" value="true"/>
+ </properties>
+ </tile>
+ <tile id="14">
+ <properties>
+ <property name="movingplatform" value="right"/>
+ </properties>
+ </tile>
+ <tile id="15">
+ <properties>
+ <property name="stop" value="true"/>
+ </properties>
+ </tile>
+ </tileset>
+ <tileset firstgid="17" name="signs" tilewidth="16" tileheight="16" tilecount="4" columns="4">
+ <image source="signs.png" width="64" height="16"/>
+ </tileset>
+ <tileset firstgid="21" name="atlas" tilewidth="16" tileheight="16" tilecount="1024" columns="32">
+ <image source="atlas.png" width="512" height="512"/>
+ </tileset>
+ <layer name="bg" width="100" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt07dNBFEYxPE9vA+ABji8DYAG8KYLvOkCb8KDBvAmBBrAmxBoAG9CoAD+BEgkLE/Le9ILJvgFK303mtHtJoIgWMAimtCMFrSiDe3oQCe60G14Zysv4aCfi0xbm3ewiz30oBd96McABjGEYYxg1PDOVp6Lfj5vvsQVrjGGcUxgElOYxgxmMYd5wztbeS76+bz5De/4wBKWsYJVrGEdG9jEFrYN72zluejn8+b8WBAUoBD7PB/gEEc4xglOcYZzXARmd7byXPTzeXM9GQ1oxA3Pt7jDPR7wiCc84wWvgdmdrTwX/Xze/NXtWwxJSEYKUpGGdGQgE1mGd7byXPTzefPPbkWIoxglKEUZylGBSlQZ3tnKc9HP581R3pU8A7byXPTzeXOUd+XrdzWoRV1Iro08F/183hylW3XI/xulW1iei34+b47y7WYjB7l/dLOR56Kfz5ujvivf35qtd+W3PBf9fN78s1tNBGHdbOS56Ofz5v92C9tsI89FP583x0VEREREREREREREREREREREREREREREPPAJYQJbxQ==
+ </data>
+ </layer>
+ <layer name="plants" width="100" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt2MttAjEQxnHDtkEfXCJBE9BJIhFog3dEqoCERxu8KYbvCFy8Ym12dvn/pDmsfLFnLHl2nAMAAAAAAEBI31XnuopeNe+dwKelGrWpkxmfqsXXk/WoJWH3Auf6qsXgyXp8BK5HlrtRFnOdf2EkB1nuRllsdf6dkRxYuhuxFKmnsnQ3AAA2TPQuTBU/nvdhqPWRYsw7EtVK+V0rNp48/2n9X7GkHlGdlN+z4uLJ817rB8WRegQ3U05/jeaVGSti6eS9gQDS9hd4jbT9BbJpJs7VU8yd0/YXsRSlry/zzP22fyhKX/8uM3erfX2jcv/9DjN3y732Yz2YuftZ/q8DAAAAAABA/q7mHjT2
+ </data>
+ </layer>
+ <layer name="treetrunks" width="100" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt2EsOAUEUheHStQyPdXjbSGNZGGAZGGAZGGAjiCMhElEDkXR3Vf1fciMxcTntdKeMAQAAAAAgPlVrTM3mvQVeOsqiSx7wyCgxZqyZJHlvgoeFclhqVuRRCDvlsCeL4H32YF338YamGeH9vFGA7+zqwWYpn31i9+rBA12ICF113d++XPuu9/GW6vfpZzSD5ysQM9fZIGeG/5mqW2aa+Y8d4zob5MzwP2vlsNFs6fzMfeuSo3I4ac4e53FJ/HyuC7VLfM0jC+scPrNieY4okrYN97+PcHRLxvQ4vy2EVDkMNENHHmXrX8/7tOuvWta/nvdpVwAAAAAAAITnDllOMZ4=
+ </data>
+ </layer>
+ <layer name="main" width="100" height="45">
+ <data encoding="base64" compression="zlib">
+ eJztmXdy00AUxlcGjgA4jQwBrgCkQAYOASSxY0KAK0AoM6GEK+BQQucKJEDC0I4ACe0P2hEgBQb4duw3ft6sZFlry5Z538xvVlrL0krf9qeUSCQSiUQikUgkEolEIlHj9NxT6gU4AU56jS5NNHWnGl2C2ukzPPgCpsCVhPox2EJ+eHiXFOiDF4Mt4EcGx1kLoxbPZuMrYmhpP3rA/gT4cd4n36V9TINr4Dq4AW6y33g/2AbaQQfoBF3RHxkoah/N6sfmENdoP/y8qqQ58Bj0Fpk37kvSv/WBfjAA9kV8np/yqlA39PjB28cM8m7V+Fn1VjXtg+r5ON71OHinCt9av79ZJ/l9h8AwGAEZkHUvdpl030l1o4eNHws4f1bjZ9VbYf2Y9Erf3cYoyDE/cuy+E+AMOAvOqejtUesUnnEaTIC3OF8ES6pQN96DD6p5+yubXhvl5H7kA/532yvVc9OLTB3K6aereF4eTIOfOF8GK2AVrIFfKll+mKqmv+L1fAXvuwrWYn7vR3jeLJgDW8BWkAZtoB10WPrOJClp649FfOclPXaB3WAP2At6vcK40S9+xKplr9Q2h8AwOMKOR3T/aYxlSVLS/Gh15Rrkx4RPPs2fxsAxrzS3HvPJ+4P//I2z4P+ZaP50AVwEl8Dl4rktbxvoTljfRHsXfvtTYbDtYZk6aqnDlIbdA6f5U9BaxyRpY3dcY8SkpQ5TGnYPnOZPre6Hy5o4rPSa8Q64C+6B+yx9AB6G+G40fwr6/nw+Ze4LNLNoP7FS+4jDq2oV5Md85b8HiuKIfM+Z0jeqfB/mpXEd5bvMI5I4p63kx4zDvSmOyPecKf2hyvdhvhrXUb7LPKJRc1pXBfmx4HBfipNsiIjLniW1Td3uXnnr9xO5KI7gF2NqhLgPtRKPW7kQxQ9qm7rdfQPfA+7B4whPwFPl3le7ir//uIG5NgxzrtPtLK7L96gPIT3s2fdf+HHWYR5BMWXdzjaCTanCWsD2TmYc4SP4VMuPG1FmvewEXd76tWGYc50ewDc4mHKv69VIz5n0/Ir82AF2gl2pwlqAysrLbMYRfqvm9KMexLWG8Sxtk0hXKGOz+MGlYy9J9kOL4no8RjBQPO4rHqeL+fq8XnHuWon38ba+Phvwuy0/7jUlxfXMcYnie3ScYbjGuUUikUgkEolEIlHr6x9k/QMl
+ </data>
+ </layer>
+ <layer name="signs" width="100" height="45">
+ <data encoding="base64" compression="zlib">
+ eJztzsEJACAMALGuoLj/rK5QqVAoyfseFwEAAAAAAHnrcwcAAAAAAABQdboHHuzuAQAAAADGu3AnAEk=
+ </data>
+ </layer>
+ <layer name="spikes" width="100" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt1sEJACAMBMFgD8b+OxXRV8CXPmdguRYuAgAAAAAAAAAAAHg12i7LrgAAgLt+/jMAAAAA8McEmTYA3Q==
+ </data>
+ </layer>
+ <layer name="events" width="100" height="45">
+ <data encoding="base64" compression="zlib">
+ eJzt1k1OAkEQhuFCMBGNwa3eDOJOLoHeROEgmngQF9wDXVkkkzDg0MzIdPXf+yQVFvNDTX/T0y0CAAAAAMBpd6EbAGBmVBXikFMeE8exVNaZnPLo4rb6fdV601pqrWrHrw/Ov7JoSsrNY+td60N2Y/B55LxRQ/lSUh73Wg9aTwORudaX7I/xOmRzlZLyaHrPY3r+2Prxbao1k79ZPDac63P9d70XJeWx0HrWetHa6PfqW+tnYN8HeZzm2iv7sJ1/5AHkx7W23ph1gVS1Wa9zWCvOXXPb7GHHjmqrlDws9DFu5BGfkOM/NPiP1DAf4uI7j67fxD77SHVPm2seKfP1/CHzcF1/8Y9rLNWfv+u+ue19redG1/vENif7eJdj+vak0ucx5BFXHnU55CGJ9AgAAAA/LkM3AMDEL3mQEv0=
+ </data>
+ </layer>
+</map>
diff --git a/tiled/map6.tmx b/tiled/map6.tmx
new file mode 100644
index 0000000..9d8b304
--- /dev/null
+++ b/tiled/map6.tmx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="80" height="65" tilewidth="16" tileheight="16" nextobjectid="1">
+ <tileset firstgid="1" name="atlas" tilewidth="16" tileheight="16" tilecount="1024" columns="32">
+ <image source="atlas.png" width="512" height="512"/>
+ </tileset>
+ <tileset firstgid="1025" name="eventset" tilewidth="16" tileheight="16" tilecount="16" columns="4">
+ <image source="eventset.png" width="64" height="64"/>
+ <tile id="1">
+ <properties>
+ <property name="portal" value="1"/>
+ <property name="target" value="5-2"/>
+ </properties>
+ </tile>
+ <tile id="2">
+ <properties>
+ <property name="portal" value="2"/>
+ <property name="target" value="7-1"/>
+ </properties>
+ </tile>
+ <tile id="3">
+ <properties>
+ <property name="floor" value="true"/>
+ </properties>
+ </tile>
+ <tile id="4">
+ <properties>
+ <property name="lowest" value="true"/>
+ </properties>
+ </tile>
+ <tile id="5">
+ <properties>
+ <property name="portal" value="3"/>
+ <property name="target" value="6-1"/>
+ </properties>
+ </tile>
+ <tile id="7">
+ <properties>
+ <property name="block" value="true"/>
+ </properties>
+ </tile>
+ <tile id="8">
+ <properties>
+ <property name="spikes" value="true"/>
+ </properties>
+ </tile>
+ <tile id="9">
+ <properties>
+ <property name="movingplatform" value="down"/>
+ </properties>
+ </tile>
+ <tile id="10">
+ <properties>
+ <property name="movingplatform" value="up"/>
+ </properties>
+ </tile>
+ <tile id="11">
+ <properties>
+ <property name="fake" value="true"/>
+ </properties>
+ </tile>
+ <tile id="12">
+ <properties>
+ <property name="checkpoint" value="true"/>
+ </properties>
+ </tile>
+ <tile id="13">
+ <properties>
+ <property name="movingplatform" value="left"/>
+ </properties>
+ </tile>
+ <tile id="14">
+ <properties>
+ <property name="movingplatform" value="right"/>
+ </properties>
+ </tile>
+ <tile id="15">
+ <properties>
+ <property name="stop" value="true"/>
+ </properties>
+ </tile>
+ </tileset>
+ <tileset firstgid="1041" name="signs" tilewidth="16" tileheight="16" tilecount="4" columns="4">
+ <image source="signs.png" width="64" height="16"/>
+ </tileset>
+ <layer name="bg" width="80" height="65">
+ <data encoding="base64" compression="zlib">
+ eJzt0VmWTgcUBeC/BlBVClVUFaomoC1NNMEEEE0kmmACEhE9wQQkekGECRDRRhcmoA8SQkxAE220yTcB/7oP977th2+dt7322qenVquNZBSjGcNHjGUc45nAx0xkEpMZzBCGMozhjKCRJprpRQu96UNfWmmjH/1pp4NOBjCQQXTRXUJe1X1mMotPmc1nfM4c5jKP+XzBAhayiClMZRqfMJ0ZdXbtKflPRfOq7vMNy1jOClayitWsYS3r+Jb1bGAji/mSr1jC1yyts2vZfyqaV3WfbWxnBzvZxQ/sZg97+ZF9/MR+DrCJ7/iezWxha51dy/5T0byq+xzlGMc5wUl+5RSnOcNZzvEb57nAQQ7xM4f5hSN1di37T0Xzqu5znd+5wU1u8Qd/cps7/MVd7vE397nIJS5zhatcq7Nr2X8qmld1n6c84zkveMm/vOI1b3jLO97zH7WGWu2B85BHPOYfntTZtew/Fc2ruk8fW/SllTb60Z92OuhkAAMZRBfdNNJEM71ooXedXcv+U9G8qvs0NZTrQ7uW/aeieVX3KXu/iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiCjmf+jpWGQ=
+ </data>
+ </layer>
+ <layer name="plants" width="80" height="65">
+ <data encoding="base64" compression="zlib">
+ eJzt2TkOwkAMBdBRqOAmcE3We7BzDXZOhbsUUUI0gSLiPcnNKEX0C8vjSQkAAAAAIN+xSOlUfP5uOEhpNPj9//TNM7J7tchvHNlN5AcAAPBXFnFfXEatWtwbqdpFbvuog/yy3CK3e9RDfgAV0+iNs6i5HlmraY5Zx9kmaiu/Wk1zzDnOLlFX+dXq+xyjx3Sjx3SjxwAAAPyXnD2EN8xSzh7CG2YpZw/R990fAAAA3/MG3HQmtg==
+ </data>
+ </layer>
+ <layer name="treetrunks" width="80" height="65">
+ <data encoding="base64" compression="zlib">
+ eJzt2DEKAjEQBdCENOoxFFlXvIVH92KmSRMJLFkkGt4rhxTDL2aYhAAAAAAAAAAAAAAwxjWN7gDgdx3zjDyZk93WnN1dfl9xkesm59ENTOYZR3fAP2vtVLv20yPnsVSZtHZqqdfvi1J/RTMRAIA5HZK7Yo9b8ocHAABAnzd7mARG
+ </data>
+ </layer>
+ <layer name="main" width="80" height="65">
+ <data encoding="base64" compression="zlib">
+ eJztmutOE0EYhndNFChXIQpFehUeOBUP6C14NiDoLaCoiaByCxgP/7wFLIn6S29BaYyHH96C75edL5muU1l2utuh+z7Jk5ndbunwtvv18G0UEUIIIYQQQgghhBBCyMEYPdK5vQ534HvYgruO7XdxqUsMmobJ7yoyuQZfYf4afoN7sA2/WtufccwX+Avz331ZcVhofmtxks8Ixg8Yj2McgyfgSTOKf+LkmBocLeh1mD4nQkbz247/zczeTt82BRsF5dc4hPkJp5HHGXgWnos7t9O3NeEC8wtyrSGuqRsL1lrnMJ/3sNmj//sw5RcizM8P5ucH8/OD+fnB/PxgfvmpRcwvK0fhMTgEh+EInIo6P5OS7ozDCViHk/AUbPZ1ReFyHt+VL8DL8Ir53jwNZ+AsnIPz8E7fVhg2S8hsFd6D901+1+ENeBPegrf7t7zgeYrMNuALuFXB369d5x/Jjuv8KwPpxzyCj+GT8h625xR5/mnvRXoqabUf8wa+7e3DDgzSe3kAH8J101upWfPvUdI7+Bmxb+Wqoy2Mu44eyzicgHU4WULfKi96/pRRY1x1dA9j29FjmYYzcDZO+ltjxqL6VnnR86eMGpO3jrYCzk+e25fwI+afrP32e1/ZNSfdAw759af150eU1Gi9VsN+79uv5mhd24n+f+3HRRxzyXHcYqomym8wUlf0+MWA85PndhkOmxot87twJU6uRRjKsOYlcz/7vvZcrgNZsbbtcdlREyW/Neu41YDz0/q9CZ+Z+qTz5xlrlda1jS5/Z78x/TiSn33OqkVe9zBISH76vJZ13cMgwd+g/WB+fjA/P5ifH8zPjyrm5+pH5tkvVDE/7UfKdw3tR9r76xn3C1XsAbv6kVn2t1P7q0q3fuRB9xNCCCGEEOLLXwxPplk=
+ </data>
+ </layer>
+ <layer name="signs" width="80" height="65">
+ <data encoding="base64" compression="zlib">
+ eJzt2DENADAMA0EPJdDwB5u1DKKodwh+tJwAAAAAAAAAAAAAAL+5Z7oAAIBtyoYEAAAG+LMBAAB4Nc6mAEE=
+ </data>
+ </layer>
+ <layer name="spikes" width="80" height="65">
+ <data encoding="base64" compression="zlib">
+ eJztwTEBAAAAwqD1T20ND6AAAAAAAAAAAAAAAAAAAAAAAAAAAL4NUUAAAQ==
+ </data>
+ </layer>
+ <layer name="events" width="80" height="65">
+ <data encoding="base64" compression="zlib">
+ eJzt2csOgjAQBVCidYUa//9rjTsXSIdQKIPnJCxgQ3PpY0qHAQAAAICMXqV3C87tKt90so+JUuYvptVyk+M8+fFP7mX+PoPxq81r171nJY+M+QAATHmoa5oa5RliT82e9LE4e904WcUYfzn5bnVnPC/Yc15z7hIXyWHqX/iR89uyba368Rny61mrZMyv1uYec+QRtZizl56/R95j/VhHfgBATxe1Bxv6Vesuff7hDBgA2rhZUwF29waAIgVv
+ </data>
+ </layer>
+</map>
diff --git a/tiled/signs.png b/tiled/signs.png
new file mode 100644
index 0000000..874ac34
--- /dev/null
+++ b/tiled/signs.png
Binary files differ
diff --git a/tiled/signs_OLD.png b/tiled/signs_OLD.png
new file mode 100644
index 0000000..70840d5
--- /dev/null
+++ b/tiled/signs_OLD.png
Binary files differ
diff --git a/tiled/tiled.lua b/tiled/tiled.lua
new file mode 100644
index 0000000..0e3b590
--- /dev/null
+++ b/tiled/tiled.lua
@@ -0,0 +1,244 @@
+-- see https://love2d.org/wiki/TiledMapLoader for latest version
+-- loader for "tiled" map editor maps (.tmx,xml-based) http://www.mapeditor.org/
+-- supports multiple layers
+-- NOTE : function ReplaceMapTileClass (tx,ty,oldTileType,newTileType,fun_callback) end
+-- NOTE : function TransmuteMap (from_to_table) end -- from_to_table[old]=new
+-- NOTE : function GetMousePosOnMap () return gMouseX+gCamX-gScreenW/2,gMouseY+gCamY-gScreenH/2 end
+
+kTileSize = 32
+kMapTileTypeEmpty = 0
+local floor = math.floor
+local ceil = math.ceil
+local max = math.max
+local min = math.min
+local abs = math.abs
+gTileMap_LayerInvisByName = {}
+
+function TiledMap_Load (filepath,tilesize,spritepath_removeold,spritepath_prefix)
+ spritepath_removeold = spritepath_removeold or "../"
+ spritepath_prefix = spritepath_prefix or ""
+ kTileSize = tilesize or kTileSize or 32
+ gTileGfx = {}
+
+ local tiletype,layers = TiledMap_Parse(filepath)
+ gMapLayers = layers
+ for first_gid,path in pairs(tiletype) do
+ path = spritepath_prefix .. string.gsub(path,"^"..string.gsub(spritepath_removeold,"%.","%%."),"")
+ local raw = love.image.newImageData(path)
+ local w,h = raw:getWidth(),raw:getHeight()
+ local gid = first_gid
+ local e = kTileSize
+ for y=0,floor(h/kTileSize)-1 do
+ for x=0,floor(w/kTileSize)-1 do
+ local sprite = love.image.newImageData(kTileSize,kTileSize)
+ sprite:paste(raw,0,0,x*e,y*e,e,e)
+ gTileGfx[gid] = love.graphics.newImage(sprite)
+ gid = gid + 1
+ end
+ end
+ end
+end
+
+function TiledMap_GetMapW () return gMapLayers.width end
+function TiledMap_GetMapH () return gMapLayers.height end
+
+-- returns the mapwidth actually used by tiles
+function TiledMap_GetMapWUsed ()
+ local maxx = 0
+ local miny = 0
+ local maxy = 0
+ for layerid,layer in pairs(gMapLayers) do
+ if (type(layer) == "table") then for ty,row in pairs(layer) do
+ if (type(row) == "table") then for tx,t in pairs(row) do
+ if (t and t ~= kMapTileTypeEmpty) then
+ miny = min(miny,ty)
+ maxy = max(maxy,ty)
+ maxx = max(maxx,tx)
+ end
+ end end
+ end end
+ end
+ return maxx + 1,miny,maxy+1
+end
+
+-- x,y= position for nearest-distance(square,not round), z= layer, maxrad= optional limit for searching
+-- returns x,y
+-- if x,y can be far outside map, set a sensible maxrad, otherwise it'll get very slow since searching outside map isn't optimized
+function TiledMap_GetNearestTileByTypeOnLayer (x,y,z,iTileType,maxrad)
+ local w = TiledMap_GetMapW()
+ local h = TiledMap_GetMapW()
+ local maxrad2 = max(x,w-x,y,h-y) if (maxrad) then maxrad2 = min(maxrad2,maxrad) end
+ if (TiledMap_GetMapTile(x,y,z) == iTileType) then return x,y end
+ for r = 1,maxrad2 do
+ for i=-r,r do
+ local xa,ya = x+i,y-r if (TiledMap_GetMapTile(xa,ya,z) == iTileType) then return xa,ya end -- top
+ local xa,ya = x+i,y+r if (TiledMap_GetMapTile(xa,ya,z) == iTileType) then return xa,ya end -- bot
+ local xa,ya = x-r,y+i if (TiledMap_GetMapTile(xa,ya,z) == iTileType) then return xa,ya end -- left
+ local xa,ya = x+r,y+i if (TiledMap_GetMapTile(xa,ya,z) == iTileType) then return xa,ya end -- right
+ end
+ end
+end
+
+function TiledMap_GetMapTile (tx,ty,layerid) -- coords in tiles
+ local row = gMapLayers[layerid][ty]
+ return row and row[tx] or kMapTileTypeEmpty
+end
+
+function TiledMap_SetMapTile (tx,ty,layerid,v) -- coords in tiles
+ local row = gMapLayers[layerid][ty]
+ if (not row) then row = {} gMapLayers[layerid][ty] = row end
+ row[tx] = v
+end
+
+-- todo : maybe optimize during parse xml for types registered as to-be-listed before parsing ?
+function TiledMap_ListAllOfTypeOnLayer (layerid,iTileType)
+ local res = {}
+ local w = TiledMap_GetMapW()
+ local h = TiledMap_GetMapH()
+ for x=0,w-1 do
+ for y=0,h-1 do
+ if (TiledMap_GetMapTile(x,y,layerid) == iTileType) then table.insert(res,{x=x,y=y}) end
+ end
+ end
+ return res
+end
+
+function TiledMap_GetLayerZByName (layername) for z,layer in ipairs(gMapLayers) do if (layer.name == layername) then return z end end end
+function TiledMap_SetLayerInvisByName (layername) gTileMap_LayerInvisByName[layername] = true end
+
+function TiledMap_IsLayerVisible (z)
+ local layer = gMapLayers[z]
+ return layer and (not gTileMap_LayerInvisByName[layer.name or "?"])
+end
+
+function TiledMap_GetTilePosUnderMouse (mx,my,camx,camy)
+ return floor((mx+camx-love.graphics.getWidth()/2)/kTileSize),
+ floor((my+camy-love.graphics.getHeight()/2)/kTileSize)
+end
+
+function TiledMap_DrawNearCam (camx,camy,fun_layercallback)
+ camx,camy = floor(camx),floor(camy)
+ local screen_w = love.graphics.getWidth()
+ local screen_h = love.graphics.getHeight()
+ local minx,maxx = floor((camx-screen_w/2)/kTileSize),ceil((camx+screen_w/2)/kTileSize)
+ local miny,maxy = floor((camy-screen_h/2)/kTileSize),ceil((camy+screen_h/2)/kTileSize)
+ for z = 1,#gMapLayers do
+ if (fun_layercallback) then fun_layercallback(z,gMapLayers[z]) end
+ if (TiledMap_IsLayerVisible(z)) then
+ for x = minx,maxx do
+ for y = miny,maxy do
+ local gfx = gTileGfx[TiledMap_GetMapTile(x,y,z)]
+ if (gfx) then
+ local sx = x*kTileSize - camx + screen_w/2
+ local sy = y*kTileSize - camy + screen_h/2
+ love.graphics.draw(gfx,sx,sy) -- x, y, r, sx, sy, ox, oy
+ end
+ end
+ end
+ end
+ end
+end
+
+
+-- ***** ***** ***** ***** ***** xml parser
+
+
+-- LoadXML from http://lua-users.org/wiki/LuaXml
+function LoadXML(s)
+ local function LoadXML_parseargs(s)
+ local arg = {}
+ string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a)
+ arg[w] = a
+ end)
+ return arg
+ end
+ 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=LoadXML_parseargs(xarg), empty=1})
+ elseif c == "" then -- start tag
+ top = {label=label, xarg=LoadXML_parseargs(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.n].label)
+ end
+ return stack[1]
+end
+
+
+-- ***** ***** ***** ***** ***** parsing the tilemap xml file
+
+local function getTilesets(node)
+ local tiles = {}
+ for k, sub in ipairs(node) do
+ if (sub.label == "tileset") then
+ tiles[tonumber(sub.xarg.firstgid)] = sub[1].xarg.source
+ end
+ end
+ return tiles
+end
+
+local function getLayers(node)
+ local layers = {}
+ layers.width = 0
+ layers.height = 0
+ for k, sub in ipairs(node) do
+ if (sub.label == "layer") then -- and sub.xarg.name == layer_name
+ layers.width = max(layers.width ,tonumber(sub.xarg.width ) or 0)
+ layers.height = max(layers.height,tonumber(sub.xarg.height) or 0)
+ local layer = {}
+ table.insert(layers,layer)
+ layer.name = sub.xarg.name
+ --~ print("layername",layer.name)
+ width = tonumber(sub.xarg.width)
+ i = 0
+ j = 0
+ for l, child in ipairs(sub[1]) do
+ if (j == 0) then
+ layer[i] = {}
+ end
+ layer[i][j] = tonumber(child.xarg.gid)
+ j = j + 1
+ if j >= width then
+ j = 0
+ i = i + 1
+ end
+ end
+ end
+ end
+ return layers
+end
+
+function TiledMap_Parse(filename)
+ local xml = LoadXML(jin.filesystem.read(filename))
+ local tiles = getTilesets(xml[2])
+ local layers = getLayers(xml[2])
+ return tiles, layers
+end \ No newline at end of file
diff --git a/tiled/tileset.png b/tiled/tileset.png
new file mode 100644
index 0000000..b2e4407
--- /dev/null
+++ b/tiled/tileset.png
Binary files differ