49a8700400
Compared to the old Boost engine, core and game have been separated, so make sure that input are read in core
468 lines
11 KiB
Lua
468 lines
11 KiB
Lua
local folder = "scenes.subgame.sonic-boost.actors."
|
|
|
|
local Entity = require(folder .. "parent")
|
|
local phys = require "datas.subgame.sonic-boost.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.subgame.sonicboost: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 core.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
|