511 lines
14 KiB
Lua
511 lines
14 KiB
Lua
|
local World = Object:extend()
|
||
|
|
||
|
local bump3dpd = require 'libs.bump-3dpd'
|
||
|
local bump2d = require 'libs.bump'
|
||
|
local tsort = require 'libs.tsort'
|
||
|
|
||
|
local actor = require "scenes.levels.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.draw.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.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.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 = {}
|
||
|
self.textures.tiles = love.graphics.newImage("assets/levels/normaltile.png")
|
||
|
self.textures.sptiles = love.graphics.newImage("assets/levels/specialtile.png")
|
||
|
self.textures.borders = love.graphics.newImage("assets/levels/borders.png")
|
||
|
|
||
|
self.textures.rail = love.graphics.newImage("assets/levels/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
|