function LevelLoadTiles() LevelData = dofile("data/levels/"..currLevel) --[[ on level format: id = tile identifier depth = order in the render force = rendering other tile instead of the one in this position overlay = render another tile id or, if multiple tiles {id, id, id,}, choose at random overlay_depth = foreground/background overlay depth type = collision type ]] LevelGetTileData() LevelTiles = LevelData.tiles LevelUpdateDimensions() LevelIndexTiles() TileCreateObjects() end function LevelExpandCanvas(horizontal,vertical) local horizontal = horizontal or 0 local vertical = vertical or 0 local h = LevelGetTileWidth() local v = LevelGetTileHeight() -- get new canvas size local newCanvasH = h + math.abs(horizontal) local newCanvasV = v + math.abs(vertical) -- lets make a new temporal canvas local ExpandedLevel = {} for i = 1, newCanvasV do ExpandedLevel[i] = {} for j = 1, newCanvasH do ExpandedLevel[i][j] = InstanceTile(0) end end -- lets guess how the new canvas and positions are offset local expand_h = 0 if horizontal < 0 then expand_h = -horizontal end local expand_v = 0 if vertical < 0 then expand_v = -vertical end -- get data from old canvas to new canvas for i = 1, #LevelTiles do for j = 1, #LevelTiles[i] do ExpandedLevel[i+expand_v][j+expand_h] = InstanceTile(LevelTiles[i][j].id) end end -- use new canvas LevelTiles = ExpandedLevel end function LevelReduceCanvas(horizontal,vertical) local horizontal = horizontal or 0 local vertical = vertical or 0 local h = LevelGetTileWidth() local v = LevelGetTileHeight() -- get new canvas size local newCanvasH = h - math.abs(horizontal) local newCanvasV = v - math.abs(vertical) -- lets make a new temporal canvas local ExpandedLevel = {} for i = 1, newCanvasV do ExpandedLevel[i] = {} for j = 1, newCanvasH do ExpandedLevel[i][j] = InstanceTile(0) end end -- lets guess how the new canvas and positions are offset local expand_h = 0 if horizontal < 0 then expand_h = -horizontal end local expand_v = 0 if vertical < 0 then expand_v = -vertical end -- get data from old canvas to new canvas for i = 1, #ExpandedLevel do for j = 1, #ExpandedLevel[i] do ExpandedLevel[i][j] = InstanceTile(LevelTiles[i+expand_v][j+expand_h].id) end end -- use new canvas LevelTiles = ExpandedLevel LevelExpandCanvas() end function LevelGetTileData() for k, v in pairs(tileset) do if v == LevelData.tileset then TileData = dofile("data/tileset/"..k..".lua") end end end function LevelReloadTiles() LevelUpdateDimensions() end function LevelUpdateDimensions() LevelData.Width = LevelGetWidth() LevelData.Height = LevelGetHeight() end function LevelGetTileHeight() return #LevelTiles end function LevelGetTileWidth() local width = 0 for i = 1, #LevelTiles do if width < #LevelTiles[i] then width = #LevelTiles[i] end end return width end function LevelGetHeight() return LevelGetTileHeight() * tileProperties.height end function LevelGetWidth() return LevelGetTileWidth() * tileProperties.width end function LevelIndexTiles() TileIndex = {} -- index from tileset local width = LevelData.tileset:getPixelWidth()/tileProperties.width local height = LevelData.tileset:getPixelHeight()/tileProperties.height for i = 0, height do for j = 0, width do TileIndex[i*width+j+1] = love.graphics.newQuad( j*tileProperties.width, i*tileProperties.height, tileProperties.width, tileProperties.height, LevelData.tileset:getDimensions() ) end end TileDataInitialize() -- instance level tiles according to the Properties for i = 1, #LevelTiles do for j = 1, #LevelTiles[i] do SetTile(i,j,LevelTiles[i][j]) end end end function TileDataInitialize() for _, Properties in pairs(TileData) do if Properties.animation ~= nil then Properties.tileset = love.graphics.newImage("assets/terrain/"..Properties.animation..".png") Properties.imgs = {} Properties.current_image = 1 Properties.current_subimage = 1 local tileset = Properties.tileset local width = tileset:getPixelWidth()/tileProperties.width local height = tileset:getPixelHeight()/tileProperties.height local image_count = 0 for i = 0, height-1 do for j = 0, width-1 do local quad = love.graphics.newQuad( j*tileProperties.width, i*tileProperties.height, tileProperties.width, tileProperties.height, tileset:getDimensions() ) image_count = image_count + 1 table.insert(Properties.imgs,quad) end end Properties.image_count = image_count end end end function InstanceTile(id) local tile = {} tile.id = id local Properties = TileData[tile.id] if Properties ~= nil then if type(Properties.overlay) == "table" then tile.display_overlay = Properties.overlay[math.random(#Properties.overlay)] else tile.display_overlay = Properties.overlay end if type(Properties.force) == "table" then tile.display = Properties.force[math.random(#Properties.force)] else tile.display = Properties.force end end return tile end function SetTile(i,j,id) LevelTiles[i][j] = InstanceTile(id) end function GridDisplay() for i = 1, #LevelTiles do for j = 1, #LevelTiles[i] do love.graphics.rectangle( "line", tileProperties.scale * (j * tileProperties.width + (levelProperties.offset.x - tileProperties.width)) - Camera.pos.x, tileProperties.scale * (i * tileProperties.height + (levelProperties.offset.y - tileProperties.height)) - Camera.pos.y, tileProperties.scale * tileProperties.width, tileProperties.scale * tileProperties.height ) end end end function TileOptimizeObjects() logPrint("Optimizing Objects...") local unoptimized = 0 local isTileOptimized = {} for i = 1, #LevelTiles do isTileOptimized[i] = {} for j= 1, #LevelTiles[i] do isTileOptimized[i][j] = false end end for i = 1, #LevelTiles do for j = 1, #LevelTiles[i] do if LevelTiles[i][j].id ~= 0 then local type = TileData[LevelTiles[i][j].id].type if type == "whole" and not isTileOptimized[i][j] then isTileOptimized[i][j] = true local n = 1 local check = true while check do check = false if LevelTiles[i][j+n] ~= nil and TileData[LevelTiles[i][j+n].id] ~= nil then local type_check = TileData[LevelTiles[i][j+n].id].type if type_check == "whole" and not isTileOptimized[i][j+n] then check = true isTileOptimized[i][j+n] = true n = n + 1 end end end local m = 1 local check = true while check do check = false local checkline = true -- for as long as line, check for l = 0, n-1 do checkline = false if LevelTiles[i+m] ~= nil and LevelTiles[i+m][j+l] ~= nil and TileData[LevelTiles[i+m][j+l].id] ~= nil then local type_check = TileData[LevelTiles[i+m][j+l].id].type if type_check == "whole" and not isTileOptimized[i+m][j+l] then checkline = true else break end else break end end if checkline then check = true for l = 0, n-1 do isTileOptimized[i+m][j+l] = true end m = m + 1 else break end end logPrint("- Group size: "..m.."x"..n) unoptimized = unoptimized + m * n local base_x = tileProperties.scale * j * tileProperties.width + tileProperties.scale * (levelProperties.offset.x - tileProperties.height) local base_y = tileProperties.scale * i * tileProperties.height + tileProperties.scale * (levelProperties.offset.y - tileProperties.height) local col = Collision:New( base_x, base_y, base_x + tileProperties.width * tileProperties.scale * n, base_y + tileProperties.height * tileProperties.scale * m ) table.insert(LoadedObjects.Collisions,col) end end end end logPrint("collisions optimized from " .. unoptimized .. " to " .. #LoadedObjects.Collisions) end function TileCreateObjects() LoadedObjects.Collisions = {} LoadedObjects.Platforms = {} LoadedObjects.Ladders = {} LoadedObjects.Hazards = {} TileOptimizeObjects() for i = 1, #LevelTiles do for j = 1, #LevelTiles[i] do if LevelTiles[i][j].id ~= 0 then local type = TileData[LevelTiles[i][j].id].type local light = TileData[LevelTiles[i][j].id].light local base_x = tileProperties.scale * j * tileProperties.width + tileProperties.scale * (levelProperties.offset.x - tileProperties.height) local base_y = tileProperties.scale * i * tileProperties.height + tileProperties.scale * (levelProperties.offset.y - tileProperties.height) if light ~= 0 and light ~= nil then CreateLight( base_x + tileProperties.width/2 * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale, light ) end -- wholes are handled in optimization now --[[if type == "whole" then local col = Collision:New( base_x, base_y, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) else]]if type == "half_bottom" then local col = Collision:New( base_x, base_y + tileProperties.height/2 * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "half_top" then local col = Collision:New( base_x, base_y , base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "half_right" then local col = Collision:New( base_x + tileProperties.height/2 * tileProperties.scale, base_y, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "half_left" then local col = Collision:New( base_x, base_y, base_x + tileProperties.height/2 * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "platform" then local plat = Collision:New( base_x, base_y + tileProperties.scale * 2, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/4 * tileProperties.scale + tileProperties.scale * 2 ) table.insert(LoadedObjects.Platforms,plat) elseif type == "ramp2_bot_left_whole" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x, base_y + k * tileProperties.scale - tileProperties.scale, base_x + k * 2 * tileProperties.scale, base_y + k * tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end -- fill lower half local col = Collision:New( base_x, base_y + tileProperties.height/2 * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "ramp2_bot_left_half" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x, base_y + tileProperties.height/2 * tileProperties.scale + k * tileProperties.scale - tileProperties.scale, base_x + k * 2 * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale + k * tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end elseif type == "ramp2_top_left_whole" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x, base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale - (k-1) * 2 * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale + tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end -- fill higher half local col = Collision:New( base_x, base_y, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "ramp2_top_left_half" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x, base_y - tileProperties.scale + k * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale - (k-1) * 2 * tileProperties.scale, base_y - tileProperties.scale + k * tileProperties.scale + tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end elseif type == "ramp2_bot_right_whole" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x + (k-8) * -2 * tileProperties.scale, base_y - tileProperties.scale + k * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y - tileProperties.scale + k * tileProperties.scale + tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end -- fill lower half local col = Collision:New( base_x, base_y + tileProperties.height/2 * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "ramp2_bot_right_half" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x + (k-8) * -2 * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale + tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end elseif type == "ramp2_top_right_half" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x + (k-8) * -2 * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale + tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end elseif type == "ramp2_top_right_whole" then for k = 1, 8 do -- do ramp owo local slope = Collision:New( base_x + (k-8) * -2 * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale + tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end -- fill higher half local col = Collision:New( base_x, base_y, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/2 * tileProperties.scale ) table.insert(LoadedObjects.Collisions,col) elseif type == "ramp1_bot_left" then for k = 1, 16 do -- do ramp owo local slope = Collision:New( base_x, base_y + k * tileProperties.scale - tileProperties.scale, base_x + k * tileProperties.scale, base_y + k * tileProperties.scale ) table.insert(LoadedObjects.Collisions,slope) end elseif type == "ladder_right" then local ladder = Collision:New( base_x + (tileProperties.width-4)* tileProperties.scale, base_y, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Ladders,ladder) elseif type == "ladder_platform_right" then local ladder = Collision:New( base_x + (tileProperties.width-4)* tileProperties.scale, base_y + tileProperties.scale * 2, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Ladders,ladder) local plat = Collision:New( base_x, base_y + tileProperties.scale * 2, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/4 * tileProperties.scale + tileProperties.scale * 2 ) table.insert(LoadedObjects.Platforms,plat) elseif type == "ladder_left" then local ladder = Collision:New( base_x, base_y, base_x + tileProperties.scale * 4, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Ladders,ladder) elseif type == "ladder_platform_left" then local ladder = Collision:New( base_x, base_y + tileProperties.scale * 2, base_x + tileProperties.scale * 4, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Ladders,ladder) local plat = Collision:New( base_x, base_y + tileProperties.scale * 2, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height/4 * tileProperties.scale + tileProperties.scale * 2 ) table.insert(LoadedObjects.Platforms,plat) elseif type == "bottom_hazard" then local hazard = Collision:New( base_x, base_y + tileProperties.height * 12/16 * tileProperties.scale, base_x + tileProperties.width * tileProperties.scale, base_y + tileProperties.height * tileProperties.scale ) table.insert(LoadedObjects.Hazards,hazard) end end end end end function AnimateTiles() for _, Properties in pairs(TileData) do if Properties ~= nil then if Properties.animation ~= nil then -- calculate subimage Properties.current_subimage = Properties.current_subimage + current_dt -- cycle image if Properties.current_subimage >= Properties.delay then Properties.current_subimage = Properties.current_subimage - Properties.delay Properties.current_image = Properties.current_image + 1 end if Properties.current_image > Properties.image_count then Properties.current_image = Properties.current_image - Properties.image_count end end end end end function DrawTile(tile,x,y,depth) local Properties = TileData[tile.id] if Properties ~= nil then if Properties.animation ~= nil then if Properties.imgs[Properties.current_image] ~= nil and Properties.depth == depth then love.graphics.draw( Properties.tileset, Properties.imgs[Properties.current_image], x, y, 0, tileProperties.scale, tileProperties.scale ) end elseif Properties.depth == depth then if Properties.force ~= nil then if Properties.force ~= 0 then love.graphics.draw( LevelData.tileset, TileIndex[tile.display], x, y, 0, tileProperties.scale, tileProperties.scale ) end else love.graphics.draw( LevelData.tileset, TileIndex[tile.id], x, y, 0, tileProperties.scale, tileProperties.scale ) end end if Properties.overlay ~= nil then if Properties.overlay_depth == depth or Properties.overlay_depth == nil and Properties.depth == depth then if Properties.overlay_animated then local overlay_properties = TileData[Properties.overlay] love.graphics.draw( overlay_properties.tileset, overlay_properties.imgs[overlay_properties.current_image], x, y, 0, tileProperties.scale, tileProperties.scale ) else love.graphics.draw( LevelData.tileset, TileIndex[tile.display_overlay], x, y, 0, tileProperties.scale, tileProperties.scale ) end end end --[[ love.graphics.setColor(0,0,1) love.graphics.print(tostring(tile.display),x+16,y) love.graphics.setColor(1,1,1) ]] end end