diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/actor-index.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/actor-index.lua new file mode 100644 index 0000000..454671c --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/actor-index.lua @@ -0,0 +1,7 @@ +local ActorIndex = {} +local Actor = "scenes.levels.actors" + +ActorIndex[1] = Actor.Ring +ActorIndex[2] = Actor.Crystal + +return ActorIndex diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/block.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/block.lua new file mode 100644 index 0000000..5d7f08e --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/block.lua @@ -0,0 +1,10 @@ +local Block = Entity:extend() + +function Block:new(level, x, y, z, w, h, d) -- On enregistre une nouvelle entité, avec par défaut sa hitbox. + Block.super.new(self, level, "block", x-16, y-10, 0, 31, 20, d) + self.name = "block" + + self:setDebugColor(255, 0, 0) +end + +return Block diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/character.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/character.lua new file mode 100644 index 0000000..355a7f7 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/character.lua @@ -0,0 +1,466 @@ +local Entity = require "scenes.levels.actors.parent" +local phys = require "datas.physics" +local playerHeight = 32 +local grindheight = 32 + +local Character = Entity:extend() + +-- Un "personnage" est une entité unique, dans le sens où elle possède un controller +-- special, qui fait le lien entre elle et les différents éléments du niveau + +-- Le code des personnages normaux et rivaux est exactement le même, les différence +-- sont géré par les controlleurs, qui s'occupent de leur envoyer les données +-- de touche, et d'effecter les réactions logique en cas de disparition de l'entité +-- (terminer le niveau pour un joueur, juste le détruire pour un rival) + +-- Le personnage ne sait pas à l'intérieur de lui même s'il est contrôler par une +-- IA ou par un vrai joueur. + +-- Il n'a pas non plus accès aux détails de la mission, qui sont aussi géré par +-- son controller special (vu par "self.charcontroller") + +function Character:new(charcontroller, rail, character, id) + self.charcontroller = charcontroller + local world = self.charcontroller.controller.world + + + local rail = rail or 2 + local y = 10 + (rail*20) + Character.super.new(self, world, "character", 16-8, y-8, 0, 16, 16, playerHeight) + self.name = "player" + self.charid = id or 1 + + self.yspeed = 0 + self.xspeed = 0 + self.zspeed = 0 + + self.ymove = false + self.onGround = true + self.groundBellow = true + self.groundTerrain = 0 + self.isJumping = false + self.isInAerialAction = false + self.canAerialAction = true + self.isInAction = false + self.canAction = true + + self.dash = false + + self:setDebugColor(0, 255, 0) + + self:characterInit(character) + + self:setSprite("character" .. self.charid, 32, 48, true) + + self.depth = -1 + + self.rings = 0 + self.score = 0 + self.combo = 0 + self.bonus = 0 + self.turn = 1 + + self.life = 3 + + self.startx = self.x + self.starty = self.y + + self.grind = false + + self.ai = isAIControlled or false +end + +function Character:characterInit(char) + self.data = game.characters:getCharacterData(char) + + self.lifeicon = self.data.assets.lifeicon +end + +function Character:update(dt) + self:updateTimers(dt) + + -- On initialise les variables de directions + local dx, dy, dz = 0, 0, 0 + + -- On commence par récupérer les inputs fait sur le pad virtuel, qui est géré + -- dans game.input.keys. On le garde dans une variable qui sera utilisée dans + -- tout l'objet joueur + + self.groundTerrain, self.groundBellow = self:getTerrain() + + self.keys = self.charcontroller:getKeys() + + self:sideSteps() + self:jumpAction() + self:jump() + self:normalAction() + + self:applyVerticalVelocity(dt) + + self:autoMove(dt) + self:snaptoRails(dt) + + self:move() +end + +-- ACTIONS FUNCTIONS -- + +function Character:sideSteps() + if self.grind == true then + if (self.keys["up"].isPressed) then + self.yspeed = - 1 * 60 * phys.side + self.ymove = true + elseif (self.keys["down"].isPressed) then + self.yspeed = 60 * phys.side + self.ymove = true + else + self.ymove = false + end + else + if (self.keys["up"].isDown) then + self.yspeed = - 1 * 60 * phys.side + self.ymove = true + elseif (self.keys["down"].isDown) then + self.yspeed = 60 * phys.side + self.ymove = true + else + self.ymove = false + end + end +end + +function Character:jump() + if (self.keys["A"].isPressed) and (self.onGround) then + self.zspeed = 60 * self.data.stats.jmp + self.onGround = false + self.isJumping = true + self.isInAction = false + end +end + +function Character:jumpAction() + if self.data.stats.jumpaction == "doublejump" then + if (self.keys["A"].isPressed) and (self.isJumping) and (self.canAerialAction) then + self.zspeed = 60 * self.data.stats.jumpaction_power + self.isInAerialAction = true + self.canAerialAction = false + end + end +end + +function Character:normalAction() + if self.data.stats.action == "spinattack" then + if (self.keys["B"].isPressed) and (self.onGround) and (self.canAction) and (self.grind == false) then + self.xspeed = math.max(self.xspeed, self.data.stats.spd * 60) + self.isInAction = true + self.canAction = false + self:addTimer("action", .5) + self:addTimer("action_cooldown", 1) + end + end +end + +-- Moving functions -- + +function Character:move() + local cols, coll_number + + self:setFilter() + + self.x, self.y, self.z, cols, coll_number = Character.super.move(self) + + for i=1, coll_number do + local col = cols[i] + if col.other.type == "collectible" then + col.other:pickedUp(self) + end + self.collisionResponse(cols[i]) + end + +end + +function Character:collisionResponse(other) + if other == nil then return 0 end + if other.type == "collectible" then + other:pickedUp() + end +end + +function Character:setFilter() + self.filter = function(item, other) + if other.type == "collectible" then return "cross" + else return "cross" end + end +end + +function Character:autoMove(dt) + local max = self.data.stats.spd + self.lockspeed = false + + + + + acc = phys.acc + + if (self.groundTerrain == 1) and (self.z <= 0) then + self.dash = true + self:addTimer("dash", 2) + acc = phys.acc + end + + if (self.groundTerrain == 2) and (self.z <= 0) then + max = phys.max * 1/3 + self.dash = false + self.acc = acc*2 + end + + if (self.groundTerrain == 4) and (self.z <= 0) then + self.dash = true + self:addTimer("dash", 2) + self.zspeed = 60 * (phys.spring * 4/5) + self.onGround = false + self.isJumping = true + self.assets.sfx["jumpboard"]:play() + end + + if (self.groundTerrain == 3) and (self.z <= 0) then + max = 0 + self.dash = false + self.acc = acc*3 + self.xspeed = self.xspeed * 3/4 + --self.lockspeed = true + end + + if (self.lockspeed == false) then + if self.dash == true then + self.xspeed = max * 60 * phys.boost + elseif self.grind == true then + self.xspeed = max * 60 + else + if self.xspeed < (max*60) then + self.xspeed = self.xspeed + acc + elseif (self.xspeed - acc*2) > (phys.max*60) then + self.xspeed = self.xspeed - acc*2 + else + self.xspeed = (max*60) + end + end + + self.x = self.x + self.xspeed * dt + end + + if self.x >= self.controller.world.width then + self:overflowLevel() + end +end + + +function Character:applyVerticalVelocity(dt) + if (self.groundBellow == false) and (self.onGround == true) then + self.onGround = false + end + + local grindBellow = self.world:getGrindAtPoint(self.x, self.y, self.turn) + + if (grindBellow == false) and (self.onGround == true) and self.z > 0 then + self.onGround = false + self.grind = false + end + + if (self.onGround == false) then + self.zspeed = self.zspeed - phys.grv + + if self.zspeed < 0 then + if (self.z + self.zspeed*dt <= 0) and (self.groundBellow == true) then + self.z = 0 + self.onGround = true + self.isJumping = false + self.grind = false -- pas vraiment nécessaire de retirer le grind ici + self.isInAerialAction = false + self.canAerialAction = true + end + + if (self.z + self.zspeed*dt <= grindheight) and (self.z + self.zspeed*dt > grindheight - 8) and (grindBellow == true) then + self.z = grindheight + self.onGround = true + self.isJumping = false + self.grind = true + self.isInAerialAction = false + self.canAerialAction = true + end + + + if (self.groundBellow == false) and (self.z < -64) then + self:die() + end + end + self.z = self.z + self.zspeed*dt + end + + if self.ground == true then + self.zspeed = 0 + end +end + +function Character:snaptoRails(dt) + local centery = self.y + 8 + local yrail = math.floor((centery - 10) / 20) + local newyrail = math.floor((centery + self.yspeed*dt - 10) / 20) + + if (newyrail < 0) and (self.yspeed < 0) then + centery = 10 + self.yspeed = 0 + elseif (newyrail >= 4) and (self.yspeed > 0) then + centery = 90 + self.yspeed = 0 + else + if (self.ymove == false) and (yrail ~= newyrail) then + if self.yspeed > 0 then + centery = newyrail * 20 + 10 + end + if self.yspeed < 0 then + centery = yrail * 20 + 10 + end + self.yspeed = 0 + else + centery = centery + self.yspeed*dt + end + end + + self.y = centery - 8 +end + +---------- INTERACTION FUNCTIONS --------- + +function Character:getTerrain() + local terrain, groundbellow + terrain = self.world:getTerrainAtPoint(self.x, self.y, self.turn) + + if (terrain == 3) then + groundbellow = false + else + groundbellow = true + end + + return terrain, groundbellow +end + +function Character:addScore(score) + self.combo = self.combo + 1 + + self.bonus = math.floor(self.combo / 5) * 2 + + self.score = self.score + score + math.floor(1/10 * score * self.bonus) + + self:addTimer("combo", 3) +end + +---------- DRAWING FUNCTIONS --------- + +function Character:animations() + self.assets.sprites["character" .. self.charid]:setCustomSpeed(math.abs(self.xspeed / 16)) + + if self.onGround then + if self.grind == true then + self:setAnimation("grind") + elseif self.isInAction == true then + self:setAnimation("action") + else + if self.xspeed == 0 then + self:setAnimation("idle") + elseif self.xspeed < phys.max*60 then + self:setAnimation("run2") + elseif self.xspeed >= phys.max*60 then + self:setAnimation("dash") + end + end + else + if self.isJumping then + self:setAnimation("jump") + else + self:setAnimation("fall") + end + end +end + +function Character:setAnimation(animation) + if (self.animation ~= animation) then + self.animation = animation + self.assets.sprites["character" .. self.charid]:changeAnimation(animation, true) + end +end + +function Character:draw() + self:animations() + if (self.z < 0) and (self.onGround == false) then + z = self.z + if self.z < -48 then z = -48 end + love.graphics.setScissor(0, 0, 424, self.y + 100) + end + self:drawSprite() + love.graphics.setScissor() +end + +function Character:drawEcho() + self:animations() + if (self.z < 0) and (self.onGround == false) then + z = self.z + if self.z < -48 then z = -48 end + love.graphics.setScissor(0, 0, 424, self.y + 100) + end + self:drawSprite(self.world.width, 0) + love.graphics.setScissor() +end + +function Character:drawShadow(tx, ty) + if (self.z < 0) and (self.onGround == false) then + return 0 + end + Character.super.drawShadow(self, tx, ty) +end + +function Character:drawShadowEcho() + if (self.z < 0) and (self.onGround == false) then + return 0 + end + Character.super.drawShadow(self, self.world.width, 0) +end + +---------- LEVEL FINISH FUNCTIONS ---------- + +function Character:overflowLevel() + self.charcontroller:newLap() +end + +function Character:finishLevel() + --love.event.quit() +end + +function Character:die() + if self.life == 0 then + self.charcontroller:die() + else + self.life = self.life - 1 + self.x = self.startx + self.y = self.starty + self.z = 0 + self.rings = 0 + end +end + +----- TIMERS ----- + +function Character:endedTimer(name) + if name == "dash" then + self.dash = false + elseif name == "combo" then + self.combo = 0 + self.bonus = 0 + elseif name == "action" then + self.isInAction = false + elseif name == "action_cooldown" then + self.canAction = true + end +end + +return Character diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/crystal.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/crystal.lua new file mode 100644 index 0000000..e1cf068 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/crystal.lua @@ -0,0 +1,21 @@ +local Entity = require("scenes.levels.actors.parent") +local Crystal = Entity:extend() + +function Crystal:new(world, x, y, z) + local z = z or 8 + Crystal.super.new(self, world, "collectible", x-8, y-8, z, 16, 16, 16) + + self:setSprite("crystal", 8, 16, true) + + self:setDebugColor(255, 255, 0) + + self.depth = 1 +end + +function Crystal:pickedUp(player) + player:addScore(10) + + self:destroy() +end + +return Crystal diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/ennemy.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/ennemy.lua new file mode 100644 index 0000000..e69de29 diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/init.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/init.lua new file mode 100644 index 0000000..1b06b23 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/init.lua @@ -0,0 +1,12 @@ +local Actor = {} + +Actor.Character = require "scenes.levels.actors.character" +Actor.Ring = require "scenes.levels.actors.ring" +Actor.Crystal = require "scenes.levels.actors.crystal" +Actor.Rail = require "scenes.levels.actors.rail" + +Actor.Index = {} +Actor.Index[01] = Actor.Ring +Actor.Index[02] = Actor.Crystal + +return Actor diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/parent.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/parent.lua new file mode 100644 index 0000000..44d84e1 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/parent.lua @@ -0,0 +1,223 @@ +local ParentEntity = Object:extend() -- On créer la classe des entitées, c'est la classe de base + +local Timer = require "scenes.levels.actors.utils.timers" + +function ParentEntity:new(world, type, x, y, z, w, h, d) -- On enregistre une nouvelle entité, avec par défaut sa hitbox. + self:setHitbox(x, y, z, w, h, d) + self:register(world) + --self:initPhysics() + --self:resetTimers() + self:setDebugColor(0,0,0) + self.destroyed = false + self.invisible = false + self.appearence = "none" + + self.type = type + + self:setSprite("", 0, 0, false) + + self:setFilter() + + self.depth = 0 + self.id = self.world.creationID + self.world.creationID = self.world.creationID + 1 + + self:resetTimers() +end + +function ParentEntity:setHitbox(x, y, z, w, h, d) + self.x = x + self.y = y + self.z = z + self.w = w + self.h = h + self.d = d +end + +function ParentEntity:setSprite(name, ox, oy, active) + self.sprite = {} + + self.sprite.name = name + self.sprite.ox = ox + self.sprite.oy = oy + self.sprite.active = active or false +end + +function ParentEntity:update(dt) + self:updateTimers(dt) +end + +function ParentEntity:register(world) + -- On enregistre la hitbox dans le monde, pour l'instant les deux parties du programmes + -- sont séparé (génération et enregistrement, peut-être qu'elles seront fusionnées) + self.world = world + self.controller = world.controller + self.assets = self.controller.assets + + self.world:register(self) +end + +function ParentEntity:destroy() + if self.destroyed == false then + self.world:remove(self) + self.destroyed = true + end +end + +function ParentEntity:move() + local x, y, z, cols, coll_number = self.x, self.y, self.z, {}, 0 + if self.destroyed == false then + x, y, z, cols, coll_number = self.world:move( + self, + self.x, + self.y, + self.z, + self.filter + ) + end + + return x, y, z, cols, coll_number +end + +function ParentEntity:drawDebug() + if self.invisible == true then + return 0 + end + + self:draw() +end + +--------------------------- DEBUG FUNCTIONS ------------------------------------ + +function ParentEntity:setDebugColor(r,g,b) + self.debugColor = {} + + self.debugColor.r = r + self.debugColor.g = g + self.debugColor.b = b +end + +---------- COLLISION ---------- + +function ParentEntity:setFilter() + self.filter = function(item, other) + return nil + end +end + +function ParentEntity:set2DFilter() + self.filter2D = function(item, other) + return "cross" + end +end + +function ParentEntity:pickedUp() + self:destroy() +end + +--------------------------- COORDINATE FUNCTIONS ------------------------------------ + +function ParentEntity:getCenter() + local x, y, z + x = math.floor(self.x + self.w / 2) + y = math.floor(self.y + self.h / 2) + z = math.floor(self.z + self.d / 2) + + return x, y, z +end + +function ParentEntity:getOrigin() + local x, y, z + y = math.floor(self.y + self.h / 2) + x = math.floor(self.x + self.w / 2) + math.floor(y / 2) + z = math.floor(self.z) + + return x, y, z +end + +function ParentEntity:getSpritePosition() + local x, y, z = self:getOrigin() + y = y - z + + return x, y, z +end + +--------------------------- DRAW FUNCTIONS ------------------------------------ + +function ParentEntity:draw() + self:drawSprite() +end + +function ParentEntity:drawEcho() + self:drawSprite(self.world.width, 0) +end + +function ParentEntity:drawSprite(tx, ty) + utils.draw.resetColor() + + local x, y, z = self:getSpritePosition() + + local tx = tx or 0 + local ty = ty or 0 + + if (self.sprite.active) then + self.assets.sprites[self.sprite.name]:drawAnimation(x + tx, y + ty, 0, 1, 1, self.sprite.ox, self.sprite.oy) + end +end + +function ParentEntity:drawShadow(tx, ty) + local x, y, z = self:getOrigin() + love.graphics.setColor(0, 0, 1, 1) + local tx = tx or 0 + local ty = ty or 0 + x = math.floor(x + tx) + y = math.floor(y + ty) + self.assets:drawImage("shadow", x, y, 0, 1, 1, 12, 3) +end + +function ParentEntity:drawShadowEcho() + self:drawShadow(self.world.width, 0) +end + +function ParentEntity:drawHitBox() + x, y, z = self:getOrigin() + + local r, g, b, a = self.debugColor.r, self.debugColor.g, self.debugColor.b, self.debugColor.a + + --utils.draw.box(self.x, self.y + self.z + self.h, self.w, self.d, self.h, r, g, b, a) + --utils.draw.box(x - (self.w/2), y - (self.w/2) - self.z, self.w, self.h, r, g, b, a) + --utils.draw.box(x - (self.w/2), y - (self.w/2) - self.z, self.w, self.d, r, g, b, a) + --utils.draw.box(x - (self.w/2), y + (self.w/2) - self.z - self.d, self.w, self.d, r, g, b, a) + --utils.draw.box(x - (self.w/2), y - (self.w/2) - self.z - self.d, self.w, self.h, r, g, b, a) + --utils.draw.box(x - (self.w/2), y + (self.w/2) - self.z - self.d, self.w, self.d, r, g, b, a) + + utils.draw.box(x - (self.w/2), y - (self.h/2), self.w, self.h, r/5, g/5, b/5, 1) + + utils.draw.box(x - (self.w/2), y - z - self.d, self.w, self.d, r, g, b, 1) + +end + +----------- TIMER RELATED FUNCTIONS ---------- + +function ParentEntity:resetTimers() + self.timers = {} +end + +function ParentEntity:addTimer(name, t) + self.timers[name] = Timer(self, name, t) + return name +end + +function ParentEntity:updateTimers(dt) + for i, v in pairs(self.timers) do + v:update(dt) + end +end + +function ParentEntity:endedTimer(name) + if name == "destroy" then + self:destroy() + end +end + +return ParentEntity diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/rail.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/rail.lua new file mode 100644 index 0000000..51e7d9d --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/rail.lua @@ -0,0 +1,25 @@ +local Entity = require("scenes.levels.actors.parent") +local Rail = Entity:extend() + +function Rail:new(world, x, y, z, id) + local z = z or 32 + self.railid = id or 2 + + Rail.super.new(self, world, "grind", x, y-4, 24, 31, 8, 8) + + self:setDebugColor(.2, .2, .2) + + self.depth = 1 +end + +function Rail:draw() + utils.draw.resetColor( ) + love.graphics.draw(self.world.textures.rail, self.world.quads.rails[self.railid], self.x + math.floor(self.y / 2), self.y - self.z -(self.d / 2)) +end + +function Rail:drawEcho() + utils.draw.resetColor( ) + love.graphics.draw(self.world.textures.rail, self.world.quads.rails[self.railid], self.x + math.floor(self.y / 2) + self.world.width, self.y - self.z -(self.d / 2)) +end + +return Rail diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/ring.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/ring.lua new file mode 100644 index 0000000..42d39ef --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/ring.lua @@ -0,0 +1,22 @@ +local Entity = require("scenes.levels.actors.parent") +local Ring = Entity:extend() + +function Ring:new(world, x, y, z) + local z = z or 8 + Ring.super.new(self, world, "collectible", x-8, y-8, z, 16, 16, 16) + + self:setSprite("ring", 8, 16, true) + + self:setDebugColor(255, 255, 0) + + self.depth = 1 +end + +function Ring:pickedUp(player) + player.rings = player.rings + 1 + player:addScore(10) + + self:destroy() +end + +return Ring diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/actors/utils/timers.lua b/sonic-boost.love/scenes/subgame/sonic-boost/actors/utils/timers.lua new file mode 100644 index 0000000..05fabdd --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/actors/utils/timers.lua @@ -0,0 +1,21 @@ +local Timer = Object:extend() + +function Timer:new(entity, name, t) + self.time = t + self.entity = entity + self.name = name +end + +function Timer:update(dt) + self.time = self.time - dt + if (self.time <= 0) then + self:finish() + end +end + +function Timer:finish() + self.entity:endedTimer(self.name) + self.entity.timers[self.name] = nil +end + +return Timer diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/background.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/background.lua new file mode 100644 index 0000000..1a9b918 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/background.lua @@ -0,0 +1,51 @@ +local Background = Object:extend() + +function Background:new(controller, levelname) + self.controller = controller + local filename = self.controller.datas.background or "forest" + self.back1 = love.graphics.newImage("assets/levels/backgrounds/" .. filename .. "-back.png") + self.back2 = love.graphics.newImage("assets/levels/backgrounds/" .. filename .. "-fore.png") +end + +function Background:destroy() + self.back1:release( ) + self.back2:release( ) +end + +function Background:draw() + local x0, x1, x2 + + local turn = self.controller.camera.turn + + x0 = self.controller.camera.view.x + (turn * self.controller.world.width) + x1 = x0 / 3 % 240 + x2 = x0 / 9 % 480 + + local sx = 1 + for i=1, 4 do + if (i == 2) or (i == 4) then + love.graphics.draw(self.back1, (i)*240 - x2, 20, 0, -1, 1) + else + love.graphics.draw(self.back1, (i-1)*240 - x2, 20, 0, 1, 1) + end + end + + for i=1, 3 do + love.graphics.draw(self.back2, (i-1)*240 - x1, 20, 0, 1, 1) + end + + --self:drawBorders() + --self:drawGround(0, 90) +end + +function Background:drawGround(x, y) + for i=1, 5 do + for j=1, 16 do + local k = 1 + ((i + j) % 2) + print(k) + love.graphics.draw(self.normalTileImage, self.normalTile[k], x + (j-3)*31 + (i-1)*10 , y + (i-1)*20) + end + end +end + +return Background diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/camera.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/camera.lua new file mode 100644 index 0000000..4fa8fde --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/camera.lua @@ -0,0 +1,122 @@ +local Camera = require "libs.hump.camera" +local CameraSystem = Object:extend() + +function CameraSystem:new(controller, x, y, target) + self.controller = controller + self.target = target + self.view = Camera(212, 30, 1, 0, true) + local width, height, flags = love.window.getMode( ) + self.screen = {} + self.screen.width = width + self.screen.height = height + self.width = 424 + self.height = 240 + self.resolution = self.screen.width / self.width + --self:limit() + + self.turn = 1 +end + +function CameraSystem:setTarget(target) + self.target = target +end + +function CameraSystem:floorCoord() + self.view.x, self.view.y = utils.math.floorCoord(self.view.x, self.view.y) +end + +function CameraSystem:update() + if (self.target ~= nil) then + self:followEntity(self.target) + end + self:limit() + self:floorCoord() +end + +function CameraSystem:move(x, y) + self.view.x = x + self.view.y = y + + self:limit() +end + +function CameraSystem:getCoord() + local camx, camy, camh, camw + camx = self.view.x - (self.width/2) + camy = self.view.y - (self.height/2) + + camw = self.width + camh = self.height + return camx, camy, camw, camh +end + +function CameraSystem:worldCoord(x, y, ox, oy, w, h) + ox, oy = ox or 0, oy or 0 + w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() + return self.view:worldCoords(x, y, ox, oy, w, h) +end + +function CameraSystem:viewCoord(x, y) + return self.view:cameraCoords(x, y, ox, oy, w, h) +end + +function CameraSystem:getScreenCoord() + local camx, camy, camh, camw + camx = self.view.x - (self.screen.width/2) + camy = self.view.y - (self.screen.height/2) + + camw = self.screen.width + camh = self.screen.height + return camx, camy, camw, camh +end + +function CameraSystem:limit() + local camx, camy = self.view.x, self.view.y + + local currentTurn, maxTurn = self.turn, self.controller.missiondata.turns + + if currentTurn == 1 then + camx = math.max(212, camx) + end + + if currentTurn == maxTurn then + camx = math.min(camx, self.controller.world.width - 212 + 31) + end + + self.view.x, self.view.y = camx, 30 +end + +function CameraSystem:followEntity(entity) + local entity = entity + + if (entity ~= nil) then + self.turn = entity.turn or 1 + + local playx, playy = entity:getCenter() + + local camx, camy = self.view.x + (self.width/2), + self.view.y + (self.height/2) + + + playx, playy = self:viewCoord(playx-16+212, 30) + playx = playx - (self.width/2) + playy = playy - (self.height/2) + + if (math.abs(playx) > 8) then + camx = camx + (playx - (8*utils.math.sign(playx))) + end + + if (playy > 16) then + camy = camy + (playy - 16) + elseif (playy < -64) then + camy = camy + (playy + 64) + end + + self.view.x, self.view.y = camx - (self.width/2), + camy - (self.height/2) + end +end + +-- Anciennes Fonctions + +return CameraSystem diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/characters.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/characters.lua new file mode 100644 index 0000000..d2e1e5a --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/characters.lua @@ -0,0 +1,120 @@ +local CharManager = Object:extend() +local Dummy = Object:extend() +local Player = Dummy:extend() +local Rival = Dummy:extend() +local actor = require "scenes.levels.actors" + + +function CharManager:new(controller) + self.controller = controller + self.list = {} +end + +function CharManager:newPlayer(character, rail) + local id = #self.list + 1 + table.insert(self.list, Player(self, id, character, rail)) +end + +function CharManager:newDummy(character, rail) + local id = #self.list + 1 + table.insert(self.list, Dummy(self, id, character, rail)) +end + +function CharManager:update(dt) + for i,v in ipairs(self.list) do + v:update(dt) + end +end + +function CharManager:getType(id) + self.list[id]:getType() +end + +-- DUMMY CHARACTER FUNCTION -- + +function Dummy:new(manager, playerid, character, rail) + self.manager = manager + self.controller = manager.controller + self.type = "dummy" + + local character = character or "sonic" + + local rail = rail + + local spritepath = "assets/sprites/characters/" .. character + self.controller.assets:addSprite("character" .. playerid, spritepath) + + self.actor = actor.Character(self, rail, character, playerid) +end + + +function Dummy:getKeys() + return game.input.fakekeys +end + +function Dummy:update(dt) + -- le Dummy ne fait rien, donc inutile +end + +function Dummy:newLap() + local currentTurn, maxTurn = self.actor.turn, self.controller.missiondata.turns + + if (currentTurn < maxTurn) then + self:wrapActor() + else + self:finishLevel() + end +end + +function Dummy:wrapActor() + self.actor.x = math.floor(self.actor.x - self.controller.world.width) + self.actor.turn = self.actor.turn + 1 +end + +function Dummy:getRealPosition() + local turn = self.actor.turn - 1 -- on calcule le tour à partir de 0 pour + -- obtenir le vrai x. Techniquement, ça ne change rien pour le joueur, + -- mais c'est plus correct comme ça + + local turnStart = (turn * self.controller.world.width) + + return turnStart + self.actor.x +end + +function Dummy:finishLevel() + -- Pour le Dummy ça n'a aucun effet +end + +function Dummy:die() + self.actor:destroy() +end + +-- PLAYER CONTROL FUNCTION -- + +function Player:new(manager, playerid, character, rail) + Player.super.new(self, manager, playerid, character, rail) + + self.type = "player" + self.controller.camera:setTarget(self.actor) + self.controller.hud:setTarget(self.actor) +end + +function Player:getKeys() + return game.input.keys +end + +function Player:wrapActor() + self.actor.x = math.floor(self.actor.x - self.controller.world.width) + self.actor.turn = self.actor.turn + 1 + self.controller.camera.view.x = self.controller.camera.view.x - self.controller.world.width +end + +function Player:finishLevel() + --love.event.quit() +end + +function Player:die() + love.event.quit() +end + +return CharManager diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/hud.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/hud.lua new file mode 100644 index 0000000..25a228c --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/hud.lua @@ -0,0 +1,127 @@ +local BattleHUD = Object:extend() + +function BattleHUD:new(controller) + self.controller = controller + self.target = nil + + self:loadAssets() +end + +function BattleHUD:setTarget(target) + self.target = target +end + +function BattleHUD:loadAssets() + self.progressbarImage = love.graphics.newImage("assets/gui/progressbar.png") + self.barBack = love.graphics.newQuad(0, 0, 211, 12, 211, 24) + self.barFore = love.graphics.newQuad(0, 12, 211, 12, 211, 24) + self.hud1 = love.graphics.newImage("assets/gui/boosthud1.png") + self.hud2 = love.graphics.newImage("assets/gui/boosthud2.png") + + self.ring = love.graphics.newImage("assets/gui/ring.png") + + + + self.font = love.graphics.newImageFont("assets/gui/hudnumbers.png", " 0123456789:/", 1) + self.font2 = love.graphics.newImageFont("assets/gui/hudsmallnumbers.png", " 0123456789", 0) + + self.time = love.graphics.newImage("assets/gui/hudtime.png") + self.score = love.graphics.newImage("assets/gui/hudscore.png") + self.bonus = love.graphics.newImage("assets/gui/hudbonus.png") + + self.controller.assets:addTileset("lifeicon", "assets/sprites/characters/charicons") +end + +function BattleHUD:destroy( ) + self.progressbarImage:release( ) + self.barBack:release( ) + self.barFore:release( ) + self.hud1:release( ) + self.hud2:release( ) +end + +function BattleHUD:update(dt) + -- +end + +function BattleHUD:draw() + love.graphics.setFont( self.font ) + self:drawFrame() + + if self.target ~= nil then + self:drawTop(0, 0) + self:drawBottom(0, 200) + end + + self.controller:resetFont( ) +end + +function BattleHUD:drawFrame() + love.graphics.draw(self.hud1, 0, 0) + love.graphics.draw(self.hud2, 0, 200) +end + +function BattleHUD:drawTop(x, y) + + local ringx, ringy = x + 289, y + 6 + love.graphics.draw(self.ring, ringx, ringy) + local rings = utils.math.numberToString(self.target.rings, 3) + + love.graphics.print(rings, ringx + 14, ringy + 1) + + + love.graphics.draw(self.score, x+8, y+3) + local score = utils.math.numberToString(self.target.score, 9) + local bonus = utils.math.numberToString(self.target.bonus, 3) + local combo = utils.math.numberToString(self.target.combo, 3) + + love.graphics.print(score, x + 8 + 42, y + 4) + + local bonusx, bonusy = x + 165, y + 1 + love.graphics.draw(self.bonus, bonusx, bonusy) + love.graphics.setFont( self.font2 ) + love.graphics.print(combo, bonusx + 28, bonusy) + love.graphics.print(bonus, bonusx + 28, bonusy + 8) + love.graphics.setFont( self.font ) + + self.controller.assets.tileset["lifeicon"]:drawTile(self.target.lifeicon, x + 360, y + 4) + local lives = utils.math.numberToString(self.target.life, 2) + love.graphics.print(lives, x + 360 + 18, y + 7 ) +end + +function BattleHUD:drawBottom(x, y) + local progressx, progressy = x + 5, y + 4 + love.graphics.draw(self.progressbarImage, self.barBack, progressx, progressy) + local width = math.floor((self.target.x / self.controller.world.width) * (211 - 16)) + width = math.max(0, width) + width = math.min(width, (211 - 16)) + love.graphics.setScissor(progressx + 8, progressy, width, 12) + love.graphics.draw(self.progressbarImage, self.barFore, progressx, progressy) + love.graphics.setScissor( ) + self.controller.assets.tileset["lifeicon"]:drawTile(self.target.lifeicon, progressx + width, progressy - 2) + if (self.controller.missiondata.turns > 1) then + local turn1 = utils.math.numberToString(self.target.turn, 2) + local turn2 = utils.math.numberToString(self.controller.missiondata.turns, 2) + love.graphics.printf(turn1 .. "/" .. turn2, progressx, progressy + 12 + 2, 210, "right") + end + + local timex, timey = x + 228, y + 1 + love.graphics.draw(self.time, timex, timey) + local timestring = self:getTimeString(self.controller.mission.timer) + + love.graphics.print(timestring, timex + 32, timey + 1) + + +end + +function BattleHUD:getTimeString(numberOfSeconds) + local numberOfSeconds = numberOfSeconds or 0 + local microseconds = math.floor((numberOfSeconds % 1) * 100) + local seconds = math.floor(numberOfSeconds % 60) + local minutes = math.floor(numberOfSeconds / 60) + return utils.math.numberToString(minutes, 2) .. ":" + .. utils.math.numberToString(seconds, 2) .. ":" + .. utils.math.numberToString(microseconds, 2) +end + +return BattleHUD diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/init.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/init.lua new file mode 100644 index 0000000..6a49ec1 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/init.lua @@ -0,0 +1,161 @@ +local Controller = Object:extend() +local GUI = require "modules.gui" +local Assets = require "modules.assets" + +local HUD = require "scenes.levels.controller.hud" +local Background = require "scenes.levels.controller.background" +local Camera = require "scenes.levels.controller.camera" +local World = require "scenes.levels.controller.world" +local PauseMenu = require "scenes.levels.controller.pause" + +local CharacterManager = require "scenes.levels.controller.characters" + +local zoneDatas = require "datas.levels.zones" + +local LoadAssets = require "scenes.levels.controller.loader" + +function Controller:new(levelid, character) + self:initMission() + + self.assets = Assets() + self.gui = GUI() + LoadAssets(self) + + self:initManagers() + + self:resetFont() + + self.levelid = levelid + self.character = character + + if self.datas.music ~= nil then + local filepath = "assets/music/" .. self.datas.music .. ".mp3" + print(filepath) + self.assets:setMusic(filepath) + self.assets:playMusic() + end + + self.pause = false + self.initiated = false + + self.world:loadWorld() + + self.characters:newPlayer("sonic", 2) + --self.characters:newDummy("sonic", 0) + --self.characters:newDummy("sonic", 1) + --self.characters:newDummy("sonic", 3) + --self.characters:newDummy("sonic", 4) +end + +function Controller:restartLevel() + self:new(self.levelid, self.character) +end + +function Controller:exitLevel() + Gamestate.switch(Scenes.options) +end + +function Controller:initManagers() + --self.loader = Loader() + self.world = World(self) + self.hud = HUD(self) + self.camera = Camera(self, 0, 0) + self.background = Background(self) + self.characters = CharacterManager(self) + self.pausemenu = PauseMenu(self) +end + +function Controller:destroy() + self.world:destroy() + + + self.world = nil + self.hud = nil + self.camera = nil + self.background = nil + self.characters = nil + self.pausemenu = nil + + self.assets:clear() +end + +function Controller:initMission(levelid) + self:getLevelData(levelid) + + self.mission = {} + self.mission.timer = 0 + self.mission.completed = false +end + +function Controller:startMission() + self.initiated = true +end + +function Controller:getLevelData(file) + local file = file or "testlevel.test1" + local level = require("datas.levels." .. file) + + self.zone = level.datas.zone + + local zone_data = zoneDatas[self.zone] + local bypass_data = level.datas.bypass_data + + self.datas = {} + + if (level.datas.bypass_zone) then + self.datas.name = bypass_data.name or zone_data.name + self.datas.borders = bypass_data.borders or zone_data.borders + self.datas.tiles = bypass_data.tiles or zone_data.tiles + self.datas.background = bypass_data.background or zone_data.background + self.datas.music = bypass_data.music or zone_data.music + else + self.datas = zone_data + end + + self.layout = level.layout + self.parts = level.parts + self.endless_parts = level.endless_parts + + self.missiondata = level.datas.missiondata + +end + +function Controller:update(dt) + + if self.pause == false then + self.assets:update(dt) + self.mission.timer = self.mission.timer + dt + self.world:update(dt) + self.camera:update(dt) + else + self.pausemenu:update(dt) + end + + if game.input.keys["start"].isPressed then + self.pause = (self.pause == false) + end + +end + +function Controller:draw() + + self.background:draw() + + self.camera.view:attach() + self.world:draw() + self.camera.view:detach() + + utils.draw.resetColor() + self.hud:draw() + + if self.pause == true then + self.pausemenu:draw(dt) + end + +end + +function Controller:resetFont() + self.assets.fonts["text"]:set() +end + +return Controller diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/loader.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/loader.lua new file mode 100644 index 0000000..c6e8cee --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/loader.lua @@ -0,0 +1,82 @@ +local Loader = Object:extend() + +function Loader:new(controller) + self.controller = controller + self.assetQueue = {} + self.pointer = 1 + self.finished = false +end + +function Loader:addElement(type, name, filepath, arg1, arg2, arg3, arg4) + local queueElement = {} + + queueElement.type = type + queueElement.name = name + queueElement.file = filepath + table.insert(self.assetQueue, queueElement) + + self.finished = false +end + +function Loader:update(dt) + +end + +function Loader:treatAssetQueueElement(id) + if self.assetQueue[id] ~= nil then + local asset = self.assetQueue[id] + if asset.type == "image" then + self.controller.assets:addImage(asset.name, asset.file) + elseif asset.type == "sprite" then + self.controller.assets:addSprite(asset.name, asset.file) + elseif asset.type == "sfx" then + self.controller.assets:addSFX(asset.name, asset.file) + end + end +end + +function Loader:treatAssetQueue() + for i = self.pointer, 10 do + if i <= #self.assetQueue then + self.finished = true + end + end + self.pointer = i +end + +local function LoadAssets(controller) + controller.assets:addImage("shadow", "assets/sprites/shadow.png") + controller.assets:addSprite("ring", "assets/sprites/items/ring") + controller.assets:addSprite("crystal", "assets/sprites/items/crystal") + + controller.assets:addFont("text", "assets/fonts/PixelOperator.ttf", 16.5) + controller.gui:addTextBox("solid", "assets/gui/dialogbox.png") + + controller.assets:addSFX("ring", "assets/sfx/gameplay/objects/ring.wav") + controller.assets:addSFX("booster", "assets/sfx/gameplay/objects/booster.wav") + controller.assets:addSFX("jumpboard", "assets/sfx/gameplay/objects/jumpboard.wav") + + controller.assets:addSFX("jump", "assets/sfx/gameplay/actions/jump-modern.wav") + controller.assets:addSFX("doublejump", "assets/sfx/gameplay/actions/doublejump.wav") + controller.assets:addSFX("spin", "assets/sfx/gameplay/actions/spin.wav") + + controller.assets:addSFX("grind", "assets/sfx/gameplay/grind/grind.wav") + controller.assets:addSFX("grindland", "assets/sfx/gameplay/grind/land.wav") + + controller.assets:addFont("menu", "assets/fonts/RussoOne-Regular.ttf", 15) + controller.assets.fonts["menu"]:setAlign("center") + controller.assets.fonts["menu"]:setFilter("border") + controller.assets.fonts["menu"]:setSpacing(true, 1) + + controller.assets:addFont("title", "assets/fonts/Teko-Bold.ttf", 22) + controller.assets.fonts["title"]:setAlign("center") + controller.assets.fonts["title"]:setFilter("doubleborder") + controller.assets.fonts["title"]:setSpacing(true, 2) + + controller.assets:addSFX("select", "assets/sfx/menu/select.wav") + controller.assets:addSFX("validate", "assets/sfx/menu/validate.wav") + controller.assets:addSFX("cancel", "assets/sfx/menu/cancel.wav") + +end + +return LoadAssets diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/pause.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/pause.lua new file mode 100644 index 0000000..5c47952 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/pause.lua @@ -0,0 +1,102 @@ +local PauseMenu = Object:extend() +local Menu = require "modules.menus" + +local ResumeWidget = Menu.Widget.Base:extend() +local RestartWidget = Menu.Widget.Base:extend() +local ExitWidget = Menu.Widget.Base:extend() + +function PauseMenu:new(controller) + self.controller = controller + self.active = false + + self.height = 56+16 + + self.assets = self.controller.assets + + self.menusystem = Menu.Controller() + self.menu = Menu.TextMenu(424/2, self.height+8, self.assets.fonts["menu"], 6) + + self.menusystem:addMenu(self.menu) + self.menu:centerText(180) + self.menu:setSound(self.assets.sfx["select"]) + self.menu.focus = true + + self.menu:addWidget(ResumeWidget(self)) + self.menu:addWidget(RestartWidget(self)) + self.menu:addWidget(ExitWidget(self)) + + self.canvas = nil + self.activeCanvas = false + self.width = 0 +end + +function PauseMenu:update(dt) + self.menusystem:update(dt) +end + +function PauseMenu:draw() + if (self.activeCanvas == false) then + local width = self.menu:getWidth() or 10 + self.width = self:drawCanvas(width, 64) + end + + love.graphics.draw(self.canvas, (424 - self.width)/2, self.height - 28) + self.menusystem:draw() +end + +function PauseMenu:drawCanvas(width, height) + local width = (math.floor( width / 16 ) + 1) * 16 + local height = height or 80 + self.canvas = love.graphics.newCanvas(width + 64, height + 64) + + CScreen:cease() + love.graphics.setCanvas( self.canvas ) + + self.controller.gui.textbox["solid"]:draw(32, 32, width, height) + self.controller.assets.fonts["title"]:draw("PAUSE", (width + 64)/2, 12, -1) + + love.graphics.setCanvas( ) + CScreen:cease() + + self.activeCanvas = true + + return width + 64 +end + +--- MENU WIDGETS + +function ResumeWidget:new(menusystem) + ResumeWidget.super.new(self) + self.label = "resume" + self.menusystem = menusystem + self.controller = menusystem.controller +end + +function ResumeWidget:action() + self.controller.pause = false +end + +function RestartWidget:new(menusystem) + RestartWidget.super.new(self) + self.label = "restart" + self.menusystem = menusystem + self.controller = menusystem.controller +end + +function RestartWidget:action() + self.controller:restartLevel() +end + +function ExitWidget:new(menusystem) + ExitWidget.super.new(self) + self.label = "exit" + self.menusystem = menusystem + self.controller = menusystem.controller +end + +function ExitWidget:action() + self.controller:exitLevel() +end + + +return PauseMenu diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/controller/world.lua b/sonic-boost.love/scenes/subgame/sonic-boost/controller/world.lua new file mode 100644 index 0000000..941ddc7 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/controller/world.lua @@ -0,0 +1,510 @@ +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 diff --git a/sonic-boost.love/scenes/subgame/sonic-boost/init.lua b/sonic-boost.love/scenes/subgame/sonic-boost/init.lua new file mode 100644 index 0000000..171fa39 --- /dev/null +++ b/sonic-boost.love/scenes/subgame/sonic-boost/init.lua @@ -0,0 +1,32 @@ +local Levels = {} +local Controller = require "scenes.levels.controller" + +function Levels:enter() + self.controller = Controller() + print("battle started") + --self.background = love.graphics.newImage("assets/background/testbackground.png") +end + +function Levels:update(dt) + game:update(dt) + game.input:update(dt) + self.controller:update(dt) +end + +function Levels:draw() + --CScreen:apply() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.rectangle("fill", 0, 0, 424, 240) + --love.graphics.draw(self.background, 0, 0) + self.controller:draw() + --CScreen:cease() +end + +function Levels:leave() + self.controller:destroy() + self.controller = nil + + collectgarbage() +end + +return Levels