summaryrefslogtreecommitdiffhomepage
path: root/not
diff options
context:
space:
mode:
authorAki <nthirtyone@gmail.com>2017-09-21 21:05:37 +0200
committerAki <nthirtyone@gmail.com>2017-09-21 21:05:37 +0200
commite9a450d65d4fb564691cdf651ef5771dd88303ae (patch)
treef49d29582dd6877f3b3c807c3f7d9d92d368f798 /not
parenteb8302723cd85adca0fbaf505cfb315f1db0299a (diff)
parentb97985def64b8bd8f93a7b391b12333595432e52 (diff)
downloadroflnauts-e9a450d65d4fb564691cdf651ef5771dd88303ae.zip
roflnauts-e9a450d65d4fb564691cdf651ef5771dd88303ae.tar.gz
roflnauts-e9a450d65d4fb564691cdf651ef5771dd88303ae.tar.bz2
Merge branch 'maps'
Diffstat (limited to 'not')
-rw-r--r--not/Button.lua4
-rw-r--r--not/Camera.lua221
-rw-r--r--not/Cloud.lua84
-rw-r--r--not/CloudGenerator.lua73
-rw-r--r--not/Element.lua24
-rw-r--r--not/Group.lua116
-rw-r--r--not/Hero.lua17
-rw-r--r--not/Layer.lua45
-rw-r--r--not/MusicPlayer.lua15
-rw-r--r--not/Object.lua7
-rw-r--r--not/PhysicalBody.lua22
-rw-r--r--not/Ray.lua44
-rw-r--r--not/SceneManager.lua8
-rw-r--r--not/Selector.lua365
-rw-r--r--not/Sprite.lua17
-rw-r--r--not/Timer.lua30
-rw-r--r--not/Trap.lua51
-rw-r--r--not/Trigger.lua18
-rw-r--r--not/World.lua565
19 files changed, 1020 insertions, 706 deletions
diff --git a/not/Button.lua b/not/Button.lua
index a2f7a19..3493a84 100644
--- a/not/Button.lua
+++ b/not/Button.lua
@@ -15,6 +15,10 @@ function Button:new (parent)
self.sprite, self.quads = parent:getSheet()
end
+function Button:getSize ()
+ return 58, 15
+end
+
function Button:setText (text)
self.text = text or ""
return self
diff --git a/not/Camera.lua b/not/Camera.lua
index aa4df5b..65395cb 100644
--- a/not/Camera.lua
+++ b/not/Camera.lua
@@ -1,37 +1,46 @@
---- `Camera`
--- Used in drawing.
-Camera = {
- x = 0,
- y = 0,
- dest_x = 0,
- dest_y = 0,
- 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
+--- Used in drawing other stuff in places.
+-- TODO: Camera is missing documentation on every important method.
+Camera = require "not.Object":extends()
+
+Camera.SHAKE_LENGTH = 0.6
+Camera.SHAKE_INTERVAL = 0.03
+
+-- TODO: Camera would really make use of vec2s (other classes would use them too).
+function Camera:new (x, y, world)
+ self.world = world
+ self:setPosition(x, y)
+ self:resetSum()
+ self:initShake()
+end
+
+function Camera:initShake ()
+ self.shakeTime = 0
+ self.shakeInterval = 0
+ self.shakeShift = {
+ theta = love.math.random() * 2,
+ radius = 0
+ }
+end
+
+function Camera:push ()
+ love.graphics.push()
+end
+
+function Camera:transform (scale, ratio, vw, vh)
+ local px, py = self:getPosition()
+ local sx, sy = self:getShake()
+ local dx, dy = (px + sx) * ratio, (py + sy) * ratio
+
+ vw, vh = vw / scale / 2, vh / scale / 2
+
+ love.graphics.scale(scale, scale)
+ love.graphics.translate(vw - dx, vh - dy)
+end
+
+function Camera:pop ()
+ love.graphics.pop()
+end
+
function Camera:setPosition (x, y)
local x = x or 0
local y = y or 0
@@ -42,102 +51,86 @@ function Camera:getPosition ()
return self.x, self.y
end
-function Camera:getPositionScaled ()
- return self.x*getScale(), self.y*getScale()
-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
+function Camera:getBoundaries (scale, vw, vh)
+ local x, y = self:getPosition()
+ local width, height = vw / scale / 2, vh / scale / 2
+ return x - width, y - height, x + width, y + height
end
-function Camera:getDestination ()
- return self.dest_x, self.dest_y
+function Camera:startShake ()
+ self.shakeTime = Camera.SHAKE_LENGTH
end
--- Translate points
-function Camera:translatePosition (x, y)
- local x = x or 0
- local y = y or 0
- return (x-self.x)*getScale(), (y-self.y)*getScale()
+local
+function limit (theta)
+ if theta > 2 then
+ return limitAngle(theta - 2)
+ end
+ if theta < 0 then
+ return limitAngle(theta + 2)
+ end
+ return theta
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) * getScale())
+-- TODO: Magic numbers present in Camera's shake.
+function Camera:shake (dt)
+ if self.shakeTime > 0 then
+ self.shakeTime = self.shakeTime - dt
+ if self.shakeInterval < 0 then
+ self.shakeShift.theta = self.shakeShift.theta - 1.3 + love.math.random() * 0.6
+ self.shakeShift.radius = 50 * self.shakeTime
+ self.shakeInterval = Camera.SHAKE_INTERVAL
else
- table.insert(r, (v + y) * getScale())
+ self.shakeShift.radius = self.shakeShift.radius * 0.66
+ self.shakeInterval = self.shakeInterval - dt
+ end
+ if self.shakeTime < 0 then
+ self.shakeShift.radius = 0
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()
+function Camera:getShake ()
+ local radius = self.shakeShift.radius
+ local theta = self.shakeShift.theta * math.pi
+ return radius * math.cos(theta), radius * math.sin(theta)
end
--- Move follow
-function Camera:follow ()
+function Camera:resetSum ()
+ self.sumX = 0
+ self.sumY = 0
+ self.sumI = 0
+end
+
+function Camera:sum (x, y)
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
+ if math.abs(x - map.center.x) < map.width/2 and
+ math.abs(y - map.center.y) < map.height/2 then
+ self.sumX = self.sumX + x
+ self.sumY = self.sumY + y
+ self.sumI = self.sumI + 1
end
- local x = sum_x / i - love.graphics.getWidth()/getScale()/2
- local y = sum_y / i - love.graphics.getHeight()/getScale()/2 + 4*getScale() -- 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())
+function Camera:getSumPostion ()
+ if self.sumI > 0 then
+ return self.sumX / self.sumI, self.sumY / self.sumI
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)
+ return 0, 0
+end
+
+function Camera:step (dt)
+ local x, y = self:getSumPostion()
+ local dx, dy = (x - self.x), (y - self.y)
+ if math.abs(dx) > 0.4 or math.abs(dy) > 0.4 then
+ x = self.x + (x - self.x) * dt * 6
+ y = self.y + (y - self.y) * dt * 6
+ end
+ self:setPosition(x, y)
+end
+
+function Camera:update (dt)
+ self:step(dt)
+ self:shake(dt)
+ self:resetSum()
end
diff --git a/not/Cloud.lua b/not/Cloud.lua
index 25169c0..4851042 100644
--- a/not/Cloud.lua
+++ b/not/Cloud.lua
@@ -1,61 +1,45 @@
-require "not.Decoration"
-
--- `Cloud`
--- That white thing moving in the background.
--- TODO: extends variables names to be readable.
-Cloud = Decoration:extends()
-Cloud.t = 1 -- type (sprite number)
-Cloud.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
- }
-}
-
--- Constructor of `Cloud`.
-function Cloud:new (x, y, t, v, world)
- if self:getImage() == nil then
- self:setImage(Sprite.newImage("assets/clouds.png"))
- end
- Cloud.__super.new(self, x, y, world, nil)
- self:setAnimationsList(animations)
- self:setVelocity(v)
- self:setType(t)
+-- Moving decorations with limited lifespan.
+Cloud = require "not.Decoration":extends()
+
+function Cloud:new (x, y, world, imagePath)
+ Cloud.__super.new(self, x, y, world, imagePath)
+ self.velocity_x = 0
+ self.velocity_y = 0
+ self.boundary_x = 0
+ self.boundary_y = 0
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
+function Cloud:setVelocity (x, y)
+ self.velocity_x = x
+ self.velocity_y = y
+end
+
+function Cloud:setBoundary (x, y)
+ self.boundary_x = x
+ self.boundary_y = y
end
-function Cloud:setVelocity (velocity)
- self.v = velocity
+
+function Cloud:setStyle (style)
+ self:setAnimation(style)
+end
+
+function Cloud:getStyle ()
+ return self:getAnimation()
+end
+
+function Cloud:testPosition ()
+ if self.x > self.boundary_x or self.y > self.boundary_y then
+ return true
+ end
end
--- Update of `Cloud`, returns x for world to delete cloud after reaching right corner.
+-- Cloud will get deleted if this function returns true.
function Cloud:update (dt)
Cloud.__super.update(self, dt)
- self.x = self.x + self.v*dt
- return self.x
+ self.x = self.x + self.velocity_x * dt
+ self.y = self.y + self.velocity_y * dt
+ return self:testPosition()
end
return Cloud
diff --git a/not/CloudGenerator.lua b/not/CloudGenerator.lua
new file mode 100644
index 0000000..e72514b
--- /dev/null
+++ b/not/CloudGenerator.lua
@@ -0,0 +1,73 @@
+--- Generates clouds over time with randomized positions and styles.
+-- Also used as factory for Clouds.
+CloudGenerator = require "not.Object":extends()
+
+require "not.Cloud"
+
+function CloudGenerator:new (atlas, animations, count, world)
+ self.world = world
+ self.atlas = atlas
+ self.quads = animations
+ self.count = count
+ self.interval = 12
+ self.timer = self.interval
+ self.layer = false
+end
+
+-- TODO: This was a bad idea. Move Cloud creation back to World, pass created Cloud here for configuration.
+function CloudGenerator:createCloud (x, y, style)
+ local cloud = Cloud(x, y, self.world, self.atlas)
+ cloud:setAnimationsList(self.quads)
+ cloud:setAnimation(style)
+ cloud:setVelocity(13, 0)
+ cloud:setBoundary(340, 320)
+ cloud.generator = self
+ cloud.layer = self.layer
+ return cloud
+end
+
+-- TODO: CloudGen's randomization methods are too static (not configurable).
+-- TODO: Random position for Clouds inside map shouldn't be random. Make them place them where no clouds are present.
+function CloudGenerator:getRandomPosition (inside)
+ local x, y
+ local map = self.world.map
+ if not inside then
+ x = map.center.x - map.width*1.2 + love.math.random(-50, 20)
+ else
+ x = love.math.random(map.center.x - map.width / 2, map.center.x + map.width / 2)
+ end
+ y = love.math.random(map.center.y - map.height / 2, map.center.y + map.height / 2)
+ return x, y
+end
+
+function CloudGenerator:getRandomStyle ()
+ local num = love.math.random(1, 3)
+ local style = "default"
+ if num > 1 then
+ style = style .. tostring(num)
+ end
+ return style
+end
+
+function CloudGenerator:run (count, inside)
+ count = count or 1
+ for i=1,count do
+ local x, y = self:getRandomPosition(inside)
+ local style = self:getRandomStyle()
+ self.world:insertCloud(self:createCloud(x, y, style))
+ end
+end
+
+function CloudGenerator:update (dt)
+ local count = self.world:getCloudsCountFrom(self)
+ if self.timer < 0 then
+ if self.count > count then
+ self.timer = self.timer + self.interval
+ self:run()
+ end
+ else
+ self.timer = self.timer - dt
+ end
+end
+
+return CloudGenerator
diff --git a/not/Element.lua b/not/Element.lua
index 24576e6..3b0d13a 100644
--- a/not/Element.lua
+++ b/not/Element.lua
@@ -1,22 +1,22 @@
-require "not.Object"
-
--- `Element`
-- Empty element used inside `Menu`.
-Element = Object:extends()
-
-Element.parent = --[[not.Menu]]nil
-Element.x = 0
-Element.y = 0
+Element = require "not.Object":extends()
function Element:new (parent)
self.parent = parent
+ self.x = 0
+ self.y = 0
end
-function Element:delete () end -- deletes Element
+-- TODO: Element's getSize is temporary. Create BoxElement and move it there.
+function Element:getSize ()
+ return 0, 0
+end
function Element:getPosition ()
return self.x, self.y
end
+
function Element:setPosition (x, y)
self.x = x or 0
self.y = y or 0
@@ -30,12 +30,14 @@ function Element:set (name, func)
return self
end
--- Called when menu tries to focus on this element.
+--- 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.
+end
+
+--- Called when Element loses focus.
+function Element:blur () end
-- LÖVE2D callbacks
function Element:draw (scale) end
diff --git a/not/Group.lua b/not/Group.lua
new file mode 100644
index 0000000..970d3bc
--- /dev/null
+++ b/not/Group.lua
@@ -0,0 +1,116 @@
+--- Element used for grouping elements and passing input to selected child based on controller set.
+Group = require "not.Element":extends()
+
+function Group:new (parent)
+ Group.__super.new(self, parent)
+ self.children = {}
+ self.margin = 0
+end
+
+function Group:addChild (element)
+ table.insert(self.children, element)
+ return element
+end
+
+-- TODO: Missing semi-important docs on Group's setPosition.
+function Group:setPosition (x, y)
+ local dx = 0
+ for _,child in ipairs(self.children) do
+ child:setPosition(x + dx, y)
+ dx = dx + child:getSize() + self.margin
+ end
+ return Group.__super.setPosition(self, x, y)
+end
+
+function Group:getSize ()
+ local twidth = -self.margin
+ local theight = 0
+ for _,child in ipairs(self.children) do
+ local cwidth, cheight = child:getSize()
+ twidth = twidth + child:getSize() + self.margin
+ if theight < cheight then
+ theight = cheight
+ end
+ end
+ return twidth, theight
+end
+
+--- Calls function with parameters for each child.
+-- @param func key of function to call
+-- @param ... parameters passed to function
+-- @return table with calls' results
+function Group:callEach (func, ...)
+ local results = {}
+ for _,child in ipairs(self.children) do
+ if type(child[func]) == "function" then
+ table.insert(results, child[func](child, ...) or nil)
+ end
+ end
+ return results
+end
+
+--- Calls function with parameters for each but one child.
+-- @param avoid child to avoid calling
+-- @param func key of function to call
+-- @param ... parameters passed to function
+-- @return table with calls' results
+function Group:callEachBut (avoid, func, ...)
+ local results = {}
+ for _,child in ipairs(self.children) do
+ if child ~= avoid then
+ if type(child[func]) == "function" then
+ table.insert(results, child[func](child, ...) or nil)
+ end
+ end
+ end
+ return results
+end
+
+--- Calls function with parameters for one child based on controller set.
+-- @param set controller set
+-- @param func key of function to call
+-- @param ... parameters passed to function
+-- @return results of called function
+function Group:callWithSet (set, func, ...)
+ for i,test in ipairs(Controller.getSets()) do
+ if test == set then
+ local child = self.children[i]
+ if child then
+ return child[func](child, ...)
+ end
+ end
+ end
+end
+
+function Group:focus ()
+ self:callEach("focus")
+ self.focused = true
+ return true
+end
+
+function Group:blur ()
+ self:callEach("blur")
+ self.focused = false
+end
+
+function Group:draw (scale)
+ self:callEach("draw", scale)
+end
+
+function Group:update (dt)
+ self:callEach("update", dt)
+end
+
+function Group:controlpressed (set, action, key)
+ if self.focused then
+ self:callWithSet(set, "controlpressed", set, action, key)
+ end
+end
+
+function Group:controlreleased (set, action, key)
+ if self.focused then
+ self:callWithSet(set, "controlreleased", set, action, key)
+ end
+end
+
+return Group
diff --git a/not/Hero.lua b/not/Hero.lua
index 039aeb8..66bc511 100644
--- a/not/Hero.lua
+++ b/not/Hero.lua
@@ -27,7 +27,7 @@ function Hero:new (name, x, y, world)
Hero.load()
Hero.__super.new(self, x, y, world, imagePath)
-- Physics
- self.group = -1-#world.Nauts
+ self.group = -1-#world:getNautsAll()
self:setBodyType("dynamic")
self:setBodyFixedRotation(true)
self:newFixture()
@@ -87,8 +87,8 @@ function Hero:update (dt)
-- 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
+ 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()
@@ -165,16 +165,17 @@ function Hero:getOffset ()
return 12,15
end
--- Draw of `Hero`
-function Hero:draw (offset_x, offset_y, scale, debug)
+function Hero:draw (debug)
if not self.isAlive then return end
- Hero.__super.draw(self, offset_x, offset_y, scale, debug)
+ Hero.__super.draw(self, debug)
end
-function Hero:drawTag (offset_x, offset_y, scale)
+-- TODO: Hero@drawTag's printf is not readable.
+function Hero:drawTag ()
local x,y = self:getPosition()
love.graphics.setFont(Font)
- love.graphics.printf(string.format("Player %d", math.abs(self.group)), (math.floor(x)+offset_x)*scale, (math.floor(y)+offset_y-26)*scale,100,'center',0,scale,scale,50,0)
+ love.graphics.setColor(255, 255, 255)
+ love.graphics.printf(string.format("Player %d", math.abs(self.group)), math.floor(x), math.floor(y)-26 ,100,'center',0,1,1,50,0)
end
-- Draw HUD of `Hero`
diff --git a/not/Layer.lua b/not/Layer.lua
new file mode 100644
index 0000000..14dac32
--- /dev/null
+++ b/not/Layer.lua
@@ -0,0 +1,45 @@
+--- A little bit more than just a Canvas.
+Layer = require "not.Object":extends()
+
+function Layer:new (width, height)
+ self.canvas = love.graphics.newCanvas(width, height)
+ self.transformScale = getScale()
+ self.transformRatio = 1
+ self.drawScale = 1
+end
+
+function Layer:delete ()
+ self.canvas = nil
+end
+
+--- Sets this layer as current canvas for drawing with love.graphics functions.
+-- @return old canvas used by love
+function Layer:setAsCanvas ()
+ local c = love.graphics.getCanvas()
+ love.graphics.setCanvas(self.canvas)
+ return c
+end
+
+function Layer:renderTo (func, ...)
+ local c = self:setAsCanvas()
+ func(...)
+ love.graphics.setCanvas(c)
+end
+
+function Layer:renderToWith (camera, func, ...)
+ camera:push()
+ camera:transform(self.transformScale, self.transformRatio, self.canvas:getDimensions())
+ self:renderTo(func, ...)
+ camera:pop()
+end
+
+function Layer:clear ()
+ self:renderTo(love.graphics.clear)
+end
+
+function Layer:draw ()
+ love.graphics.setColor(255, 255, 255, 255)
+ love.graphics.draw(self.canvas, nil, nil, nil, self.drawScale, self.drawScale)
+end
+
+return Layer
diff --git a/not/MusicPlayer.lua b/not/MusicPlayer.lua
index 4634ed9..17beda4 100644
--- a/not/MusicPlayer.lua
+++ b/not/MusicPlayer.lua
@@ -1,8 +1,5 @@
-require "not.Object"
-
---- `MusicPlayer`
--- Simple music player object that plays and loops selected track.
-MusicPlayer = Object:extends()
+--- Simple music player object that stores, playes and loops tracks..
+MusicPlayer = require "not.Object":extends()
function MusicPlayer:new (trackName)
self.tracks = {}
@@ -13,8 +10,9 @@ function MusicPlayer:new (trackName)
end
function MusicPlayer:delete ()
- self.tracks = nil
self:stop()
+ self.tracks = nil
+ self.source = nil
end
function MusicPlayer:setTrack (trackName)
@@ -40,7 +38,10 @@ function MusicPlayer:getCurrentTrack ()
end
end
-function MusicPlayer:play ()
+function MusicPlayer:play (trackName)
+ if trackName then
+ self:setTrack(trackName)
+ end
self.source:play()
end
diff --git a/not/Object.lua b/not/Object.lua
index 30b91b5..352e9e3 100644
--- a/not/Object.lua
+++ b/not/Object.lua
@@ -1,3 +1,8 @@
--- Wrapping library to game's hierarchy in a shameless way.
+--- Wrapping library to game's hierarchy in a shameless way.
Object = require "lib.object.Object"
+
+--- Called before Object references are removed from parent.
+-- This is not called when Object is garbage collected.
+function Object:delete () end
+
return Object
diff --git a/not/PhysicalBody.lua b/not/PhysicalBody.lua
index 804c706..5081836 100644
--- a/not/PhysicalBody.lua
+++ b/not/PhysicalBody.lua
@@ -62,22 +62,30 @@ function PhysicalBody:update (dt)
PhysicalBody.__super.update(self, dt)
end
--- Draw of `PhysicalBody`.
-function PhysicalBody:draw (offset_x, offset_y, scale, debug)
- PhysicalBody.__super.draw(self, offset_x, offset_y, scale)
+function PhysicalBody:draw (debug)
+ PhysicalBody.__super.draw(self, debug)
if debug then
for _,fixture in pairs(self.body:getFixtureList()) do
local category = fixture:getCategory()
+ -- TODO: Fixture drawing of PhysicalBodies could take activity into account in every case.
if category == 1 then
- love.graphics.setColor(255, 69, 0, 140)
+ love.graphics.setColor(255, 69, 0, 150)
end
if category == 2 then
- love.graphics.setColor(137, 255, 0, 120)
+ love.graphics.setColor(137, 255, 0, 150)
end
if category == 3 then
- love.graphics.setColor(137, 0, 255, 40)
+ love.graphics.setColor(137, 0, 255, 50)
end
- love.graphics.polygon("fill", self.world.camera:translatePoints(self.body:getWorldPoints(fixture:getShape():getPoints())))
+ if category == 4 then
+ if self.body:isActive() then
+ love.graphics.setColor(255, 230, 0, 50)
+ else
+ love.graphics.setColor(255, 230, 0, 10)
+ end
+ end
+ local camera = self.world.camera
+ love.graphics.polygon("fill", self.body:getWorldPoints(fixture:getShape():getPoints()))
end
end
end
diff --git a/not/Ray.lua b/not/Ray.lua
index 16a9bee..4ae640a 100644
--- a/not/Ray.lua
+++ b/not/Ray.lua
@@ -1,21 +1,10 @@
-require "not.Object"
+--- That awesome effect that blinks when player dies!
+Ray = require "not.Object":extends()
---- `Ray`
--- That awesome effect that blinks when player dies!
-Ray = Object:extends()
-
-Ray.naut =--[[not.Hero]]nil
-Ray.world =--[[not.World]]nil
-Ray.canvas =--[[love.graphics.newCanvas]]nil
-Ray.delay = 0.3
-
-function Ray:new (naut, world)
- self.naut = naut
+function Ray:new (source, world)
+ self.source = source
self.world = world
- -- Cavas, this is temporary, I believe.
- local scale = getScale()
- local w, h = love.graphics.getWidth(), love.graphics.getHeight()
- self.canvas = love.graphics.newCanvas(w/scale, h/scale)
+ self.delay = 0.3
end
function Ray:update (dt)
@@ -26,25 +15,16 @@ function Ray:update (dt)
return false
end
-function Ray:draw (offset_x, offset_y, scale)
- love.graphics.setCanvas(self.canvas)
- love.graphics.clear()
+-- TODO: Ray should use Camera boundaries just-in-case.
+-- TODO: Ray uses magic numbers.
+function Ray:draw ()
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)
+
+ local x, y = self.source:getPosition()
+
+ love.graphics.line(x, y, -x, -y)
end
return Ray
diff --git a/not/SceneManager.lua b/not/SceneManager.lua
index c076448..4f9edfd 100644
--- a/not/SceneManager.lua
+++ b/not/SceneManager.lua
@@ -9,7 +9,10 @@ end
-- This function should be removed when multiple scenes will be handled properly by SceneManager and other things.
function SceneManager:changeScene (scene)
- table.remove(self.scenes, #self.scenes)
+ local removed = table.remove(self.scenes, #self.scenes)
+ if removed then
+ removed:delete()
+ end
return self:addScene(scene)
end
@@ -20,7 +23,8 @@ end
-- Not nice, not nice.
function SceneManager:removeTopScene ()
- table.remove(self.scenes, #self.scenes)
+ local scene = table.remove(self.scenes, #self.scenes)
+ scene:delete()
end
function SceneManager:getAllScenes ()
diff --git a/not/Selector.lua b/not/Selector.lua
index ef78778..ee6f0e3 100644
--- a/not/Selector.lua
+++ b/not/Selector.lua
@@ -1,282 +1,195 @@
-require "not.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?
-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 = Element:extends()
+-- Element for selecting variable from list.
+Selector = require "not.Element":extends()
-Selector.width = 0
-Selector.height = 0
-Selector.margin = 0
-Selector.focused = false
-Selector.global = false
-Selector.delay = 2
-Selector.first = false
-Selector.list = --[[]]nil
-Selector.sets = --[[]]nil
-Selector.locks = --[[]]nil
-Selector.selections = --[[]]nil
-Selector.shape = "portrait"
-Selector.sprite = --[[]]nil
-Selector.quads = --[[]]nil
-Selector.icons_i = --[[]]nil
-Selector.icons_q = --[[]]nil
+Selector.DEFAULT_DELAY = 2
+Selector.SHAPE_PORTRAIT = 1
+Selector.SHAPE_PANORAMA = 2
--- Constructor
-function Selector:new (parent)
+function Selector:new (list, group, parent)
Selector.__super.new(self, parent)
- self.sprite, self.quads = parent:getSheet()
+ self.atlas, self.quads = parent:getSheet()
+ self.group = group
+ self.list = list
+ self.delay = Selector.DEFAULT_DELAY
+ self.shape = Selector.SHAPE_PORTRAIT
+ self.focused = false
+ self.lock = false
+ self.index = 1
end
--- Size of single block
+-- TODO: See `not/Element@getSize`.
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
+ if self.shape == Selector.SHAPE_PORTRAIT then
+ return 32, 32
+ end
+ if self.shape == Selector.SHAPE_PANORAMA then
+ return 80, 42
+ end
end
--- Initialize Selector with current settings.
-function Selector:init ()
- -- Make sure that there is list present
- if self.list == nil then
- self.list = {}
+--- Makes sure that n is in <1, total> range.
+local
+function limit (n, total)
+ if n > total then
+ return limit(n - total, total)
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
+ if n < 1 then
+ return limit(n + total, total)
end
- return self
+ return n
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)
+--- Chooses item with an index.
+-- @param index selected item's index
+-- @return old index
+function Selector:setIndex (index)
+ local old = self.index
+ self.index = limit(index, #self.list)
+ return old
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
+function Selector:rollRandom (exclude)
+ local exclude = exclude or {}
+ local index = love.math.random(1, #self.list)
+ local elgible = true
+ for _,i in ipairs(exclude) do
+ if index == i then
+ elgible = false
+ break
+ end
+ end
+ if not elgible or not self:isUnique(self.list[index]) then
+ table.insert(exclude, index)
+ return self:rollRandom(exclude)
end
+ return index
end
--- Check if given number is locked
-function Selector:isLocked (n)
- local n = n or 1
- return self.locks[n]
+--- Returns selected item's value.
+-- @return item selected from the list
+function Selector:getSelected ()
+ return self.list[self.index]
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
+--- Checks if selection is locked and returns item's value.
+-- @return item selected from the list if Selector is locked, nil otherwise
+function Selector:getLocked ()
+ if self.lock then
+ return self:getSelected()
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]
+--- Checks if Selected value is unique in group's scope.
+-- @param index optional parameter to fill in place of currently selected item
+-- @return boolean answering question
+function Selector:isUnique (item)
+ local item = item or self:getSelected()
+ if self.group then
+ local locked = self.group:callEachBut(self, "getLocked")
+ for _,value in pairs(locked) do
+ if value == item then
+ return false
+ end
+ end
+ end
+ return true
end
--- Get value from list by selection
-function Selector:getListValue (i)
- return self.list[i]
+function Selector:getText ()
+ return tostring(self:getSelected())
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
+function Selector:focus ()
+ self.focused = true
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
+function Selector:blur ()
+ self.focused = false
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
+-- TODO: Temporary function to determine quad to use. Will be obsolete when BoxElement will be done. See also `not/Element@getSize`.
+function Selector:getShapeString ()
+ if self.shape == Selector.SHAPE_PORTRAIT then
+ return "portrait"
end
- if not eligible or self:isLocked(random) then
- table.insert(avoids, random)
- return self:rollRandom(avoid)
- else
- return random
+ if self.shape == Selector.SHAPE_PANORAMA then
+ return "panorama"
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)
+function Selector:draw (scale)
+ local x, y = self:getPosition()
+ local w, h = self:getSize()
+
+ local boxType = "normal"
+ if self:getLocked() then
+ boxType = "active"
+ end
+
+ love.graphics.setColor(255, 255, 255, 255)
+ if not self:isUnique() then
+ love.graphics.setColor(120, 120, 120, 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)
+ love.graphics.draw(self.atlas, self.quads[self:getShapeString()][boxType], x*scale, y*scale, 0, scale, scale)
+ -- TODO: That is one way to draw icon for selected value. Find better one. See: `config/menus/host`.
+ if self.icons_atlas and self.icons_quads then
+ love.graphics.draw(self.icons_atlas, self.icons_quads[self.index], (x+2)*scale, (y+3)*scale, 0, scale, scale)
end
- love.graphics.draw(icon, iconq, (x+2)*scale, (y+3)*scale, 0, scale, scale)
+
+ love.graphics.setColor(255, 255, 255, 255)
+
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)
+ local al, ar = self.quads.arrow_r, self.quads.arrow_l
+ if self.lock then
+ al, ar = ar, al
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)
+ love.graphics.draw(self.atlas, ar, (x+0-2-math.floor(self.delay))*scale, (y+dy)*scale, 0, scale, scale)
+ love.graphics.draw(self.atlas, al, (x+w-4+math.floor(self.delay))*scale, (y+dy)*scale, 0, scale, scale)
end
+
+ love.graphics.setFont(Font)
+ love.graphics.printf(self:getText(), (x-w)*scale, (y+h+1)*scale, w*3, "center", 0, scale, scale)
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
+ if self.delay > Selector.DEFAULT_DELAY then
+ self.delay = self.delay - Selector.DEFAULT_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
+ local handler = self[action]
+ if handler then
+ handler(self)
end
end
end
+function Selector:left ()
+ if not self.lock then
+ self:setIndex(self.index - 1)
+ end
+end
+
+function Selector:right ()
+ if not self.lock then
+ self:setIndex(self.index + 1)
+ end
+end
+
+function Selector:attack ()
+ self.lock = true
+end
+
+-- Selector doesn't actually jump, haha, I tricked you!
+function Selector:jump ()
+ self.lock = false
+end
+
return Selector
diff --git a/not/Sprite.lua b/not/Sprite.lua
index 3951e6e..ec23eac 100644
--- a/not/Sprite.lua
+++ b/not/Sprite.lua
@@ -11,6 +11,7 @@ Sprite.frame = 1
Sprite.delay = .1
-- Constructor of `Sprite`.
+-- TODO: Sprites' in general don't take actual Image in constructor. That is not only case of Decoration.
function Sprite:new (imagePath)
if type(imagePath) == "string" then
self:setImage(Sprite.newImage(imagePath))
@@ -84,26 +85,24 @@ 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: Sprite@draw requires a serious review!
-- 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
-
+function Sprite:draw (debug)
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)
+ local scaleX = self:getHorizontalMirror()
+ local scaleY = self:getVerticalMirror()
-- 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
+ local draw_y = approx(y)
+ local draw_x = math.floor(x)
- if i then
+ if i and not self.hidden 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())
diff --git a/not/Timer.lua b/not/Timer.lua
new file mode 100644
index 0000000..9ae0de8
--- /dev/null
+++ b/not/Timer.lua
@@ -0,0 +1,30 @@
+Timer = require "not.Trigger":extends()
+
+function Timer:new (delay)
+ Timer.__super.new(self)
+ self.delay = delay
+ self.left = 0
+ self.active = false
+ self.restart = false
+end
+
+function Timer:start ()
+ self.left = self.delay
+ self.active = true
+end
+
+function Timer:update (dt)
+ if self.active then
+ if self.left < 0 then
+ self:emit()
+ self.active = false
+ if self.restart then
+ self:start()
+ end
+ else
+ self.left = self.left - dt
+ end
+ end
+end
+
+return Timer
diff --git a/not/Trap.lua b/not/Trap.lua
new file mode 100644
index 0000000..0867a36
--- /dev/null
+++ b/not/Trap.lua
@@ -0,0 +1,51 @@
+Trap = require "not.PhysicalBody":extends()
+
+function Trap:new (direction, x, y, world, imagePath)
+ Trap.__super.new(self, x, y, world, imagePath)
+ self:setAnimationsList(require("config.animations.flames"))
+ self:setBodyType("static")
+
+ local mirror = 1
+ if direction == "left" then mirror = -1 end
+ local fixture = self:addFixture({0, 0, 41 * mirror, 0, 41 * mirror, 18, 0, 18})
+ fixture:setCategory(4)
+ fixture:setMask(1,3,4)
+ fixture:setUserData({direction})
+ fixture:setSensor(true)
+
+ self.mirror = mirror
+end
+
+function Trap:fadeIn ()
+ self.hidden = false
+ self:setBodyActive(true)
+ if self.animations.fadein then
+ self:setAnimation("fadein")
+ end
+end
+
+function Trap:fadeOut ()
+ self:setBodyActive(false)
+ if self.animations.fadeout then
+ self:setAnimation("fadeout")
+ else
+ self.hidden = true
+ end
+end
+
+function Trap:getHorizontalMirror ()
+ return self.mirror
+end
+
+function Trap:goToNextFrame ()
+ if self.current.repeated or not (self.frame == self.current.frames) then
+ self.frame = (self.frame % self.current.frames) + 1
+ elseif self.current == self.animations.fadeout then
+ self:setAnimation("default")
+ self.hidden = true
+ else
+ self:setAnimation("default")
+ end
+end
+
+return Trap
diff --git a/not/Trigger.lua b/not/Trigger.lua
new file mode 100644
index 0000000..c6ef7c7
--- /dev/null
+++ b/not/Trigger.lua
@@ -0,0 +1,18 @@
+Trigger = require "not.Object":extends()
+
+function Trigger:new ()
+ self.calls = {}
+end
+
+function Trigger:register (func, ...)
+ local call = {func = func, params = {...}}
+ table.insert(self.calls, call)
+end
+
+function Trigger:emit ()
+ for _,call in pairs(self.calls) do
+ call.func(unpack(call.params))
+ end
+end
+
+return Trigger
diff --git a/not/World.lua b/not/World.lua
index c73a3da..4523efa 100644
--- a/not/World.lua
+++ b/not/World.lua
@@ -1,82 +1,183 @@
---- `World`
--- Used to manage physical world and everything inside it: clouds, platforms, nauts, background etc.
+--- 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 = require "not.Scene":extends()
-World.world =--[[love.physics.newWorld]]nil
-World.Nauts =--[[{not.Hero}]]nil
-World.Platforms =--[[{not.Platform}]]nil
-World.Clouds =--[[{not.Cloud}]]nil
-World.Decorations =--[[{not.Decoration}]]nil
-World.Effects =--[[{not.Effect}]]nil
-World.Rays =--[[{not.Ray}]]nil
-World.camera =--[[not.Camera]]nil
-World.music =--[[not.Music]]nil
-World.clouds_delay = 5
-World.map =--[[config.maps.*]]nil
-World.background =--[[image?]]nil
-World.lastNaut = false
-
require "not.Platform"
require "not.Player"
-require "not.Cloud"
require "not.Effect"
require "not.Decoration"
require "not.Ray"
+require "not.Cloud"
+require "not.CloudGenerator"
+require "not.Layer"
+require "not.Timer"
+require "not.Trap"
+require "not.Entity"
--- Constructor of `World` ZA WARUDO!
+--- ZA WARUDO!
+-- TODO: Missing documentation on most of World's methods.
function World:new (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 = {}
- -- Map and misc.
- local map = map or "default"
- self:loadMap(map)
+ self.world:setCallbacks(self:getContactCallbacks())
+
+ self.lastNaut = false
+ self.entities = {}
+ self.map = map
+
+ self.camera = Camera(self.map.center.x, self.map.center.y, self)
+
+ self:initLayers()
+ self:buildMap()
self:spawnNauts(nauts)
- self.camera = Camera:new(self)
- musicPlayer:setTrack(self.map.theme)
- musicPlayer:play()
+
+ musicPlayer:play(self.map.theme)
end
-- The end of the world
function World:delete ()
- for _,platform in pairs(self.Platforms) do
- platform:delete()
+ for _,entity in pairs(self.entities) do
+ entity:delete()
end
- for _,naut in pairs(self.Nauts) do
- naut:delete()
+ for layer in self.layers() do
+ layer:delete()
end
self.world:destroy()
+ collectgarbage()
+end
+
+--- Custom iterator for layers table.
+-- Iterates over elements in reversed order. Doesn't pay attention to any changes in table.
+local
+function layersIterator (layers)
+ local i = layers.n + 1
+ return function ()
+ i = i - 1
+ return layers[i]
+ end
+end
+
+--- Layers in World may exists as two references. Every reference is stored inside `instance.layers`.
+-- First reference is indexed with number, it exists for every layer.
+-- Second reference is indexed with string, it exists only for selected layers.
+-- Mentioned special layers are initialized in this method.
+-- Additionally layer count is stored inside `instance.layers.n`.
+-- Layers are drawn in reverse order, meaning that `instance.layers[1]` will be on the top.
+-- Calling `instance.layers` will return iterator.
+function World:initLayers ()
+ self.layers = setmetatable({}, {__call = layersIterator})
+ self.layers.n = 0
+ do
+ local width, height = love.graphics.getWidth() / getScale(), love.graphics.getHeight() / getScale()
+ local rays = self:addLayer(width, height)
+ rays.transformScale = 1
+ rays.transformRatio = 0
+ rays.drawScale = getScale()
+ self.layers.rays = rays
+ end
+ do
+ local width, height = love.graphics.getDimensions()
+ self.layers.tags = self:addLayer(width, height)
+ self.layers.platforms = self:addLayer(width, height)
+ self.layers.effects = self:addLayer(width, height)
+ self.layers.heroes = self:addLayer(width, height)
+ self.layers.decorations = self:addLayer(width, height)
+ self.layers.clouds = self:addLayer(width, height)
+ end
+end
+
+-- TODO: Make collisions for category 3 more customizable or create new category for traps/area effects.
+local
+function createFlame (self, x, y, direction, timerIn, timerOut)
+ local trap = Trap(direction, x, y, self, "assets/decorations/205-flames.png")
+
+ trap.layer = self.layers.platforms
+
+ timerIn:register(trap.fadeIn, trap)
+ timerOut:register(trap.fadeOut, trap)
+
+ self:insertEntity(trap)
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)
+local
+function getAnimations (a)
+ if type(a) == "string" then
+ return require("config.animations." .. a)
+ end
+ if type(a) == "table" then
+ return a
+ end
+end
+
+--- Builds map using one of tables frin config files located in `config/maps/` directory.
+-- TODO: Clean World@buildMap. Possibly explode into more methods.
+-- TODO: Move buildMap along with getAnimations to Factory.
+function World:buildMap ()
+ local width, height = love.graphics.getDimensions()
+
+ for _,op in pairs(self.map.create) do
+ if op.platform then
+ -- TODO: Merge configs imported from other files to currently processed element.
+ local config = love.filesystem.load(string.format("config/platforms/%s.lua", op.platform))()
+ local platform = Platform(config.animations, config.shape, op.x, op.y, self, config.sprite)
+ platform.layer = self.layers.platforms
+ self:insertEntity(platform)
+ end
+ if op.decoration or op.background then
+ local imagePath = op.decoration or op.background
+ local entity = Decoration(0, 0, self, imagePath)
+
+ local x, y = 0, 0
+ if op.x and op.y then
+ x = op.x
+ y = op.y
+ elseif op.animations then
+ entity:setAnimationsList(getAnimations(op.animations))
+ _,_,x,y = bg:getAnimation()[1]:getViewport()
+ bg:setPosition(x / -2, y / -2)
+ else
+ local image = love.graphics.newImage(imagePath)
+ x = image:getWidth() / -2
+ y = image:getHeight() / -2
+ end
+ entity:setPosition(x, y)
+
+ local layer = self.layers.decorations
+ if op.ratio then
+ layer = self:addLayer(width, height)
+ layer.transformRatio = op.ratio
+ if op.background then
+ layer.transformScale = getRealScale()
+ end
+ end
+ entity.layer = layer
+
+ self:insertEntity(entity)
+ end
+ if op.clouds then
+ local animations = getAnimations(op.animations)
+ local cg = CloudGenerator(op.clouds, animations, op.count, self)
+ if op.ratio then
+ cg.layer = self:addLayer(width, height)
+ cg.layer.transformRatio = op.ratio
+ end
+ self:insertEntity(cg)
+ cg:run(op.count, true)
+ end
+ -- TODO: Make flames and other traps more configurable through map config file.
+ if op.flames then
+ local timerIn = Timer(10)
+ local timerOut = Timer(5)
+
+ timerIn:register(timerOut.start, timerOut)
+ timerOut:register(timerIn.start, timerIn)
+
+ createFlame(self, -62, 16, "right", timerIn, timerOut)
+ createFlame(self, 63, 16, "left", timerIn, timerOut)
+
+ self:insertEntity(timerIn)
+ self:insertEntity(timerOut)
+ timerOut:start()
end
end
end
@@ -96,89 +197,107 @@ function World:getSpawnPosition ()
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(animations, polygon, x, y, self, sprite))
+function World:addLayer (width, height)
+ local layer = Layer(width, height)
+ local n = self.layers.n + 1
+ self.layers[n] = layer
+ self.layers.n = n
+ return layer
end
--- Add new naut to the world
--- TODO: separate two methods for `not.Hero` and `not.Player`.
+-- TODO: Standardize `create*` methods with corresponding constructors. Pay attention to both params' order and names.
function World:createNaut (x, y, name)
- local naut = Player(name, x, y, self)
- 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(x, y, self, sprite))
+ local h = Player(name, x, y, self)
+ table.insert(self.entities, h)
+ h.layer = self.layers.heroes
+ return h
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(x, y, t, v, self))
+function World:createEffect (name, x, y)
+ local e = Effect(name, x, y, self)
+ table.insert(self.entities, e)
+ e.layer = self.layers.effects
+ return e
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+love.math.random(-50,20)
- else
- x = love.math.random(m.center_x-m.width/2,m.center_x+m.width/2)
- end
- y = love.math.random(m.center_y-m.height/2, m.center_y+m.height/2)
- t = love.math.random(1,3)
- v = love.math.random(8,18)
- self:createCloud(x, y, t, v)
+function World:createRay (naut)
+ local r = Ray(naut, self)
+ table.insert(self.entities, r)
+ r.layer = self.layers.rays
+ return r
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(name, x, y, self))
+function World:insertCloud (cloud)
+ table.insert(self.entities, cloud)
+ if not cloud.layer then
+ cloud.layer = self.layers.clouds
+ end
+ return cloud
end
--- Add a ray
-function World:createRay (naut)
- table.insert(self.Rays, Ray(naut, self))
+--- Verbose wrapper for inserting entities into entities table.
+-- @param entity entity to insert
+function World:insertEntity (entity)
+ if entity then
+ table.insert(self.entities, entity)
+ return entity
+ end
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)
+--- Searches entities for those which return true with filtering function.
+-- @param filter function with entity as parameter
+-- @return table containing results of search
+function World:getEntities (filter)
+ local result = {}
+ for _,entity in pairs(self.entities) do
+ if filter(entity) then
+ table.insert(result, entity)
end
end
- return nauts
+ return result
end
--- are alive
-function World:getNautsAlive ()
- local nauts = {}
- for _,naut in self.Nauts do
- if naut.isAlive then
- table.insert(nauts, naut)
+
+--- Counts entities returning true with filtering function.
+-- @param filter function with entity as parameter
+-- @return entity count
+function World:countEntities (filter)
+ local count = 0
+ for _,entity in pairs(self.entities) do
+ if filter(entity) then
+ count = count + 1
end
end
- return nauts
+ return count
+end
+
+function World:getCloudsCount ()
+ return self:countEntities(function (entity)
+ return entity:is(Cloud)
+ end)
end
--- all of them
+
+function World:getCloudsCountFrom (generator)
+ return self:countEntities(function (entity)
+ return entity:is(Cloud) and entity.generator == generator
+ end)
+end
+
function World:getNautsAll ()
- return self.Nauts
+ return self:getEntities(function (entity)
+ return entity:is(require("not.Hero")) and not entity.body:isDestroyed()
+ end)
+end
+
+function World:getNautsPlayable ()
+ return self:getEntities(function (entity)
+ return entity:is(require("not.Hero")) and entity.lives > -1
+ end)
+end
+
+function World:getNautsAlive ()
+ return self:getEntities(function (entity)
+ return entity:is(require("not.Hero")) and entity.isAlive
+ end)
end
-- get Map name
@@ -206,121 +325,63 @@ end
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, _)
+
+ for key,entity in pairs(self.entities) do
+ if entity:update(dt) then
+ table.remove(self.entities, key):delete()
end
end
- -- Rays
- for _,ray in pairs(self.Rays) do
- if ray:update(dt) then
- table.remove(self.Rays, _)
- end
+
+ -- TODO: Possibly rename Camera@sum because this code part in World doesn't make sense without reading further.
+ self.camera:sum(self.map.center.x, self.map.center.y)
+ for _,hero in pairs(self:getNautsAll()) do
+ self.camera:sum(hero:getPosition())
end
+
+ -- Some additional debug info.
+ local stats = love.graphics.getStats()
+ dbg_msg = string.format("%sMap: %s\nClouds: %d\nLoaded: %d\nMB: %.2f", dbg_msg, self.map.filename, self:getCloudsCount(), stats.images, stats.texturememory / 1024 / 1024)
end
--- Draw
-function World:draw ()
- -- Camera stuff
- local offset_x, offset_y = self.camera:getOffsets()
- local scale = getScale()
- local scaler = getRealScale()
-
- -- 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)
+function World:draw ()
+ for _,entity in pairs(self.entities) do
+ if entity.draw and entity.layer then
+ entity.layer:renderToWith(self.camera, entity.draw, entity, debug)
+ end
+ if entity.drawTag then
+ self.layers.tags:renderToWith(self.camera, entity.drawTag, entity, debug)
+ end
end
- -- Draw effects
- for _,effect in pairs(self.Effects) do
- effect:draw(offset_x,offset_y, scale)
+ for layer in self.layers() do
+ layer:draw()
+ layer:clear()
end
- -- Draw player
- for _,naut in pairs(self.Nauts) do
- naut:draw(offset_x, offset_y, scale, debug)
- end
+ -- TODO: Debug information could possibly get its own layer so it could follow flow of draw method.
+ if debug then
+ local center = self.map.center
+ local ax, ay, bx, by = self.camera:getBoundaries(getScale(), love.graphics.getDimensions())
- -- Draw ground
- for _,platform in pairs(self.Platforms) do
- platform:draw(offset_x, offset_y, scale, debug)
- end
+ love.graphics.setLineWidth(1 / getScale())
+ love.graphics.setLineStyle("rough")
- -- Draw rays
- for _,ray in pairs(self.Rays) do
- ray:draw(offset_x, offset_y, scale)
- end
+ self.camera:push()
+ self.camera:transform(getScale(), 1, love.graphics.getDimensions())
- -- 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
+ love.graphics.line(ax,center.y,bx,center.y)
+ love.graphics.line(center.x,ay,center.x,by)
- for _,naut in pairs(self.Nauts) do
- naut:drawTag(offset_x, offset_y, scale)
+ love.graphics.setColor(200,200,200)
+ love.graphics.line(ax,0,bx,0)
+ love.graphics.line(0,ay,0,by)
+ self.camera:pop()
end
-
- -- Draw HUDs
- for _,naut in pairs(self.Nauts) do
+
+ -- TODO: Draw method beyond this point is a very, very dark place (portraits drawing to review).
+ local scale = getScale()
+ for _,naut in pairs(self:getNautsAll()) 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
@@ -330,15 +391,29 @@ function World:draw ()
end
end
--- Box2D callbacks
--- TODO: Rather than here, these contacts should be in `Hero` (most likely).
--- TODO: Explode these into more functions.\
--- TODO: Stop using magical numbers:
--- [1] -> Platform
--- [2] -> Hero
--- [3] -> Punch sensor
-function World.beginContact (a, b, coll)
- if a:getCategory() == 1 then
+--- Wraps World's beginContact and endContact to functions usable as callbacks for Box2D's world.
+-- Only difference in new functions is absence of self as first argument.
+-- @return wrapper for beginContact
+-- @return wrapper for endContact
+function World:getContactCallbacks ()
+ local b = function (a, b, coll)
+ self:beginContact(a, b, coll)
+ end
+ local e = function (a, b, coll)
+ self:endContact(a, b, coll)
+ end
+ return b, e
+end
+
+-- TODO: Move these constants to a proper place (I have no idea where proper place is).
+local COLL_HERO = 2
+local COLL_PLATFORM = 1
+local COLL_PUNCH = 3
+local COLL_TRAP = 4
+
+-- TODO: Review current state of both Box2D callbacks (again).
+function World:beginContact (a, b, coll)
+ if a:getCategory() == COLL_PLATFORM then
local x,y = coll:getNormal()
if y < -0.6 then
b:getUserData():land()
@@ -348,37 +423,49 @@ function World.beginContact (a, b, coll)
b:getUserData():playSound(3)
end
end
- if a:getCategory() == 3 then
- if b:getCategory() == 2 then
+ if a:getCategory() == COLL_PUNCH then
+ if b:getCategory() == COLL_HERO then
b:getUserData():damage(a:getUserData()[2])
end
- if b:getCategory() == 3 then
+ if b:getCategory() == COLL_PUNCH then
a:getBody():getUserData():damage(b:getUserData()[2])
b:getBody():getUserData():damage(a:getUserData()[2])
local x1,y1 = b:getBody():getUserData():getPosition()
local x2,y2 = a:getBody():getUserData():getPosition()
local x = (x2 - x1) / 2 + x1 - 12
local y = (y2 - y1) / 2 + y1 - 15
- a:getBody():getUserData().world:createEffect("clash", x, y)
+ self:createEffect("clash", x, y)
end
end
- if b:getCategory() == 3 then
- if a:getCategory() == 2 then
+ if b:getCategory() == COLL_PUNCH then
+ if a:getCategory() == COLL_HERO then
a:getUserData():damage(b:getUserData()[2])
end
end
+ if a:getCategory() == COLL_TRAP then
+ if b:getCategory() == COLL_HERO then
+ b:getUserData():damage(a:getUserData()[1])
+ end
+ end
+ if b:getCategory() == COLL_TRAP then
+ if a:getCategory() == COLL_HERO then
+ a:getUserData():damage(b:getUserData()[1])
+ end
+ end
end
-function World.endContact (a, b, coll)
- if a:getCategory() == 1 then
+
+function World:endContact (a, b, coll)
+ if a:getCategory() == COLL_PLATFORM then
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 filename = self.map.filename
+ local map = love.filesystem.load(filename)()
+ map.filename = filename
local nauts = {}
for _,naut in pairs(self:getNautsAll()) do
table.insert(nauts, {naut.name, naut:getControllerSet()})