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 --- 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 ++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1192 insertions(+) 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 (limited to 'not') 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 -- cgit v1.1