From 84278ed41f61c586dbb38dd99c45ee33e2f58c77 Mon Sep 17 00:00:00 2001 From: Aki Date: Thu, 16 Mar 2017 19:05:50 +0100 Subject: Moved ? -> not.?; Renamed Player -> Hero --- cloud.lua | 63 -------- decoration.lua | 33 ---- effect.lua | 68 -------- main.lua | 2 +- not/Cloud.lua | 63 ++++++++ not/Decoration.lua | 34 ++++ not/Effect.lua | 68 ++++++++ not/Hero.lua | 449 +++++++++++++++++++++++++++++++++++++++++++++++++++++ not/Platform.lua | 73 +++++++++ not/Sprite.lua | 81 ++++++++++ not/World.lua | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++ platform.lua | 73 --------- player.lua | 449 ----------------------------------------------------- sprite.lua | 81 ---------- world.lua | 424 -------------------------------------------------- 15 files changed, 1193 insertions(+), 1192 deletions(-) delete mode 100644 cloud.lua delete mode 100644 decoration.lua delete mode 100644 effect.lua create mode 100644 not/Cloud.lua create mode 100644 not/Decoration.lua create mode 100644 not/Effect.lua create mode 100644 not/Hero.lua create mode 100644 not/Platform.lua create mode 100644 not/Sprite.lua create mode 100644 not/World.lua delete mode 100644 platform.lua delete mode 100644 player.lua delete mode 100644 sprite.lua delete mode 100644 world.lua diff --git a/cloud.lua b/cloud.lua deleted file mode 100644 index c12e236..0000000 --- a/cloud.lua +++ /dev/null @@ -1,63 +0,0 @@ --- `Cloud` --- That white thing moving in the background. - --- WHOLE CODE HAS FLAG OF "need a cleanup" - --- Metatable of `Cloud` --- nils initialized in constructor -Cloud = { - x = 0, -- position horizontal - y = 0, -- position vertical - t = 1, -- type (sprite number) - v = 13, -- velocity - sprite = nil, - quads = { - [1] = love.graphics.newQuad( 1, 1, 158,47, 478,49), - [2] = love.graphics.newQuad(160, 1, 158,47, 478,49), - [3] = love.graphics.newQuad(319, 1, 158,47, 478,49) - } -} - --- Constructor of `Cloud` -function Cloud:new(x, y, t, v) - -- Meta - local o = {} - setmetatable(o, self) - self.__index = self - -- Load spritesheet to metatable if not yet loaded - if self.sprite == nil then - self.sprite = love.graphics.newImage("assets/clouds.png") - end - -- Init - o.x = x or self.x - o.y = y or self.y - o.t = t or self.t - o.v = v or self.v - return o -end - --- Position -function Cloud:getPosition() - return self.x, self.y -end - --- Update of `Cloud`, returns x for world to delete cloud after reaching right corner -function Cloud:update(dt) - self.x = self.x + self.v*dt - return self.x -end - --- Draw `Cloud` -function Cloud:draw(offset_x, offset_y, scale) - -- locals - local offset_x = offset_x or 0 - local offset_y = offset_y or 0 - local scale = scale or 1 - local x, y = self:getPosition() - -- pixel grid - local draw_x = (math.floor(x) + offset_x) * scale - local draw_y = (math.floor(y) + offset_y) * scale - -- draw - love.graphics.setColor(255,255,255,255) - love.graphics.draw(self.sprite, self.quads[self.t], draw_x, draw_y, 0, scale, scale) -end \ No newline at end of file diff --git a/decoration.lua b/decoration.lua deleted file mode 100644 index ffb007d..0000000 --- a/decoration.lua +++ /dev/null @@ -1,33 +0,0 @@ -Decoration = { - world = nil, - sprite = nil, - x = 0, - y = 0 -} -Decoration.__index = Decoration -setmetatable(Decoration, Sprite) -function Decoration:new(x, y, sprite) - local o = {} - setmetatable(o, self) - o:setImage(love.graphics.newImage(sprite)) - o:setPosition(x,y) - return o -end -function Decoration:setPosition(x, y) - self.x, self.y = x, y -end -function Decoration:getPosition() - return self.x, self.y -end -function Decoration:draw(offset_x, offset_y, scale) - -- locals - local offset_x = offset_x or 0 - local offset_y = offset_y or 0 - local scale = scale or 1 - local x, y = self:getPosition() - -- pixel grid - local draw_x = (math.floor(x) + offset_x) * scale - local draw_y = (math.floor(y) + offset_y) * scale - -- draw - Sprite.draw(self, draw_x, draw_y, 0, scale, scale) -end \ No newline at end of file diff --git a/effect.lua b/effect.lua deleted file mode 100644 index 4e327a1..0000000 --- a/effect.lua +++ /dev/null @@ -1,68 +0,0 @@ --- `Effect` --- Short animation with graphics that plays in various situation. - --- Metatable of `Effect` --- nils initialized in constructor -Effect = { - x = 0, - y = 0, - delay = 0.06, - initial = nil, - frame = 1, - animation = nil, - sprite = nil, - quads = require "effects" -} - --- Construct of `Effect` -function Effect:new(name, x, y) - -- Meta - local o = {} - setmetatable(o, self) - self.__index = self - -- Load spritesheet to metatable if not yet loaded - if self.sprite == nil then - self.sprite = love.graphics.newImage("assets/effects.png") - end - -- Init - o.initial = o.delay - o.animation = name - o.x = x or self.x - o.y = y or self.y - return o -end - --- Position -function Effect:getPosition() - return self.x, self.y -end - --- Animation and return flag for deletion after completion --- returns true if completed and ready to delete -function Effect:update(dt) - self.delay = self.delay - dt - if self.delay < 0 then - if self.frame < self.quads[self.animation].frames then - self.frame = self.frame + 1 - self.delay = self.delay + self.initial - else - return true -- delete - end - end - return false -end - --- Draw me with scale and offsets, senpai -function Effect:draw(offset_x, offset_y, scale) - -- locals - local offset_x = offset_x or 0 - local offset_y = offset_y or 0 - local scale = scale or 1 - local x, y = self:getPosition() - -- pixel grid - local draw_x = (math.floor(x) + offset_x) * scale - local draw_y = (math.floor(y) + offset_y) * scale - -- draw - love.graphics.setColor(255,255,255,255) - love.graphics.draw(self.sprite, self.quads[self.animation][self.frame], draw_x, draw_y, 0, scale, scale) -end \ No newline at end of file diff --git a/main.lua b/main.lua index deff1a1..bbcf0e0 100644 --- a/main.lua +++ b/main.lua @@ -34,7 +34,7 @@ function newImage(path) end -- Require -require "world" +require "not.World" require "camera" require "menu" require "controller" diff --git a/not/Cloud.lua b/not/Cloud.lua new file mode 100644 index 0000000..c12e236 --- /dev/null +++ b/not/Cloud.lua @@ -0,0 +1,63 @@ +-- `Cloud` +-- That white thing moving in the background. + +-- WHOLE CODE HAS FLAG OF "need a cleanup" + +-- Metatable of `Cloud` +-- nils initialized in constructor +Cloud = { + x = 0, -- position horizontal + y = 0, -- position vertical + t = 1, -- type (sprite number) + v = 13, -- velocity + sprite = nil, + quads = { + [1] = love.graphics.newQuad( 1, 1, 158,47, 478,49), + [2] = love.graphics.newQuad(160, 1, 158,47, 478,49), + [3] = love.graphics.newQuad(319, 1, 158,47, 478,49) + } +} + +-- Constructor of `Cloud` +function Cloud:new(x, y, t, v) + -- Meta + local o = {} + setmetatable(o, self) + self.__index = self + -- Load spritesheet to metatable if not yet loaded + if self.sprite == nil then + self.sprite = love.graphics.newImage("assets/clouds.png") + end + -- Init + o.x = x or self.x + o.y = y or self.y + o.t = t or self.t + o.v = v or self.v + return o +end + +-- Position +function Cloud:getPosition() + return self.x, self.y +end + +-- Update of `Cloud`, returns x for world to delete cloud after reaching right corner +function Cloud:update(dt) + self.x = self.x + self.v*dt + return self.x +end + +-- Draw `Cloud` +function Cloud:draw(offset_x, offset_y, scale) + -- locals + local offset_x = offset_x or 0 + local offset_y = offset_y or 0 + local scale = scale or 1 + local x, y = self:getPosition() + -- pixel grid + local draw_x = (math.floor(x) + offset_x) * scale + local draw_y = (math.floor(y) + offset_y) * scale + -- draw + love.graphics.setColor(255,255,255,255) + love.graphics.draw(self.sprite, self.quads[self.t], draw_x, draw_y, 0, scale, scale) +end \ No newline at end of file diff --git a/not/Decoration.lua b/not/Decoration.lua new file mode 100644 index 0000000..7f020e1 --- /dev/null +++ b/not/Decoration.lua @@ -0,0 +1,34 @@ +require "not.Sprite" +Decoration = { + world = nil, + sprite = nil, + x = 0, + y = 0 +} +Decoration.__index = Decoration +setmetatable(Decoration, Sprite) +function Decoration:new(x, y, sprite) + local o = {} + setmetatable(o, self) + o:setImage(love.graphics.newImage(sprite)) + o:setPosition(x,y) + return o +end +function Decoration:setPosition(x, y) + self.x, self.y = x, y +end +function Decoration:getPosition() + return self.x, self.y +end +function Decoration:draw(offset_x, offset_y, scale) + -- locals + local offset_x = offset_x or 0 + local offset_y = offset_y or 0 + local scale = scale or 1 + local x, y = self:getPosition() + -- pixel grid + local draw_x = (math.floor(x) + offset_x) * scale + local draw_y = (math.floor(y) + offset_y) * scale + -- draw + Sprite.draw(self, draw_x, draw_y, 0, scale, scale) +end \ No newline at end of file diff --git a/not/Effect.lua b/not/Effect.lua new file mode 100644 index 0000000..4e327a1 --- /dev/null +++ b/not/Effect.lua @@ -0,0 +1,68 @@ +-- `Effect` +-- Short animation with graphics that plays in various situation. + +-- Metatable of `Effect` +-- nils initialized in constructor +Effect = { + x = 0, + y = 0, + delay = 0.06, + initial = nil, + frame = 1, + animation = nil, + sprite = nil, + quads = require "effects" +} + +-- Construct of `Effect` +function Effect:new(name, x, y) + -- Meta + local o = {} + setmetatable(o, self) + self.__index = self + -- Load spritesheet to metatable if not yet loaded + if self.sprite == nil then + self.sprite = love.graphics.newImage("assets/effects.png") + end + -- Init + o.initial = o.delay + o.animation = name + o.x = x or self.x + o.y = y or self.y + return o +end + +-- Position +function Effect:getPosition() + return self.x, self.y +end + +-- Animation and return flag for deletion after completion +-- returns true if completed and ready to delete +function Effect:update(dt) + self.delay = self.delay - dt + if self.delay < 0 then + if self.frame < self.quads[self.animation].frames then + self.frame = self.frame + 1 + self.delay = self.delay + self.initial + else + return true -- delete + end + end + return false +end + +-- Draw me with scale and offsets, senpai +function Effect:draw(offset_x, offset_y, scale) + -- locals + local offset_x = offset_x or 0 + local offset_y = offset_y or 0 + local scale = scale or 1 + local x, y = self:getPosition() + -- pixel grid + local draw_x = (math.floor(x) + offset_x) * scale + local draw_y = (math.floor(y) + offset_y) * scale + -- draw + love.graphics.setColor(255,255,255,255) + love.graphics.draw(self.sprite, self.quads[self.animation][self.frame], draw_x, draw_y, 0, scale, scale) +end \ No newline at end of file diff --git a/not/Hero.lua b/not/Hero.lua new file mode 100644 index 0000000..7ddc724 --- /dev/null +++ b/not/Hero.lua @@ -0,0 +1,449 @@ +-- `Hero` +-- Entity controlled by a player. It has a physical body and a sprite. Can play animations and interact with other instances of the same class. +-- Collision category: [2] + +-- WHOLE CODE HAS FLAG OF "need a cleanup" +require "not.Sprite" + +-- Metatable of `Hero` +-- nils initialized in constructor +Hero = { + -- General and physics + name = "empty", + body = nil, + shape = nil, + fixture = nil, + sprite = nil, + rotate = 0, -- "angle" would sound better + facing = 1, + max_velocity = 105, + world = nil, -- game world + -- Combat + combo = 0, + lives = 3, + spawntimer = 2, + alive = true, + punchcd = 0.25, + punchdir = 0, -- a really bad thing + -- Movement + inAir = true, + salto = false, + jumpactive = false, + jumpdouble = true, + jumptimer = 0.16, + jumpnumber = 2, + -- Keys + controlset = nil, + -- HUD + portrait_sprite = nil, + portrait_frame = nil, + portrait_sheet = require "nautsicons", + portrait_box = love.graphics.newQuad( 0, 15, 32,32, 80,130), + -- Sounds + sfx = require "sounds", + -- Animations table + animations = require "animations" +} +Hero.__index = Hero +setmetatable(Hero, Sprite) + +-- Constructor of `Hero` +function Hero:new (game, world, x, y, name) + -- Meta + local o = {} + setmetatable(o, self) + -- Physics + local group = -1-#game.Nauts + o.body = love.physics.newBody(world, x, y, "dynamic") + o.shape = love.physics.newRectangleShape(10, 16) + o.fixture = love.physics.newFixture(o.body, o.shape, 8) + o.fixture:setUserData(o) + o.fixture:setCategory(2) + o.fixture:setMask(2) + o.fixture:setGroupIndex(group) + o.body:setFixedRotation(true) + -- Misc + o.name = name or "empty" + o:setImage(newImage("assets/nauts/"..o.name..".png")) + o.world = game + o.punchcd = 0 + -- Animation + o.current = o.animations.default + o:createEffect("respawn") + -- Portrait load for first object created + if self.portrait_sprite == nil then + self.portrait_sprite = love.graphics.newImage("assets/portraits.png") + self.portrait_frame = love.graphics.newImage("assets/menu.png") + end + return o +end + +-- Control set managment +function Hero:assignControlSet(set) + self.controlset = set +end +function Hero:getControlSet() + return self.controlset +end + +-- Update callback of `Hero` +function Hero:update(dt) + -- hotfix? for destroyed bodies + if self.body:isDestroyed() then return end + -- locals + local x, y = self.body:getLinearVelocity() + local isDown = Controller.isDown + local controlset = self:getControlSet() + + -- # VERTICAL MOVEMENT + -- Jumping + if self.jumpactive and self.jumptimer > 0 then + self.body:setLinearVelocity(x,-160) + self.jumptimer = self.jumptimer - dt + end + + -- Salto + if self.salto and (self.current == self.animations.walk or self.current == self.animations.default) then + self.rotate = (self.rotate + 17 * dt * self.facing) % 360 + elseif self.rotate ~= 0 then + self.rotate = 0 + end + + -- # HORIZONTAL MOVEMENT + -- Walking + if isDown(controlset, "left") then + self.facing = -1 + self.body:applyForce(-250, 0) + -- Controlled speed limit + if x < -self.max_velocity then + self.body:applyForce(250, 0) + end + end + if isDown(controlset, "right") then + self.facing = 1 + self.body:applyForce(250, 0) + -- Controlled speed limit + if x > self.max_velocity then + self.body:applyForce(-250, 0) + end + end + + -- Custom linear damping + if not isDown(controlset, "left") and + not isDown(controlset, "right") + then + local face = nil + if x < -12 then + face = 1 + elseif x > 12 then + face = -1 + else + face = 0 + end + self.body:applyForce(40*face,0) + if not self.inAir then + self.body:applyForce(80*face,0) + end + end + + Sprite.update(self, dt) + + -- # DEATH + -- We all die in the end. + local m = self.world.map + if (self.body:getX() < m.center_x - m.width*1.5 or self.body:getX() > m.center_x + m.width*1.5 or + self.body:getY() < m.center_y - m.height*1.5 or self.body:getY() > m.center_y + m.height*1.5) and + self.alive + then + self:die() + end + + -- respawn + if self.spawntimer > 0 then + self.spawntimer = self.spawntimer - dt + end + if self.spawntimer <= 0 and not self.alive and self.lives >= 0 then + self:respawn() + end + + -- # PUNCH + -- Cooldown + self.punchcd = self.punchcd - dt + if not self.body:isDestroyed() then -- This is weird + for _,fixture in pairs(self.body:getFixtureList()) do + if fixture:getUserData() ~= self then + fixture:setUserData({fixture:getUserData()[1] - dt, fixture:getUserData()[2]}) + if fixture:getUserData()[1] < 0 then + fixture:destroy() + end + end + end + end + + -- Stop vertical + local c,a = self.current, self.animations + if (c == a.attack_up or c == a.attack_down or c == a.attack) and self.frame < c.frames then + if self.punchdir == 0 then + self.body:setLinearVelocity(0,0) + else + self.body:setLinearVelocity(38*self.facing,0) + end + end + + if self.punchcd <= 0 and self.punchdir == 1 then + self.punchdir = 0 + end +end + +-- Controller callbacks +function Hero:controlpressed(set, action, key) + if set ~= self:getControlSet() then return end + local isDown = Controller.isDown + local controlset = self:getControlSet() + -- Jumping + if action == "jump" then + if self.jumpnumber > 0 then + -- General jump logics + self.jumpactive = true + --self:playSound(6) + -- Spawn proper effect + if not self.inAir then + self:createEffect("jump") + else + self:createEffect("doublejump") + end + -- Start salto if last jump + if self.jumpnumber == 1 then + self.salto = true + end + -- Animation clear + if (self.current == self.animations.attack) or + (self.current == self.animations.attack_up) or + (self.current == self.animations.attack_down) then + self:setAnimation("default") + end + -- Remove jump + self.jumpnumber = self.jumpnumber - 1 + end + end + + -- Walking + if (action == "left" or action == "right") and + (self.current ~= self.animations.attack) and + (self.current ~= self.animations.attack_up) and + (self.current ~= self.animations.attack_down) then + self:setAnimation("walk") + end + + -- Punching + if action == "attack" and self.punchcd <= 0 then + local f = self.facing + self.salto = false + if isDown(controlset, "up") then + -- Punch up + if self.current ~= self.animations.damage then + self:setAnimation("attack_up") + end + self:hit("up") + elseif isDown(controlset, "down") then + -- Punch down + if self.current ~= self.animations.damage then + self:setAnimation("attack_down") + end + self:hit("down") + else + -- Punch horizontal + if self.current ~= self.animations.damage then + self:setAnimation("attack") + end + if f == 1 then + self:hit("right") + else + self:hit("left") + end + self.punchdir = 1 + end + end +end +function Hero:controlreleased(set, action, key) + if set ~= self:getControlSet() then return end + local isDown = Controller.isDown + local controlset = self:getControlSet() + -- Jumping + if action == "jump" then + self.jumpactive = false + self.jumptimer = Hero.jumptimer -- take initial from metatable + end + -- Walking + if (action == "left" or action == "right") and not + (isDown(controlset, "left") or isDown(controlset, "right")) and + self.current == self.animations.walk + then + self:setAnimation("default") + end +end + +-- Draw of `Hero` +function Hero:draw(offset_x, offset_y, scale, debug) + -- draw only alive + if not self.alive then return end + -- locals + local offset_x = offset_x or 0 + local offset_y = offset_y or 0 + local scale = scale or 1 + local debug = debug or false + local x, y = self:getPosition() + -- pixel grid ; `approx` selected to prevent floating characters on certain conditions + local approx = math.floor + if (y - math.floor(y)) > 0.5 then approx = math.ceil end + local draw_y = (approx(y) + offset_y) * scale + local draw_x = (math.floor(x) + offset_x) * scale + -- sprite draw + Sprite.draw(self, draw_x, draw_y, self.rotate, self.facing*scale, scale, 12, 15) + -- debug draw + if debug then + for _,fixture in pairs(self.body:getFixtureList()) do + if fixture:getCategory() == 2 then + love.graphics.setColor(137, 255, 0, 120) + else + love.graphics.setColor(137, 0, 255, 40) + end + love.graphics.polygon("fill", self.world.camera:translatePoints(self.body:getWorldPoints(fixture:getShape():getPoints()))) + end + for _,contact in pairs(self.body:getContactList()) do + love.graphics.setColor(255, 0, 0, 255) + love.graphics.setPointSize(scale) + love.graphics.points(self.world.camera:translatePoints(contact:getPositions())) + end + end +end + +-- getPosition +function Hero:getPosition() + return self.body:getPosition() +end + +-- Draw HUD of `Hero` +-- elevation: 1 bottom, 0 top +function Hero:drawHUD(x,y,scale,elevation) + -- hud displays only if player is alive + if self.alive then + love.graphics.setColor(255,255,255,255) + love.graphics.draw(self.portrait_frame, self.portrait_box, (x)*scale, (y)*scale, 0, scale, scale) + love.graphics.draw(self.portrait_sprite, self.portrait_sheet[self.name], (x+2)*scale, (y+3)*scale, 0, scale, scale) + local dy = 30 * elevation + love.graphics.setFont(Font) + love.graphics.print((self.combo*10).."%",(x+2)*scale,(y-3+dy)*scale,0,scale,scale) + love.graphics.print(math.max(0, self.lives),(x+24)*scale,(y-3+dy)*scale,0,scale,scale) + end +end + +-- Change animation of `Hero` +-- default, walk, attack, attack_up, attack_down, damage +function Hero:nextFrame() + local isDown = Controller.isDown + local controlset = self:getControlSet() + if self.current.repeated or not (self.frame == self.current.frames) then + self.frame = (self.frame % self.current.frames) + 1 + elseif isDown(controlset, "right") or isDown(controlset, "left") then + -- If nonrepeatable animation is finished and player is walking + self:setAnimation("walk") + elseif self.current == self.animations.damage then + self:setAnimation("default") + end +end + +-- Spawn `Effect` relative to `Hero` +function Hero:createEffect(name) + if name == "trail" or name == "hit" then + -- 16px effect: -7 -7 + self.world:createEffect(name, self.body:getX()-8, self.body:getY()-8) + elseif name ~= nil then + -- 24px effect: -12 -15 + self.world:createEffect(name, self.body:getX()-12, self.body:getY()-15) + end +end + +-- Punch of `Hero` +-- direction: left, right, up, down +-- creates temporary fixture for player's body that acts as sensor; fixture is deleted after time set in UserData[1]; deleted by Hero:update(dt) +function Hero:hit(direction) + -- start cooldown + self.punchcd = Hero.punchcd -- INITIAL from metatable + -- actual punch + local fixture + if direction == "left" then + fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(-2,-6, -20,-6, -20,6, -2,6), 0) + end + if direction == "right" then + fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(2,-6, 20,-6, 20,6, 2,6), 0) + end + if direction == "up" then + fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(-8,-4, -8,-20, 8,-20, 8,-4), 0) + end + if direction == "down" then + fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(-8,4, -8,20, 8,20, 8,4), 0) + end + fixture:setSensor(true) + fixture:setCategory(3) + fixture:setMask(1,3) + fixture:setGroupIndex(self.fixture:getGroupIndex()) + fixture:setUserData({0.08, direction}) + -- sound + self:playSound(4) +end + +-- Taking damage of `Hero` by successful hit test +-- currently called from World's startContact +function Hero:damage(direction) + local horizontal, vertical = 0, 0 + if direction == "left" then + horizontal = -1 + end + if direction == "right" then + horizontal = 1 + end + if direction == "up" then + vertical = -1 + end + if direction == "down" then + vertical = 1 + end + self:createEffect("hit") + local x,y = self.body:getLinearVelocity() + self.body:setLinearVelocity(x,0) + self.body:applyLinearImpulse((42+10*self.combo)*horizontal, (68+10*self.combo)*vertical + 15) + self:setAnimation("damage") + self.combo = math.min(27, self.combo + 1) + self.punchcd = 0.08 + self.combo*0.006 + self:playSound(2) +end + +-- DIE +function Hero:die() + self:playSound(1) + self.combo = Hero.combo -- INITIAL from metatable + self.lives = self.lives - 1 + self.alive = false + self.spawntimer = Hero.spawntimer -- INITIAL from metatable + self.body:setActive(false) + self.world:onNautKilled(self) +end + +-- And then respawn. Like Jon Snow. +function Hero:respawn() + self.alive = true + self.body:setLinearVelocity(0,0) + self.body:setPosition(self.world:getSpawnPosition()) + self.body:setActive(true) + self:createEffect("respawn") + self:playSound(7) +end + +-- Sounds +function Hero:playSound(sfx, force) + if self.alive or force then + local source = love.audio.newSource(self.sfx[sfx]) + source:play() + end +end diff --git a/not/Platform.lua b/not/Platform.lua new file mode 100644 index 0000000..9b7b03c --- /dev/null +++ b/not/Platform.lua @@ -0,0 +1,73 @@ +-- `Platform` +-- Static platform physical object with a sprite. `Players` can walk on it. +-- Collision category: [1] + +-- WHOLE CODE HAS FLAG OF "need a cleanup" +require "not.Sprite" + +-- Metatable of `Platform` +-- nils initialized in constructor +Platform = { + body = nil, + shape = nil, + fixture = nil, + world = nil, +} +Platform.__index = Platform +setmetatable(Platform, Sprite) + +-- Constructor of `Platform` +function Platform:new (game, world, x, y, shape, sprite, animations) + local o = {} + setmetatable(o, self) + o.body = love.physics.newBody(world, x, y) + -- MULTIPLE SHAPES NEED TO BE REWRITED! + o.shape = {} + if type(shape[1]) == "number" then + local poly = love.physics.newPolygonShape(shape) + table.insert(o.shape, poly) + o.fixture = love.physics.newFixture(o.body, poly) + o.fixture:setCategory(1) + o.fixture:setFriction(0.2) + else + for i,v in pairs(shape) do + local poly = love.physics.newPolygonShape(v) + table.insert(o.shape, poly) + local fixture = love.physics.newFixture(o.body, poly) + fixture:setCategory(1) + fixture:setFriction(0.2) + end + end + -- END HERE + o:setImage(love.graphics.newImage(sprite)) + o:setAnimationsList(animations) + o.world = game + return o +end + +-- Position +function Platform:getPosition() + return self.body:getPosition() +end + +-- Draw of `Platform` +function Platform:draw (offset_x, offset_y, scale, debug) + -- locals + local offset_x = offset_x or 0 + local offset_y = offset_y or 0 + local scale = scale or 1 + local debug = debug or false + local x, y = self:getPosition() + -- pixel grid + local draw_x = (math.floor(x) + offset_x) * scale + local draw_y = (math.floor(y) + offset_y) * scale + -- sprite draw + Sprite.draw(self, draw_x, draw_y, 0, scale, scale) + -- debug draw + if debug then + love.graphics.setColor(255, 69, 0, 140) + for i,v in pairs(self.shape) do + love.graphics.polygon("fill", self.world.camera:translatePoints(self.body:getWorldPoints(v:getPoints()))) + end + end +end \ No newline at end of file diff --git a/not/Sprite.lua b/not/Sprite.lua new file mode 100644 index 0000000..1cc46f7 --- /dev/null +++ b/not/Sprite.lua @@ -0,0 +1,81 @@ +-- `Sprite` +-- Abstract class for drawable animated entities. + +-- Metatable +Sprite = { + animations--[[table with animations]], + current--[[animations.default]], + image--[[love.graphics.newImage()]], + frame = 1, + delay = .1, +} +Sprite.__index = Sprite + +-- Cleans up reference to image on deletion. +function Sprite:delete() + self.image = nil +end + +-- Sets an Image as a image. +function Sprite:setImage(image) + self.image = image +end +-- Returns current image Image. +function Sprite:getImage() + return self.image +end + +-- Sets new animations list. +function Sprite:setAnimationsList(t) + if t then + self.animations = t + self:setAnimation("default") + end +end + +-- Sets current animation by table key. +function Sprite:setAnimation(animation) + self.frame = 1 + self.delay = Sprite.delay -- INITIAL from metatable + self.current = self.animations[animation] +end +-- Returns current animation table. +function Sprite:getAnimation() + return self.current +end + +-- Get frame quad for drawing. +function Sprite:getQuad() + if self.animations and self.current then + return self.current[self.frame] + end +end + +-- Drawing self to LOVE2D buffer. +-- If there is no Quad, it will draw entire image. +function Sprite:draw(...) + local s, q = self:getImage(), self:getQuad() + if s then + love.graphics.setColor(255,255,255,255) + if q then love.graphics.draw(s, q, ...) + else love.graphics.draw(s, ...) end + end +end +-- Animation updating. +function Sprite:update(dt) + if self.animations and self.current then + self.delay = self.delay - dt + if self.delay < 0 then + self.delay = self.delay + Sprite.delay -- INITIAL from metatable + self:nextFrame() + end + end +end +-- Moving to the next frame. +function Sprite:nextFrame() + if self.current.repeated or not (self.frame == self.current.frames) then + self.frame = (self.frame % self.current.frames) + 1 + else + self:setAnimation("default") + end +end \ No newline at end of file diff --git a/not/World.lua b/not/World.lua new file mode 100644 index 0000000..5afc5bf --- /dev/null +++ b/not/World.lua @@ -0,0 +1,424 @@ +-- `World` +-- Used to manage physical world and everything inside it: clouds, platforms, nauts, background etc. + +-- WHOLE CODE HAS FLAG OF "need a cleanup" + +require "not.Platform" +require "not.Hero" +require "not.Cloud" +require "not.Effect" +require "not.Decoration" +require "ray" + +-- Metatable of `World` +-- nils initialized in constructor +World = { + -- inside + world = nil, + Nauts = nil, + Platforms = nil, + Clouds = nil, + Decorations = nil, + Effects = nil, + Rays = nil, + camera = nil, + -- cloud generator + clouds_delay = 5, + -- Map + map = nil, + background = nil, + -- Gameplay status + lastNaut = false, + -- "WINNER" + win_move = 0, + -- Music + music = nil +} + +-- Constructor of `World` ZA WARUDO! +function World:new(map, nauts) + -- Meta + local o = {} + setmetatable(o, self) + self.__index = self + -- Physical world initialization + love.physics.setMeter(64) + o.world = love.physics.newWorld(0, 9.81*64, true) + o.world:setCallbacks(o.beginContact, o.endContact) + -- Empty tables for objects + local n = {} + o.Nauts = n + local p = {} + o.Platforms = {} + local c = {} + o.Clouds = c + local e = {} + o.Effects = e + local d = {} + o.Decorations = d + local r = {} + o.Rays = r + -- Random init + math.randomseed(os.time()) + -- Map + local map = map or "default" + o:loadMap(map) + -- Nauts + o:spawnNauts(nauts) + -- Create camera + o.camera = Camera:new(o) + -- Play music + o.music = Music:new(o.map.theme) + return o +end + +-- The end of the world +function World:delete() + self.world:destroy() + for _,platform in pairs(self.Platforms) do + platform:delete() + end + for _,naut in pairs(self.Nauts) do + naut:delete() + end + self.music:delete() + self = nil +end + +-- Load map from file +function World:loadMap(name) + local name = name or "default" + name = "maps/" .. name .. ".lua" + local map = love.filesystem.load(name) + self.map = map() + -- Platforms + for _,platform in pairs(self.map.platforms) do + self:createPlatform(platform.x, platform.y, platform.shape, platform.sprite, platform.animations) + end + -- Decorations + for _,decoration in pairs(self.map.decorations) do + self:createDecoration(decoration.x, decoration.y, decoration.sprite) + end + -- Background + self.background = love.graphics.newImage(self.map.background) + -- Clouds + if self.map.clouds then + for i=1,6 do + self:randomizeCloud(false) + end + end +end + +-- Spawn all the nauts for the round +function World:spawnNauts(nauts) + for _,naut in pairs(nauts) do + local x,y = self:getSpawnPosition() + local spawn = self:createNaut(x, y, naut[1]) + spawn:assignControlSet(naut[2]) + end +end + +-- Get respawn location +function World:getSpawnPosition() + local n = math.random(1, #self.map.respawns) + return self.map.respawns[n].x, self.map.respawns[n].y +end + +-- Add new platform to the world +function World:createPlatform(x, y, polygon, sprite, animations) + table.insert(self.Platforms, Platform:new(self, self.world, x, y, polygon, sprite, animations)) +end + +-- Add new naut to the world +function World:createNaut(x, y, name) + local naut = Hero:new(self, self.world, x, y, name) + table.insert(self.Nauts, naut) + return naut +end + +-- Add new decoration to the world +function World:createDecoration(x, y, sprite) + table.insert(self.Decorations, Decoration:new(x, y, sprite)) +end + +-- Add new cloud to the world +function World:createCloud(x, y, t, v) + table.insert(self.Clouds, Cloud:new(x, y, t, v)) +end + +-- Randomize Cloud creation +function World:randomizeCloud(outside) + if outside == nil then + outside = true + else + outside = outside + end + local x,y,t,v + local m = self.map + if outside then + x = m.center_x-m.width*1.2+math.random(-50,20) + else + x = math.random(m.center_x-m.width/2,m.center_x+m.width/2) + end + y = math.random(m.center_y-m.height/2, m.center_y+m.height/2) + t = math.random(1,3) + v = math.random(8,18) + self:createCloud(x, y, t, v) +end + +-- Add an effect behind nauts +function World:createEffect(name, x, y) + table.insert(self.Effects, Effect:new(name, x, y)) +end + +-- Add a ray +function World:createRay(naut) + table.insert(self.Rays, Ray:new(naut, self)) +end + +-- get Nauts functions +-- more than -1 lives +function World:getNautsPlayable() + local nauts = {} + for _,naut in pairs(self.Nauts) do + if naut.lives > -1 then + table.insert(nauts, naut) + end + end + return nauts +end +-- are alive +function World:getNautsAlive() + local nauts = {} + for _,naut in self.Nauts do + if naut.alive then + table.insert(nauts, naut) + end + end + return nauts +end +-- all of them +function World:getNautsAll() + return self.Nauts +end + +-- get Map name +function World:getMapName() + return self.map.name +end + +-- Event: when player is killed +function World:onNautKilled(naut) + self.camera:startShake() + self:createRay(naut) + local nauts = self:getNautsPlayable() + if self.lastNaut then + changeScene(Menu:new()) + elseif #nauts < 2 then + self.lastNaut = true + naut:playSound(5, true) + end +end + +function World:getBounce(f) + local f = f or 1 + return math.sin(self.win_move*f*math.pi) +end + +-- LÖVE2D callbacks +-- Update ZU WARUDO +function World:update(dt) + -- Physical world + self.world:update(dt) + -- Camera + self.camera:update(dt) + -- Engine world: Nauts, Grounds (kek) and Decorations - all Animateds (top kek) + for _,naut in pairs(self.Nauts) do + naut:update(dt) + end + for _,platform in pairs(self.Platforms) do + platform:update(dt) + end + for _,decoration in pairs(self.Decorations) do + decoration:update(dt) + end + -- Clouds + if self.map.clouds then + -- generator + local n = table.getn(self.Clouds) + self.clouds_delay = self.clouds_delay - dt + if self.clouds_delay < 0 and + n < 18 + then + self:randomizeCloud() + self.clouds_delay = self.clouds_delay + World.clouds_delay -- World.clouds_delay is initial + end + -- movement + for _,cloud in pairs(self.Clouds) do + if cloud:update(dt) > 340 then + table.remove(self.Clouds, _) + end + end + end + -- Effects + for _,effect in pairs(self.Effects) do + if effect:update(dt) then + table.remove(self.Effects, _) + end + end + -- Rays + for _,ray in pairs(self.Rays) do + if ray:update(dt) then + table.remove(self.Rays, _) + end + end + -- Bounce `winner` + self.win_move = self.win_move + dt + if self.win_move > 2 then + self.win_move = self.win_move - 2 + end +end +-- Draw +function World:draw() + -- Camera stuff + local offset_x, offset_y = self.camera:getOffsets() + local scale = self.camera.scale + local scaler = self.camera.scaler + + -- Background + love.graphics.draw(self.background, 0, 0, 0, scaler, scaler) + + -- This needs to be reworked! + -- Draw clouds + for _,cloud in pairs(self.Clouds) do + cloud:draw(offset_x, offset_y, scale) + end + + -- Draw decorations + for _,decoration in pairs(self.Decorations) do + decoration:draw(offset_x, offset_y, scale) + end + + -- Draw effects + for _,effect in pairs(self.Effects) do + effect:draw(offset_x,offset_y, scale) + end + + -- Draw player + for _,naut in pairs(self.Nauts) do + naut:draw(offset_x, offset_y, scale, debug) + end + + -- Draw ground + for _,platform in pairs(self.Platforms) do + platform:draw(offset_x, offset_y, scale, debug) + end + + -- Draw rays + for _,ray in pairs(self.Rays) do + ray:draw(offset_x, offset_y, scale) + end + + -- draw center + if debug then + local c = self.camera + local w, h = love.graphics.getWidth(), love.graphics.getHeight() + -- draw map center + love.graphics.setColor(130,130,130) + love.graphics.setLineWidth(1) + love.graphics.setLineStyle("rough") + local cx, cy = c:getPositionScaled() + local x1, y1 = c:translatePosition(self.map.center_x, cy) + local x2, y2 = c:translatePosition(self.map.center_x, cy+h) + love.graphics.line(x1,y1,x2,y2) + local x1, y1 = c:translatePosition(cx, self.map.center_y) + local x2, y2 = c:translatePosition(cx+w, self.map.center_y) + love.graphics.line(x1,y1,x2,y2) + -- draw ox, oy + love.graphics.setColor(200,200,200) + love.graphics.setLineStyle("rough") + local cx, cy = c:getPositionScaled() + local x1, y1 = c:translatePosition(0, cy) + local x2, y2 = c:translatePosition(0, cy+h) + love.graphics.line(x1,y1,x2,y2) + local x1, y1 = c:translatePosition(cx, 0) + local x2, y2 = c:translatePosition(cx+w, 0) + love.graphics.line(x1,y1,x2,y2) + end + + -- Draw HUDs + for _,naut in pairs(self.Nauts) do + -- I have no idea where to place them T_T + -- let's do: bottom-left, bottom-right, top-left, top-right + local w, h = love.graphics.getWidth()/scale, love.graphics.getHeight()/scale + local y, e = 1, 1 + if _ < 3 then y, e = h-33, 0 end + naut:drawHUD(1+(_%2)*(w-34), y, scale, e) + end + + -- Draw winner + if self.lastNaut then + local w, h = love.graphics.getWidth()/scale, love.graphics.getHeight()/scale + local angle = self:getBounce(2) + local dy = self:getBounce()*3 + love.graphics.setFont(Bold) + love.graphics.printf("WINNER",(w/2)*scale,(42+dy)*scale,336,"center",(angle*5)*math.pi/180,scale,scale,168,12) + love.graphics.setFont(Font) + love.graphics.printf("rofl, now kill yourself", w/2*scale, 18*scale, 160, "center", 0, scale, scale, 80, 3) + end +end + +-- Box2D callbacks +-- beginContact +function World.beginContact(a, b, coll) + if a:getCategory() == 1 then + local x,y = coll:getNormal() + if y < -0.6 then + print(b:getUserData().name .. " is not in air") + -- Move them to Hero + b:getUserData().inAir = false + b:getUserData().jumpnumber = 2 + b:getUserData().salto = false + b:getUserData():createEffect("land") + end + local vx, vy = b:getUserData().body:getLinearVelocity() + if math.abs(x) == 1 or (y < -0.6 and x == 0) then + b:getUserData():playSound(3) + end + end + if a:getCategory() == 3 then + b:getUserData():damage(a:getUserData()[2]) + end + if b:getCategory() == 3 then + a:getUserData():damage(b:getUserData()[2]) + end +end +-- endContact +function World.endContact(a, b, coll) + if a:getCategory() == 1 then + print(b:getUserData().name .. " is in air") + -- Move them to Hero + b:getUserData().inAir = true + end +end + +-- Controller callbacks +function World:controlpressed(set, action, key) + if key == "f6" and debug then + local map = self:getMapName() + local nauts = {} + for _,naut in pairs(self:getNautsAll()) do + table.insert(nauts, {naut.name, naut:getControlSet()}) + end + local new = World:new(map, nauts) + changeScene(new) + end + for k,naut in pairs(self:getNautsAll()) do + naut:controlpressed(set, action, key) + end +end +function World:controlreleased(set, action, key) + for k,naut in pairs(self:getNautsAll()) do + naut:controlreleased(set, action, key) + end +end \ No newline at end of file diff --git a/platform.lua b/platform.lua deleted file mode 100644 index 9d75e66..0000000 --- a/platform.lua +++ /dev/null @@ -1,73 +0,0 @@ --- `Platform` --- Static platform physical object with a sprite. `Players` can walk on it. --- Collision category: [1] - --- WHOLE CODE HAS FLAG OF "need a cleanup" -require "sprite" - --- Metatable of `Platform` --- nils initialized in constructor -Platform = { - body = nil, - shape = nil, - fixture = nil, - world = nil, -} -Platform.__index = Platform -setmetatable(Platform, Sprite) - --- Constructor of `Platform` -function Platform:new (game, world, x, y, shape, sprite, animations) - local o = {} - setmetatable(o, self) - o.body = love.physics.newBody(world, x, y) - -- MULTIPLE SHAPES NEED TO BE REWRITED! - o.shape = {} - if type(shape[1]) == "number" then - local poly = love.physics.newPolygonShape(shape) - table.insert(o.shape, poly) - o.fixture = love.physics.newFixture(o.body, poly) - o.fixture:setCategory(1) - o.fixture:setFriction(0.2) - else - for i,v in pairs(shape) do - local poly = love.physics.newPolygonShape(v) - table.insert(o.shape, poly) - local fixture = love.physics.newFixture(o.body, poly) - fixture:setCategory(1) - fixture:setFriction(0.2) - end - end - -- END HERE - o:setImage(love.graphics.newImage(sprite)) - o:setAnimationsList(animations) - o.world = game - return o -end - --- Position -function Platform:getPosition() - return self.body:getPosition() -end - --- Draw of `Platform` -function Platform:draw (offset_x, offset_y, scale, debug) - -- locals - local offset_x = offset_x or 0 - local offset_y = offset_y or 0 - local scale = scale or 1 - local debug = debug or false - local x, y = self:getPosition() - -- pixel grid - local draw_x = (math.floor(x) + offset_x) * scale - local draw_y = (math.floor(y) + offset_y) * scale - -- sprite draw - Sprite.draw(self, draw_x, draw_y, 0, scale, scale) - -- debug draw - if debug then - love.graphics.setColor(255, 69, 0, 140) - for i,v in pairs(self.shape) do - love.graphics.polygon("fill", self.world.camera:translatePoints(self.body:getWorldPoints(v:getPoints()))) - end - end -end \ No newline at end of file diff --git a/player.lua b/player.lua deleted file mode 100644 index a5b4bd2..0000000 --- a/player.lua +++ /dev/null @@ -1,449 +0,0 @@ --- `Player` --- Entity controlled by a player. It has a physical body and a sprite. Can play animations and interact with other instances of the same class. --- Collision category: [2] - --- WHOLE CODE HAS FLAG OF "need a cleanup" -require "sprite" - --- Metatable of `Player` --- nils initialized in constructor -Player = { - -- General and physics - name = "empty", - body = nil, - shape = nil, - fixture = nil, - sprite = nil, - rotate = 0, -- "angle" would sound better - facing = 1, - max_velocity = 105, - world = nil, -- game world - -- Combat - combo = 0, - lives = 3, - spawntimer = 2, - alive = true, - punchcd = 0.25, - punchdir = 0, -- a really bad thing - -- Movement - inAir = true, - salto = false, - jumpactive = false, - jumpdouble = true, - jumptimer = 0.16, - jumpnumber = 2, - -- Keys - controlset = nil, - -- HUD - portrait_sprite = nil, - portrait_frame = nil, - portrait_sheet = require "nautsicons", - portrait_box = love.graphics.newQuad( 0, 15, 32,32, 80,130), - -- Sounds - sfx = require "sounds", - -- Animations table - animations = require "animations" -} -Player.__index = Player -setmetatable(Player, Sprite) - --- Constructor of `Player` -function Player:new (game, world, x, y, name) - -- Meta - local o = {} - setmetatable(o, self) - -- Physics - local group = -1-#game.Nauts - o.body = love.physics.newBody(world, x, y, "dynamic") - o.shape = love.physics.newRectangleShape(10, 16) - o.fixture = love.physics.newFixture(o.body, o.shape, 8) - o.fixture:setUserData(o) - o.fixture:setCategory(2) - o.fixture:setMask(2) - o.fixture:setGroupIndex(group) - o.body:setFixedRotation(true) - -- Misc - o.name = name or "empty" - o:setImage(newImage("assets/nauts/"..o.name..".png")) - o.world = game - o.punchcd = 0 - -- Animation - o.current = o.animations.default - o:createEffect("respawn") - -- Portrait load for first object created - if self.portrait_sprite == nil then - self.portrait_sprite = love.graphics.newImage("assets/portraits.png") - self.portrait_frame = love.graphics.newImage("assets/menu.png") - end - return o -end - --- Control set managment -function Player:assignControlSet(set) - self.controlset = set -end -function Player:getControlSet() - return self.controlset -end - --- Update callback of `Player` -function Player:update(dt) - -- hotfix? for destroyed bodies - if self.body:isDestroyed() then return end - -- locals - local x, y = self.body:getLinearVelocity() - local isDown = Controller.isDown - local controlset = self:getControlSet() - - -- # VERTICAL MOVEMENT - -- Jumping - if self.jumpactive and self.jumptimer > 0 then - self.body:setLinearVelocity(x,-160) - self.jumptimer = self.jumptimer - dt - end - - -- Salto - if self.salto and (self.current == self.animations.walk or self.current == self.animations.default) then - self.rotate = (self.rotate + 17 * dt * self.facing) % 360 - elseif self.rotate ~= 0 then - self.rotate = 0 - end - - -- # HORIZONTAL MOVEMENT - -- Walking - if isDown(controlset, "left") then - self.facing = -1 - self.body:applyForce(-250, 0) - -- Controlled speed limit - if x < -self.max_velocity then - self.body:applyForce(250, 0) - end - end - if isDown(controlset, "right") then - self.facing = 1 - self.body:applyForce(250, 0) - -- Controlled speed limit - if x > self.max_velocity then - self.body:applyForce(-250, 0) - end - end - - -- Custom linear damping - if not isDown(controlset, "left") and - not isDown(controlset, "right") - then - local face = nil - if x < -12 then - face = 1 - elseif x > 12 then - face = -1 - else - face = 0 - end - self.body:applyForce(40*face,0) - if not self.inAir then - self.body:applyForce(80*face,0) - end - end - - Sprite.update(self, dt) - - -- # DEATH - -- We all die in the end. - local m = self.world.map - if (self.body:getX() < m.center_x - m.width*1.5 or self.body:getX() > m.center_x + m.width*1.5 or - self.body:getY() < m.center_y - m.height*1.5 or self.body:getY() > m.center_y + m.height*1.5) and - self.alive - then - self:die() - end - - -- respawn - if self.spawntimer > 0 then - self.spawntimer = self.spawntimer - dt - end - if self.spawntimer <= 0 and not self.alive and self.lives >= 0 then - self:respawn() - end - - -- # PUNCH - -- Cooldown - self.punchcd = self.punchcd - dt - if not self.body:isDestroyed() then -- This is weird - for _,fixture in pairs(self.body:getFixtureList()) do - if fixture:getUserData() ~= self then - fixture:setUserData({fixture:getUserData()[1] - dt, fixture:getUserData()[2]}) - if fixture:getUserData()[1] < 0 then - fixture:destroy() - end - end - end - end - - -- Stop vertical - local c,a = self.current, self.animations - if (c == a.attack_up or c == a.attack_down or c == a.attack) and self.frame < c.frames then - if self.punchdir == 0 then - self.body:setLinearVelocity(0,0) - else - self.body:setLinearVelocity(38*self.facing,0) - end - end - - if self.punchcd <= 0 and self.punchdir == 1 then - self.punchdir = 0 - end -end - --- Controller callbacks -function Player:controlpressed(set, action, key) - if set ~= self:getControlSet() then return end - local isDown = Controller.isDown - local controlset = self:getControlSet() - -- Jumping - if action == "jump" then - if self.jumpnumber > 0 then - -- General jump logics - self.jumpactive = true - --self:playSound(6) - -- Spawn proper effect - if not self.inAir then - self:createEffect("jump") - else - self:createEffect("doublejump") - end - -- Start salto if last jump - if self.jumpnumber == 1 then - self.salto = true - end - -- Animation clear - if (self.current == self.animations.attack) or - (self.current == self.animations.attack_up) or - (self.current == self.animations.attack_down) then - self:setAnimation("default") - end - -- Remove jump - self.jumpnumber = self.jumpnumber - 1 - end - end - - -- Walking - if (action == "left" or action == "right") and - (self.current ~= self.animations.attack) and - (self.current ~= self.animations.attack_up) and - (self.current ~= self.animations.attack_down) then - self:setAnimation("walk") - end - - -- Punching - if action == "attack" and self.punchcd <= 0 then - local f = self.facing - self.salto = false - if isDown(controlset, "up") then - -- Punch up - if self.current ~= self.animations.damage then - self:setAnimation("attack_up") - end - self:hit("up") - elseif isDown(controlset, "down") then - -- Punch down - if self.current ~= self.animations.damage then - self:setAnimation("attack_down") - end - self:hit("down") - else - -- Punch horizontal - if self.current ~= self.animations.damage then - self:setAnimation("attack") - end - if f == 1 then - self:hit("right") - else - self:hit("left") - end - self.punchdir = 1 - end - end -end -function Player:controlreleased(set, action, key) - if set ~= self:getControlSet() then return end - local isDown = Controller.isDown - local controlset = self:getControlSet() - -- Jumping - if action == "jump" then - self.jumpactive = false - self.jumptimer = Player.jumptimer -- take initial from metatable - end - -- Walking - if (action == "left" or action == "right") and not - (isDown(controlset, "left") or isDown(controlset, "right")) and - self.current == self.animations.walk - then - self:setAnimation("default") - end -end - --- Draw of `Player` -function Player:draw(offset_x, offset_y, scale, debug) - -- draw only alive - if not self.alive then return end - -- locals - local offset_x = offset_x or 0 - local offset_y = offset_y or 0 - local scale = scale or 1 - local debug = debug or false - local x, y = self:getPosition() - -- pixel grid ; `approx` selected to prevent floating characters on certain conditions - local approx = math.floor - if (y - math.floor(y)) > 0.5 then approx = math.ceil end - local draw_y = (approx(y) + offset_y) * scale - local draw_x = (math.floor(x) + offset_x) * scale - -- sprite draw - Sprite.draw(self, draw_x, draw_y, self.rotate, self.facing*scale, scale, 12, 15) - -- debug draw - if debug then - for _,fixture in pairs(self.body:getFixtureList()) do - if fixture:getCategory() == 2 then - love.graphics.setColor(137, 255, 0, 120) - else - love.graphics.setColor(137, 0, 255, 40) - end - love.graphics.polygon("fill", self.world.camera:translatePoints(self.body:getWorldPoints(fixture:getShape():getPoints()))) - end - for _,contact in pairs(self.body:getContactList()) do - love.graphics.setColor(255, 0, 0, 255) - love.graphics.setPointSize(scale) - love.graphics.points(self.world.camera:translatePoints(contact:getPositions())) - end - end -end - --- getPosition -function Player:getPosition() - return self.body:getPosition() -end - --- Draw HUD of `Player` --- elevation: 1 bottom, 0 top -function Player:drawHUD(x,y,scale,elevation) - -- hud displays only if player is alive - if self.alive then - love.graphics.setColor(255,255,255,255) - love.graphics.draw(self.portrait_frame, self.portrait_box, (x)*scale, (y)*scale, 0, scale, scale) - love.graphics.draw(self.portrait_sprite, self.portrait_sheet[self.name], (x+2)*scale, (y+3)*scale, 0, scale, scale) - local dy = 30 * elevation - love.graphics.setFont(Font) - love.graphics.print((self.combo*10).."%",(x+2)*scale,(y-3+dy)*scale,0,scale,scale) - love.graphics.print(math.max(0, self.lives),(x+24)*scale,(y-3+dy)*scale,0,scale,scale) - end -end - --- Change animation of `Player` --- default, walk, attack, attack_up, attack_down, damage -function Player:nextFrame() - local isDown = Controller.isDown - local controlset = self:getControlSet() - if self.current.repeated or not (self.frame == self.current.frames) then - self.frame = (self.frame % self.current.frames) + 1 - elseif isDown(controlset, "right") or isDown(controlset, "left") then - -- If nonrepeatable animation is finished and player is walking - self:setAnimation("walk") - elseif self.current == self.animations.damage then - self:setAnimation("default") - end -end - --- Spawn `Effect` relative to `Player` -function Player:createEffect(name) - if name == "trail" or name == "hit" then - -- 16px effect: -7 -7 - self.world:createEffect(name, self.body:getX()-8, self.body:getY()-8) - elseif name ~= nil then - -- 24px effect: -12 -15 - self.world:createEffect(name, self.body:getX()-12, self.body:getY()-15) - end -end - --- Punch of `Player` --- direction: left, right, up, down --- creates temporary fixture for player's body that acts as sensor; fixture is deleted after time set in UserData[1]; deleted by Player:update(dt) -function Player:hit(direction) - -- start cooldown - self.punchcd = Player.punchcd -- INITIAL from metatable - -- actual punch - local fixture - if direction == "left" then - fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(-2,-6, -20,-6, -20,6, -2,6), 0) - end - if direction == "right" then - fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(2,-6, 20,-6, 20,6, 2,6), 0) - end - if direction == "up" then - fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(-8,-4, -8,-20, 8,-20, 8,-4), 0) - end - if direction == "down" then - fixture = love.physics.newFixture(self.body, love.physics.newPolygonShape(-8,4, -8,20, 8,20, 8,4), 0) - end - fixture:setSensor(true) - fixture:setCategory(3) - fixture:setMask(1,3) - fixture:setGroupIndex(self.fixture:getGroupIndex()) - fixture:setUserData({0.08, direction}) - -- sound - self:playSound(4) -end - --- Taking damage of `Player` by successful hit test --- currently called from World's startContact -function Player:damage(direction) - local horizontal, vertical = 0, 0 - if direction == "left" then - horizontal = -1 - end - if direction == "right" then - horizontal = 1 - end - if direction == "up" then - vertical = -1 - end - if direction == "down" then - vertical = 1 - end - self:createEffect("hit") - local x,y = self.body:getLinearVelocity() - self.body:setLinearVelocity(x,0) - self.body:applyLinearImpulse((42+10*self.combo)*horizontal, (68+10*self.combo)*vertical + 15) - self:setAnimation("damage") - self.combo = math.min(27, self.combo + 1) - self.punchcd = 0.08 + self.combo*0.006 - self:playSound(2) -end - --- DIE -function Player:die() - self:playSound(1) - self.combo = Player.combo -- INITIAL from metatable - self.lives = self.lives - 1 - self.alive = false - self.spawntimer = Player.spawntimer -- INITIAL from metatable - self.body:setActive(false) - self.world:onNautKilled(self) -end - --- And then respawn. Like Jon Snow. -function Player:respawn() - self.alive = true - self.body:setLinearVelocity(0,0) - self.body:setPosition(self.world:getSpawnPosition()) - self.body:setActive(true) - self:createEffect("respawn") - self:playSound(7) -end - --- Sounds -function Player:playSound(sfx, force) - if self.alive or force then - local source = love.audio.newSource(self.sfx[sfx]) - source:play() - end -end diff --git a/sprite.lua b/sprite.lua deleted file mode 100644 index 1cc46f7..0000000 --- a/sprite.lua +++ /dev/null @@ -1,81 +0,0 @@ --- `Sprite` --- Abstract class for drawable animated entities. - --- Metatable -Sprite = { - animations--[[table with animations]], - current--[[animations.default]], - image--[[love.graphics.newImage()]], - frame = 1, - delay = .1, -} -Sprite.__index = Sprite - --- Cleans up reference to image on deletion. -function Sprite:delete() - self.image = nil -end - --- Sets an Image as a image. -function Sprite:setImage(image) - self.image = image -end --- Returns current image Image. -function Sprite:getImage() - return self.image -end - --- Sets new animations list. -function Sprite:setAnimationsList(t) - if t then - self.animations = t - self:setAnimation("default") - end -end - --- Sets current animation by table key. -function Sprite:setAnimation(animation) - self.frame = 1 - self.delay = Sprite.delay -- INITIAL from metatable - self.current = self.animations[animation] -end --- Returns current animation table. -function Sprite:getAnimation() - return self.current -end - --- Get frame quad for drawing. -function Sprite:getQuad() - if self.animations and self.current then - return self.current[self.frame] - end -end - --- Drawing self to LOVE2D buffer. --- If there is no Quad, it will draw entire image. -function Sprite:draw(...) - local s, q = self:getImage(), self:getQuad() - if s then - love.graphics.setColor(255,255,255,255) - if q then love.graphics.draw(s, q, ...) - else love.graphics.draw(s, ...) end - end -end --- Animation updating. -function Sprite:update(dt) - if self.animations and self.current then - self.delay = self.delay - dt - if self.delay < 0 then - self.delay = self.delay + Sprite.delay -- INITIAL from metatable - self:nextFrame() - end - end -end --- Moving to the next frame. -function Sprite:nextFrame() - if self.current.repeated or not (self.frame == self.current.frames) then - self.frame = (self.frame % self.current.frames) + 1 - else - self:setAnimation("default") - end -end \ No newline at end of file diff --git a/world.lua b/world.lua deleted file mode 100644 index 9c308b8..0000000 --- a/world.lua +++ /dev/null @@ -1,424 +0,0 @@ --- `World` --- Used to manage physical world and everything inside it: clouds, platforms, nauts, background etc. - --- WHOLE CODE HAS FLAG OF "need a cleanup" - -require "platform" -require "player" -require "cloud" -require "effect" -require "decoration" -require "ray" - --- Metatable of `World` --- nils initialized in constructor -World = { - -- inside - world = nil, - Nauts = nil, - Platforms = nil, - Clouds = nil, - Decorations = nil, - Effects = nil, - Rays = nil, - camera = nil, - -- cloud generator - clouds_delay = 5, - -- Map - map = nil, - background = nil, - -- Gameplay status - lastNaut = false, - -- "WINNER" - win_move = 0, - -- Music - music = nil -} - --- Constructor of `World` ZA WARUDO! -function World:new(map, nauts) - -- Meta - local o = {} - setmetatable(o, self) - self.__index = self - -- Physical world initialization - love.physics.setMeter(64) - o.world = love.physics.newWorld(0, 9.81*64, true) - o.world:setCallbacks(o.beginContact, o.endContact) - -- Empty tables for objects - local n = {} - o.Nauts = n - local p = {} - o.Platforms = {} - local c = {} - o.Clouds = c - local e = {} - o.Effects = e - local d = {} - o.Decorations = d - local r = {} - o.Rays = r - -- Random init - math.randomseed(os.time()) - -- Map - local map = map or "default" - o:loadMap(map) - -- Nauts - o:spawnNauts(nauts) - -- Create camera - o.camera = Camera:new(o) - -- Play music - o.music = Music:new(o.map.theme) - return o -end - --- The end of the world -function World:delete() - self.world:destroy() - for _,platform in pairs(self.Platforms) do - platform:delete() - end - for _,naut in pairs(self.Nauts) do - naut:delete() - end - self.music:delete() - self = nil -end - --- Load map from file -function World:loadMap(name) - local name = name or "default" - name = "maps/" .. name .. ".lua" - local map = love.filesystem.load(name) - self.map = map() - -- Platforms - for _,platform in pairs(self.map.platforms) do - self:createPlatform(platform.x, platform.y, platform.shape, platform.sprite, platform.animations) - end - -- Decorations - for _,decoration in pairs(self.map.decorations) do - self:createDecoration(decoration.x, decoration.y, decoration.sprite) - end - -- Background - self.background = love.graphics.newImage(self.map.background) - -- Clouds - if self.map.clouds then - for i=1,6 do - self:randomizeCloud(false) - end - end -end - --- Spawn all the nauts for the round -function World:spawnNauts(nauts) - for _,naut in pairs(nauts) do - local x,y = self:getSpawnPosition() - local spawn = self:createNaut(x, y, naut[1]) - spawn:assignControlSet(naut[2]) - end -end - --- Get respawn location -function World:getSpawnPosition() - local n = math.random(1, #self.map.respawns) - return self.map.respawns[n].x, self.map.respawns[n].y -end - --- Add new platform to the world -function World:createPlatform(x, y, polygon, sprite, animations) - table.insert(self.Platforms, Platform:new(self, self.world, x, y, polygon, sprite, animations)) -end - --- Add new naut to the world -function World:createNaut(x, y, name) - local naut = Player:new(self, self.world, x, y, name) - table.insert(self.Nauts, naut) - return naut -end - --- Add new decoration to the world -function World:createDecoration(x, y, sprite) - table.insert(self.Decorations, Decoration:new(x, y, sprite)) -end - --- Add new cloud to the world -function World:createCloud(x, y, t, v) - table.insert(self.Clouds, Cloud:new(x, y, t, v)) -end - --- Randomize Cloud creation -function World:randomizeCloud(outside) - if outside == nil then - outside = true - else - outside = outside - end - local x,y,t,v - local m = self.map - if outside then - x = m.center_x-m.width*1.2+math.random(-50,20) - else - x = math.random(m.center_x-m.width/2,m.center_x+m.width/2) - end - y = math.random(m.center_y-m.height/2, m.center_y+m.height/2) - t = math.random(1,3) - v = math.random(8,18) - self:createCloud(x, y, t, v) -end - --- Add an effect behind nauts -function World:createEffect(name, x, y) - table.insert(self.Effects, Effect:new(name, x, y)) -end - --- Add a ray -function World:createRay(naut) - table.insert(self.Rays, Ray:new(naut, self)) -end - --- get Nauts functions --- more than -1 lives -function World:getNautsPlayable() - local nauts = {} - for _,naut in pairs(self.Nauts) do - if naut.lives > -1 then - table.insert(nauts, naut) - end - end - return nauts -end --- are alive -function World:getNautsAlive() - local nauts = {} - for _,naut in self.Nauts do - if naut.alive then - table.insert(nauts, naut) - end - end - return nauts -end --- all of them -function World:getNautsAll() - return self.Nauts -end - --- get Map name -function World:getMapName() - return self.map.name -end - --- Event: when player is killed -function World:onNautKilled(naut) - self.camera:startShake() - self:createRay(naut) - local nauts = self:getNautsPlayable() - if self.lastNaut then - changeScene(Menu:new()) - elseif #nauts < 2 then - self.lastNaut = true - naut:playSound(5, true) - end -end - -function World:getBounce(f) - local f = f or 1 - return math.sin(self.win_move*f*math.pi) -end - --- LÖVE2D callbacks --- Update ZU WARUDO -function World:update(dt) - -- Physical world - self.world:update(dt) - -- Camera - self.camera:update(dt) - -- Engine world: Nauts, Grounds (kek) and Decorations - all Animateds (top kek) - for _,naut in pairs(self.Nauts) do - naut:update(dt) - end - for _,platform in pairs(self.Platforms) do - platform:update(dt) - end - for _,decoration in pairs(self.Decorations) do - decoration:update(dt) - end - -- Clouds - if self.map.clouds then - -- generator - local n = table.getn(self.Clouds) - self.clouds_delay = self.clouds_delay - dt - if self.clouds_delay < 0 and - n < 18 - then - self:randomizeCloud() - self.clouds_delay = self.clouds_delay + World.clouds_delay -- World.clouds_delay is initial - end - -- movement - for _,cloud in pairs(self.Clouds) do - if cloud:update(dt) > 340 then - table.remove(self.Clouds, _) - end - end - end - -- Effects - for _,effect in pairs(self.Effects) do - if effect:update(dt) then - table.remove(self.Effects, _) - end - end - -- Rays - for _,ray in pairs(self.Rays) do - if ray:update(dt) then - table.remove(self.Rays, _) - end - end - -- Bounce `winner` - self.win_move = self.win_move + dt - if self.win_move > 2 then - self.win_move = self.win_move - 2 - end -end --- Draw -function World:draw() - -- Camera stuff - local offset_x, offset_y = self.camera:getOffsets() - local scale = self.camera.scale - local scaler = self.camera.scaler - - -- Background - love.graphics.draw(self.background, 0, 0, 0, scaler, scaler) - - -- This needs to be reworked! - -- Draw clouds - for _,cloud in pairs(self.Clouds) do - cloud:draw(offset_x, offset_y, scale) - end - - -- Draw decorations - for _,decoration in pairs(self.Decorations) do - decoration:draw(offset_x, offset_y, scale) - end - - -- Draw effects - for _,effect in pairs(self.Effects) do - effect:draw(offset_x,offset_y, scale) - end - - -- Draw player - for _,naut in pairs(self.Nauts) do - naut:draw(offset_x, offset_y, scale, debug) - end - - -- Draw ground - for _,platform in pairs(self.Platforms) do - platform:draw(offset_x, offset_y, scale, debug) - end - - -- Draw rays - for _,ray in pairs(self.Rays) do - ray:draw(offset_x, offset_y, scale) - end - - -- draw center - if debug then - local c = self.camera - local w, h = love.graphics.getWidth(), love.graphics.getHeight() - -- draw map center - love.graphics.setColor(130,130,130) - love.graphics.setLineWidth(1) - love.graphics.setLineStyle("rough") - local cx, cy = c:getPositionScaled() - local x1, y1 = c:translatePosition(self.map.center_x, cy) - local x2, y2 = c:translatePosition(self.map.center_x, cy+h) - love.graphics.line(x1,y1,x2,y2) - local x1, y1 = c:translatePosition(cx, self.map.center_y) - local x2, y2 = c:translatePosition(cx+w, self.map.center_y) - love.graphics.line(x1,y1,x2,y2) - -- draw ox, oy - love.graphics.setColor(200,200,200) - love.graphics.setLineStyle("rough") - local cx, cy = c:getPositionScaled() - local x1, y1 = c:translatePosition(0, cy) - local x2, y2 = c:translatePosition(0, cy+h) - love.graphics.line(x1,y1,x2,y2) - local x1, y1 = c:translatePosition(cx, 0) - local x2, y2 = c:translatePosition(cx+w, 0) - love.graphics.line(x1,y1,x2,y2) - end - - -- Draw HUDs - for _,naut in pairs(self.Nauts) do - -- I have no idea where to place them T_T - -- let's do: bottom-left, bottom-right, top-left, top-right - local w, h = love.graphics.getWidth()/scale, love.graphics.getHeight()/scale - local y, e = 1, 1 - if _ < 3 then y, e = h-33, 0 end - naut:drawHUD(1+(_%2)*(w-34), y, scale, e) - end - - -- Draw winner - if self.lastNaut then - local w, h = love.graphics.getWidth()/scale, love.graphics.getHeight()/scale - local angle = self:getBounce(2) - local dy = self:getBounce()*3 - love.graphics.setFont(Bold) - love.graphics.printf("WINNER",(w/2)*scale,(42+dy)*scale,336,"center",(angle*5)*math.pi/180,scale,scale,168,12) - love.graphics.setFont(Font) - love.graphics.printf("rofl, now kill yourself", w/2*scale, 18*scale, 160, "center", 0, scale, scale, 80, 3) - end -end - --- Box2D callbacks --- beginContact -function World.beginContact(a, b, coll) - if a:getCategory() == 1 then - local x,y = coll:getNormal() - if y < -0.6 then - print(b:getUserData().name .. " is not in air") - -- Move them to Player - b:getUserData().inAir = false - b:getUserData().jumpnumber = 2 - b:getUserData().salto = false - b:getUserData():createEffect("land") - end - local vx, vy = b:getUserData().body:getLinearVelocity() - if math.abs(x) == 1 or (y < -0.6 and x == 0) then - b:getUserData():playSound(3) - end - end - if a:getCategory() == 3 then - b:getUserData():damage(a:getUserData()[2]) - end - if b:getCategory() == 3 then - a:getUserData():damage(b:getUserData()[2]) - end -end --- endContact -function World.endContact(a, b, coll) - if a:getCategory() == 1 then - print(b:getUserData().name .. " is in air") - -- Move them to Player - b:getUserData().inAir = true - end -end - --- Controller callbacks -function World:controlpressed(set, action, key) - if key == "f6" and debug then - local map = self:getMapName() - local nauts = {} - for _,naut in pairs(self:getNautsAll()) do - table.insert(nauts, {naut.name, naut:getControlSet()}) - end - local new = World:new(map, nauts) - changeScene(new) - end - for k,naut in pairs(self:getNautsAll()) do - naut:controlpressed(set, action, key) - end -end -function World:controlreleased(set, action, key) - for k,naut in pairs(self:getNautsAll()) do - naut:controlreleased(set, action, key) - end -end \ No newline at end of file -- cgit v1.1