540 lines
13 KiB
Lua
540 lines
13 KiB
Lua
local folder = "scenes.subgame.sonic-boost.actors."
|
|
|
|
local Entity = require(folder .. "parent")
|
|
local phys = require "datas.gamedata.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.dashCam = 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.view = {}
|
|
self.view.x = self.x
|
|
self.view.y = self.y
|
|
self.view.dx = 0
|
|
|
|
self.relx = 0
|
|
self.relxspeed = 0
|
|
|
|
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 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
|
|
elseif self.data.stats.jumpaction == "jumpdash" then
|
|
if (self.keys["A"].isPressed) and (self.isJumping) and (self.canAerialAction) then
|
|
self.zspeed = 60 * 1
|
|
self.xspeed = math.max(self.xspeed, self.data.stats.spd * 60 * 1.2)
|
|
self.isInAerialAction = true
|
|
self.canAerialAction = false
|
|
self.dashCam = true
|
|
self:addTimer("dashCam", .5)
|
|
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 * 1.5)
|
|
self.isInAction = true
|
|
self.canAction = false
|
|
self.dashCam = true
|
|
self:addTimer("action", .5)
|
|
self:addTimer("action_cooldown", 1)
|
|
self:addTimer("dashCam", .5)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Moving functions --
|
|
|
|
function Character:move()
|
|
local cols, coll_number
|
|
|
|
self:setFilter()
|
|
|
|
self:updateRelativePosition()
|
|
|
|
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:updateRelativePosition()
|
|
local MAXVIEW = 80
|
|
local MINVIEW = -20
|
|
if not ((self.onGround == false) and (self.z < 0)) then
|
|
if (self.dashCam) or (self.dash) then
|
|
self.relx = math.min(self.relx + 0.4, MAXVIEW)
|
|
else
|
|
if self.keys["left"].isDown then
|
|
if self.relx > 0 then
|
|
self.relx = math.max(self.relx - 0.3, 0)
|
|
elseif self.relx <= 0 then
|
|
self.relx = math.max(self.relx - 0.1, MINVIEW)
|
|
end
|
|
elseif self.keys["right"].isDown then
|
|
if self.relx >= 0 then
|
|
if self.relx <= MAXVIEW*3/4 then
|
|
self.relx = math.min(self.relx + 0.2, MAXVIEW*3/4)
|
|
else
|
|
self.relx = math.max(self.relx - 0.1, MAXVIEW*3/4)
|
|
end
|
|
elseif self.relx < 0 then
|
|
self.relx = math.min(self.relx + 0.3, 0)
|
|
end
|
|
else
|
|
if self.relx > 0 then
|
|
self.relx = math.max(self.relx - 0.3, 0)
|
|
elseif self.relx < 0 then
|
|
self.relx = math.min(self.relx + 0.3, 0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
self.x = self.view.x + self.relx
|
|
end
|
|
|
|
function Character:getViewCenter()
|
|
local x, y, z
|
|
x = math.floor(self.view.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 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.view.x = self.view.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.view.x = self.startx
|
|
self.relx = 0
|
|
self.dash = false
|
|
self.dashCam = false
|
|
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 == "dashCam" then
|
|
self.dashCam = false
|
|
elseif name == "action_cooldown" then
|
|
self.canAction = true
|
|
end
|
|
end
|
|
|
|
return Character
|