sonic-bluestreak/sonic-boost.love/scenes/subgame/sonic-boost/controller/world.lua

511 lines
14 KiB
Lua
Raw Normal View History

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