summaryrefslogtreecommitdiffhomepage
path: root/not
diff options
context:
space:
mode:
Diffstat (limited to 'not')
-rw-r--r--not/Button.lua76
-rw-r--r--not/Camera.lua145
-rw-r--r--not/Cloud.lua71
-rw-r--r--not/Controller.lua201
-rw-r--r--not/Decoration.lua33
-rw-r--r--not/Effect.lua46
-rw-r--r--not/Element.lua50
-rw-r--r--not/Header.lua48
-rw-r--r--not/Hero.lua284
-rw-r--r--not/Menu.lua144
-rw-r--r--not/Music.lua25
-rw-r--r--not/PhysicalBody.lua93
-rw-r--r--not/Platform.lua37
-rw-r--r--not/Player.lua159
-rw-r--r--not/Ray.lua52
-rw-r--r--not/Selector.lua290
-rw-r--r--not/Settings.lua72
-rw-r--r--not/Sprite.lua152
-rw-r--r--not/World.lua419
19 files changed, 2397 insertions, 0 deletions
diff --git a/not/Button.lua b/not/Button.lua
new file mode 100644
index 0000000..91aca45
--- /dev/null
+++ b/not/Button.lua
@@ -0,0 +1,76 @@
+--- `Button`
+-- Menu element that can be activated by user.
+Button = {
+ parent = --[[not.Menu]]nil,
+ x = 0,
+ y = 0,
+ text = "",
+ focused = false,
+ sprite,
+ quads,
+ delay = 2,
+ parent,
+}
+
+-- `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)
+ self.text = text or ""
+ return self
+end
+
+function Button:focus(next)
+ self.focused = true
+ return true
+end
+function Button:blur ()
+ self.focused = false
+end
+
+function Button:active () end
+function Button:isEnabled ()
+ return true
+end
+
+function Button:draw (scale)
+ local x,y = self:getPosition()
+ local quad = self.quads
+ local sprite = self.sprite
+ if self:isEnabled() then
+ love.graphics.setColor(255, 255, 255, 255)
+ else
+ love.graphics.setColor(140, 140, 140, 255)
+ end
+ love.graphics.draw(sprite, quad.button.normal, x*scale, y*scale, 0, scale, scale)
+ if self.focused then
+ love.graphics.draw(sprite, quad.arrow_l, (x+54+math.floor(self.delay))*scale, (y+5)*scale, 0, scale, scale)
+ love.graphics.draw(sprite, quad.arrow_r, (x-2-math.floor(self.delay))*scale, (y+5)*scale, 0, scale, scale)
+ end
+ 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)
+ 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)
+ if action == "attack" and self.focused and self:isEnabled() then
+ self:active()
+ end
+end
+
+return Button
diff --git a/not/Camera.lua b/not/Camera.lua
new file mode 100644
index 0000000..63489f3
--- /dev/null
+++ b/not/Camera.lua
@@ -0,0 +1,145 @@
+--- `Camera`
+-- Used in drawing.
+Camera = {
+ x = 0,
+ y = 0,
+ dest_x = 0,
+ dest_y = 0,
+ scale = getScale(),
+ scaler = getRealScale(),
+ shake = 0,
+ timer = 0,
+ delay = 0,
+ origin_x = 0,
+ origin_y = 0,
+ shake_x = 0,
+ shake_y = 0,
+ world = --[[not.World]]nil,
+}
+
+-- Constructor of `Camera`
+function Camera:new (world)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.world = world
+ o:setPosition(o:follow())
+ o:setDestination(o:follow())
+ return o
+end
+
+-- Drawing offsets
+function Camera:getOffsets ()
+ return -self.x,-self.y
+end
+
+-- Position
+function Camera:setPosition (x, y)
+ local x = x or 0
+ local y = y or 0
+ self.x, self.y = x, y
+end
+
+function Camera:getPosition ()
+ return self.x, self.y
+end
+
+function Camera:getPositionScaled ()
+ return self.x*self.scale, self.y*self.scale
+end
+
+-- Destination
+function Camera:setDestination (x, y)
+ local x = x or 0
+ local y = y or 0
+ self.dest_x, self.dest_y = x, y
+end
+
+function Camera:getDestination ()
+ return self.dest_x, self.dest_y
+end
+
+-- Translate points
+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
+end
+
+function Camera:translatePoints(...)
+ local a = {...}
+ local r = {}
+ local x,y = self:getOffsets()
+ for k,v in pairs(a) do
+ if k%2 == 1 then
+ table.insert(r, (v + x) * self.scale)
+ else
+ table.insert(r, (v + y) * self.scale)
+ end
+ end
+ return r
+end
+
+-- Shake it
+-- Really bad script, but for now it works
+function Camera:shake ()
+ if self.shake_x == 0 then
+ self.shake_x = math.random(-10, 10) * 2
+ elseif self.shake_x > 0 then
+ self.shake_x = math.random(-10, -1) * 2
+ elseif self.shake_x < 0 then
+ self.shake_x = math.random(10, 1) * 2
+ end
+ if self.shake_y == 0 then
+ self.shake_y = math.random(-10, 10) * 2
+ elseif self.shake_y > 0 then
+ self.shake_y = math.random(-10, -1) * 2
+ elseif self.shake_y < 0 then
+ self.shake_y = math.random(10, 1) * 2
+ end
+ local x = self.origin_x + self.shake_x
+ local y = self.origin_y + self.shake_y
+ self:setDestination(x, y)
+end
+
+function Camera:startShake ()
+ self.timer = 0.3
+ self.origin_x, self.origin_y = self:getPosition()
+end
+
+-- Move follow
+function Camera:follow ()
+ local map = self.world.map
+ local sum_x,sum_y,i = map.center_x, map.center_y, 1
+ for k,naut in pairs(self.world.Nauts) do
+ local naut_x,naut_y = naut:getPosition()
+ if math.abs(naut_x - map.center_x) < map.width/2 and
+ math.abs(naut_y - map.center_y) < map.height/2 then
+ i = i + 1
+ sum_x = naut_x + sum_x
+ sum_y = naut_y + sum_y
+ end
+ end
+ local x = sum_x / i - love.graphics.getWidth()/self.scale/2
+ local y = sum_y / i - love.graphics.getHeight()/self.scale/2 + 4*self.scale -- hotfix
+ return x,y
+end
+
+-- Update
+function Camera:update (dt)
+ if self.timer > 0 then
+ self.timer = self.timer - dt
+ if self.delay <= 0 then
+ self:shake()
+ self.delay = 0.02
+ else
+ self.delay = self.delay - dt
+ end
+ else
+ self:setDestination(self:follow())
+ end
+ local dx, dy = self:getDestination()
+ dx = (dx - self.x) * 6 * dt
+ dy = (dy - self.y) * 6 * dt
+ self:setPosition(self.x + dx, self.y + dy)
+end
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/not/Controller.lua b/not/Controller.lua
new file mode 100644
index 0000000..8a2a863
--- /dev/null
+++ b/not/Controller.lua
@@ -0,0 +1,201 @@
+--- `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`.
+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
+function Controller.controlreleased(set, action, key) end
+
+-- Create new controls set.
+function Controller.registerSet(left, right, up, down, attack, jump, joystick)
+ if not Controller.isJoystickUnique(joystick) then return end
+ local set = {}
+ set.left = left or "left"
+ set.right = right or "right"
+ set.up = up or "up"
+ set.down = down or "down"
+ set.attack = attack or "return"
+ set.jump = jump or "rshift"
+ set.joystick = joystick
+ table.insert(Controller.sets, set)
+ print(set, left, right, up, down, attack, jump, joystick)
+ return set
+end
+
+-- Reset table of controls sets.
+function Controller.reset()
+ local t = {}
+ Controller.sets = t
+end
+
+-- Get table of controls sets.
+function Controller.getSets()
+ return Controller.sets
+end
+
+-- Checks if given joystick is unique in current set of Controller sets
+function Controller.isJoystickUnique(joystick)
+ if joystick ~= nil then
+ for _,set in pairs(Controller.sets) do
+ if set.joystick == joystick then return false end
+ end
+ end
+ return true
+end
+
+-- Tests all sets if they have control assigned to given key and joystick.
+function Controller.testSets(key, joystick)
+ for i,set in pairs(Controller.sets) do
+ local action = Controller.testControl(set, key, joystick)
+ if action ~= nil then
+ return set, action
+ end
+ end
+ return nil, nil
+end
+
+-- Tests given set if it has controll assigned to given key and joystick.
+function Controller.testControl(set, key, joystick)
+ -- First test if it is joystick and if it is correct one
+ if joystick == set.joystick then
+ if key == set.left then
+ return "left"
+ elseif key == set.right then
+ return "right"
+ elseif key == set.up then
+ return "up"
+ elseif key == set.down then
+ return "down"
+ elseif key == set.attack then
+ return "attack"
+ elseif key == set.jump then
+ return "jump"
+ end
+ end
+end
+
+-- Checks if given action of given set is down
+function Controller.isDown(set, action)
+ if set ~= nil then
+ if set.joystick == nil then
+ return love.keyboard.isDown(set[action])
+ else
+ if not Controller.isAxis(set[action]) then
+ return set.joystick:isGamepadDown(set[action])
+ else
+ return Controller.getAxisState(set.joystick, set[action])
+ end
+ end
+ end
+end
+
+-- Return key name from given axis and value
+function Controller.createAxisName(axis, value)
+ local key = "axis:"..axis
+ if value == 0 then
+ key = key.."0"
+ elseif value > 0 then
+ key = key.."+"
+ else
+ key = key.."-"
+ end
+ return key
+end
+
+-- Checks if given key is an axis
+function Controller.isAxis(key)
+ if string.find(key, "axis:") then
+ return true
+ else
+ return false
+ end
+end
+
+-- Checks state of key assigned to axis of given joystick
+function Controller.getAxisState(joystick, key)
+ if Controller.axes[joystick] then
+ local state = Controller.axes[joystick][key]
+ if state ~= nil then
+ return state
+ else
+ return false
+ end
+ end
+end
+
+-- Sets state of key assigned to axis of given joystick
+function Controller.setAxisState(joystick, key, state)
+ if Controller.axes[joystick] == nil then
+ Controller.axes[joystick] = {}
+ end
+ Controller.axes[joystick][key] = state
+end
+
+-- Simulate pressing key on an axis
+function Controller.axisPress(joystick, axis, value)
+ local key = Controller.createAxisName(axis, value)
+ local set, action = Controller.testSets(key, joystick)
+ local state = Controller.getAxisState(joystick, key)
+ if not state then
+ print(joystick, set, action, key)
+ Controller.setAxisState(joystick, key, true)
+ Controller.controlpressed(set, action, key)
+ end
+end
+
+-- Simulate releasing key on an axis
+function Controller.axisRelease(joystick, axis, value)
+ local key = Controller.createAxisName(axis, value)
+ local set, action = Controller.testSets(key, joystick)
+ local state = Controller.getAxisState(joystick, key)
+ if state then
+ Controller.setAxisState(joystick, key,false)
+ Controller.controlreleased(set, action, key)
+ end
+end
+
+-- Callbacks from LÖVE2D
+-- Load gamepad mappings from db file and init module
+function Controller.load()
+ love.joystick.loadGamepadMappings("gamecontrollerdb.txt")
+end
+
+-- Gamepad input callbacks
+function Controller.gamepadaxis(joystick, axis, value)
+ if value ~= 0 then
+ if math.abs(value) > Controller.deadzone then
+ Controller.axisPress(joystick, axis, value)
+ else
+ Controller.axisRelease(joystick, axis, value)
+ end
+ else
+ Controller.axisRelease(joystick, axis, 1)
+ Controller.axisRelease(joystick, axis, -1)
+ end
+end
+function Controller.gamepadpressed(joystick, key)
+ local set, action = Controller.testSets(key, joystick)
+ print(joystick, set, action, key)
+ Controller.controlpressed(set, action, key)
+end
+function Controller.gamepadreleased(joystick, key)
+ local set, action = Controller.testSets(key, joystick)
+ Controller.controlreleased(set, action, key)
+end
+
+-- Keyboard input callbacks
+function Controller.keypressed(key)
+ local set, action = Controller.testSets(key, nil)
+ print(nil, set, action, key)
+ Controller.controlpressed(set, action, key)
+end
+function Controller.keyreleased(key)
+ local set, action = Controller.testSets(key, nil)
+ Controller.controlreleased(set, action, key)
+end \ No newline at end of file
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/not/Header.lua b/not/Header.lua
new file mode 100644
index 0000000..a563ab2
--- /dev/null
+++ b/not/Header.lua
@@ -0,0 +1,48 @@
+--- `Header`
+-- Swinging title.
+Header = {
+ parent = --[[not.Menu]]nil,
+ x = 0,
+ y = 0,
+ text = "",
+ bounce = 2,
+}
+
+-- `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)
+ self.text = text or ""
+ return self
+end
+
+function Header:getBounce (f)
+ local f = f or 1
+ return math.sin(self.bounce*f*math.pi)
+end
+
+-- LÖVE2D callbacks
+function Header:draw (scale)
+ local angle = self:getBounce(2)
+ local dy = self:getBounce()*4
+ local x,y = self:getPosition()
+ love.graphics.setColor(255,255,255,255)
+ 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)
+ 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
+
+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
diff --git a/not/Menu.lua b/not/Menu.lua
new file mode 100644
index 0000000..8ef1861
--- /dev/null
+++ b/not/Menu.lua
@@ -0,0 +1,144 @@
+--- `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.
+Menu = {
+ scale = getScale(),
+ elements = --[[{not.Element}]]nil,
+ active = 1,
+ 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 = { -- 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)
+ },
+ portrait = {
+ normal = love.graphics.newQuad( 0, 15, 32,32, 80,130),
+ active = love.graphics.newQuad(32, 15, 32,32, 80,130)
+ },
+ panorama = {
+ normal = love.graphics.newQuad(0,47, 80,42, 80,130),
+ active = love.graphics.newQuad(0,88, 80,42, 80,130)
+ },
+ arrow_l = love.graphics.newQuad(68, 0, 6, 6, 80,130),
+ arrow_r = love.graphics.newQuad(74, 0, 6, 6, 80,130),
+ stars = {
+ love.graphics.newQuad( 0, 0, 320, 200, 640,200),
+ love.graphics.newQuad(320, 0, 320, 200, 640,200)
+ },
+ }
+}
+
+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:init (name)
+ self.music = Music:new("menu.ogg")
+ self:open(name)
+end
+
+function Menu:delete ()
+ self.music:delete()
+end
+
+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 ()
+ return self.sprite, self.quads
+end
+
+-- Cycle elements
+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 ()
+ self.elements[self.active]:blur()
+ if self.active == 1 then
+ self.active = #self.elements
+ else
+ self.active = self.active - 1
+ end
+ if not self.elements[self.active]:focus() then
+ self:previous()
+ end
+end
+
+-- LÖVE2D callbacks
+function Menu:update (dt)
+ for _,element in pairs(self.elements) do
+ element:update(dt)
+ end
+ self.asteroids_bounce = self.asteroids_bounce + dt*0.1
+ if self.asteroids_bounce > 2 then self.asteroids_bounce = self.asteroids_bounce - 2 end
+ self.stars_delay = self.stars_delay - dt
+ if self.stars_delay < 0 then
+ self.stars_delay = self.stars_delay + Menu.stars_delay --Menu.stars_delay is initial
+ if self.stars_frame == 2 then
+ self.stars_frame = 1
+ else
+ self.stars_frame = 2
+ end
+ end
+end
+function Menu:draw ()
+ local scale = self.scale
+ local scaler = getRealScale()
+ love.graphics.draw(self.background, 0, 0, 0, scaler, scaler)
+ love.graphics.draw(self.stars, self.quads.stars[self.stars_frame], 0, 0, 0, scaler, scaler)
+ love.graphics.draw(self.asteroids, 0, math.floor(64+math.sin(self.asteroids_bounce*math.pi)*4)*scaler, 0, scaler, scaler)
+ love.graphics.setFont(Font)
+ for _,element in pairs(self.elements) do
+ element:draw(scale)
+ end
+end
+
+-- Controller callbacks
+function Menu:controlpressed (set, action, key)
+ if self.allowMove then
+ if action == "down" then
+ self:next()
+ end
+ if action == "up" then
+ self:previous()
+ end
+ end
+ for _,element in pairs(self.elements) do
+ element:controlpressed(set, action, key)
+ end
+end
+function Menu:controlreleased (set, action, key)
+ for _,element in pairs(self.elements) do
+ element:controlreleased(set, action, key)
+ end
+end \ No newline at end of file
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/not/Ray.lua b/not/Ray.lua
new file mode 100644
index 0000000..bbe11c1
--- /dev/null
+++ b/not/Ray.lua
@@ -0,0 +1,52 @@
+-- `Ray`
+-- That awesome effect that blinks when player dies!
+
+-- WHOLE CODE HAS FLAG OF "need a cleanup"
+
+Ray = {
+ naut = nil,
+ world = nil,
+ canvas = nil,
+ delay = 0.3
+}
+function Ray:new(naut, world)
+ -- Meta
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ -- Init
+ o.naut = naut
+ o.world = world
+ -- Cavas, this is temporary, I believe.
+ local scale = o.world.camera.scale
+ local w, h = love.graphics.getWidth(), love.graphics.getHeight()
+ o.canvas = love.graphics.newCanvas(w/scale, h/scale)
+ return o
+end
+function Ray:update(dt)
+ self.delay = self.delay - dt
+ if self.delay < 0 then
+ return true -- delete
+ end
+ return false
+end
+function Ray:draw(offset_x, offset_y, scale)
+ love.graphics.setCanvas(self.canvas)
+ love.graphics.clear()
+ love.graphics.setColor(255, 247, 228, 247)
+ love.graphics.setLineStyle("rough")
+ love.graphics.setLineWidth(self.delay*160)
+ local x, y = self.naut:getPosition()
+ local m = self.world.map
+ local dy = m.height
+ if y > m.center_y then
+ dy = -dy
+ end
+ love.graphics.line(-x+offset_x,-y+offset_y-dy*0.7,x+offset_x,y+dy*0.7+offset_y)
+ -- reset
+ love.graphics.setCanvas()
+ love.graphics.setLineWidth(1)
+ love.graphics.setColor(255,255,255,255)
+ -- draw on screen
+ love.graphics.draw(self.canvas, 0, 0, 0, scale, scale)
+end
diff --git a/not/Selector.lua b/not/Selector.lua
new file mode 100644
index 0000000..8e03457
--- /dev/null
+++ b/not/Selector.lua
@@ -0,0 +1,290 @@
+--- `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?
+selector:new(menu)
+ :setPosition(x, y)
+ :setMargin(8) -- each block has marigin on both sides; they do stack
+ :setSize(32, 32) -- size of single graphics frame
+ :set("list", require "nautslist")
+ :set("icons_i", love.graphics.newImage("assets/portraits.png"))
+ :set("icons_q", require "portraits")
+ :set("global", false) -- true: single selector; false: selector for each controller set present
+ :init()
+]]
+Selector = {
+ parent = --[[not.Menu]]nil,
+ x = 0,
+ y = 0,
+ width = 0,
+ height = 0,
+ margin = 0,
+ focused = false,
+ global = false,
+ delay = 2,
+ first = false,
+ list,
+ sets,
+ locks,
+ selections,
+ shape = "portrait",
+ sprite,
+ quads,
+ icons_i,
+ 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({}, self)
+ o.parent = parent
+ o.sprite, o.quads = parent:getSheet()
+ return o
+end
+
+-- Size of single block
+function Selector:getSize ()
+ return self.width, self.height
+end
+function Selector:setSize (width, height)
+ self.width, self.height = width, height
+ return self
+end
+
+-- Spacing between two blocks
+function Selector:getMargin ()
+ return self.margin
+end
+function Selector:setMargin (margin)
+ self.margin = margin
+ return self
+end
+
+-- Initialize Selector with current settings.
+function Selector:init ()
+ -- Make sure that there is list present
+ if self.list == nil then
+ self.list = {}
+ end
+ -- Initialize global Selector
+ if self.global then
+ self.sets = {}
+ self.locks = {false}
+ self.selections = {1}
+ -- Initialize Selector for Controllers
+ else
+ self.sets = Controller.getSets()
+ self.locks = {}
+ self.selections = {}
+ for n=1,#self.sets do
+ self.locks[n] = false
+ self.selections[n] = 1
+ end
+ end
+ return self
+end
+
+-- Cycle through list on given number
+function Selector:next (n)
+ local current = self.selections[n]
+ self:setSelection(n, current + 1)
+end
+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)
+ 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
+ end
+end
+
+-- Check if given number is locked
+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)
+ -- Functception. It sounds like fun but it isn't.
+ local function limit(new, total)
+ if new > total then
+ return limit(new - total, total)
+ elseif new < 1 then
+ return limit(total + new, total)
+ else
+ return new
+ end
+ end
+ local n = n or 1
+ local old = self.selections[n]
+ self.selections[n] = limit(new, #self.list)
+ return old
+end
+
+-- Get value of selection of given number
+function Selector:getSelection (n)
+ local n = n or 1
+ return self.selections[n]
+end
+
+-- Get value from list by selection
+function Selector:getListValue (i)
+ return self.list[i]
+end
+
+-- Checks if selection of given number is unique within Selector scope.
+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
+ return false
+ end
+ end
+ return true
+end
+
+-- Get list of selections, checks if not locked are allowed.
+function Selector:getFullSelection (allowed)
+ local allowed = allowed
+ if allowed == nil then allowed = false end
+ local t = {}
+ for n,v in pairs(self.selections) do
+ local name = self:getListValue(self:getSelection(n))
+ local locked = self:isLocked(n)
+ if locked or allowed then
+ local a = {name}
+ if self.sets[n] then table.insert(a, self.sets[n]) end
+ table.insert(t, a)
+ end
+ end
+ return t
+end
+
+-- Rolls and returns random selection from list that is not locked.
+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.
+ local avoids = avoids or {}
+ local total = #self.list
+ local random = love.math.random(1, total)
+ local eligible = true
+ for _,avoid in ipairs(avoids) do
+ if random == avoid then
+ eligible = false
+ break
+ end
+ end
+ if not eligible or self:isLocked(random) then
+ table.insert(avoids, random)
+ return self:rollRandom(avoid)
+ else
+ return random
+ end
+end
+
+-- Draw single block of Selector
+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))
+ local locked = self:isLocked(n)
+ local sprite = self.sprite
+ local quad = self.quads
+ local icon = self.icons_i
+ local iconq = self.icons_q[name]
+ local w,h = self:getSize()
+ local unique = self:isUnique(n)
+ if unique then
+ love.graphics.setColor(255, 255, 255, 255)
+ else
+ love.graphics.setColor(140, 140, 140, 255)
+ end
+ if not locked then
+ love.graphics.draw(sprite, quad[self.shape].normal, x*scale, y*scale, 0, scale, scale)
+ else
+ love.graphics.draw(sprite, quad[self.shape].active, x*scale, y*scale, 0, scale, scale)
+ end
+ love.graphics.draw(icon, iconq, (x+2)*scale, (y+3)*scale, 0, scale, scale)
+ if self.focused then
+ local dy = (h-6)/2
+ if not locked then
+ love.graphics.draw(sprite, quad.arrow_l, (x+0-2-math.floor(self.delay))* scale, (y+dy)*scale, 0, scale, scale)
+ love.graphics.draw(sprite, quad.arrow_r, (x+w-4+math.floor(self.delay))*scale, (y+dy)*scale, 0, scale, scale)
+ else
+ love.graphics.draw(sprite, quad.arrow_r, (x+0-2-math.floor(self.delay))* scale, (y+dy)*scale, 0, scale, scale)
+ love.graphics.draw(sprite, quad.arrow_l, (x+w-4+math.floor(self.delay))*scale, (y+dy)*scale, 0, scale, scale)
+ end
+ end
+ if (self:getSelection(n) ~= 1 or self.first) then
+ love.graphics.setFont(Font)
+ love.graphics.setColor(255, 255, 255, 255)
+ love.graphics.printf(string.upper(name), (x-w)*scale, (y+h+1)*scale, w*3, "center", 0, scale, scale)
+ end
+end
+
+-- Menu callbacks
+function Selector:focus () -- Called when Element gains focus
+ self.focused = true
+ return true
+end
+function Selector:blur () -- Called when Element loses focus
+ self.focused = false
+end
+
+-- LÖVE2D callbacks
+function Selector:draw (scale)
+ local x,y = self:getPosition()
+ local margin = self:getMargin()
+ local width = self:getSize()
+ x = x - #self.selections*0.5*(margin+margin+width)
+ for n=1,#self.selections do
+ self:drawBlock(n, x+(margin+width)*(n-1)+margin*n, y, scale)
+ end
+end
+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
+ end
+end
+
+-- Controller callbacks
+-- 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)
+ if action == "left" and not locked then self:previous(n) end
+ if action == "right" and not locked then self:next(n) end
+ if action == "attack" then
+ local name = self:getListValue(self:getSelection(n))
+ if name == "random" then
+ self:setSelection(n, self:rollRandom({1,2})) -- avoid empty naut
+ self.locks[n] = true
+ else
+ -- If not empty or if first is allowed. Additionaly must be unique selection.
+ if (self:getSelection(n) ~= 1 or self.first) and self:isUnique(n) then
+ self.locks[n] = true
+ end
+ end
+ end
+ if action == "jump" then
+ if locked then
+ self.locks[n] = false
+ end
+ end
+ end
+end
+
+return Selector
diff --git a/not/Settings.lua b/not/Settings.lua
new file mode 100644
index 0000000..e3316f9
--- /dev/null
+++ b/not/Settings.lua
@@ -0,0 +1,72 @@
+--- `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
+ local def = love.filesystem.newFile("settings.default")
+ local new = love.filesystem.newFile("settings")
+ new:open("w") def:open("r")
+ new:write(def:read())
+ new:close() def:close()
+ end
+ local getSettings = love.filesystem.load("settings")
+ Settings.current = getSettings()
+ Controller.reset()
+ local joysticksList = love.joystick.getJoysticks() -- local list for editing
+ for _,set in pairs(Settings.current) do
+ local isJoystick = set[7]
+ local joystick
+ if isJoystick then
+ -- take and remove first joystick from list
+ joystick = joysticksList[1]
+ table.remove(joysticksList, 1)
+ end
+ if not isJoystick or joystick then
+ Controller.registerSet(set[1], set[2], set[3], set[4], set[5], set[6], joystick)
+ end
+ end
+ end
+end
+
+function Settings.save()
+ local new = love.filesystem.newFile("settings")
+ local sets = Settings.current
+ local string = "return {\n"
+ for i,set in pairs(sets) do
+ string = string .. "\t{"
+ for j,word in pairs(set) do
+ if j ~= 7 then
+ string = string .. "\"" .. word .. "\", "
+ else
+ if word then
+ string = string .. "true"
+ else
+ string = string .. "false"
+ end
+ end
+ end
+ string = string .. "},\n"
+ end
+ string = string .. "}"
+ new:open("w")
+ new:write(string)
+ new:close()
+end
+
+function Settings.change(n, left, right, up, down, attack, jump, joystick)
+ local bool
+ if joystick then
+ bool = true
+ else
+ bool = false
+ end
+ -- Save current settings
+ Settings.current[n] = {left, right, up, down, attack, jump, bool}
+ Settings.save()
+ -- Load settings
+ Settings.load()
+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/not/World.lua b/not/World.lua
new file mode 100644
index 0000000..bbceec4
--- /dev/null
+++ b/not/World.lua
@@ -0,0 +1,419 @@
+--- `World`
+-- Used to manage physical world and everything inside it: clouds, platforms, nauts, background etc.
+-- TODO: Possibly move common parts of `World` and `Menu` to abstract class `Scene`.
+World = {
+ 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
+ map = nil,
+ background = nil,
+ -- Gameplay status
+ lastNaut = false,
+ -- "WINNER"
+ win_move = 0,
+ -- Music
+ 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)
+ 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)
+ 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 and misc.
+ local map = map or "default"
+ 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 ()
+ for _,platform in pairs(self.Platforms) do
+ platform:delete()
+ end
+ for _,naut in pairs(self.Nauts) do
+ naut:delete()
+ end
+ self.music:delete()
+ self.world:destroy()
+end
+
+-- Load map from file
+-- TODO: Change current map model to function-based one.
+function World:loadMap (name)
+ local name = name or "default"
+ local map = love.filesystem.load(string.format("config/maps/%s.lua", name))
+ self.map = map()
+ -- Platforms
+ for _,platform in pairs(self.map.platforms) do
+ self:createPlatform(platform.x, platform.y, platform.shape, platform.sprite, platform.animations)
+ end
+ -- Decorations
+ for _,decoration in pairs(self.map.decorations) do
+ self:createDecoration(decoration.x, decoration.y, decoration.sprite)
+ end
+ -- Background
+ self.background = love.graphics.newImage(self.map.background)
+ -- Clouds
+ if self.map.clouds then
+ for i=1,6 do
+ self:randomizeCloud(false)
+ end
+ end
+end
+
+-- Spawn all the nauts for the round
+function World:spawnNauts (nauts)
+ for _,naut in pairs(nauts) do
+ local x,y = self:getSpawnPosition()
+ local spawn = self:createNaut(x, y, naut[1])
+ spawn:assignControllerSet(naut[2])
+ end
+end
+
+-- Get respawn location
+function World:getSpawnPosition ()
+ local n = math.random(1, #self.map.respawns)
+ return self.map.respawns[n].x, self.map.respawns[n].y
+end
+
+-- Add new platform to the world
+-- 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
+-- 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
+-- 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
+-- 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)
+ if outside == nil then
+ outside = true
+ else
+ outside = outside
+ end
+ local x,y,t,v
+ local m = self.map
+ if outside then
+ x = m.center_x-m.width*1.2+math.random(-50,20)
+ else
+ x = math.random(m.center_x-m.width/2,m.center_x+m.width/2)
+ end
+ y = math.random(m.center_y-m.height/2, m.center_y+m.height/2)
+ t = math.random(1,3)
+ v = math.random(8,18)
+ self:createCloud(x, y, t, v)
+end
+
+-- Add an effect behind nauts
+-- 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)
+ table.insert(self.Rays, Ray:new(naut, self))
+end
+
+-- get Nauts functions
+-- more than -1 lives
+function World:getNautsPlayable ()
+ local nauts = {}
+ for _,naut in pairs(self.Nauts) do
+ if naut.lives > -1 then
+ table.insert(nauts, naut)
+ end
+ end
+ return nauts
+end
+-- are alive
+function World:getNautsAlive ()
+ local nauts = {}
+ for _,naut in self.Nauts do
+ if naut.isAlive then
+ table.insert(nauts, naut)
+ end
+ end
+ return nauts
+end
+-- all of them
+function World:getNautsAll ()
+ return self.Nauts
+end
+
+-- get Map name
+function World:getMapName ()
+ return self.map.name
+end
+
+-- Event: when player is killed
+function World:onNautKilled (naut)
+ self.camera:startShake()
+ self:createRay(naut)
+ local nauts = self:getNautsPlayable()
+ if self.lastNaut then
+ changeScene(Menu:new())
+ elseif #nauts < 2 then
+ self.lastNaut = true
+ naut:playSound(5, true)
+ end
+end
+
+function World:getBounce (f)
+ local f = f or 1
+ return math.sin(self.win_move*f*math.pi)
+end
+
+-- LÖVE2D callbacks
+-- Update ZU WARUDO
+function World:update (dt)
+ self.world:update(dt)
+ self.camera:update(dt)
+ -- Engine world: Nauts, Grounds (kek) and Decorations - all Animateds (top kek)
+ for _,naut in pairs(self.Nauts) do
+ naut:update(dt)
+ end
+ for _,platform in pairs(self.Platforms) do
+ platform:update(dt)
+ end
+ for _,decoration in pairs(self.Decorations) do
+ decoration:update(dt)
+ end
+ -- Clouds
+ if self.map.clouds then
+ -- generator
+ local n = table.getn(self.Clouds)
+ self.clouds_delay = self.clouds_delay - dt
+ if self.clouds_delay < 0 and
+ n < 18
+ then
+ self:randomizeCloud()
+ self.clouds_delay = self.clouds_delay + World.clouds_delay -- World.clouds_delay is initial
+ end
+ -- movement
+ for _,cloud in pairs(self.Clouds) do
+ if cloud:update(dt) > 340 then
+ table.remove(self.Clouds, _)
+ end
+ end
+ end
+ -- Effects
+ for _,effect in pairs(self.Effects) do
+ if effect:update(dt) then
+ table.remove(self.Effects, _)
+ end
+ end
+ -- Rays
+ for _,ray in pairs(self.Rays) do
+ if ray:update(dt) then
+ table.remove(self.Rays, _)
+ end
+ end
+ -- Bounce `winner`
+ self.win_move = self.win_move + dt
+ if self.win_move > 2 then
+ self.win_move = self.win_move - 2
+ end
+end
+-- Draw
+function World:draw ()
+ -- Camera stuff
+ local offset_x, offset_y = self.camera:getOffsets()
+ local scale = self.camera.scale
+ local scaler = self.camera.scaler
+
+ -- Background
+ love.graphics.draw(self.background, 0, 0, 0, scaler, scaler)
+
+ -- TODO: this needs to be reworked!
+ -- Draw clouds
+ for _,cloud in pairs(self.Clouds) do
+ cloud:draw(offset_x, offset_y, scale)
+ end
+
+ -- Draw decorations
+ for _,decoration in pairs(self.Decorations) do
+ decoration:draw(offset_x, offset_y, scale)
+ end
+
+ -- Draw effects
+ for _,effect in pairs(self.Effects) do
+ effect:draw(offset_x,offset_y, scale)
+ end
+
+ -- Draw player
+ for _,naut in pairs(self.Nauts) do
+ naut:draw(offset_x, offset_y, scale, debug)
+ end
+
+ -- Draw ground
+ for _,platform in pairs(self.Platforms) do
+ platform:draw(offset_x, offset_y, scale, debug)
+ end
+
+ -- Draw rays
+ for _,ray in pairs(self.Rays) do
+ ray:draw(offset_x, offset_y, scale)
+ end
+
+ -- draw center
+ if debug then
+ local c = self.camera
+ local w, h = love.graphics.getWidth(), love.graphics.getHeight()
+ -- draw map center
+ love.graphics.setColor(130,130,130)
+ love.graphics.setLineWidth(1)
+ love.graphics.setLineStyle("rough")
+ local cx, cy = c:getPositionScaled()
+ local x1, y1 = c:translatePosition(self.map.center_x, cy)
+ local x2, y2 = c:translatePosition(self.map.center_x, cy+h)
+ love.graphics.line(x1,y1,x2,y2)
+ local x1, y1 = c:translatePosition(cx, self.map.center_y)
+ local x2, y2 = c:translatePosition(cx+w, self.map.center_y)
+ love.graphics.line(x1,y1,x2,y2)
+ -- draw ox, oy
+ love.graphics.setColor(200,200,200)
+ love.graphics.setLineStyle("rough")
+ local cx, cy = c:getPositionScaled()
+ local x1, y1 = c:translatePosition(0, cy)
+ local x2, y2 = c:translatePosition(0, cy+h)
+ love.graphics.line(x1,y1,x2,y2)
+ local x1, y1 = c:translatePosition(cx, 0)
+ local x2, y2 = c:translatePosition(cx+w, 0)
+ love.graphics.line(x1,y1,x2,y2)
+ end
+
+ -- Draw HUDs
+ for _,naut in pairs(self.Nauts) do
+ -- I have no idea where to place them T_T
+ -- let's do: bottom-left, bottom-right, top-left, top-right
+ local w, h = love.graphics.getWidth()/scale, love.graphics.getHeight()/scale
+ local y, e = 1, 1
+ if _ < 3 then y, e = h-33, 0 end
+ naut:drawHUD(1+(_%2)*(w-34), y, scale, e)
+ end
+
+ -- Draw winner
+ if self.lastNaut then
+ local w, h = love.graphics.getWidth()/scale, love.graphics.getHeight()/scale
+ local angle = self:getBounce(2)
+ local dy = self:getBounce()*3
+ love.graphics.setFont(Bold)
+ love.graphics.printf("WINNER",(w/2)*scale,(42+dy)*scale,336,"center",(angle*5)*math.pi/180,scale,scale,168,12)
+ love.graphics.setFont(Font)
+ love.graphics.printf("rofl, now kill yourself", w/2*scale, 18*scale, 160, "center", 0, scale, scale, 80, 3)
+ end
+end
+
+-- Box2D callbacks
+-- beginContact
+function World.beginContact (a, b, coll)
+ if a:getCategory() == 1 then
+ local x,y = coll:getNormal()
+ if y < -0.6 then
+ -- TODO: move landing to `not.Hero`
+ -- Move them to Hero
+ b:getUserData().inAir = false
+ b:getUserData().jumpCounter = 2
+ b:getUserData().salto = false
+ b:getUserData():createEffect("land")
+ end
+ local vx, vy = b:getUserData().body:getLinearVelocity()
+ if math.abs(x) == 1 or (y < -0.6 and x == 0) then
+ b:getUserData():playSound(3)
+ end
+ end
+ if a:getCategory() == 3 then
+ b:getUserData():damage(a:getUserData()[2])
+ end
+ if b:getCategory() == 3 then
+ a:getUserData():damage(b:getUserData()[2])
+ end
+end
+-- endContact
+function World.endContact (a, b, coll)
+ if a:getCategory() == 1 then
+ -- Move them to Hero
+ b:getUserData().inAir = true
+ end
+end
+
+-- Controller callbacks
+-- 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 = {}
+ for _,naut in pairs(self:getNautsAll()) do
+ table.insert(nauts, {naut.name, naut:getControlSet()})
+ end
+ local new = World:new(map, nauts)
+ changeScene(new)
+ end
+ for k,naut in pairs(self:getNautsAll()) do
+ naut:controlpressed(set, action, key)
+ end
+end
+function World:controlreleased (set, action, key)
+ for k,naut in pairs(self:getNautsAll()) do
+ naut:controlreleased(set, action, key)
+ end
+end \ No newline at end of file