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.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