summaryrefslogtreecommitdiffhomepage
path: root/not/World.lua
diff options
context:
space:
mode:
Diffstat (limited to 'not/World.lua')
-rw-r--r--not/World.lua565
1 files changed, 326 insertions, 239 deletions
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()})