local World = Object:extend() local bump3dpd = require 'libs.level-libs.bump-3dpd' local bump2d = require 'libs.level-libs.bump' local tsort = require 'libs.level-libs.tsort' local actor = require "scenes.subgame.sonic-boost.actors" local tilesize = 31 ---------- WORLD FUNCTIONS ---------- function World:new(controller) self.controller = controller self.creationID = 1 self.entities = bump3dpd.newWorld() self.chunks = {} self:loadAssets() self.worldloaded = false end function World:destroy() self.chunks = {} for i,v in ipairs(self.entities:getItems()) do v:destroy( ) end end ---------- ENTITIES MANAGEMENT FUNCTIONS ---------- function World:addEntitiesFromChunk(chunkpos) local chunkdata, isTrue = self:getChunkData(chunkpos, 1) local objectdata = chunkdata.objects local chunkx = (chunkpos - 1) * (8*31) print("trying to get objects from chunk at pos " .. chunkpos) for i=1, 5 do for j=1, 8 do local y = (i-1)*20 + 10 local x = chunkx + (j-1)*31 + 16 local id = objectdata[i][j] --print(chunkpos) self:addEntityFromID(id, x, y) end end end function World:addRailsFromChunk(chunkpos) local chunkdata, isTrue = self:getChunkData(chunkpos, 1) local objectdata = chunkdata.grind local chunkx = (chunkpos - 1) * (8*31) print("trying to get rails from chunk at pos " .. chunkpos) for i=1, 5 do for j=1, 8 do local y = (i-1)*20 + 10 local x = chunkx + (j-1)*31 local id = objectdata[i][j] --print(chunkpos) if id ~= 0 then actor.Rail(self, x, y, 10, id) end end end end function World:addEntityFromID(id, x, y) if actor.Index[id] == nil then if (id ~= 0) then print("WARNING: Object " .. id .. " doesn't exist") end return false end actor.Index[id](self, x, y) end function World:register(entity) self.entities:add(entity, entity.x, entity.y, entity.z, entity.w, entity.h, entity.d) end function World:remove(entity) if entity.destroyed == false then self.entities:remove(entity) print("entity " .. entity.id .. " removed") end end function World:move(entity, dx, dy, dz, filter) local dx, dy, dz = dx, dy, dz local newx, newy, newz newx, newy, newz, cols, coll_number = self.entities:move(entity, dx, dy, dz, filter) return newx, newy, newz, cols, coll_number end function World:updateEntity(entity, dx, dy, dz) newx, newy, newz, cols, coll_number = self.entities:update(entity, dx, dy, dz) end ---------- UPDATE FUNCTIONS ---------- function World:update(dt) for _, item in ipairs(self.entities:getItems()) do item:update(dt) --self.world2D:update(item, item.x, item.y) end end ---------- DRAW FUNCTIONS ---------- function World:draw() local chunksize = 8 * tilesize local camstart = math.floor(math.max(0, (self.controller.camera.view.x - 212) / chunksize)) -- self:drawBorder(-10) for i=0, 3 do -- on fait commencer la boucle à 0 pour regarder celui *avant* la caméra, -- pour éviter des soucis d'affichage. local chunkid = camstart + i self:drawChunk((chunkid - 1) * chunksize, 0, chunkid) end local itemsToDraw = self.entities:getItems() for _, item in ipairs(itemsToDraw) do item:drawShadow() if self.controller.camera.turn < self.controller.missiondata.turns then item:drawShadowEcho() end end table.sort(itemsToDraw, sortItemsToDraw) for _, item in ipairs(itemsToDraw) do item:draw() if self.controller.camera.turn < self.controller.missiondata.turns then item:drawEcho() end --item:drawHitBox() end self:drawBorder(100) end function World:drawChunk(x, y, chunkid, turn) local chunkdata, isTrue = self:getChunkData(chunkid, self.controller.camera.turn) local terraindata = chunkdata.terrain for i=1, 5 do for j=1, 8 do local tiley = (i-1)*20 - 4 local tilex = x + (j-1)*31 + (i-1)*10 local tileid = terraindata[i][j] local variant = 1 + ((i + j) % 2) if (isTrue == false) then love.graphics.setColor(.75, .75, .75, 1) end self:drawTile(tilex, tiley, tileid, variant) utils.graphics.resetColor() end end end function World:drawTile(x, y, type, variant) if type == 0 then love.graphics.draw(self.textures.tiles, self.quads.tiles[variant], x, y) else love.graphics.draw(self.textures.sptiles, self.quads.sptiles[type], x, y-2) end end function World:drawBorder(y) local x = math.floor((self.controller.camera.view.x - 212) / 80) * 80 for i=1, 7 do love.graphics.draw(self.textures.borders, self.quads.borders, x + (i-1)*80, y, 0, 1, 1) end end ---------- LOADING FUNCTIONS ---------- function World:loadWorld() self.chunks = {} self:addChunk("basics", 00) local layout = self.controller.layout for i,v in ipairs(layout) do --print("Decompressing group of parts " .. i .. " located in " .. v[1] .. " (containing " .. #v[2] .. " parts)") -- self.controller.layout est un tableau composée d'une série de tableau, -- chacuns en deux partie : le premier (v[1]) contient le nom du fichier -- où se trouve des partie de niveau, et la seconde partie (v[2]) une liste -- d'identifiant de partie de niveau à rajouter. -- Ce format permet de simplifier l'écriture de layout, si on a besoin de -- plusieur partie de niveau se trouvant dans le même fichier. for j,w in ipairs(v[2]) do -- On dit au gestionnaire de terrain d'ajouter la partie numéro j se trouvant -- dans v[2]. Le nom du fichier contenant les parties sera toujours v[1] self:addPart(v[1], w) end end end function World:addPart(filename, part) local filename = filename or "self" local partdata = {} -- On recupère les données de chunks if (filename == "self") or (filename == "") then -- Si le nom est "self", cela veut dire que fichier est le même que celui ou -- se trouve le layout, qui est forcément le fichier du niveau. Cela veut dire -- qu'il faut utiliser les parties de niveau du niveau actuel, localisée dans -- self.controller.parts partdata = self.controller.parts else -- Si c'est un autre nom, on charge dans "partdata" la liste des partie de niveau -- du fichier qu'on réfère. local chunkfile = require("datas.subgame.sonic-boost.chunks." .. filename) partdata = chunkfile.parts end -- On charge ensuite la partie de niveau voulue, qui correspond à l'identifiant -- qu'on a envoyé dans cette fonction (part) dans la liste partdata. local chunklist = partdata[part] for i, v in ipairs(chunklist) do -- chunklist fonctionne de la même manière que partlist : -- chaque entrée dans la liste (v) contient deux partie. -- v[1]: l'identifiant du fichier de chunk où se trouve le chunk voulu -- v[2]: la liste de chunks voulu. chaque entrée dedans est un nombre, -- correspondant à un chunk --print("- Decompressing group of chunk " .. i .. " located in " .. v[1] .. " (containing " .. #v[2] .. " chunk)") for j, w in ipairs(v[2]) do local chunkfile = v[1] if (chunkfile == "") or (chunkfile == "self") then -- si le fichier de chunk est "self", on lui indique que le chunk se trouve -- dans le même fichier que le fichier de partie. chunkfile = filename end self:addChunk(chunkfile, w) end end end function World:addChunk(filename, chunk) if filename == "self" then filename = "basic" end --print("--- Adding chunk id " .. chunk .. " located in " .. filename) local chunkfile = require("datas.subgame.sonic-boost.chunks." .. filename) chunkdata = chunkfile.chunks local chunkpos = #self.chunks table.insert(self.chunks, chunkdata[chunk]) self:addEntitiesFromChunk(chunkpos) self:addRailsFromChunk(chunkpos) self:updateSize() end ---------- ASSETS LOADING FUNCTIONS ---------- function World:loadAssets() self.textures = {} local folder = "assets/backgrounds/sideview/" self.textures.tiles = love.graphics.newImage(folder .. "normaltile.png") self.textures.sptiles = love.graphics.newImage(folder .. "specialtile.png") self.textures.borders = love.graphics.newImage(folder .. "borders.png") self.textures.rail = love.graphics.newImage(folder .. "rails/basic.png") self.quads = {} self.quads.tiles = {} self.quads.sptiles = {} self.quads.rails = {} local tileline = self.controller.datas.tiles or 0 local borderline = self.controller.datas.borders or 0 local w, h = self.textures.tiles:getDimensions() self.quads.tiles[1] = love.graphics.newQuad( 0, tileline*24, 40, 24, w, h) self.quads.tiles[2] = love.graphics.newQuad(40, tileline*24, 40, 24, w, h) local w, h = self.textures.sptiles:getDimensions() local h2 = math.floor(h / 26) for i=1, h2 do self.quads.sptiles[i] = love.graphics.newQuad(0, (i-1)*26, 40, 26, w, h) end local w, h = self.textures.borders:getDimensions() self.quads.borders = love.graphics.newQuad(0, borderline*10, 80, 10, w, h) local w, h = self.textures.rail:getDimensions() for i=1, 3 do self.quads.rails[i] = love.graphics.newQuad((i-1)*31, 0, 31, 8, w, h) end end ---------- DATA FUNCTIONS ---------- function World:getChunkData(chunkid, turn) local turn = turn or 1 local currentTurn, maxTurn = turn, self.controller.missiondata.turns if chunkid <= 0 then if currentTurn <= 1 then return self:getFakeChunk() else chunkid = (chunkid - 1) chunkid = utils.math.wrap(chunkid, 0, #self.chunks) return self.chunks[chunkid + 1], true end elseif chunkid > #self.chunks then if currentTurn >= maxTurn then return self:getFakeChunk() else chunkid = (chunkid - 1) chunkid = utils.math.wrap(chunkid, 0, #self.chunks) return self.chunks[chunkid + 1], true end else return self.chunks[chunkid], true end end function World:getFakeChunk() local fakedata = {} local emptyline = {00, 00, 00, 00, 00, 00, 00, 00} fakedata.objects = {emptyline, emptyline, emptyline, emptyline, emptyline} fakedata.terrain = {emptyline, emptyline, emptyline, emptyline, emptyline} fakedata.grind = {emptyline, emptyline, emptyline, emptyline, emptyline} return fakedata, false end function World:getTerrainAtPoint(x, y, turn) local chunkid = self:getChunkAtPoint(x, y) local chunkdata, isTrue = self:getChunkData(chunkid, turn) local i = math.floor(y / 20) + 1 local j = math.floor((x % (8*31)) / 31) + 1 local terrain = chunkdata.terrain[i][j] --print(terrain) return terrain end function World:getGrindAtPoint(x, y, turn) local chunkid = self:getChunkAtPoint(x, y) local chunkdata, isTrue = self:getChunkData(chunkid, turn) local i = math.floor(y / 20) + 1 local j = math.floor((x % (8*31)) / 31) + 1 local grind = chunkdata.grind[i][j] --print(terrain) return grind > 0 end function World:getChunkAtPoint(x, y) return math.floor(x / (8*31)) + 1 end ---------- USEFULL FUNCTIONS ---------- function World:updateSize() self.size = #self.chunks * 8 self.width = self.size * 31 end function World:getZSortedItems() local bump2d = require 'libs.bump' local tsort = require 'libs.tsort' local world2d = bump2d.newWorld() local world = self.entities for _, item in ipairs(world:getItems()) do if item.invisible ~= true and item.destroyed ~= true then local x,y,z,w,h,d = world:getCube(item) if world2d:hasItem(item) then world2d:update(item, x, y + z) else world2d:add(item, x, y + z, w, h + d) end end end local graph = tsort.new() local noOverlap = {} -- Iterate through all visible items, and calculate ordering of all pairs -- of overlapping items. -- TODO: Each pair is calculated twice currently. Maybe this is slow? for _, itemA in ipairs(world2d:getItems()) do repeat local x, y, w, h = world2d:getRect(itemA) local otherItemsFilter = function(other) return other ~= itemA end local overlapping, len = world2d:queryRect(x, y, w, h, otherItemsFilter) if len == 0 then table.insert(noOverlap, itemA) break end local _, aY, aZ, _, aH, aD = world:getCube(itemA) aDepth = itemA.depth aID = itemA.id aType = itemA.type --print("testing overlap with itemA " .. aID .. " (" .. aType .. ")") for _, itemB in ipairs(overlapping) do local _, bY, bZ, _, bH, bD = world:getCube(itemB) bDepth = itemB.depth bID = itemB.id bType = itemB.type --print("itemB " .. aID .. " overlap with item " .. bID .. " (" .. bType .. ")") if aZ + aD <= bZ then -- item A is completely above item B graph:add(itemB, itemA) --print("itemA is completely above itemB") elseif bZ + bD <= aZ then -- item B is completely above item A graph:add(itemA, itemB) --print("itemB is completely above itemA") elseif aY + aH <= bY then -- item A is completely behind item B graph:add(itemA, itemB) --print("itemA is completely behind itemB") elseif bY + bH <= aY then -- item B is completely behind item A graph:add(itemB, itemA) --print("itemB is completely behind itemA") elseif aY + aZ + aH + aD == bY + bZ + bH + bD then -- item A's forward-most point is the same than item B's forward-most point --print("itemA's forward-most point is the same than itemB's forward-most point") if aDepth > bDepth then graph:add(itemB, itemA) elseif aDepth < bDepth then graph:add(itemA, itemB) else if aID > bID then graph:add(itemA, itemB) elseif aID < bID then graph:add(itemB, itemA) else err("two object can't have the same ID") end end elseif aY + aZ + aH + aD >= bY + bZ + bH + bD then -- item A's forward-most point is in front of item B's forward-most point graph:add(itemB, itemA) --print("itemA's forward-most point is in front of itemB's forward-most point") else -- item B's forward-most point is in front of item A's forward-most point graph:add(itemA, itemB) --print("itemB's forward-most point is in front of itemA's forward-most point") end end until true end local sorted, err = graph:sort() if err then error(err) end for _, item in ipairs(noOverlap) do table.insert(sorted, item) end return sorted end function sortItemsToDraw(a, b) return a.y < b.y end return World