diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | animated.lua | 81 | ||||
-rw-r--r-- | cloud.lua | 63 | ||||
-rw-r--r-- | config/animations/effects.lua (renamed from effects.lua) | 21 | ||||
-rw-r--r-- | config/animations/hero.lua (renamed from animations.lua) | 0 | ||||
-rw-r--r-- | config/maps.lua (renamed from maplist.lua) | 0 | ||||
-rw-r--r-- | config/maps/aiguillon.lua (renamed from maps/aiguillon.lua) | 0 | ||||
-rw-r--r-- | config/maps/alpha abyss.lua (renamed from maps/alpha abyss.lua) | 0 | ||||
-rw-r--r-- | config/maps/default.lua (renamed from maps/default.lua) | 0 | ||||
-rw-r--r-- | config/maps/readme.md (renamed from maps/readme.md) | 0 | ||||
-rw-r--r-- | config/maps/ribbit.lua (renamed from maps/ribbit.lua) | 0 | ||||
-rw-r--r-- | config/maps/rill.lua (renamed from maps/rill.lua) | 0 | ||||
-rw-r--r-- | config/maps/sorona.lua (renamed from maps/sorona.lua) | 0 | ||||
-rw-r--r-- | config/maps/starstorm.lua (renamed from maps/starstorm.lua) | 0 | ||||
-rw-r--r-- | config/menus/credits.lua (renamed from config/menucredits.lua) | 6 | ||||
-rw-r--r-- | config/menus/host.lua (renamed from config/menuhost.lua) | 15 | ||||
-rw-r--r-- | config/menus/main.lua (renamed from config/menumain.lua) | 12 | ||||
-rw-r--r-- | config/menus/select.lua (renamed from config/menuselect.lua) | 15 | ||||
-rw-r--r-- | config/menus/settings.lua (renamed from config/menusettings.lua) | 8 | ||||
-rw-r--r-- | config/nauts.lua (renamed from nautslist.lua) | 5 | ||||
-rw-r--r-- | config/sounds.lua (renamed from sounds.lua) | 0 | ||||
-rw-r--r-- | decoration.lua | 33 | ||||
-rw-r--r-- | effect.lua | 68 | ||||
-rw-r--r-- | element.lua | 42 | ||||
-rw-r--r-- | iconsList.lua | 37 | ||||
-rw-r--r-- | main.lua | 79 | ||||
-rw-r--r-- | mapicons.lua | 12 | ||||
-rw-r--r-- | music.lua | 27 | ||||
-rw-r--r-- | nautsicons.lua | 11 | ||||
-rw-r--r-- | not/Button.lua (renamed from button.lua) | 56 | ||||
-rw-r--r-- | not/Camera.lua (renamed from camera.lua) | 8 | ||||
-rw-r--r-- | not/Cloud.lua | 71 | ||||
-rw-r--r-- | not/Controller.lua (renamed from controller.lua) | 14 | ||||
-rw-r--r-- | not/Decoration.lua | 33 | ||||
-rw-r--r-- | not/Effect.lua | 46 | ||||
-rw-r--r-- | not/Element.lua | 50 | ||||
-rw-r--r-- | not/Header.lua (renamed from header.lua) | 45 | ||||
-rw-r--r-- | not/Hero.lua | 284 | ||||
-rw-r--r-- | not/Menu.lua (renamed from menu.lua) | 78 | ||||
-rw-r--r-- | not/Music.lua | 25 | ||||
-rw-r--r-- | not/PhysicalBody.lua | 93 | ||||
-rw-r--r-- | not/Platform.lua | 37 | ||||
-rw-r--r-- | not/Player.lua | 159 | ||||
-rw-r--r-- | not/Ray.lua (renamed from ray.lua) | 0 | ||||
-rw-r--r-- | not/Selector.lua (renamed from selector.lua) | 79 | ||||
-rw-r--r-- | not/Settings.lua (renamed from settings.lua) | 12 | ||||
-rw-r--r-- | not/Sprite.lua | 152 | ||||
-rw-r--r-- | not/World.lua (renamed from world.lua) | 179 | ||||
-rw-r--r-- | platform.lua | 73 | ||||
-rw-r--r-- | player.lua | 449 | ||||
-rw-r--r-- | readme.md | 4 |
51 files changed, 1295 insertions, 1191 deletions
@@ -1,6 +1,6 @@ all: - zip not-nautz maps/*.lua config/*.lua assets/*.png assets/sounds/*.ogg assets/platforms/*.png assets/nauts/*.png assets/music/*.ogg assets/decorations/*.png assets/backgrounds/*.png *.lua gamecontrollerdb.txt settings.default + zip not-nautz not/*.lua config/maps/*.lua config/menus/*.lua config/*.lua assets/*.png assets/sounds/*.ogg assets/platforms/*.png assets/nauts/*.png assets/music/*.ogg assets/decorations/*.png assets/backgrounds/*.png *.lua gamecontrollerdb.txt settings.default mv not-nautz.zip ../not-nautz.love clean: - $(RM) ../not-nautz.love
\ No newline at end of file + rm ../not-nautz.love
\ No newline at end of file diff --git a/animated.lua b/animated.lua deleted file mode 100644 index 160ee44..0000000 --- a/animated.lua +++ /dev/null @@ -1,81 +0,0 @@ --- `Animated` --- Abstract class for drawable animated entities. - --- Metatable -Animated = { - animations--[[table with animations]], - current--[[animations.default]], - sprite--[[love.graphics.newImage()]], - frame = 1, - delay = .1, -} -Animated.__index = Animated - --- Cleans up reference to sprite on deletion. -function Animated:delete() - self.sprite = nil -end - --- Sets an Image as a sprite. -function Animated:setSprite(image) - self.sprite = image -end --- Returns current sprite Image. -function Animated:getSprite() - return self.sprite -end - --- Sets new animations list. -function Animated:setAnimationsList(t) - if t then - self.animations = t - self:setAnimation("default") - end -end - --- Sets current animation by table key. -function Animated:setAnimation(animation) - self.frame = 1 - self.delay = Animated.delay -- INITIAL from metatable - self.current = self.animations[animation] -end --- Returns current animation table. -function Animated:getAnimation() - return self.current -end - --- Get frame quad for drawing. -function Animated: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 sprite. -function Animated:draw(...) - local s, q = self:getSprite(), 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 Animated:update(dt) - if self.animations and self.current then - self.delay = self.delay - dt - if self.delay < 0 then - self.delay = self.delay + Animated.delay -- INITIAL from metatable - self:nextFrame() - end - end -end --- Moving to the next frame. -function Animated: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/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/effects.lua b/config/animations/effects.lua index 6946d10..dd6d55e 100644 --- a/effects.lua +++ b/config/animations/effects.lua @@ -16,14 +16,16 @@ local quads = { [2] = love.graphics.newQuad( 24, 0, 24,24, 168,120), [3] = love.graphics.newQuad( 48, 0, 24,24, 168,120), [4] = love.graphics.newQuad( 72, 0, 24,24, 168,120), - frames = 4 + frames = 4, + repeated = false }, doublejump = { [1] = love.graphics.newQuad( 0, 24, 24,24, 168,120), [2] = love.graphics.newQuad( 24, 24, 24,24, 168,120), [3] = love.graphics.newQuad( 48, 24, 24,24, 168,120), [4] = love.graphics.newQuad( 72, 24, 24,24, 168,120), - frames = 4 + frames = 4, + repeated = false }, land = { [1] = love.graphics.newQuad( 0, 48, 24,24, 168,120), @@ -31,7 +33,8 @@ local quads = { [3] = love.graphics.newQuad( 48, 48, 24,24, 168,120), [4] = love.graphics.newQuad( 72, 48, 24,24, 168,120), [5] = love.graphics.newQuad( 96, 48, 24,24, 168,120), - frames = 5 + frames = 5, + repeated = false }, respawn = { [1] = love.graphics.newQuad( 0, 72, 24,24, 168,120), @@ -41,7 +44,8 @@ local quads = { [5] = love.graphics.newQuad( 96, 72, 24,24, 168,120), [6] = love.graphics.newQuad(120, 72, 24,24, 168,120), [7] = love.graphics.newQuad(144, 72, 24,24, 168,120), - frames = 7 + frames = 7, + repeated = false }, clash = { [1] = love.graphics.newQuad( 0, 96, 24,24, 168,120), @@ -50,20 +54,23 @@ local quads = { [4] = love.graphics.newQuad( 72, 96, 24,24, 168,120), [5] = love.graphics.newQuad( 96, 96, 24,24, 168,120), [6] = love.graphics.newQuad(120, 96, 24,24, 168,120), - frames = 6 + frames = 6, + repeated = false }, trail = { [1] = love.graphics.newQuad(104, 0, 16,16, 168,120), [2] = love.graphics.newQuad(120, 0, 16,16, 168,120), [3] = love.graphics.newQuad(136, 0, 16,16, 168,120), [4] = love.graphics.newQuad(152, 0, 16,16, 168,120), - frames = 4 + frames = 4, + repeated = false }, hit = { [1] = love.graphics.newQuad(106, 18, 16,16, 168,120), [2] = love.graphics.newQuad(122, 18, 16,16, 168,120), [3] = love.graphics.newQuad(138, 18, 16,16, 168,120), - frames = 3 + frames = 3, + repeated = false } } return quads
\ No newline at end of file diff --git a/animations.lua b/config/animations/hero.lua index 881da49..881da49 100644 --- a/animations.lua +++ b/config/animations/hero.lua diff --git a/maplist.lua b/config/maps.lua index 32e89a5..32e89a5 100644 --- a/maplist.lua +++ b/config/maps.lua diff --git a/maps/aiguillon.lua b/config/maps/aiguillon.lua index 40d3928..40d3928 100644 --- a/maps/aiguillon.lua +++ b/config/maps/aiguillon.lua diff --git a/maps/alpha abyss.lua b/config/maps/alpha abyss.lua index 0dd2c61..0dd2c61 100644 --- a/maps/alpha abyss.lua +++ b/config/maps/alpha abyss.lua diff --git a/maps/default.lua b/config/maps/default.lua index 05b8dc9..05b8dc9 100644 --- a/maps/default.lua +++ b/config/maps/default.lua diff --git a/maps/readme.md b/config/maps/readme.md index dc139ad..dc139ad 100644 --- a/maps/readme.md +++ b/config/maps/readme.md diff --git a/maps/ribbit.lua b/config/maps/ribbit.lua index c3f5c78..c3f5c78 100644 --- a/maps/ribbit.lua +++ b/config/maps/ribbit.lua diff --git a/maps/rill.lua b/config/maps/rill.lua index 83c02f2..83c02f2 100644 --- a/maps/rill.lua +++ b/config/maps/rill.lua diff --git a/maps/sorona.lua b/config/maps/sorona.lua index 8ec4727..8ec4727 100644 --- a/maps/sorona.lua +++ b/config/maps/sorona.lua diff --git a/maps/starstorm.lua b/config/maps/starstorm.lua index 7f00633..7f00633 100644 --- a/maps/starstorm.lua +++ b/config/maps/starstorm.lua diff --git a/config/menucredits.lua b/config/menus/credits.lua index 9d38da4..77cba62 100644 --- a/config/menucredits.lua +++ b/config/menus/credits.lua @@ -1,7 +1,7 @@ local menu = ... -local button = require "button" -local element = require "element" +local button = require "not.Button" +local element = require "not.Element" local width, height = love.graphics.getWidth()/getRealScale(), love.graphics.getHeight()/getRealScale() local bx = width/2-29 @@ -11,7 +11,7 @@ return { :setText("Go back") :setPosition(bx,144) :set("active", function (self) - self.parent:load("menumain") + self.parent:open("main") end) , element:new(menu) diff --git a/config/menuhost.lua b/config/menus/host.lua index 64ce0e7..b318a5b 100644 --- a/config/menuhost.lua +++ b/config/menus/host.lua @@ -1,13 +1,16 @@ local menu = ... -local button = require "button" -local selector = require "selector" +local button = require "not.Button" +local selector = require "not.Selector" local width, height = love.graphics.getWidth()/getScale(), love.graphics.getHeight()/getScale() local bx = width/2-29 local map_selector = selector:new(menu) +require "iconsList" +local icons, maps = getMapsIconsList() + return { map_selector :setPosition(width/2, 40) @@ -15,9 +18,9 @@ return { :setMargin(0) :set("global", true) :set("first", true) - :set("list", require "maplist") + :set("list", maps) :set("icons_i", love.graphics.newImage("assets/maps.png")) - :set("icons_q", require "mapicons") + :set("icons_q", icons) :set("shape", "panorama") :init() , @@ -29,14 +32,14 @@ return { end) :set("active", function (self) MAP = map_selector:getFullSelection(true)[1][1] -- please, don't kill me for this, kek - self.parent:load("menuselect") + self.parent:open("select") end) , button:new(menu) :setText("Go back") :setPosition(bx,117) :set("active", function (self) - self.parent:load("menumain") + self.parent:open("main") end) , }
\ No newline at end of file diff --git a/config/menumain.lua b/config/menus/main.lua index 50b3d56..236c011 100644 --- a/config/menumain.lua +++ b/config/menus/main.lua @@ -1,8 +1,8 @@ local menu = ... -local button = require "button" -local header = require "header" -local element = require "element" +local button = require "not.Button" +local header = require "not.Header" +local element = require "not.Element" local width, height = love.graphics.getWidth()/getScale(), love.graphics.getHeight()/getScale() local bx = width/2-29 @@ -14,7 +14,7 @@ return { :setText("Start") :setPosition(bx, 80) :set("active", function (self) - self.parent:load("menuhost") + self.parent:open("host") end) , button:new(menu) @@ -28,14 +28,14 @@ return { :setText("Settings") :setPosition(bx, 112) :set("active", function (self) - self.parent:load("menusettings") + self.parent:open("settings") end) , button:new(menu) :setText("Credits") :setPosition(bx, 128) :set("active", function (self) - self.parent:load("menucredits") + self.parent:open("credits") end) , button:new(menu) diff --git a/config/menuselect.lua b/config/menus/select.lua index 19f46ab..804b4eb 100644 --- a/config/menuselect.lua +++ b/config/menus/select.lua @@ -1,8 +1,8 @@ local menu = ... -local button = require "button" -local selector = require "selector" -local element = require "element" +local button = require "not.Button" +local selector = require "not.Selector" +local element = require "not.Element" local width, height = love.graphics.getWidth()/getScale(), love.graphics.getHeight()/getScale() local bx = width/2-29 @@ -10,15 +10,18 @@ local bx = width/2-29 local naut_selector = selector:new(menu) local start_button = button:new(menu) +require "iconsList" +local nautsIcons, nautsList = getNautsIconsList() + return { naut_selector :setPosition(width/2,60) :setMargin(8) :setSize(32, 32) - :set("list", require "nautslist") + :set("list", nautsList) :set("global", false) :set("icons_i", love.graphics.newImage("assets/portraits.png")) - :set("icons_q", require "nautsicons") + :set("icons_q", nautsIcons) :init() , start_button @@ -41,7 +44,7 @@ return { :setText("Go back") :setPosition(bx,150) :set("active", function (self) - self.parent:load("menuhost") + self.parent:open("host") end) , element:new(menu) diff --git a/config/menusettings.lua b/config/menus/settings.lua index 1631e4f..ae0403d 100644 --- a/config/menusettings.lua +++ b/config/menus/settings.lua @@ -1,8 +1,8 @@ local menu = ... -local button = require "button" -local selector = require "selector" -local element = require "element" +local button = require "not.Button" +local selector = require "not.Selector" +local element = require "not.Element" local width, height = love.graphics.getWidth()/getRealScale(), love.graphics.getHeight()/getRealScale() local bx = width/2-29 @@ -105,7 +105,7 @@ local a = { :setText("Go back") :setPosition(bx,144) :set("active", function (self) - self.parent:load("menumain") + self.parent:open("main") end) , dimmer diff --git a/nautslist.lua b/config/nauts.lua index d0c7a61..2eea71a 100644 --- a/nautslist.lua +++ b/config/nauts.lua @@ -32,4 +32,9 @@ return { "biker", -- chucho "vrooom", -- lux "shutter", -- max + "disco", -- esc rocco + "yarr", -- ted pirate + "blblal", -- blabl zork + "kong", -- ronimo + "rock", -- rock } diff --git a/sounds.lua b/config/sounds.lua index c30af16..c30af16 100644 --- a/sounds.lua +++ b/config/sounds.lua diff --git a/decoration.lua b/decoration.lua deleted file mode 100644 index 3ca6f76..0000000 --- a/decoration.lua +++ /dev/null @@ -1,33 +0,0 @@ -Decoration = { - world = nil, - sprite = nil, - x = 0, - y = 0 -} -Decoration.__index = Decoration -setmetatable(Decoration, Animated) -function Decoration:new(x, y, sprite) - local o = {} - setmetatable(o, self) - o.sprite = 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 - Animated.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/element.lua b/element.lua deleted file mode 100644 index f9d1c3d..0000000 --- a/element.lua +++ /dev/null @@ -1,42 +0,0 @@ --- `Element` --- Empty element for `Menu` creation. Can be anything. -Element = { - x = 0, - y = 0, - parent -} -function Element:new(parent) - local o = {} - setmetatable(o, self) - self.__index = self - o.parent = parent - return o -end -function Element:delete() end -- deletes Element -function Element:getPosition() return self.x, self.y end -- gives x,y of Element -function Element:setPosition(x,y) - self.x, self.y = x, y - return self -end -function Element:set(name, func) - if type(name) == "string" and func ~= nil then - self[name] = func - end - return self -end - --- Menu callbacks -function Element:focus() -- Called when Element gains focus - return false -end -function Element:blur() end -- Called when Element loses focus - --- LÖVE2D callbacks -function Element:draw(scale) end -function Element:update(dt) end - --- Controller callbacks -function Element:controlpressed(set, action, key) end -function Element:controlreleased(set, action, key) end - -return Element
\ No newline at end of file diff --git a/iconsList.lua b/iconsList.lua new file mode 100644 index 0000000..4a384dc --- /dev/null +++ b/iconsList.lua @@ -0,0 +1,37 @@ +-- TODO: These should be part of non-existent AssetsManager or something similar. +local function testAvoidList (i, avoidList) + for key,value in pairs(avoidList) do + if i == value then + table.remove(avoidList, key) + return false + end + end + return true +end + +function createIconsList (sheetWidth, sheetHeight, iconWidth, keysList, avoidList) + local avoidList = avoidList or {} + local iconsList, newKeysList = {}, {} + local iconsNumber = math.floor(sheetWidth / iconWidth) + local iconHeight = sheetHeight + for i=1,iconsNumber do + if testAvoidList(i, avoidList) then + iconsList[keysList[i]] = love.graphics.newQuad((i-1)*iconWidth, 0, iconWidth, iconHeight, sheetWidth, sheetHeight) + table.insert(newKeysList, keysList[i]) + end + end + return iconsList, newKeysList +end + +function getNautsIconsList (avoidList) + local avoidList = avoidList or {32,33,34,35,36} + local keysList = require "config.nauts" + local iconsList, newKeysList = createIconsList(1008, 27, 28, keysList, avoidList) + return iconsList, newKeysList +end + +function getMapsIconsList (avoidList) + local keysList = require "config.maps" + local iconsList, newKeysList = createIconsList(532, 37, 76, keysList, avoidList) + return iconsList, newKeysList +end @@ -1,10 +1,11 @@ --- "NOTNAUTS" --- WHOLE CODE HAS FLAG OF "need a cleanup" +--- Roflnauts 2 +-- TODO: Any lua source file in root directory that is not `main` (this file), `conf` should be moved to a proper directory. Its name should be changed to show what it contains. -- Pretend you didn't see this -- This is work for scene manager +-- TODO: Create SceneManager or similar class. Scene = nil -function changeScene(scene) +function changeScene (scene) if Scene ~= nil then Scene:delete() end @@ -12,58 +13,41 @@ function changeScene(scene) end -- Should be moved to scene/camera -function getScale() +-- TODO: move following functions to `Camera`. +function getScale () return math.max(1, math.floor(math.max(love.graphics.getWidth() / 320, love.graphics.getHeight() / 180))) end -function getRealScale() + +function getRealScale () return math.max(1, math.max(love.graphics.getWidth() / 320, love.graphics.getHeight() / 180)) end --- Should be moved to Sprite metaclass (non-existent yet) -function newImage(path) - local imagedata = love.image.newImageData(path) - local transparency = function(x, y, r, g, b, a) - if (r == 0 and g == 128 and b == 64) or - (r == 0 and g == 240 and b == 6) then - a = 0 - end - return r, g, b, a - end - imagedata:mapPixel(transparency) - local image = love.graphics.newImage(imagedata) - return image -end -- Require -require "world" -require "camera" -require "menu" -require "controller" -require "music" -require "settings" +require "iconsList" +require "not.World" +require "not.Camera" +require "not.Menu" +require "not.Controller" +require "not.Settings" -- Temporary debug debug = false -- LÖVE2D callbacks -function love.load() - -- Graphics +function love.load () love.graphics.setBackgroundColor(90, 90, 90) love.graphics.setDefaultFilter("nearest", "nearest") - -- Font + -- TODO: Move fonts somewhere else out of global scope. Font = love.graphics.newImageFont("assets/font-normal.png", " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,:;-_/\\!@#$%^&*?=+~`|'\"()[]{}<>", -1) Bold = love.graphics.newImageFont("assets/font-big.png", " 0123456789AEFILNORSTUW", -2) Font:setLineHeight(9/16) - love.graphics.setFont(Font) - -- Modules + love.graphics.setFont(Font) Controller.load() Settings.load() - -- Scene Scene = Menu:new() end -function love.update(dt) - Scene:update(dt) -end -function love.draw() + +function love.draw () Scene:draw() if debug then local scale = getScale() @@ -74,21 +58,20 @@ function love.draw() love.graphics.print("Current FPS: "..tostring(love.timer.getFPS()), 10, 10+9*scale, 0, scale, scale) end end -function love.quit() - Settings.save() -end + +function love.update (dt) Scene:update(dt) end +function love.quit () Settings.save() end + -- Pass input to Controller -function love.gamepadaxis(joystick, axis, value) Controller.gamepadaxis(joystick, axis, value) end -function love.gamepadpressed(joystick, key) Controller.gamepadpressed(joystick, key) end -function love.gamepadreleased(joystick, key) Controller.gamepadreleased(joystick, key) end -function love.keypressed(key) Controller.keypressed(key) end -function love.keyreleased(key) Controller.keyreleased(key) end +function love.gamepadaxis (joystick, axis, value) Controller.gamepadaxis(joystick, axis, value) end +function love.gamepadpressed (joystick, key) Controller.gamepadpressed(joystick, key) end +function love.gamepadreleased (joystick, key) Controller.gamepadreleased(joystick, key) end +function love.keypressed (key) Controller.keypressed(key) end +function love.keyreleased (key) Controller.keyreleased(key) end -- Controller callbacks -function Controller.controlpressed(set, action, key) - -- pass to current Scene +function Controller.controlpressed (set, action, key) Scene:controlpressed(set, action, key) - -- globals if key == "escape" then love.event.quit() end @@ -96,7 +79,7 @@ function Controller.controlpressed(set, action, key) debug = not debug end end -function Controller.controlreleased(set, action, key) - -- pass to current Scene + +function Controller.controlreleased (set, action, key) Scene:controlreleased(set, action, key) end
\ No newline at end of file diff --git a/mapicons.lua b/mapicons.lua deleted file mode 100644 index 4893da7..0000000 --- a/mapicons.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Maps icons list generation file --- REWORK NEEDED, it is so similar to `nautsicons.lua` they could be merged together into one function that returns icon quad sequences. -local maps = require "maplist" -local w, h = 532, 37 -local icons = {} - -local i = 0 -for _,map in pairs(maps) do - icons[map] = love.graphics.newQuad(i*76, 0, 76, 37, w, h) - i = i + 1 -end -return icons diff --git a/music.lua b/music.lua deleted file mode 100644 index 1ac26bb..0000000 --- a/music.lua +++ /dev/null @@ -1,27 +0,0 @@ --- `Music` --- Simple music player object that plays and loops selected track in single Scene. - --- WHOLE CODE HAS FLAG OF "need a cleanup" - --- Metatable of `Music` --- nils initialized in constructor -Music = { - track = nil, - source = nil -} -function Music:new(track) - -- Meta - local o = {} - setmetatable(o, self) - self.__index = self - -- Init - o.track = track - o.source = love.audio.newSource("assets/music/" .. o.track) - o.source:setLooping(true) - o.source:setVolume(.7) - o.source:play() - return o -end -function Music:delete() - self.source:stop() -end
\ No newline at end of file diff --git a/nautsicons.lua b/nautsicons.lua deleted file mode 100644 index 6c09a8f..0000000 --- a/nautsicons.lua +++ /dev/null @@ -1,11 +0,0 @@ --- Spritesheet for character portraits -local nauts = require "nautslist" -local w, h = 1008, 27 -local icons = {} - -local i = 0 -for _,naut in pairs(nauts) do - icons[naut] = love.graphics.newQuad(i*28, 0, 28, 27, w, h) - i = i + 1 -end -return icons diff --git a/button.lua b/not/Button.lua index c5681b2..91aca45 100644 --- a/button.lua +++ b/not/Button.lua @@ -1,51 +1,48 @@ --- `Button` --- Button used in `Menu` - +--- `Button` +-- Menu element that can be activated by user. Button = { - text = "", - focused = false, + parent = --[[not.Menu]]nil, x = 0, y = 0, + text = "", + focused = false, sprite, quads, delay = 2, parent, } -function Button:new(parent) - local o = {} - setmetatable(o, self) - self.__index = self +-- `Button` is a child of `Element`. +require "not.Element" +Button.__index = Button +setmetatable(Button, Element) + +function Button:new (parent) + local o = setmetatable({}, self) o.parent = parent o.sprite, o.quads = parent:getSheet() return o end -function Button:setText(text) + +function Button:setText (text) self.text = text or "" return self end -function Button:setPosition(x, y) - self.x = x or 0 - self.y = y or 0 - return self -end -function Button:getPosition() return self.x,self.y end + function Button:focus(next) self.focused = true return true end -function Button:blur() +function Button:blur () self.focused = false end -function Button:active() end -function Button:isEnabled() return true end -function Button:set(name, func) - if type(name) == "string" and type(func) == "function" then - self[name] = func - end - return self + +function Button:active () end +function Button:isEnabled () + return true end -function Button:draw(scale) + +function Button:draw (scale) local x,y = self:getPosition() local quad = self.quads local sprite = self.sprite @@ -62,17 +59,18 @@ function Button:draw(scale) love.graphics.setFont(Font) love.graphics.printf(self.text, (x+2)*scale, (y+4)*scale, 54, "center", 0, scale, scale) end -function Button:update(dt) + +function Button:update (dt) self.delay = self.delay + dt if self.delay > Button.delay then -- Button.delay is initial self.delay = self.delay - Button.delay end end -function Button:controlpressed(set, action, key) + +function Button:controlpressed (set, action, key) if action == "attack" and self.focused and self:isEnabled() then self:active() end end -function Button:controlreleased(set, action, key) end -return Button
\ No newline at end of file +return Button diff --git a/camera.lua b/not/Camera.lua index 2ae7c78..63489f3 100644 --- a/camera.lua +++ b/not/Camera.lua @@ -1,7 +1,5 @@ --- `Camera` +--- `Camera` -- Used in drawing. - --- Metatable of `Camera` Camera = { x = 0, y = 0, @@ -16,7 +14,7 @@ Camera = { origin_y = 0, shake_x = 0, shake_y = 0, - world = nil, -- game world + world = --[[not.World]]nil, } -- Constructor of `Camera` @@ -62,7 +60,7 @@ function Camera:getDestination () end -- Translate points -function Camera:translatePosition(x, y) +function Camera:translatePosition (x, y) local x = x or 0 local y = y or 0 return (x-self.x)*self.scale, (y-self.y)*self.scale diff --git a/not/Cloud.lua b/not/Cloud.lua new file mode 100644 index 0000000..3bc5377 --- /dev/null +++ b/not/Cloud.lua @@ -0,0 +1,71 @@ +--- `Cloud` +-- That white thing moving in the background. +-- TODO: extends variables names to be readable. +Cloud = { + t = 1, -- type (sprite number) + v = 13 -- velocity +} + +-- TODO: allow maps to use other quads and sprites for clouds +-- TODO: you know this isn't right, don't you? +local animations = { + default = { + [1] = love.graphics.newQuad( 1, 1, 158,47, 478,49), + frames = 1, + repeated = true + }, + default2 = { + [1] = love.graphics.newQuad(160, 1, 158,47, 478,49), + frames = 1, + repeated = true + }, + default3 = { + [1] = love.graphics.newQuad(319, 1, 158,47, 478,49), + frames = 1, + repeated = true + } +} + +-- `Cloud` is a child of `Decoration`. +require "not.Decoration" +Cloud.__index = Cloud +setmetatable(Cloud, Decoration) + +-- Constructor of `Cloud`. +function Cloud:new (x, y, t, v) + local o = setmetatable({}, self) + o:init(x, y, t, v) + -- Load spritesheet statically. + if self:getImage() == nil then + self:setImage(Sprite.newImage("assets/clouds.png")) + end + return o +end + +-- Initializer of `Cloud`. +function Cloud:init (x, y, t, v) + Decoration.init(self, x, y, nil) + self:setAnimationsList(animations) + self:setVelocity(v) + self:setType(t) +end + +-- Setters for cloud type and velocity. +function Cloud:setType (type) + local animation = "default" + if type > 1 then + animation = animation .. type + end + self:setAnimation(animation) + self.t = type +end +function Cloud:setVelocity (velocity) + self.v = velocity +end + +-- Update of `Cloud`, returns x for world to delete cloud after reaching right corner. +function Cloud:update (dt) + Decoration.update(self, dt) + self.x = self.x + self.v*dt + return self.x +end diff --git a/controller.lua b/not/Controller.lua index f035162..8a2a863 100644 --- a/controller.lua +++ b/not/Controller.lua @@ -1,14 +1,12 @@ --- `Controller` +--- `Controller` -- Module to manage player input. -- It uses `love.keypressed`, `love.keyreleased`, `love.gamepadreleased`, `love.gamepadpressed`, `love.joystickadded`, so be sure not to use them by yourself. -- Rather than that use functions provided by this module: `Controller.controlpressed` and `Controller.controlreleased`. --- For information on additional functions, look below. - --- Namespace -Controller = {} -Controller.sets = {} -Controller.axes = {} -Controller.deadzone = .3 +Controller = { + sets = {}, + axes = {}, + deadzone = .3 +} -- Declared to avoid calling nil. Be sure to define yours after this line is performed. function Controller.controlpressed(set, action, key) end diff --git a/not/Decoration.lua b/not/Decoration.lua new file mode 100644 index 0000000..9dc2bdd --- /dev/null +++ b/not/Decoration.lua @@ -0,0 +1,33 @@ +--- `Decoration` +-- Positioned sprite used to decorate maps with additional graphics. +Decoration = { + world = --[[not.World]]nil, + x = 0, + y = 0 +} + +-- `Decoration` is a child of `Sprite`. +require "not.Sprite" +Decoration.__index = Decoration +setmetatable(Decoration, Sprite) + +-- Constructor of `Decoration`. +function Decoration:new (x, y, imagePath) + local o = setmetatable({}, self) + o:init(x, y, imagePath) + return o +end + +-- Initializer of `Decoration`. +function Decoration:init (x, y, imagePath) + Sprite.init(self, imagePath) + self:setPosition(x, y) +end + +-- Position-related methods. +function Decoration:getPosition () + return self.x, self.y +end +function Decoration:setPosition (x, y) + self.x, self.y = x, y +end
\ No newline at end of file diff --git a/not/Effect.lua b/not/Effect.lua new file mode 100644 index 0000000..dd7570a --- /dev/null +++ b/not/Effect.lua @@ -0,0 +1,46 @@ +--- `Effect` +-- Short animation with graphics that plays in various situation. +-- TODO: animation is currently slower than it used to be, check if it is ok; if not then make it possible to change it to 0.06 delay. +Effect = { + finished = false, +} + +-- `Effect` is a child of `Decoration`. +require "not.Decoration" +Effect.__index = Effect +setmetatable(Effect, Decoration) + +-- Constructor of `Effect`. +function Effect:new (name, x, y) + local o = setmetatable({}, self) + o:init(name, x, y) + -- Load spritesheet statically. + if self:getImage() == nil then + self:setImage(Sprite.newImage("assets/effects.png")) + end + return o +end + +-- Initializer of `Effect`. +function Effect:init (name, x, y) + Decoration.init(self, x, y, nil) + self:setAnimationsList(require("config.animations.effects")) + self:setAnimation(name) +end + +-- Update of `Effect`. +-- Returns true if animation is finished and effect is ready to be deleted. +function Effect:update (dt) + Decoration.update(self, dt) + return self.finished +end + +-- Overridden from `not.Sprite`. +-- Sets finished flag if reached last frame of played animation. +function Effect:goToNextFrame () + if not (self.frame == self.current.frames) then + self.frame = (self.frame % self.current.frames) + 1 + else + self.finished = true + end +end diff --git a/not/Element.lua b/not/Element.lua new file mode 100644 index 0000000..e6d91da --- /dev/null +++ b/not/Element.lua @@ -0,0 +1,50 @@ +--- `Element` +-- Empty element used inside `Menu`. +Element = { + parent = --[[not.Menu]]nil, + x = 0, + y = 0 +} + +Element.__index = Element + +function Element:new (parent) + local o = setmetatable({}, self) + o.parent = parent + return o +end + +function Element:delete () end -- deletes Element + +function Element:getPosition () + return self.x, self.y +end +function Element:setPosition (x, y) + self.x = x or 0 + self.y = y or 0 + return self +end + +function Element:set (name, func) + if type(name) == "string" and func ~= nil then + self[name] = func + end + return self +end + +-- Called when menu tries to focus on this element. +-- If it will return false then menu will skip element and go to next in list. +function Element:focus () + return false +end +function Element:blur () end -- Called when Element loses focus. + +-- LÖVE2D callbacks +function Element:draw (scale) end +function Element:update (dt) end + +-- Controller callbacks +function Element:controlpressed (set, action, key) end +function Element:controlreleased (set, action, key) end + +return Element diff --git a/header.lua b/not/Header.lua index d67e582..a563ab2 100644 --- a/header.lua +++ b/not/Header.lua @@ -1,41 +1,36 @@ --- `Header` --- It dances! - +--- `Header` +-- Swinging title. Header = { + parent = --[[not.Menu]]nil, x = 0, y = 0, text = "", - parent, bounce = 2, } -function Header:new(parent) - local o = {} - setmetatable(o, self) - self.__index = self + +-- `Header` is a child of `Element`. +require "not.Element" +Header.__index = Header +setmetatable(Header, Element) + +function Header:new (parent) + local o = setmetatable({}, self) o.parent = parent return o end -function Header:setText(text) + +function Header:setText (text) self.text = text or "" return self end -function Header:setPosition(x, y) - self.x = x or 0 - self.y = y or 0 - return self -end -function Header:getBounce(f) + +function Header:getBounce (f) local f = f or 1 return math.sin(self.bounce*f*math.pi) end -function Header:getPosition() return self.x,self.y end -- gives x,y of Element -function Header:focus() - return false -end -function Header:blur() end -- Called when Element loses focus -- LÖVE2D callbacks -function Header:draw(scale) +function Header:draw (scale) local angle = self:getBounce(2) local dy = self:getBounce()*4 local x,y = self:getPosition() @@ -43,15 +38,11 @@ function Header:draw(scale) love.graphics.setFont(Bold) love.graphics.printf(string.upper(self.text),x*scale,(y+dy)*scale,400,"center",(angle*5)*math.pi/180,scale,scale,200,12) end -function Header:update(dt) +function Header:update (dt) self.bounce = self.bounce + dt*0.7 if self.bounce > Header.bounce then -- Header.bounce is initial self.bounce = self.bounce - Header.bounce end end --- Controller callbacks -function Header:controlpressed(set, action, key) end -function Header:controlreleased(set, action, key) end - -return Header
\ No newline at end of file +return Header diff --git a/not/Hero.lua b/not/Hero.lua new file mode 100644 index 0000000..feb61da --- /dev/null +++ b/not/Hero.lua @@ -0,0 +1,284 @@ +--- `Hero` +-- Hero (often referred to as: "naut") entity that exists in a game world. +-- Collision category: [2] +Hero = { + -- General and physics + name = "empty", + angle = 0, + facing = 1, + max_velocity = 105, + world = --[[not.World]]nil, + group = nil, + -- Combat + combo = 0, + lives = 3, + spawntimer = 2, + isAlive = true, + punchCooldown = 0.25, + punchdir = 0, -- a really bad thing + -- Movement + inAir = true, + salto = false, + isJumping = false, + isWalking = false, + jumpTimer = 0.16, + jumpCounter = 2, + -- Statics + portrait_sprite = nil, + portrait_frame = nil, + portrait_sheet = getNautsIconsList(), + portrait_box = love.graphics.newQuad( 0, 15, 32,32, 80,130), + sfx = require "config.sounds", +} + +-- `Hero` is a child of `PhysicalBody`. +require "not.PhysicalBody" +Hero.__index = Hero +setmetatable(Hero, PhysicalBody) + +-- Constructor of `Hero`. +function Hero:new (game, world, x, y, name) + local o = setmetatable({}, self) + o:init(name, game, x, y) + -- Load portraits statically. + 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 + +-- Initializer of `Hero`. +function Hero:init (name, world, x, y) + -- Find imagePath based on hero name. + local fileName = name or Hero.name -- INITIAL from metatable + local imagePath = string.format("assets/nauts/%s.png", fileName) + -- `PhysicalBody` initialization. + PhysicalBody.init(self, world, x, y, imagePath) + self:setBodyType("dynamic") + self:setBodyFixedRotation(true) + self.group = -1-#world.Nauts + -- Main fixture initialization. + local fixture = self:addFixture({-5,-8, 5,-8, 5,8, -5,8}, 8) + fixture:setUserData(self) + fixture:setCategory(2) + fixture:setMask(2) + fixture:setGroupIndex(self.group) + -- Actual `Hero` initialization. + self.world = world + self.punchCooldown = 0 + self.name = name + self:setAnimationsList(require("config.animations.hero")) + self:createEffect("respawn") +end + +-- Update callback of `Hero` +function Hero:update (dt) + PhysicalBody.update(self, dt) + if self.body:isDestroyed() then return end + + -- Salto + if self.salto and (self.current == self.animations.walk or self.current == self.animations.default) then + self.angle = (self.angle + 17 * dt * self.facing) % 360 + elseif self.angle ~= 0 then + self.angle = 0 + end + + -- Custom linear damping. + if not self.isWalking then + local face = nil + local x, y = self:getLinearVelocity() + if x < -12 then + face = 1 + elseif x > 12 then + face = -1 + else + face = 0 + end + self:applyForce(40*face,0) + if not self.inAir then + self:applyForce(80*face,0) + end + end + + -- Could you please die? + -- TODO: World/Map function for testing if Point is inside playable area. + local m = self.world.map + local x, y = self:getPosition() + if (x < m.center_x - m.width*1.5 or x > m.center_x + m.width*1.5 or + y < m.center_y - m.height*1.5 or y > m.center_y + m.height*1.5) and + self.isAlive + then + self:die() + end + + -- Respawn timer. + if self.spawntimer > 0 then + self.spawntimer = self.spawntimer - dt + end + if self.spawntimer <= 0 and not self.isAlive and self.lives >= 0 then + self:respawn() + end + + -- # PUNCH + -- Cooldown + self.punchCooldown = self.punchCooldown - dt + if not self.body:isDestroyed() then -- TODO: This is weird + for _,fixture in pairs(self.body:getFixtureList()) do -- TODO: getFixtures from `PhysicalBody` or similar. + 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:setLinearVelocity(0,0) + else + self:setLinearVelocity(38*self.facing,0) + end + end + + if self.punchCooldown <= 0 and self.punchdir == 1 then + self.punchdir = 0 + end +end + +-- TODO: comment them and place them somewhere properly +function Hero:getAngle () + return self.angle +end +function Hero:getHorizontalMirror () + return self.facing +end +function Hero:getOffset () + return 12,15 +end + +-- Draw of `Hero` +function Hero:draw (offset_x, offset_y, scale, debug) + if not self.isAlive then return end + PhysicalBody.draw(self, offset_x, offset_y, scale, debug) +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.isAlive 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).."%",(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:goToNextFrame () + if self.current.repeated or not (self.frame == self.current.frames) then + self.frame = (self.frame % self.current.frames) + 1 + elseif self.isWalking then + 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 + +-- Creates temporary fixture for hero's body that acts as sensor. +-- direction: ("left", "right", "up", "down") +-- Sensor fixture is deleted after time set in UserData[1]; deleted by `not.Hero.update`. +-- TODO: Magic numbers present in `not.Hero.punch`. +function Hero:punch (direction) + self.punchCooldown = Hero.punchCooldown -- INITIAL from metatable + -- Choose shape based on punch direction. + local shape + if direction == "left" then shape = {-2,-6, -20,-6, -20,6, -2,6} end + if direction == "right" then shape = {2,-6, 20,-6, 20,6, 2,6} end + if direction == "up" then shape = {-8,-4, -8,-20, 8,-20, 8,-4} end + if direction == "down" then shape = {-8,4, -8,20, 8,20, 8,4} end + -- Create and set sensor fixture. + local fixture = self:addFixture(shape, 0) + fixture:setSensor(true) + fixture:setCategory(3) + fixture:setMask(1,3) + fixture:setGroupIndex(self.group) + fixture:setUserData({0.08, direction}) + self:playSound(4) +end + +-- Taking damage of `Hero` by successful hit test +-- currently called from World's startContact +-- TODO: attack functions needs to be renamed, because even I have problems understanding them. +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:getLinearVelocity() + self:setLinearVelocity(x,0) + self:applyLinearImpulse((42+self.combo)*horizontal, (68+self.combo)*vertical + 15) + self:setAnimation("damage") + self.combo = math.min(999, self.combo + 10) + self.punchCooldown = 0.08 + self.combo*0.0006 + self:playSound(2) +end + +-- DIE +function Hero:die () + self:playSound(1) + self.combo = Hero.combo -- INITIAL from metatable + self.lives = self.lives - 1 + self.isAlive = false + self.spawntimer = Hero.spawntimer -- INITIAL from metatable + self:setBodyActive(false) + self.world:onNautKilled(self) +end + +-- And then respawn. Like Jon Snow. +function Hero:respawn () + self.isAlive = true + self:setLinearVelocity(0,0) + self:setPosition(self.world:getSpawnPosition()) -- TODO: I'm not convinced about getting new position like this. + self:setBodyActive(true) + self:createEffect("respawn") + self:playSound(7) +end + +-- Sounds +-- TODO: Possibly export to nonexistent SoundEmitter class. Can be used by World (Stage), too. +function Hero:playSound (sfx, force) + if self.isAlive or force then + local source = love.audio.newSource(self.sfx[sfx]) + source:play() + end +end @@ -1,24 +1,20 @@ --- `Menu` (Scene) +--- `Menu` -- It creates single screen of a menu -- I do know that model I used here and in `World` loading configuration files is not flawless but I did not want to rewrite `World`s one but wanted to keep things similar at least in project scope. - -require "music" - --- Here it begins Menu = { scale = getScale(), - elements, --table + elements = --[[{not.Element}]]nil, active = 1, - music, - sprite, - background, - asteroids, - stars, + music = --[[not.Music]]nil, + sprite = --[[love.graphics.newImage]]nil, + background = --[[love.graphics.newImage]]nil, + asteroids = --[[love.graphics.newImage]]nil, + stars = --[[love.graphics.newImage]]nil, asteroids_bounce = 0, stars_frame = 1, stars_delay = 0.8, allowMove = true, - quads = { + quads = { -- TODO: Could be moved to config file or perhaps QuadManager to manage all quads for animations etc. button = { normal = love.graphics.newQuad(0, 0, 58,15, 80,130), active = love.graphics.newQuad(0, 0, 58,15, 80,130) @@ -39,46 +35,54 @@ Menu = { }, } } -function Menu:new(name) - local o = {} - setmetatable(o, self) - self.__index = self - self.sprite = love.graphics.newImage("assets/menu.png") - self.background = love.graphics.newImage("assets/backgrounds/menu.png") - self.asteroids = love.graphics.newImage("assets/asteroids.png") - self.stars = love.graphics.newImage("assets/stars.png") - o.elements = {} - o:load(name) - o.music = Music:new("menu.ogg") + +Menu.__index = Menu + +require "not.Music" + +function Menu:new (name) + local o = setmetatable({}, self) + -- Load statically. + if self.sprite == nil then + self.sprite = love.graphics.newImage("assets/menu.png") + self.background = love.graphics.newImage("assets/backgrounds/menu.png") + self.asteroids = love.graphics.newImage("assets/asteroids.png") + self.stars = love.graphics.newImage("assets/stars.png") + end + o:init(name) return o end -function Menu:delete() + +function Menu:init (name) + self.music = Music:new("menu.ogg") + self:open(name) +end + +function Menu:delete () self.music:delete() end --- Load menu from file -function Menu:load(name) - local name = "config/" .. (name or "menumain") .. ".lua" - local menu = love.filesystem.load(name) - self.active = 1 - self.elements = menu(self) +function Menu:open (name) + local name = name or "main" + self.active = Menu.active --Menu.active is initial + self.elements = love.filesystem.load(string.format("config/menus/%s.lua", name))(self) self.elements[self.active]:focus() end -- Return reference to quads table and menu sprite -function Menu:getSheet() +function Menu:getSheet () return self.sprite, self.quads end -- Cycle elements -function Menu:next() +function Menu:next () self.elements[self.active]:blur() self.active = (self.active%#self.elements)+1 if not self.elements[self.active]:focus() then self:next() end end -function Menu:previous() +function Menu:previous () self.elements[self.active]:blur() if self.active == 1 then self.active = #self.elements @@ -91,7 +95,7 @@ function Menu:previous() end -- LÖVE2D callbacks -function Menu:update(dt) +function Menu:update (dt) for _,element in pairs(self.elements) do element:update(dt) end @@ -107,7 +111,7 @@ function Menu:update(dt) end end end -function Menu:draw() +function Menu:draw () local scale = self.scale local scaler = getRealScale() love.graphics.draw(self.background, 0, 0, 0, scaler, scaler) @@ -120,7 +124,7 @@ function Menu:draw() end -- Controller callbacks -function Menu:controlpressed(set, action, key) +function Menu:controlpressed (set, action, key) if self.allowMove then if action == "down" then self:next() @@ -133,7 +137,7 @@ function Menu:controlpressed(set, action, key) element:controlpressed(set, action, key) end end -function Menu:controlreleased(set, action, key) +function Menu:controlreleased (set, action, key) for _,element in pairs(self.elements) do element:controlreleased(set, action, key) end diff --git a/not/Music.lua b/not/Music.lua new file mode 100644 index 0000000..ee930f4 --- /dev/null +++ b/not/Music.lua @@ -0,0 +1,25 @@ +--- `Music` +-- Simple music player object that plays and loops selected track in single Scene. +Music = { + source = --[[love.audio.newSource]]nil +} + +Music.__index = Music + +function Music:new (trackName) + local o = setmetatable({}, self) + o:init(trackName) + return o +end + +-- TODO: trackName should be passed without file extension. +function Music:init (trackName) + self.source = love.audio.newSource("assets/music/" .. trackName) + self.source:setLooping(true) + self.source:setVolume(.7) + self.source:play() +end + +function Music:delete () + self.source:stop() +end
\ No newline at end of file diff --git a/not/PhysicalBody.lua b/not/PhysicalBody.lua new file mode 100644 index 0000000..e9625fa --- /dev/null +++ b/not/PhysicalBody.lua @@ -0,0 +1,93 @@ +--- `PhysicalBody` +-- Abstract class for drawable entity existing in `not.World`. +PhysicalBody = { + body =--[[love.physics.newBody]]nil, +} + +-- `PhysicalBody` is a child of `Sprite`. +require "not.Sprite" +PhysicalBody.__index = PhysicalBody +setmetatable(PhysicalBody, Sprite) + +--[[ Constructor of `PhysicalBody`. +function PhysicalBody:new (world, x, y, imagePath) + local o = setmetatable({}, self) + o:init(world, x, y, imagePath) + return o +end +]] + +-- Initializer of `PhysicalBody`. +function PhysicalBody:init (world, x, y, imagePath) + Sprite.init(self, imagePath) + self.body = love.physics.newBody(world.world, x, y) +end + +-- Add new fixture to body. +function PhysicalBody:addFixture (shape, density) + local shape = love.physics.newPolygonShape(shape) + local fixture = love.physics.newFixture(self.body, shape, density) + return fixture +end + +-- Position-related methods. +function PhysicalBody:getPosition () + return self.body:getPosition() +end +function PhysicalBody:setPosition (x, y) + self.body:setPosition(x, y) +end + +-- Velocity-related methods. +function PhysicalBody:setLinearVelocity (x, y) + self.body:setLinearVelocity(x, y) +end +function PhysicalBody:getLinearVelocity () + return self.body:getLinearVelocity() +end + +-- Various setters from Body. +-- type: BodyType ("static", "dynamic", "kinematic") +function PhysicalBody:setBodyType (type) + self.body:setType(type) +end +function PhysicalBody:setBodyFixedRotation (bool) + self.body:setFixedRotation(bool) +end +function PhysicalBody:setBodyActive (bool) + self.body:setActive(bool) +end + +-- Physical influence methods. +function PhysicalBody:applyLinearImpulse (x, y) + self.body:applyLinearImpulse(x, y) +end +function PhysicalBody:applyForce (x, y) + self.body:applyForce(x, y) +end + +-- Update of `PhysicalBody`. +function PhysicalBody:update (dt) + Sprite.update(self, dt) +end + +-- Draw of `PhysicalBody`. +function PhysicalBody:draw (offset_x, offset_y, scale, debug) + Sprite.draw(self, offset_x, offset_y, scale) + if debug then + for _,fixture in pairs(self.body:getFixtureList()) do + local category = fixture:getCategory() + if category == 1 then + love.graphics.setColor(255, 69, 0, 140) + end + if category == 2 then + love.graphics.setColor(137, 255, 0, 120) + end + if category == 3 then + love.graphics.setColor(137, 0, 255, 40) + end + -- TODO: `world` is not a member of `PhysicalBody` or its instance normally. + love.graphics.polygon("fill", self.world.camera:translatePoints(self.body:getWorldPoints(fixture:getShape():getPoints()))) + end + end +end diff --git a/not/Platform.lua b/not/Platform.lua new file mode 100644 index 0000000..3748c47 --- /dev/null +++ b/not/Platform.lua @@ -0,0 +1,37 @@ +--- `Platform` +-- Static platform physical object with a sprite. `Players` can walk on it. +-- Collision category: [1] +-- TODO: reformat code to follow new code patterns +-- TODO: comment uncovered code parts +Platform = { + world = --[[not.World]]nil, +} + +-- `Platform` is a child of `PhysicalBody`. +require "not.PhysicalBody" +Platform.__index = Platform +setmetatable(Platform, PhysicalBody) + +-- Constructor of `Platform` +function Platform:new (animations, shape, game, x, y, sprite) + local o = setmetatable({}, self) + o:init(animations, shape, game, x, y, sprite) + return o +end + +-- Initializer of `Platform`. +function Platform:init (animations, shape, world, x, y, imagePath) + PhysicalBody.init(self, world, x, y, imagePath) + self:setAnimationsList(animations) + self.world = world + -- Create table of shapes if single shape is passed. + if type(shape[1]) == "number" then + shape = {shape} + end + -- Add all shapes from as fixtures to body. + for _,single in pairs(shape) do + local fixture = self:addFixture(single) + fixture:setCategory(1) + fixture:setFriction(0.2) + end +end
\ No newline at end of file diff --git a/not/Player.lua b/not/Player.lua new file mode 100644 index 0000000..2a4b2e6 --- /dev/null +++ b/not/Player.lua @@ -0,0 +1,159 @@ +--- `Player` +-- Special `not.Hero` controllable by a player. +Player = { + -- TODO: move functions and properties related to controls from `not.Hero`. + controllerSet = --[[Controller.sets.*]]nil, +} + +-- `Player` is a child of `Hero`. +require "not.Hero" +Player.__index = Player +setmetatable(Player, Hero) + +-- Constructor of `Player`. +function Player:new (name, game, x, y) + local o = setmetatable({}, self) + o:init(name, game, x, y) + -- Load portraits statically to `not.Hero`. + -- TODO: this is heresy, put it into `load` method or something similar. + if Hero.portrait_sprite == nil then + Hero.portrait_sprite = love.graphics.newImage("assets/portraits.png") + Hero.portrait_frame = love.graphics.newImage("assets/menu.png") + end + return o +end + +-- Initializer of `Player`. +function Player:init (...) + Hero.init(self, ...) +end + +-- Controller set manipulation. +function Player:assignControllerSet (set) + self.controllerSet = set +end +function Player:getControllerSet () + return self.controllerSet +end + +-- Check if control of assigned controller is pressed. +function Player:isControlDown (control) + return Controller.isDown(self:getControllerSet(), control) +end + +-- Update of `Player`. +function Player:update (dt) + Hero.update(self, dt) -- TODO: It would be probably a good idea to add return to update functions to terminate if something goes badly in parent's update. + if self.body:isDestroyed() then return end + local x, y = self:getLinearVelocity() + -- Jumping. + if self.isJumping and self.jumpTimer > 0 then + self:setLinearVelocity(x,-160) + self.jumpTimer = self.jumpTimer - dt + end + + -- Walking. + if self:isControlDown("left") then + self.facing = -1 + self:applyForce(-250, 0) + -- Controlled speed limit + if x < -self.max_velocity then + self:applyForce(250, 0) + end + end + if self:isControlDown("right") then + self.facing = 1 + self:applyForce(250, 0) + -- Controlled speed limit + if x > self.max_velocity then + self:applyForce(-250, 0) + end + end +end + +-- Controller callbacks. +function Player:controlpressed (set, action, key) + if set ~= self:getControllerSet() then return end + -- Jumping + if action == "jump" then + if self.jumpCounter > 0 then + -- General jump logics + self.isJumping = 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.jumpCounter == 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.jumpCounter = self.jumpCounter - 1 + end + end + + -- Walking + if (action == "left" or action == "right") then + self.isWalking = true + if (self.current ~= self.animations.attack) and + (self.current ~= self.animations.attack_up) and + (self.current ~= self.animations.attack_down) then + self:setAnimation("walk") + end + end + + -- Punching + if action == "attack" and self.punchCooldown <= 0 then + local f = self.facing + self.salto = false + if self:isControlDown("up") then + -- Punch up + if self.current ~= self.animations.damage then + self:setAnimation("attack_up") + end + self:punch("up") + elseif self:isControlDown("down") then + -- Punch down + if self.current ~= self.animations.damage then + self:setAnimation("attack_down") + end + self:punch("down") + else + -- Punch horizontal + if self.current ~= self.animations.damage then + self:setAnimation("attack") + end + if f == 1 then + self:punch("right") + else + self:punch("left") + end + self.punchdir = 1 + end + end +end +function Player:controlreleased (set, action, key) + if set ~= self:getControllerSet() then return end + -- Jumping + if action == "jump" then + self.isJumping = false + self.jumpTimer = Hero.jumpTimer -- take initial from metatable + end + -- Walking + if (action == "left" or action == "right") then + self.isWalking = false + if not (self:isControlDown("left") or self:isControlDown("right")) and + self.current == self.animations.walk then + self:setAnimation("default") + end + end +end diff --git a/selector.lua b/not/Selector.lua index 61401e7..8e03457 100644 --- a/selector.lua +++ b/not/Selector.lua @@ -1,4 +1,4 @@ --- `Selector` (Element) +--- `Selector` -- Used in Menu for selecting various things from list. Works for each Controller set or globally. --[[ How to use `Selector` in `Menu` config file? @@ -12,9 +12,8 @@ selector:new(menu) :set("global", false) -- true: single selector; false: selector for each controller set present :init() ]] - Selector = { - parent, + parent = --[[not.Menu]]nil, x = 0, y = 0, width = 0, @@ -35,53 +34,39 @@ Selector = { icons_q } +-- `Selector` is a child of `Element`. +require "not.Element" +Selector.__index = Selector +setmetatable(Selector, Element) + -- Constructor -function Selector:new(parent) - local o = {} - setmetatable(o, self) - self.__index = self +function Selector:new (parent) + local o = setmetatable({}, self) o.parent = parent o.sprite, o.quads = parent:getSheet() return o end --- Position -function Selector:getPosition() - return self.x, self.y -end -function Selector:setPosition(x,y) - self.x, self.y = x, y - return self -end - -- Size of single block -function Selector:getSize() +function Selector:getSize () return self.width, self.height end -function Selector:setSize(width, height) +function Selector:setSize (width, height) self.width, self.height = width, height return self end -- Spacing between two blocks -function Selector:getMargin() +function Selector:getMargin () return self.margin end -function Selector:setMargin(margin) +function Selector:setMargin (margin) self.margin = margin return self end --- General setter for Menu configuration files -function Selector:set(name, func) - if type(name) == "string" and func ~= nil then - self[name] = func - end - return self -end - -- Initialize Selector with current settings. -function Selector:init() +function Selector:init () -- Make sure that there is list present if self.list == nil then self.list = {} @@ -105,17 +90,17 @@ function Selector:init() end -- Cycle through list on given number -function Selector:next(n) +function Selector:next (n) local current = self.selections[n] self:setSelection(n, current + 1) end -function Selector:previous(n) +function Selector:previous (n) local current = self.selections[n] self:setSelection(n, current - 1) end -- Get number associated with a given set -function Selector:checkNumber(set) +function Selector:checkNumber (set) if self.global then return 1 end -- For global Selector for n,check in pairs(self.sets) do if check == set then return n end @@ -123,13 +108,13 @@ function Selector:checkNumber(set) end -- Check if given number is locked -function Selector:isLocked(n) +function Selector:isLocked (n) local n = n or 1 return self.locks[n] end -- Sets value of selection of given number. Returns old. -function Selector:setSelection(n, new) +function Selector:setSelection (n, new) -- Functception. It sounds like fun but it isn't. local function limit(new, total) if new > total then @@ -147,18 +132,18 @@ function Selector:setSelection(n, new) end -- Get value of selection of given number -function Selector:getSelection(n) +function Selector:getSelection (n) local n = n or 1 return self.selections[n] end -- Get value from list by selection -function Selector:getListValue(i) +function Selector:getListValue (i) return self.list[i] end -- Checks if selection of given number is unique within Selector scope. -function Selector:isUnique(n) +function Selector:isUnique (n) local selection = self:getSelection(n) for fn,v in pairs(self.selections) do if fn ~= n and self:isLocked(fn) and v == selection then @@ -169,7 +154,7 @@ function Selector:isUnique(n) end -- Get list of selections, checks if not locked are allowed. -function Selector:getFullSelection(allowed) +function Selector:getFullSelection (allowed) local allowed = allowed if allowed == nil then allowed = false end local t = {} @@ -186,7 +171,7 @@ function Selector:getFullSelection(allowed) end -- Rolls and returns random selection from list that is not locked. -function Selector:rollRandom(avoids) +function Selector:rollRandom (avoids) -- Me: You should make it simpler. -- Inner me: Nah, it works. Leave it. -- Me: Ok, let's leave it as it is. @@ -209,7 +194,7 @@ function Selector:rollRandom(avoids) end -- Draw single block of Selector -function Selector:drawBlock(n, x, y, scale) +function Selector:drawBlock (n, x, y, scale) if self.quads == nil or self.sprite == nil then return end local x, y = x or 0, y or 0 local name = self:getListValue(self:getSelection(n)) @@ -249,16 +234,16 @@ function Selector:drawBlock(n, x, y, scale) end -- Menu callbacks -function Selector:focus() -- Called when Element gains focus +function Selector:focus () -- Called when Element gains focus self.focused = true return true end -function Selector:blur() -- Called when Element loses focus +function Selector:blur () -- Called when Element loses focus self.focused = false end -- LÖVE2D callbacks -function Selector:draw(scale) +function Selector:draw (scale) local x,y = self:getPosition() local margin = self:getMargin() local width = self:getSize() @@ -267,7 +252,7 @@ function Selector:draw(scale) self:drawBlock(n, x+(margin+width)*(n-1)+margin*n, y, scale) end end -function Selector:update(dt) +function Selector:update (dt) self.delay = self.delay + dt if self.delay > Selector.delay then -- Selector.delay is initial self.delay = self.delay - Selector.delay @@ -275,7 +260,8 @@ function Selector:update(dt) end -- Controller callbacks -function Selector:controlpressed(set, action, key) +-- TODO: Add action to perform when key is pressed and selector is locked in e.g. to move into character selection from map selection. +function Selector:controlpressed (set, action, key) if set and self.focused then local n = self:checkNumber(set) local locked = self:isLocked(n) @@ -300,6 +286,5 @@ function Selector:controlpressed(set, action, key) end end end -function Selector:controlreleased(set, action, key) end -return Selector
\ No newline at end of file +return Selector diff --git a/settings.lua b/not/Settings.lua index e73b997..e3316f9 100644 --- a/settings.lua +++ b/not/Settings.lua @@ -1,5 +1,9 @@ -Settings = {} -Settings.current = {} +--- `Settings` +-- Stores, loads, saves and changes game settings including Controller sets. +Settings = { + current = {} +} + function Settings.load() if Controller then if not love.filesystem.exists("settings") then @@ -27,6 +31,7 @@ function Settings.load() end end end + function Settings.save() local new = love.filesystem.newFile("settings") local sets = Settings.current @@ -51,6 +56,7 @@ function Settings.save() new:write(string) new:close() end + function Settings.change(n, left, right, up, down, attack, jump, joystick) local bool if joystick then @@ -63,4 +69,4 @@ function Settings.change(n, left, right, up, down, attack, jump, joystick) Settings.save() -- Load settings Settings.load() -end
\ No newline at end of file +end diff --git a/not/Sprite.lua b/not/Sprite.lua new file mode 100644 index 0000000..25d85f1 --- /dev/null +++ b/not/Sprite.lua @@ -0,0 +1,152 @@ +--- `Sprite` +-- Abstract class for drawable animated entities. +Sprite = { + animations =--[[table with animations]]nil, + current =--[[animations.default]]nil, + image =--[[love.graphics.newImage]]nil, + frame = 1, + delay = .1, +} +Sprite.__index = Sprite + +--[[ Constructor of `Sprite`. +function Sprite:new (imagePath) + local o = setmetatable({}, self) + o:init(imagePath) + return o +end +]] + +-- Cleans up reference to image on deletion. +function Sprite:delete () + self.image = nil +end + +-- Initializes new Sprite instance. +function Sprite:init (imagePath) + if type(imagePath) == "string" then + self:setImage(Sprite.newImage(imagePath)) + end +end + +-- Creates new Image object from path. Key-colours two shades of green. Static. +function Sprite.newImage (path) + local imagedata = love.image.newImageData(path) + local transparency = function(x, y, r, g, b, a) + if (r == 0 and g == 128 and b == 64) or + (r == 0 and g == 240 and b == 6) then + a = 0 + end + return r, g, b, a + end + imagedata:mapPixel(transparency) + local image = love.graphics.newImage(imagedata) + return image +end + +-- Sets an Image as an 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 + +-- TODO: Following five methods are stupid, do something about them! +-- Sprite can't be moved by itself. Positioning should be handled by children's methods. +function Sprite:getPosition () + return 0,0 +end +-- Sprite can't be rotated by itself. Rotation should be handled by children's methods. +function Sprite:getAngle () + return 0 +end +-- Sprite can't be mirrored by itself. Mirroring should be handled by children's methods. +function Sprite:getHorizontalMirror () + return 1 +end +function Sprite:getVerticalMirror () + return 1 +end +-- Sprite can't be offset by itself. Offsetting should be handled by children's methods. +function Sprite:getOffset () + return 0,0 +end + +-- Drawing self to LOVE2D buffer. +-- If there is no Quad, it will draw entire image. It won't draw anything if there is no image. +-- TODO: it doesn't follow same pattern as `not.Hero.draw`. It should implement so it can be called from `not.World`. +-- TODO: change children if above changes are in effect: `not.Platform`, `not.Decoration`. +function Sprite:draw (offset_x, offset_y, scale) + local offset_x = offset_x or 0 + local offset_y = offset_y or 0 + + local i, q = self:getImage(), self:getQuad() + local x, y = self:getPosition() + local angle = self:getAngle() + + local scaleX = self:getHorizontalMirror()*(scale or 1) + local scaleY = self:getVerticalMirror()*(scale or 1) + + -- 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 + + if i then + love.graphics.setColor(255,255,255,255) + if q then + love.graphics.draw(i, q, draw_x, draw_y, angle, scaleX, scaleY, self:getOffset()) + else + love.graphics.draw(i, draw_x, draw_y, angle, scaleX, scaleY, self:getOffset()) + 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:goToNextFrame() + end + end +end + +-- Moving to the next frame. +function Sprite:goToNextFrame () + 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/not/World.lua index 9c308b8..bbceec4 100644 --- a/world.lua +++ b/not/World.lua @@ -1,27 +1,15 @@ --- `World` +--- `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 +-- TODO: Possibly move common parts of `World` and `Menu` to abstract class `Scene`. World = { - -- inside - world = nil, - Nauts = nil, - Platforms = nil, - Clouds = nil, - Decorations = nil, - Effects = nil, - Rays = nil, - camera = nil, + world = --[[love.physics.newWorld]]nil, + Nauts = --[[{not.Hero}]]nil, + Platforms = --[[{not.Platform}]]nil, + Clouds = --[[{not.Cloud}]]nil, + Decorations = --[[{not.Decoration}]]nil, + Effects = --[[{not.Effect}]]nil, + Rays = --[[{not.Ray}]]nil, + camera = --[[not.Camera]]nil, -- cloud generator clouds_delay = 5, -- Map @@ -35,46 +23,48 @@ World = { music = nil } +World.__index = World + +require "not.Platform" +require "not.Player" +require "not.Cloud" +require "not.Effect" +require "not.Decoration" +require "not.Ray" +require "not.Music" + -- Constructor of `World` ZA WARUDO! -function World:new(map, nauts) - -- Meta - local o = {} - setmetatable(o, self) - self.__index = self - -- Physical world initialization +function World:new (map, nauts) + local o = setmetatable({}, self) + o:init(map, nauts) + return o +end + +-- Init za warudo +function World:init (map, nauts) + -- Box2D physical world. 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 + self.world = love.physics.newWorld(0, 9.81*64, true) + self.world:setCallbacks(self.beginContact, self.endContact) + -- Tables for entities. TODO: It is still pretty bad! + self.Nauts = {} + self.Platforms = {} + self.Clouds = {} + self.Effects = {} + self.Decorations = {} + self.Rays = {} + -- Random init; TODO: use LOVE2D's random. math.randomseed(os.time()) - -- Map + -- Map and misc. 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 + self:loadMap(map) + self:spawnNauts(nauts) + self.camera = Camera:new(self) + self.music = Music:new(self.map.theme) end -- The end of the world -function World:delete() - self.world:destroy() +function World:delete () for _,platform in pairs(self.Platforms) do platform:delete() end @@ -82,14 +72,14 @@ function World:delete() naut:delete() end self.music:delete() - self = nil + self.world:destroy() end -- Load map from file -function World:loadMap(name) +-- TODO: Change current map model to function-based one. +function World:loadMap (name) local name = name or "default" - name = "maps/" .. name .. ".lua" - local map = love.filesystem.load(name) + local map = love.filesystem.load(string.format("config/maps/%s.lua", name)) self.map = map() -- Platforms for _,platform in pairs(self.map.platforms) do @@ -110,44 +100,49 @@ function World:loadMap(name) end -- Spawn all the nauts for the round -function World:spawnNauts(nauts) +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]) + spawn:assignControllerSet(naut[2]) end end -- Get respawn location -function World:getSpawnPosition() +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)) +-- TODO: it would be nice if function parameters would be same as `not.Platform.new`. +function World:createPlatform (x, y, polygon, sprite, animations) + table.insert(self.Platforms, Platform:new(animations, polygon, self, x, y, sprite)) end -- Add new naut to the world -function World:createNaut(x, y, name) - local naut = Player:new(self, self.world, x, y, name) +-- TODO: separate two methods for `not.Hero` and `not.Player`. +function World:createNaut (x, y, name) + local naut = Player:new(name, self, x, y) table.insert(self.Nauts, naut) return naut end -- Add new decoration to the world -function World:createDecoration(x, y, sprite) +-- TODO: `not.World.create*` functions often have different naming for parameters. It is not ground-breaking but it makes reading code harder for no good reason. +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) +-- TODO: extend variables names to provide better readability. +-- TODO: follow new parameters in `not.Cloud.new` based on `not.Cloud.init`. +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) +function World:randomizeCloud (outside) if outside == nil then outside = true else @@ -167,18 +162,20 @@ function World:randomizeCloud(outside) end -- Add an effect behind nauts -function World:createEffect(name, x, y) +-- TODO: follow new parameters in `not.Effect.new` based on `not.Effect.init`. +-- TODO: along with `createRay` move this nearer reast of `create*` methods for readability. +function World:createEffect (name, x, y) table.insert(self.Effects, Effect:new(name, x, y)) end -- Add a ray -function World:createRay(naut) +function World:createRay (naut) table.insert(self.Rays, Ray:new(naut, self)) end -- get Nauts functions -- more than -1 lives -function World:getNautsPlayable() +function World:getNautsPlayable () local nauts = {} for _,naut in pairs(self.Nauts) do if naut.lives > -1 then @@ -188,27 +185,27 @@ function World:getNautsPlayable() return nauts end -- are alive -function World:getNautsAlive() +function World:getNautsAlive () local nauts = {} for _,naut in self.Nauts do - if naut.alive then + if naut.isAlive then table.insert(nauts, naut) end end return nauts end -- all of them -function World:getNautsAll() +function World:getNautsAll () return self.Nauts end -- get Map name -function World:getMapName() +function World:getMapName () return self.map.name end -- Event: when player is killed -function World:onNautKilled(naut) +function World:onNautKilled (naut) self.camera:startShake() self:createRay(naut) local nauts = self:getNautsPlayable() @@ -220,17 +217,15 @@ function World:onNautKilled(naut) end end -function World:getBounce(f) +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 +function World:update (dt) 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 @@ -279,7 +274,7 @@ function World:update(dt) end end -- Draw -function World:draw() +function World:draw () -- Camera stuff local offset_x, offset_y = self.camera:getOffsets() local scale = self.camera.scale @@ -288,7 +283,7 @@ function World:draw() -- Background love.graphics.draw(self.background, 0, 0, 0, scaler, scaler) - -- This needs to be reworked! + -- TODO: this needs to be reworked! -- Draw clouds for _,cloud in pairs(self.Clouds) do cloud:draw(offset_x, offset_y, scale) @@ -370,14 +365,14 @@ end -- Box2D callbacks -- beginContact -function World.beginContact(a, b, coll) +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 + -- TODO: move landing to `not.Hero` + -- Move them to Hero b:getUserData().inAir = false - b:getUserData().jumpnumber = 2 + b:getUserData().jumpCounter = 2 b:getUserData().salto = false b:getUserData():createEffect("land") end @@ -394,16 +389,16 @@ function World.beginContact(a, b, coll) end end -- endContact -function World.endContact(a, b, coll) +function World.endContact (a, b, coll) if a:getCategory() == 1 then - print(b:getUserData().name .. " is in air") - -- Move them to Player + -- Move them to Hero b:getUserData().inAir = true end end -- Controller callbacks -function World:controlpressed(set, action, key) +-- TODO: names of this methods don't follow naming patterns in this project. See `Controller` and change it. +function World:controlpressed (set, action, key) if key == "f6" and debug then local map = self:getMapName() local nauts = {} @@ -417,7 +412,7 @@ function World:controlpressed(set, action, key) naut:controlpressed(set, action, key) end end -function World:controlreleased(set, action, key) +function World:controlreleased (set, action, key) for k,naut in pairs(self:getNautsAll()) do naut:controlreleased(set, action, key) end diff --git a/platform.lua b/platform.lua deleted file mode 100644 index 908cf73..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 "animated" - --- Metatable of `Platform` --- nils initialized in constructor -Platform = { - body = nil, - shape = nil, - fixture = nil, - world = nil, -} -Platform.__index = Platform -setmetatable(Platform, Animated) - --- 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:setSprite(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 - Animated.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 775dd60..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 "animated" - --- 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, Animated) - --- 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:setSprite(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 - - Animated.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 - Animated.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 @@ -1,2 +1,2 @@ -# Notnauts -Well, yep.
\ No newline at end of file +# Roflnauts 2 +More information available [here](https://www.awesomenauts.com/forum/viewtopic.php?f=12&t=45632). |