From ecd2221020d454112144b4022b260b561cf884e9 Mon Sep 17 00:00:00 2001 From: Caleb John Date: Sat, 19 Oct 2013 14:54:10 -0600 Subject: [PATCH 01/32] allows fireplace to close --- src/nodes/door.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nodes/door.lua b/src/nodes/door.lua index cdf404e31..8ed50af9b 100644 --- a/src/nodes/door.lua +++ b/src/nodes/door.lua @@ -141,6 +141,7 @@ end function Door:hide() if self.hideable and not self.hidden then self.hidden = true + self.position = self.position_shown sound.playSfx( 'unreveal' ) Tween.start( self.movetime, self.position, self.position_hidden ) end From a15997f66e7ee2cb6c65e49085125a7b8bf0b61b Mon Sep 17 00:00:00 2001 From: Caleb John Date: Sat, 19 Oct 2013 20:43:17 -0600 Subject: [PATCH 02/32] uses deepcopy to prevent mutation --- src/nodes/door.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/door.lua b/src/nodes/door.lua index 8ed50af9b..0426d120c 100644 --- a/src/nodes/door.lua +++ b/src/nodes/door.lua @@ -141,7 +141,7 @@ end function Door:hide() if self.hideable and not self.hidden then self.hidden = true - self.position = self.position_shown + self.position = utils.deepcopy(self.position_shown) sound.playSfx( 'unreveal' ) Tween.start( self.movetime, self.position, self.position_hidden ) end From e96498b42cb7eb8b414204b87002c6d4797af6b6 Mon Sep 17 00:00:00 2001 From: Caleb John Date: Wed, 23 Oct 2013 01:20:36 -0600 Subject: [PATCH 03/32] adjusts when show and hide are called and allowed to execute --- src/level.lua | 3 ++- src/nodes/door.lua | 11 +++++++---- src/nodes/hiddendoortrigger.lua | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/level.lua b/src/level.lua index a46d1384a..36fa1cf90 100644 --- a/src/level.lua +++ b/src/level.lua @@ -310,7 +310,8 @@ function Level:enter( previous, door, position ) self.player:respawn() end if self.doors[ door ].node then - self.doors[ door ].node:show() + -- passing previous will allow the door to check the level against its own + self.doors[ door ].node:show(previous) self.player.freeze = false end end diff --git a/src/nodes/door.lua b/src/nodes/door.lua index 0426d120c..d0c1f32ec 100644 --- a/src/nodes/door.lua +++ b/src/nodes/door.lua @@ -44,6 +44,7 @@ function Door.new(node, collider) -- generic support for hidden doors if door.hideable then + -- necessary for opening/closing doors with a trigger door.hidden = true door.sprite = love.graphics.newImage('images/' .. node.properties.sprite .. '.png') door.sprite_width = tonumber( node.properties.sprite_width ) @@ -130,16 +131,18 @@ function Door:keypressed( button, player) end -- everything below this is required for hidden doors -function Door:show() - if self.hideable and self.hidden then +function Door:show(previous) + -- level check is to ensure that the player is using a switch and not re-entering a level + if self.hideable and self.hidden and ( not previous or previous.name ~= self.level ) then self.hidden = false sound.playSfx( 'reveal' ) Tween.start( self.movetime, self.position, self.position_shown ) end end -function Door:hide() - if self.hideable and not self.hidden then +function Door:hide(previous) + -- level check is to allow door to close on re-entry or close command + if self.hideable and (previous.name == self.level or not self.hidden) then self.hidden = true self.position = utils.deepcopy(self.position_shown) sound.playSfx( 'unreveal' ) diff --git a/src/nodes/hiddendoortrigger.lua b/src/nodes/hiddendoortrigger.lua index cae3a59cc..23b06a17a 100644 --- a/src/nodes/hiddendoortrigger.lua +++ b/src/nodes/hiddendoortrigger.lua @@ -41,8 +41,8 @@ end function HiddenDoorTrigger:update(dt) end -function HiddenDoorTrigger:enter() - Gamestate.currentState().doors[self.target].node:hide() +function HiddenDoorTrigger:enter(previous) + Gamestate.currentState().doors[self.target].node:hide(previous) end function HiddenDoorTrigger:draw() From b9aad159357b28781f9f8b5bf2f47a71c76094ad Mon Sep 17 00:00:00 2001 From: didory123 Date: Fri, 25 Oct 2013 16:50:27 -0700 Subject: [PATCH 04/32] Update credits.lua Add Gent to credits (forestcave level) --- src/credits.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/credits.lua b/src/credits.lua index 23230daa1..c213f4e93 100644 --- a/src/credits.lua +++ b/src/credits.lua @@ -44,6 +44,7 @@ end state.credits = { app.i18n('credits'), '6sutters', + '8bitgentleman', 'a8252359', 'aaronpetykowski', 'academania', From fe0f27fbf98824d428d679e12de39cc7cec30ea1 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 25 Oct 2013 17:45:41 -0700 Subject: [PATCH 05/32] Make the torch quieter. Fix #1142 --- src/audio/sfx/fire_thrown.ogg | Bin 35600 -> 33831 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/audio/sfx/fire_thrown.ogg b/src/audio/sfx/fire_thrown.ogg index 12b7f89ae8df158ee5b7bf627ff029052a6e3c1b..c853dd7ac02829c8a3d6883d20806d8faefc5d8f 100644 GIT binary patch delta 29152 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 delta 30956 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 From fc49e3c93daf01a9ebd7955bd5c9f50b0b53176d Mon Sep 17 00:00:00 2001 From: Caleb John Date: Fri, 25 Oct 2013 19:18:13 -0600 Subject: [PATCH 06/32] add previous check in door:hide --- src/nodes/door.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/door.lua b/src/nodes/door.lua index d0c1f32ec..7cce32a65 100644 --- a/src/nodes/door.lua +++ b/src/nodes/door.lua @@ -142,7 +142,7 @@ end function Door:hide(previous) -- level check is to allow door to close on re-entry or close command - if self.hideable and (previous.name == self.level or not self.hidden) then + if self.hideable and ( (previous and previous.name == self.level) or not self.hidden ) then self.hidden = true self.position = utils.deepcopy(self.position_shown) sound.playSfx( 'unreveal' ) From 3c8182e30985ba2890d075fd297afa141243af9b Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 25 Oct 2013 19:32:53 -0700 Subject: [PATCH 07/32] Fix first round of leaks by zeroing out levels --- src/level.lua | 30 +++++++++++++++++++++++------- src/main.lua | 11 +++++++++-- src/nodes/enemies/hippy.lua | 6 ++++-- src/nodes/enemy.lua | 8 ++++++-- src/nodes/splat.lua | 16 ++++++---------- 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/level.lua b/src/level.lua index a46d1384a..5f137248c 100644 --- a/src/level.lua +++ b/src/level.lua @@ -198,7 +198,7 @@ function Level.new(name) level:addNode(node) elseif NodeClass then v.objectlayer = 'nodes' - node = NodeClass.new( v, level.collider, level) + node = NodeClass.new(v, level.collider, level) node.drawHeight = v.height level:addNode(node) end @@ -270,7 +270,7 @@ function Level:restartLevel() end -function Level:enter( previous, door, position ) +function Level:enter(previous, door, position) self.respawn = false self.state = 'idle' @@ -425,10 +425,6 @@ function Level:quit() end end -function Level:leave() - self.state = 'idle' -end - function Level:exit(levelName, doorName) self.respawn = false if self.state ~= 'idle' then @@ -535,6 +531,7 @@ function Level:floorspaceNodeDraw() end end +-- Called by Gamestate.switch when changing levels function Level:leave() for i,node in pairs(self.nodes) do if node.leave then node:leave() end @@ -542,6 +539,23 @@ function Level:leave() node:collide_end(self.player) end end + + self.previous = nil + self.player = nil + self.map = nil + self.tileset = nil + self.collider = nil + self.offset = nil + self.music = nil + self.spawn = nil + self.overworldName = nil + self.title = nil + self.environment = nil + self.boundary = nil + self.transition = nil + self.events = nil + self.nodes = nil + self.doors = nil end function Level:keyreleased( button ) @@ -639,10 +653,12 @@ function Level:updatePan(dt) end function Level:addNode(node) - if node.containerLevel then + -- FIXME: This seems like a very bad idea + if node.containerLevel and node.containerLevel.collider then node.containerLevel.collider:remove(node.bb) node.containerLevel:removeNode(node) end + node.containerLevel = self table.insert(self.nodes, node) end diff --git a/src/main.lua b/src/main.lua index 682a42260..6f0d3894b 100644 --- a/src/main.lua +++ b/src/main.lua @@ -127,11 +127,11 @@ function love.load(arg) end if args["d"] then - debugger.set( true, false ) + debugger.set(true, false) end if args["b"] then - debugger.set( true, true ) + debugger.set(true, true) end if args["locale"] ~= "" then @@ -223,6 +223,7 @@ end function love.draw() if testing then return end + camera:set() Gamestate.draw() fonts.set('arial') @@ -250,6 +251,12 @@ function love.draw() love.graphics.print( love.timer.getFPS() .. ' FPS', love.graphics.getWidth() - 100, 5, 0, 1, 1 ) fonts.revert() end + + -- REMOVE ME + if debugger.on then + collectgarbage("collect") + love.graphics.print('Memory (MB): ' .. utils.round(collectgarbage('count') / 1000) , 10,10) + end end -- Override the default screenshot functionality so we can disable the fps before taking it diff --git a/src/nodes/enemies/hippy.lua b/src/nodes/enemies/hippy.lua index 0d243b59f..b482afd42 100644 --- a/src/nodes/enemies/hippy.lua +++ b/src/nodes/enemies/hippy.lua @@ -33,8 +33,10 @@ return { left = {'loop', {'1-2,1'}, 0.25} } }, - splat = function( enemy ) - enemy.splat = splat:add(enemy.position.x, enemy.position.y, enemy.width, enemy.height) + splat = function(enemy) + local s = splat.new(enemy.position.x, enemy.position.y, enemy.width, enemy.height) + s:add(enemy.position.x, enemy.position.y, enemy.width, enemy.height) + return s end, update = function( dt, enemy, player ) if enemy.position.x > player.position.x then diff --git a/src/nodes/enemy.lua b/src/nodes/enemy.lua index 85e9005f4..041e32134 100644 --- a/src/nodes/enemy.lua +++ b/src/nodes/enemy.lua @@ -137,7 +137,7 @@ function Enemy:animation() end end -function Enemy:hurt( damage ) +function Enemy:hurt(damage) if self.dead then return end if self.props.die_sound then sound.playSfx( self.props.die_sound ) end @@ -147,7 +147,11 @@ function Enemy:hurt( damage ) if self.hp <= 0 then self.state = 'dying' self:cancel_flash() - if self.props.splat then self.props.splat( self )end + + if self.containerLevel and self.props.splat then + table.insert(self.containerLevel.nodes, 1, self.props.splat(self)) + end + self.collider:setGhost(self.bb) self.collider:setGhost(self.attack_bb) diff --git a/src/nodes/splat.lua b/src/nodes/splat.lua index 8aca297a4..eb4fbb039 100644 --- a/src/nodes/splat.lua +++ b/src/nodes/splat.lua @@ -12,12 +12,12 @@ local quads = { love.graphics.newQuad(splatterSize.width * 2, 0, splatterSize.width, splatterSize.height, splatters:getWidth(), splatters:getHeight()), } - -Splat.splats = {} - function Splat.new(node) - Splat.node = {x=0, width=0} - return Splat + local splat = {} + setmetatable(splat, Splat) + splat.splats = {} + splat.node = {x=0, width=0} + return splat end function Splat:setup_stencils() @@ -58,7 +58,7 @@ end function Splat:add(x,y,width,height) - table.insert( self.splats, { + table.insert(self.splats, { position = { x = x, y = y @@ -72,11 +72,7 @@ function Splat:add(x,y,width,height) if not self.stencils then self:setup_stencils() - return self - else - return false end - end function Splat:draw() From e1e211c6e44916782dbd7a5324cdc66871ffb29b Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 25 Oct 2013 19:59:20 -0700 Subject: [PATCH 08/32] Fix memory leak in the sprite class --- src/debugger.lua | 2 +- src/level.lua | 12 ++++++++++++ src/main.lua | 10 ++++------ src/nodes/sprite.lua | 42 ++++++++++++++++++++++-------------------- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/debugger.lua b/src/debugger.lua index 91d8293b0..566d8cafd 100644 --- a/src/debugger.lua +++ b/src/debugger.lua @@ -115,7 +115,7 @@ function Debugger:draw() end love.graphics.setColor( 255, 255, 255, 255 ) fonts.set('big') - love.graphics.print( math.floor( collectgarbage( 'count' ) / 10 ) / 10 , window.screen_width - 30, window.screen_height - 10,0,0.5,0.5 ) + love.graphics.print( math.floor( collectgarbage( 'count' ) / 1000 ), window.screen_width - 30, window.screen_height - 10,0,0.5,0.5 ) fonts.revert() end diff --git a/src/level.lua b/src/level.lua index 5f137248c..30657c925 100644 --- a/src/level.lua +++ b/src/level.lua @@ -144,6 +144,7 @@ function Level.new(name) level.over = false level.state = 'idle' -- TODO: Use state machine level.name = name + level.timers = {} assert( love.filesystem.exists( "maps/" .. name .. ".lua" ), "maps/" .. name .. ".lua not found.\n\n" .. @@ -198,6 +199,7 @@ function Level.new(name) level:addNode(node) elseif NodeClass then v.objectlayer = 'nodes' + --print(nodePath) node = NodeClass.new(v, level.collider, level) node.drawHeight = v.height level:addNode(node) @@ -540,6 +542,11 @@ function Level:leave() end end + for _,timer in pairs(self.timers) do + Timer.cancel(timer) + end + + self.timers = nil self.previous = nil self.player = nil self.map = nil @@ -663,6 +670,11 @@ function Level:addNode(node) table.insert(self.nodes, node) end +function Level:addPeriodic(interval, func) + table.insert(self.timers, Timer.addPeriodic(interval, func)) +end + + function Level:removeNode(node) node.containerLevel = nil for k,v in pairs(self.nodes) do diff --git a/src/main.lua b/src/main.lua index 6f0d3894b..99b928255 100644 --- a/src/main.lua +++ b/src/main.lua @@ -188,6 +188,10 @@ function love.update(dt) tween.update(dt > 0 and dt or 0.001) timer.update(dt) sound.cleanup() + + if debugger.on then + collectgarbage("collect") + end end function love.keyreleased(key) @@ -251,12 +255,6 @@ function love.draw() love.graphics.print( love.timer.getFPS() .. ' FPS', love.graphics.getWidth() - 100, 5, 0, 1, 1 ) fonts.revert() end - - -- REMOVE ME - if debugger.on then - collectgarbage("collect") - love.graphics.print('Memory (MB): ' .. utils.round(collectgarbage('count') / 1000) , 10,10) - end end -- Override the default screenshot functionality so we can disable the fps before taking it diff --git a/src/nodes/sprite.lua b/src/nodes/sprite.lua index 22a0e2147..c2926732c 100644 --- a/src/nodes/sprite.lua +++ b/src/nodes/sprite.lua @@ -1,5 +1,4 @@ local anim8 = require 'vendor/anim8' -local Timer = require 'vendor/timer' local utils = require 'utils' local Sprite = {} @@ -9,18 +8,18 @@ Sprite.__index = Sprite local sprite_cache = {} local function load_sprite(name) - if sprite_cache[name] then - return sprite_cache[name] - end - - local image = love.graphics.newImage(name) - image:setFilter('nearest', 'nearest') - sprite_cache[name] = image - return image + if sprite_cache[name] then + return sprite_cache[name] + end + + local image = love.graphics.newImage(name) + image:setFilter('nearest', 'nearest') + sprite_cache[name] = image + return image end -function Sprite.new(node, collider) +function Sprite.new(node, collider, level) local sprite = {} local p = node.properties setmetatable(sprite, Sprite) @@ -38,8 +37,6 @@ function Sprite.new(node, collider) sprite.width = p.width end - sprite.node = node - if sprite.animation then sprite.random = p.random == 'true' sprite.speed = p.speed and tonumber(p.speed) or 0.20 @@ -58,15 +55,12 @@ function Sprite.new(node, collider) sprite.animation.status = 'stopped' --randomize the play interval local window = p.window and tonumber(p.window) or 5 - local interval = ( math.random( window * 100 ) / 100 ) + ( #sprite.animation.frames * sprite.speed ) - Timer.addPeriodic( interval, function() - sprite.animation:gotoFrame(1) - sprite.animation.status = 'playing' - end) + sprite.interval = (math.random(window * 100) / 100 ) + ( #sprite.animation.frames * sprite.speed) end end + sprite.dt = 0 sprite.x = node.x sprite.y = node.y @@ -74,9 +68,17 @@ function Sprite.new(node, collider) end function Sprite:update(dt) - if self.animation then - self.animation:update(dt) - end + self.dt = self.dt + dt + + if self.random and self.dt > self.interval then + self.dt = 0 + self.animation:gotoFrame(1) + self.animation.status = 'playing' + end + + if self.animation then + self.animation:update(dt) + end end function Sprite:draw() From 9d8110ef4ea59c26c0dbd0484c12ca51bc8f4a3d Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 25 Oct 2013 20:02:37 -0700 Subject: [PATCH 09/32] Fix sprite randomness --- src/nodes/sprite.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/sprite.lua b/src/nodes/sprite.lua index c2926732c..01eeef41d 100644 --- a/src/nodes/sprite.lua +++ b/src/nodes/sprite.lua @@ -60,7 +60,7 @@ function Sprite.new(node, collider, level) end - sprite.dt = 0 + sprite.dt = math.random() sprite.x = node.x sprite.y = node.y From 544b1f7a7abda29987af36af97957a1a54570da3 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 25 Oct 2013 20:06:53 -0700 Subject: [PATCH 10/32] Remove level.timers (it was a WIP) --- src/level.lua | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/level.lua b/src/level.lua index 30657c925..7f8dfa074 100644 --- a/src/level.lua +++ b/src/level.lua @@ -144,7 +144,6 @@ function Level.new(name) level.over = false level.state = 'idle' -- TODO: Use state machine level.name = name - level.timers = {} assert( love.filesystem.exists( "maps/" .. name .. ".lua" ), "maps/" .. name .. ".lua not found.\n\n" .. @@ -542,11 +541,6 @@ function Level:leave() end end - for _,timer in pairs(self.timers) do - Timer.cancel(timer) - end - - self.timers = nil self.previous = nil self.player = nil self.map = nil @@ -670,10 +664,6 @@ function Level:addNode(node) table.insert(self.nodes, node) end -function Level:addPeriodic(interval, func) - table.insert(self.timers, Timer.addPeriodic(interval, func)) -end - function Level:removeNode(node) node.containerLevel = nil From 8e0075bb001fc0e12450b4782a95723aba8e3574 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 25 Oct 2013 20:08:16 -0700 Subject: [PATCH 11/32] Remove errant print statement --- src/level.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/src/level.lua b/src/level.lua index 7f8dfa074..6ac2a7f57 100644 --- a/src/level.lua +++ b/src/level.lua @@ -198,7 +198,6 @@ function Level.new(name) level:addNode(node) elseif NodeClass then v.objectlayer = 'nodes' - --print(nodePath) node = NodeClass.new(v, level.collider, level) node.drawHeight = v.height level:addNode(node) From 8e670f81e00041adc8d27dfc229b04f482e48dd9 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 25 Oct 2013 22:36:04 -0700 Subject: [PATCH 12/32] The first few screens now only require 30mb, instead of 143mb. --- src/character.lua | 223 ++++++++++++++------------- src/main.lua | 27 +++- src/overworld.lua | 290 ++++++++++++++++++++---------------- src/scanning.lua | 43 +++++- src/splash.lua | 45 ++++-- src/test/runner.lua | 1 + src/test/test_character.lua | 18 +++ src/update.lua | 9 +- 8 files changed, 397 insertions(+), 259 deletions(-) create mode 100644 src/test/test_character.lua diff --git a/src/character.lua b/src/character.lua index b728021d5..3d10b1855 100644 --- a/src/character.lua +++ b/src/character.lua @@ -9,65 +9,86 @@ local sprite_map = json.decode(contents) local characters = {} -for i,p in pairs( love.filesystem.enumerate( 'characters' ) ) do +local module = {} - -- bring in the data from the character file - local contents, _ = love.filesystem.read('characters/' .. p) - local character = json.decode(contents) +-- Just for backwards compat +module.name = 'abed' - if character.animations then --merge - local base = utils.deepcopy(character.animations) - character.animations = utils.deepcopy(sprite_map) - for k,v in pairs(base) do - character.animations[k] = v - end - else - character.animations = utils.deepcopy(sprite_map) +local _character = 'abed' +local _costume = 'base' + +function module.pick(character, costume) + if not love.filesystem.exists("characters/" .. character .. ".json") then + error("Unknown character " .. character) + end + + if not love.filesystem.exists("images/characters/" .. character .. "/" .. costume .. ".png") then + error("Unknown costume " .. costume .. " for character " .. character) + end + + _character = character + _costume = costume +end + +for i,p in pairs({}) do -- + + -- bring in the data from the character file + local contents, _ = love.filesystem.read('characters/' .. p) + local character = json.decode(contents) + + if character.animations then --merge + local base = utils.deepcopy(character.animations) + character.animations = utils.deepcopy(sprite_map) + for k,v in pairs(base) do + character.animations[k] = v end + else + character.animations = utils.deepcopy(sprite_map) + end + + -- build the character + character.beam = love.graphics.newImage( 'images/characters/' .. character.name .. '/beam.png') + character.beam:setFilter('nearest', 'nearest') + + character.count = 1 + + character.sheets = {} + character.sheets.base = love.graphics.newImage( 'images/characters/' .. character.name .. '/base.png') + character.sheets.base:setFilter('nearest', 'nearest') - -- build the character - character.beam = love.graphics.newImage( 'images/characters/' .. character.name .. '/beam.png') - character.beam:setFilter('nearest', 'nearest') - - character.count = 1 - - character.sheets = {} - character.sheets.base = love.graphics.newImage( 'images/characters/' .. character.name .. '/base.png') - character.sheets.base:setFilter('nearest', 'nearest') - - character.mask = love.graphics.newQuad(0, character.offset, 48, 35, character.sheets.base:getWidth(), character.sheets.base:getHeight()) - - character.positions = require( 'positions/' .. character.name ) - - character._grid = anim8.newGrid( 48, 48, character.sheets.base:getWidth(), character.sheets.base:getHeight() ) - character._warp = anim8.newGrid( 36, 300, character.beam:getWidth(), character.beam:getHeight() ) - - for state, _ in pairs( character.animations ) do - local data = character.animations[ state ] - if state == 'warp' then - character.animations[ state ] = anim8.newAnimation(data[1], character._warp(unpack(data[2])), data[3]) - else - if type( data[1] ) == 'string' then - -- positionless - character.animations[ state ] = anim8.newAnimation(data[1], character._grid(unpack(data[2])), data[3]) - else - -- positioned - for i, _ in pairs( data ) do - character.animations[ state ][i] = anim8.newAnimation(data[i][1], character._grid(unpack(data[i][2])), data[i][3]) - end - end + character.mask = love.graphics.newQuad(0, character.offset, 48, 35, character.sheets.base:getWidth(), character.sheets.base:getHeight()) + + character.positions = require( 'positions/' .. character.name ) + + character._grid = anim8.newGrid( 48, 48, character.sheets.base:getWidth(), character.sheets.base:getHeight() ) + character._warp = anim8.newGrid( 36, 300, character.beam:getWidth(), character.beam:getHeight() ) + + for state, _ in pairs( character.animations ) do + local data = character.animations[ state ] + if state == 'warp' then + character.animations[ state ] = anim8.newAnimation(data[1], character._warp(unpack(data[2])), data[3]) + else + if type( data[1] ) == 'string' then + -- positionless + character.animations[ state ] = anim8.newAnimation(data[1], character._grid(unpack(data[2])), data[3]) + else + -- positioned + for i, _ in pairs( data ) do + character.animations[ state ][i] = anim8.newAnimation(data[i][1], character._grid(unpack(data[i][2])), data[i][3]) end + end end - - character.costumemap = {} - character.categorytocostumes = {} - for _,c in pairs( character.costumes ) do - character.costumemap[ c.sheet ] = c - character.categorytocostumes[ c.category ] = character.categorytocostumes[ c.category ] or {} - table.insert( character.categorytocostumes[ c.category ], c ) - end + end + + character.costumemap = {} + character.categorytocostumes = {} + for _,c in pairs( character.costumes ) do + character.costumemap[ c.sheet ] = c + character.categorytocostumes[ c.category ] = character.categorytocostumes[ c.category ] or {} + table.insert( character.categorytocostumes[ c.category ], c ) + end - characters[ character.name ] = character + characters[ character.name ] = character end local Character = {} @@ -80,96 +101,98 @@ Character.costume = 'base' Character.warpin = false function Character:reset() - self.state = 'idle' - self.direction = 'right' + self.state = 'idle' + self.direction = 'right' end -function Character:setCharacter( name ) - if character == self.name then return end +function Character:setCharacter(name) + if character == self.name then + return + end - if self.characters[name] then - self.name = name - self.costume = 'base' - return - end + if self.characters[name] then + self.name = name + self.costume = 'base' + return + end - error( "Invalid character ( " .. name .. " ) requested!" ) + error( "Invalid character ( " .. name .. " ) requested!" ) end -function Character:setCostume( costume ) - if costume == self.costume then return end +function Character:setCostume(costume) + if costume == self.costume then return end - if self:hasCostume(costume) then - self.costume = costume - return - end - - error( "Undefined costume ( " .. costume .. " ) requested for character ( " .. self.name .. " )" ) + if self:hasCostume(costume) then + self.costume = costume + return + end + + error( "Undefined costume ( " .. costume .. " ) requested for character ( " .. self.name .. " )" ) end -function Character:hasCostume( costume ) - for _,c in pairs( self:current().costumes ) do - if c.sheet == costume then - return true - end +function Character:hasCostume(costume) + for _,c in pairs(self:current().costumes) do + if c.sheet == costume then + return true end - return false + end + return false end function Character:current() - return self.characters[self.name] + return self.characters[self.name] end function Character:sheet() - return self:getSheet( self.name, self.costume ) + return self:getSheet(self.name, self.costume) end function Character:getSheet(char,costume) - if not self.characters[char].sheets[costume] then - self.characters[char].sheets[costume] = love.graphics.newImage( 'images/characters/' .. char .. '/' .. costume .. '.png') - self.characters[char].sheets[costume]:setFilter('nearest', 'nearest') - end - return self.characters[char].sheets[costume] + if not self.characters[char].sheets[costume] then + self.characters[char].sheets[costume] = love.graphics.newImage('images/characters/' .. char .. '/' .. costume .. '.png') + self.characters[char].sheets[costume]:setFilter('nearest', 'nearest') + end + return self.characters[char].sheets[costume] end function Character:updateAnimation(dt) - self:animation():update(dt) + self:animation():update(dt) end function Character:animation() - return self.characters[self.name].animations[self.state][self.direction] + return self.characters[self.name].animations[self.state][self.direction] end function Character:warpUpdate(dt) - self:current().animations.warp:update(dt) + self:current().animations.warp:update(dt) end function Character:respawn() - self.warpin = true - self:current().animations.warp:gotoFrame(1) - self:current().animations.warp:resume() - sound.playSfx( "respawn" ) - Timer.add(0.30, function() self.warpin = false end) + self.warpin = true + self:current().animations.warp:gotoFrame(1) + self:current().animations.warp:resume() + sound.playSfx( "respawn" ) + Timer.add(0.30, function() self.warpin = false end) end function Character:draw() end function Character:getCategory() - return self:current().costumemap[ self.costume ].category + return self:current().costumemap[ self.costume ].category end function Character:getOverworld() - return self:current().costumemap[ self.costume ].ow + return self:current().costumemap[ self.costume ].ow end -function Character:findRelatedCostume( char ) - --returns the requested character's costume that is most similar to the current character - local costumes = self.characters[ char ].categorytocostumes[ self:getCategory() ] - if costumes then return costumes[math.random(#costumes)].sheet end - return 'base' +function Character:findRelatedCostume(char) + --returns the requested character's costume that is most similar to the current character + local costumes = self.characters[char].categorytocostumes[self:getCategory()] + if costumes then return costumes[math.random(#costumes)].sheet end + return 'base' end -Character:reset() +--Character:reset() -return Character +return module diff --git a/src/main.lua b/src/main.lua index 682a42260..e3275aab0 100644 --- a/src/main.lua +++ b/src/main.lua @@ -16,7 +16,6 @@ local timer = require 'vendor/timer' local cli = require 'vendor/cliargs' local mixpanel = require 'vendor/mixpanel' - local debugger = require 'debugger' local camera = require 'camera' local fonts = require 'fonts' @@ -105,15 +104,22 @@ function love.load(arg) if args["position"] ~= "" then position = args["position"] end + + + -- Choose character and costume + local char = "abed" + local costume = "base" if args["character"] ~= "" then - character:setCharacter( args["c"] ) + char = args["c"] end if args["costume"] ~= "" then - character:setCostume( args["o"] ) + costume = args["o"] end + character.pick(char, costume) + if args["vol-mute"] == 'all' then sound.disabled = true elseif args["vol-mute"] == 'music' then @@ -252,6 +258,21 @@ function love.draw() end end +--function love.draw() +--end +-- +--function love.update(dt) +--end +-- +--function love.load() +--end +-- +--function love.keyreleased() +--end +-- +--function love.keypressed() +--end + -- Override the default screenshot functionality so we can disable the fps before taking it local newScreenshot = love.graphics.newScreenshot function love.graphics.newScreenshot() diff --git a/src/overworld.lua b/src/overworld.lua index 95058a736..e2ce3c4f3 100644 --- a/src/overworld.lua +++ b/src/overworld.lua @@ -18,82 +18,7 @@ map.height = 111 local scale = 2 -local overworld = { - love.graphics.newImage('images/overworld/world_01.png'), - love.graphics.newImage('images/overworld/world_02.png'), - love.graphics.newImage('images/overworld/world_03.png'), - love.graphics.newImage('images/overworld/world_04.png'), - love.graphics.newImage('images/overworld/world_05.png'), - love.graphics.newImage('images/overworld/world_06.png'), - love.graphics.newImage('images/overworld/world_07.png'), - love.graphics.newImage('images/overworld/world_08.png'), -} - -local overlay = { - love.graphics.newImage('images/overworld/world_overlay_01.png'), - love.graphics.newImage('images/overworld/world_overlay_02.png'), - false, - false, - love.graphics.newImage('images/overworld/world_overlay_05.png'), - love.graphics.newImage('images/overworld/world_overlay_06.png'), - false, - false, -} - -local board = love.graphics.newImage('images/overworld/titleboard.png') - - -local charactersprites = love.graphics.newImage( 'images/characters/' .. Character.name .. '/overworld.png') - -local g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) - ---flags -local flag_image = love.graphics.newImage('images/overworld/flag.png') -local flags = {} - --- free_ride_ferry -local wheelchair = love.graphics.newImage('images/overworld/free_ride_ferry.png') -local wc_x1, wc_x2, wc_y1, wc_y2 = 1685, 1956, 816, 680 -local offset_x, offset_y = math.floor( wheelchair:getHeight() / 2 ) - 10, math.floor( wheelchair:getWidth() / 2 ) - --- animated water -local watersprite = love.graphics.newImage('images/overworld/world_water.png') -local h2o = anim8.newGrid(36, 36, watersprite:getWidth(), watersprite:getHeight()) -local water = anim8.newAnimation('loop', h2o('1-2,1'), 1) - --- cloud puffs -local cloudpuffsprite = love.graphics.newImage('images/overworld/cloud_puff.png') -local spunk_image = anim8.newGrid(100,67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight()) --- ( cloud animations will be generated on the fly ) - --- gay sparkles -local sparklesprite = love.graphics.newImage('images/overworld/gay_sparkle.png') -local bling = anim8.newGrid(24, 24, sparklesprite:getWidth(), sparklesprite:getHeight()) -local sparkles = {{1028,456},{1089,442},{1403,440},{1348,591},{1390,633},{1273,698},{1160,657},{1088,702},{1048,665},{1072,604},{1060,552},{1104,548},{1172,555},{1199,727},{1263,735},{1313,505},{1337,459},{1358,429},{1270,617},{1289,571},{1123,505},{1124,472},{1359,709},{1389,555},{1376,677},{1057,624},{1169,710},{1149,592},{1297,639}} -for _,_sp in pairs(sparkles) do - _sp[3] = anim8.newAnimation('loop', bling('1-4,1','1-4,2'), ( math.random(15) / 100 ) + 0.15) - _sp[3]:gotoFrame( math.random( 8 ) ) -end - --- overworld clouds -local cloudquads = { - love.graphics.newQuad( 0, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --small - love.graphics.newQuad( 100, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --medium - love.graphics.newQuad( 200, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --large - love.graphics.newQuad( 300, 0, 200, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ) --x-large -} -local clouds = {} -function insertrandomcloud(nofade) - table.insert( clouds, { - x = math.random( map.width * map.tileWidth ), -- x position - y = math.random( map.height * map.tileHeight ), -- y position - q = math.random( #cloudquads ), -- quad ( cloud size ) - s = ( math.random( 15 ) + 5 ) * ( math.random(2) == 1 and 1 or -1 ), -- speed / direction - o = nofade and 0.8 or 0 -- opacity - } ) -end -for i=0,15 do insertrandomcloud(true) end - +-- FIXME: Put in a JSON file -- overworld state machine state.zones = { greendale= { x=66, y=100, UP=nil, DOWN=nil, RIGHT='forest_2', LEFT=nil, visited = true, name='Greendale', level='studyroom' }, @@ -122,50 +47,152 @@ function state:init() self:reset() end -function state:enter(previous) +function state:insertrandomcloud(map, nofade) + table.insert(self.clouds, { + x = math.random(map.width * map.tileWidth), -- x position + y = math.random(map.height * map.tileHeight), -- y position + q = math.random(#self.cloudquads), -- quad ( cloud size ) + s = (math.random(15) + 5) * (math.random(2) == 1 and 1 or -1), -- speed / direction + o = nofade and 0.8 or 0 -- opacity + }) +end - self.previous = previous +function state:enter(previous) + self.overworld = { + love.graphics.newImage('images/overworld/world_01.png'), + love.graphics.newImage('images/overworld/world_02.png'), + love.graphics.newImage('images/overworld/world_03.png'), + love.graphics.newImage('images/overworld/world_04.png'), + love.graphics.newImage('images/overworld/world_05.png'), + love.graphics.newImage('images/overworld/world_06.png'), + love.graphics.newImage('images/overworld/world_07.png'), + love.graphics.newImage('images/overworld/world_08.png'), + } + + self.overlay = { + love.graphics.newImage('images/overworld/world_overlay_01.png'), + love.graphics.newImage('images/overworld/world_overlay_02.png'), + false, + false, + love.graphics.newImage('images/overworld/world_overlay_05.png'), + love.graphics.newImage('images/overworld/world_overlay_06.png'), + false, + false, + } + + self.board = love.graphics.newImage('images/overworld/titleboard.png') + self.charactersprites = love.graphics.newImage( 'images/characters/' .. Character.name .. '/overworld.png') + + local g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) + + --flags + self.flag_image = love.graphics.newImage('images/overworld/flag.png') + self.flags = {} + + -- free_ride_ferry + self.wheelchair = love.graphics.newImage('images/overworld/free_ride_ferry.png') + local wc_x1, wc_x2, wc_y1, wc_y2 = 1685, 1956, 816, 680 + local offset_x, offset_y = math.floor( wheelchair:getHeight() / 2 ) - 10, math.floor( wheelchair:getWidth() / 2 ) + + -- animated water + self.watersprite = love.graphics.newImage('images/overworld/world_water.png') + self.h2o = anim8.newGrid(36, 36, self.watersprite:getWidth(), self.watersprite:getHeight()) + self.water = anim8.newAnimation('loop', self.h2o('1-2,1'), 1) + + -- cloud puffs + self.cloudpuffsprite = love.graphics.newImage('images/overworld/cloud_puff.png') + self.spunk_image = anim8.newGrid(100,67, self.cloudpuffsprite:getWidth(), self.cloudpuffsprite:getHeight()) + -- ( cloud animations will be generated on the fly ) + + -- gay sparkles + self.sparklesprite = love.graphics.newImage('images/overworld/gay_sparkle.png') + self.bling = anim8.newGrid(24, 24, sparklesprite:getWidth(), sparklesprite:getHeight()) + self.sparkles = { + {1028,456},{1089,442},{1403,440},{1348,591},{1390,633},{1273,698},{1160,657},{1088,702},{1048,665},{1072,604}, + {1060,552},{1104,548},{1172,555},{1199,727},{1263,735},{1313,505},{1337,459},{1358,429},{1270,617},{1289,571}, + {1123,505},{1124,472},{1359,709},{1389,555},{1376,677},{1057,624},{1169,710},{1149,592},{1297,639} + } + + for _,_sp in pairs(self.sparkles) do + _sp[3] = anim8.newAnimation('loop', bling('1-4,1','1-4,2'), (math.random(15) / 100) + 0.15) + _sp[3]:gotoFrame(math.random( 8 )) + end + + -- overworld clouds + self.cloudquads = { + love.graphics.newQuad( 0, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --small + love.graphics.newQuad( 100, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --medium + love.graphics.newQuad( 200, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --large + love.graphics.newQuad( 300, 0, 200, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ) --x-large + } - local owd = Character:getOverworld() + self.clouds = {} - charactersprites = love.graphics.newImage( 'images/characters/' .. Character.name .. '/overworld.png') + for i=0,15 do + insertrandomcloud(true) + end - g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) + self.previous = previous - camera:scale(scale, scale) - camera.max.x = map.width * map.tileWidth - (window.width * 2) + local owd = Character:getOverworld() + local charactersprites = love.graphics.newImage('images/characters/' .. Character.name .. '/overworld.png') - fonts.set( 'big' ) + g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) - sound.playMusic( "overworld" ) + camera:scale(scale, scale) + camera.max.x = map.width * map.tileWidth - (window.width * 2) - self.stand = anim8.newAnimation('once', g(owd, 1), 1) - self.walk = anim8.newAnimation('loop', g(owd,2,owd,3), 0.2) - self.facing = 1 + fonts.set('big') + sound.playMusic("overworld") - local player = Player.factory() + self.stand = anim8.newAnimation('once', g(owd, 1), 1) + self.walk = anim8.newAnimation('loop', g(owd, 2, owd, 3), 0.2) + self.facing = 1 - for _,level in ipairs(player.visitedLevels) do - for _,mapInfo in pairs(self.zones) do - if mapInfo.level == level then - mapInfo.visited = true - table.insert( flags, { - x = mapInfo.x, - y = mapInfo.y - } ) - break - end - end - end - self:reset(player.currentLevel.overworldName) + local player = Player.factory() + for _,level in ipairs(player.visitedLevels) do + for _,mapInfo in pairs(self.zones) do + if mapInfo.level == level then + mapInfo.visited = true + table.insert( flags, { + x = mapInfo.x, + y = mapInfo.y + } ) + break + end + end + end + + self:reset(player.currentLevel.overworldName) end function state:leave() - camera:scale(window.scale) - fonts.reset() + camera:scale(window.scale) + fonts.reset() + + self.cloudquads = nil + self.clouds = nil + self.watersprite = nil + self.h2o = nil + self.water = nil + + -- cloud puffs + self.cloudpuffsprite = nil + self.spunk_image = nil + + self.sparklesprite = nil + self.bling = nil + self.sparkles = nil + self.overworld = nil + self.overlay = nil + self.board = nil + self.charactersprites = nil + self.flag_image = nil + self.flags = nil + self.spunks = nil end function state:reset(level) @@ -187,20 +214,20 @@ function state:reset(level) end function state:update(dt) - water:update(dt) + self.water:update(dt) - for _,_sp in pairs(sparkles) do + for _,_sp in pairs(self.sparkles) do _sp[3]:update(dt) end - for i,cloud in pairs( clouds ) do + for i,cloud in pairs(self.clouds) do if cloud then cloud.x = cloud.x + ( cloud.s * dt ) / ( cloud.q / 2 ) if cloud.o <= 0.8 then cloud.o = cloud.o + dt end -- fade in --check for out of bounds if cloud.x + 200 < 0 or cloud.x > map.width * map.tileWidth then - clouds[i] = false - insertrandomcloud() + self.clouds[i] = false + self:insertrandomcloud() end end end @@ -254,7 +281,7 @@ function state:update(dt) spunk.x = spunk.x + spunk.dx * dt spunk.y = spunk.y + spunk.dy * dt spunk.spunk:update(dt) - if spunk.y + ( cloudpuffsprite:getHeight() * 2 ) < 0 then + if spunk.y + (self.cloudpuffsprite:getHeight() * 2 ) < 0 then self.spunks[i] = nil end end @@ -338,7 +365,7 @@ end function state:draw() - love.graphics.setBackgroundColor(133, 185, 250) + love.graphics.setBackgroundColor(133, 185, 250) for x=math.floor( camera.x / 36 ), math.floor( ( camera.x + camera:getWidth() ) / 36 ) do for y=math.floor( camera.y / 36 ), math.floor( ( camera.y + camera:getHeight() ) / 36 ) do @@ -346,7 +373,7 @@ function state:draw() end end - for i, image in ipairs(overworld) do + for i, image in ipairs(self.overworld) do local x = (i - 1) % 4 local y = i > 4 and 1 or 0 love.graphics.draw(image, x * image:getWidth(), y * image:getHeight()) @@ -354,43 +381,44 @@ function state:draw() for _,spunk in pairs(self.spunks) do if spunk then - spunk.spunk:draw( cloudpuffsprite, spunk.x, spunk.y ) + spunk.spunk:draw(self.cloudpuffsprite, spunk.x, spunk.y ) end end - for _,_sp in pairs(sparkles) do - _sp[3]:draw( sparklesprite, _sp[1] - 12, _sp[2] - 12 ) + for _,_sp in pairs(self.sparkles) do + _sp[3]:draw(self.sparklesprite, _sp[1] - 12, _sp[2] - 12 ) end --flags - for _,flag in pairs(flags) do + for _,flag in pairs(self.flags) do if flag then - love.graphics.setColor( 255, 255, 255, 255 ) - love.graphics.draw( flag_image, flag['x']*map.tileWidth+10,flag['y']*map.tileHeight-24 ) + love.graphics.setColor(255, 255, 255, 255) + love.graphics.draw(self.flag_image, flag['x'] * map.tileWidth + 10, flag['y'] * map.tileHeight - 24) end end local face_offset = self.facing == -1 and 36 or 0 + if self.moving then self.walk:draw(charactersprites, math.floor(self.tx) + face_offset - 7, math.floor(self.ty) - 15,0,self.facing,1) else self.stand:draw(charactersprites, math.floor(self.tx) + face_offset - 7, math.floor(self.ty) - 15,0,self.facing,1) end - if ( self.ty == wc_y1 and self.tx > wc_x1 and self.tx <= wc_x2 ) or - ( self.tx == wc_x2 and self.ty > wc_y2 and self.ty <= wc_y1 ) then + if (self.ty == wc_y1 and self.tx > wc_x1 and self.tx <= wc_x2) or + (self.tx == wc_x2 and self.ty > wc_y2 and self.ty <= wc_y1) then -- follow the player - love.graphics.draw( wheelchair, self.tx - offset_x, self.ty - offset_y ) + love.graphics.draw(self.wheelchair, self.tx - offset_x, self.ty - offset_y) elseif self.zone == self.zones['caverns'] or ( self.tx == wc_x2 and self.ty <= wc_y2 ) then -- cavern dock - love.graphics.draw( wheelchair, wc_x2 - offset_x, wc_y2 - offset_y ) + love.graphics.draw(self.wheelchair, wc_x2 - offset_x, wc_y2 - offset_y) else -- island dock - love.graphics.draw( wheelchair, wc_x1 - offset_x, wc_y1 - offset_y ) + love.graphics.draw(self.wheelchair, wc_x1 - offset_x, wc_y1 - offset_y) end - for i, image in ipairs(overlay) do + for i, image in ipairs(self.overlay) do if image then local x = (i - 1) % 4 local y = i > 4 and 1 or 0 @@ -398,23 +426,23 @@ function state:draw() end end - love.graphics.setColor( 255, 255, 255, 255 ) + love.graphics.setColor(255, 255, 255, 255) - for _,cloud in pairs(clouds) do + for _,cloud in pairs(self.clouds) do if cloud then love.graphics.setColor( 255, 255, 255, cloud.o * 255 ) - love.graphics.drawq( cloudpuffsprite, cloudquads[cloud.q], cloud.x, cloud.y ) + love.graphics.drawq(self.cloudpuffsprite, self.cloudquads[cloud.q], cloud.x, cloud.y ) love.graphics.setColor( 255, 255, 255, 255 ) end end - love.graphics.draw(board, camera.x + window.width - board:getWidth() / 2, + love.graphics.draw(self.board, camera.x + window.width - board:getWidth() / 2, camera.y + window.height + board:getHeight() * 2) love.graphics.printf(self:title(), - camera.x + window.width - board:getWidth() / 2, - camera.y + window.height + board:getHeight() * 2.5 - 10, - board:getWidth(), 'center') + camera.x + window.width - self.board:getWidth() / 2, + camera.y + window.height + self.board:getHeight() * 2.5 - 10, + self.board:getWidth(), 'center') end return state diff --git a/src/scanning.lua b/src/scanning.lua index c0118c875..71b9e01e9 100644 --- a/src/scanning.lua +++ b/src/scanning.lua @@ -26,12 +26,11 @@ function state:keypressed( button ) Gamestate.switch("splash") return true else - Gamestate.switch("select") + Gamestate.switch("splash") end end function state:update(dt) - self.backgroundanimate:update(dt) self.namesanimate:update(dt) self.computeranimate:update(dt) @@ -50,7 +49,6 @@ function state:update(dt) self.annieanimation:update(dt) self.troyanimation:update(dt) self.pierceanimation:update(dt) - end function state:draw() @@ -90,7 +88,6 @@ function state:draw() end function state:refresh() - -- sets length of time for animation local rtime = 10 @@ -174,8 +171,44 @@ function state:refresh() state.pierceanimation = anim8.newAnimation('once', g16('4, 3', '2-5, 1', '1-5, 2', '1-3, 3', '4, 3'), stime/12, {[1]=6*ctime}) -- animation runs for rtime secs - Timer.add(rtime, function() Gamestate.switch("select") end) + Timer.add(rtime, function() Gamestate.switch("splash") end) +end + +function state:leave() + self.backgrounds = nil + self.names = nil + self.computer = nil + self.description = nil + self.scanbar = nil + self.scanwords = nil + self.blank = nil + self.isprites = nil + self.iscan = nil + self.jeff = nil + self.britta = nil + self.abed = nil + self.shirley = nil + self.annie = nil + self.troy = nil + self.pierce = nil + + state.backgroundanimate = nil + state.namesanimate = nil + state.computeranimate = nil + state.descriptionanimate = nil + state.scanbaranimate = nil + state.scanwordsanimate = nil + state.blankanimate = nil + state.ispritesanimate = nil + state.iscananimate = nil + state.jeffanimation = nil + state.brittaanimation = nil + state.abedanimation = nil + state.shirleyanimation = nil + state.annieanimation = nil + state.troyanimation = nil + state.pierceanimation = nil end return state diff --git a/src/splash.lua b/src/splash.lua index 4246d6f98..9af55a829 100644 --- a/src/splash.lua +++ b/src/splash.lua @@ -14,23 +14,7 @@ local menu = require 'menu' function splash:init() - self.cityscape = love.graphics.newImage("images/menu/cityscape.png") - self.logo = love.graphics.newImage("images/menu/logo.png") - self.splash = love.graphics.newImage("images/openingmenu.png") - self.arrow = love.graphics.newImage("images/menu/small_arrow.png") - self.logo_position = {y=-self.logo:getHeight()} - self.logo_position_final = self.logo:getHeight() / 2 + 40 - self.text = "" - tween(4, self.logo_position, { y=self.logo_position_final}) - -- sparkles - self.sparklesprite = love.graphics.newImage('images/cornelius_sparkles.png') - self.bling = anim8.newGrid(24, 24, self.sparklesprite:getWidth(), self.sparklesprite:getHeight()) - self.sparkles = {{55,34},{42,112},{132,139},{271,115},{274,50}} - for _,_sp in pairs(self.sparkles) do - _sp[3] = anim8.newAnimation('loop', self.bling('1-4,1'), 0.22 + math.random() / 10) - _sp[3]:gotoFrame( math.random( 4 ) ) - end self.menu = menu.new({ 'start', 'controls', 'options', 'credits', 'exit' }) @@ -49,6 +33,26 @@ function splash:init() end function splash:enter(a) + self.cityscape = love.graphics.newImage("images/menu/cityscape.png") + self.logo = love.graphics.newImage("images/menu/logo.png") + self.splash = love.graphics.newImage("images/openingmenu.png") + self.arrow = love.graphics.newImage("images/menu/small_arrow.png") + self.logo_position = {y=-self.logo:getHeight()} + self.logo_position_final = self.logo:getHeight() / 2 + 40 + self.text = "" + + tween(4, self.logo_position, { y=self.logo_position_final}) + + -- sparkles + self.sparklesprite = love.graphics.newImage('images/cornelius_sparkles.png') + self.bling = anim8.newGrid(24, 24, self.sparklesprite:getWidth(), self.sparklesprite:getHeight()) + self.sparkles = {{55,34},{42,112},{132,139},{271,115},{274,50}} + + for _,_sp in pairs(self.sparkles) do + _sp[3] = anim8.newAnimation('loop', self.bling('1-4,1'), 0.22 + math.random() / 10) + _sp[3]:gotoFrame( math.random( 4 ) ) + end + fonts.set( 'big' ) self.text = string.format(app.i18n('s_or_s_select_item'), controls:getKey('JUMP'), controls:getKey('ATTACK') ) @@ -70,6 +74,15 @@ function splash:update(dt) end function splash:leave() + self.cityscape = nil + self.logo = nil + self.splash = nil + self.arrow = nil + self.text = nil + self.sparkles = nil + self.sparklesprite = nil + self.bling = nil + fonts.reset() if self.handle then diff --git a/src/test/runner.lua b/src/test/runner.lua index 5f02d67e1..ca2d5977c 100644 --- a/src/test/runner.lua +++ b/src/test/runner.lua @@ -10,6 +10,7 @@ lunatest.suite("test/test_updater") lunatest.suite("test/test_cheat") lunatest.suite("test/test_inventory") lunatest.suite("test/test_inputcontroller") +lunatest.suite("test/test_character") -- Don't change these lines love.audio.setVolume(0) diff --git a/src/test/test_character.lua b/src/test/test_character.lua new file mode 100644 index 000000000..4d7285890 --- /dev/null +++ b/src/test/test_character.lua @@ -0,0 +1,18 @@ +local character = require "src/character" + +--Should fail +function test_pick_unknown_character() + assert_error(function() + character.pick('unknown', 'base') + end, "Unknown character should fail") +end + +function test_pick_unknown_costume() + assert_error(function() + character.pick('abed', 'unknown') + end, "Unknown character should fail") +end + +function test_pick_known_combination() + character.pick('abed', 'base') +end diff --git a/src/update.lua b/src/update.lua index b8607d343..23cc81b9a 100644 --- a/src/update.lua +++ b/src/update.lua @@ -9,16 +9,16 @@ local window = require 'window' local screen = Gamestate.new() function screen:init() - self.message = "" - self.progress = 0 self.updater = sparkle.newUpdater(app.config.iteration or "0.0.0", app.config.feedurl or "") - self.logo = love.graphics.newImage('images/menu/splash.png') - self.time = 0 end function screen:enter() + self.message = "" + self.progress = 0 + self.time = 0 + self.logo = love.graphics.newImage('images/menu/splash.png') self.bg = sound.playMusic("opening") self.updater:start() end @@ -45,6 +45,7 @@ function screen:update(dt) end function screen:leave() + self.logo = nil love.graphics.setColor(255, 255, 255, 255) end From 81d666eeabff1033c4a56c645a1a1770eed85e2b Mon Sep 17 00:00:00 2001 From: didory123 Date: Fri, 25 Oct 2013 22:58:34 -0700 Subject: [PATCH 13/32] remove -1 --- src/player.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/player.lua b/src/player.lua index 25a4cb751..60568bd97 100644 --- a/src/player.lua +++ b/src/player.lua @@ -593,8 +593,8 @@ function Player:hurt(damage) self.invulnerable = true if damage ~= nil then - self.healthText.x = self.position.x + self.width / 2 - self.healthText.y = self.position.y + --self.healthText.x = self.position.x + self.width / 2 + --self.healthText.y = self.position.y self.healthVel.y = -35 self.damageTaken = damage self.health = math.max(self.health - damage, 0) From 0afdb32117502b193712fcc80d734ee69244104a Mon Sep 17 00:00:00 2001 From: didory123 Date: Fri, 25 Oct 2013 23:55:10 -0700 Subject: [PATCH 14/32] make it flash damage --- src/player.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/player.lua b/src/player.lua index 60568bd97..7ad4d3297 100644 --- a/src/player.lua +++ b/src/player.lua @@ -23,8 +23,6 @@ for i=20,0,-1 do healthbar:getWidth(), healthbar:getHeight())) end -local health = love.graphics.newImage('images/damage.png') - local Player = {} Player.__index = Player Player.isPlayer = true @@ -579,6 +577,7 @@ end -- @param damage The amount of damage to deal to the player -- function Player:hurt(damage) + if self.invulnerable or self.godmode then return end @@ -593,9 +592,9 @@ function Player:hurt(damage) self.invulnerable = true if damage ~= nil then - --self.healthText.x = self.position.x + self.width / 2 - --self.healthText.y = self.position.y - self.healthVel.y = -35 + -- self.healthText.x = self.position.x + self.width / 2 + -- self.healthText.y = self.position.y + -- self.healthVel.y = -35 self.damageTaken = damage self.health = math.max(self.health - damage, 0) end @@ -721,7 +720,8 @@ function Player:draw() end if self.rebounding and self.damageTaken > 0 then - love.graphics.draw(health, self.healthText.x, self.healthText.y) + love.graphics.setColor( 255, 0, 0, 255 ) + love.graphics.print(self.damageTaken * -1, self.position.x + 45, self.position.y, 0, 0.7, 0.7) end love.graphics.setColor( 255, 255, 255, 255 ) From f74e30758c58f37883ebd3b2cf7770be1a4f331a Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 26 Oct 2013 00:03:14 -0700 Subject: [PATCH 15/32] Fix the character select screen and overworld. These scenes no longer import all their resources when imported. That happens now on enter. --- src/character.lua | 36 +++- src/characterstrip.lua | 2 +- src/debugger.lua | 2 +- src/flyin.lua | 18 +- src/scanning.lua | 23 +-- src/select.lua | 392 ++++++++++++++++++++---------------- src/selectbackground.lua | 135 +++++++------ src/test/test_character.lua | 14 ++ src/verticalparticles.lua | 59 +++--- 9 files changed, 391 insertions(+), 290 deletions(-) diff --git a/src/character.lua b/src/character.lua index 3d10b1855..e48b5f25b 100644 --- a/src/character.lua +++ b/src/character.lua @@ -17,19 +17,45 @@ module.name = 'abed' local _character = 'abed' local _costume = 'base' -function module.pick(character, costume) +function module.pick(name, costume) + if not love.filesystem.exists("characters/" .. name .. ".json") then + error("Unknown character " .. name) + end + + if not love.filesystem.exists("images/characters/" .. name .. "/" .. costume .. ".png") then + error("Unknown costume " .. costume .. " for character " .. name) + end + + _character = name + _costume = costume +end + +function module.load(character) if not love.filesystem.exists("characters/" .. character .. ".json") then error("Unknown character " .. character) end - if not love.filesystem.exists("images/characters/" .. character .. "/" .. costume .. ".png") then - error("Unknown costume " .. costume .. " for character " .. character) + local contents, _ = love.filesystem.read('characters/' .. character .. ".json") + return json.decode(contents) +end + +-- Load the current character. Do all the crazy stuff too +function module.current() + if not love.filesystem.exists("characters/" .. character .. ".json") then + error("Unknown character " .. character) end - _character = character - _costume = costume + local contents, _ = love.filesystem.read('characters/' .. character .. ".json") + return json.decode(contents) end + +function module.getCostumeImage(character, costume) + local path = "images/characters/" .. character .. "/" .. costume .. ".png" + return love.graphics.newImage(path) +end + + for i,p in pairs({}) do -- -- bring in the data from the character file diff --git a/src/characterstrip.lua b/src/characterstrip.lua index 93111beeb..0b0616004 100644 --- a/src/characterstrip.lua +++ b/src/characterstrip.lua @@ -3,11 +3,11 @@ -- A single colored strip, on which a character appears for selection. -- Created by tjvezina ---------------------------------------------------------------------- +local window = require 'window' local CharacterStrip = {} CharacterStrip.__index = CharacterStrip -local window = require 'window' local stripSize = 35 -- Thickness of the strip local moveSize = 300 -- Pixels travelled from ratio 0 to 1 diff --git a/src/debugger.lua b/src/debugger.lua index 566d8cafd..85e19f83c 100644 --- a/src/debugger.lua +++ b/src/debugger.lua @@ -115,7 +115,7 @@ function Debugger:draw() end love.graphics.setColor( 255, 255, 255, 255 ) fonts.set('big') - love.graphics.print( math.floor( collectgarbage( 'count' ) / 1000 ), window.screen_width - 30, window.screen_height - 10,0,0.5,0.5 ) + love.graphics.print(math.floor(collectgarbage('count')), window.screen_width - 30, window.screen_height - 10,0,0.5,0.5 ) fonts.revert() end diff --git a/src/flyin.lua b/src/flyin.lua index ea057ba9e..e2753d4f7 100644 --- a/src/flyin.lua +++ b/src/flyin.lua @@ -6,7 +6,7 @@ local TunnelParticles = require "tunnelparticles" local flyin = Gamestate.new() local sound = require 'vendor/TEsound' local Timer = require 'vendor/timer' -local Character = require 'character' +local character = require 'character' local utils = require 'utils' function flyin:init( ) @@ -16,23 +16,23 @@ end function flyin:enter( prev ) self.flying = {} self.characterorder = {} - for i,c in pairs(Character.characters) do - if c.name ~= Character.name then + for i,c in pairs(character.characters) do + if c.name ~= character.name then table.insert(self.characterorder, c.name) end end self.characterorder = utils.shuffle( self.characterorder, 5 ) - table.insert( self.characterorder, Character.name ) + table.insert( self.characterorder, character.name ) local time = 0 for _,name in pairs( self.characterorder ) do Timer.add(time, function() table.insert( self.flying, { n = name, - c = name == Character.name and Character.costume or Character:findRelatedCostume(name), + c = name == character.name and character.costume or character:findRelatedCostume(name), x = window.width / 2, y = window.height / 2, t = math.random( ( math.pi * 2 ) * 10000 ) / 10000, - r = name == Character.name and 0 or ( math.random( 4 ) - 1 ) * ( math.pi / 2 ), + r = name == character.name and 0 or ( math.random( 4 ) - 1 ) * ( math.pi / 2 ), s = 0.1, show = true }) @@ -51,11 +51,11 @@ function flyin:draw() local v = flyin.flying[i] if v.show then love.graphics.setColor( 255, 255, 255, 255 ) - Character.characters[v.n].animations.flyin:draw( Character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) + character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) -- black mask while coming out of 'tunnel' if v.s <= 1 then love.graphics.setColor( 0, 0, 0, 255 * ( 1 - v.s ) ) - Character.characters[v.n].animations.flyin:draw( Character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) + character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) end end end @@ -75,7 +75,7 @@ end function flyin:update(dt) TunnelParticles.update(dt) for k,v in pairs(flyin.flying) do - if v.n ~= Character.name then + if v.n ~= character.name then v.x = v.x + ( math.cos( v.t ) * dt * v.s * 90 ) v.y = v.y + ( math.sin( v.t ) * dt * v.s * 90 ) end diff --git a/src/scanning.lua b/src/scanning.lua index 71b9e01e9..a28105808 100644 --- a/src/scanning.lua +++ b/src/scanning.lua @@ -19,15 +19,14 @@ function state:enter(previous) self.previous = previous end - function state:keypressed( button ) - Timer.clear() - if button == "START" then - Gamestate.switch("splash") - return true - else - Gamestate.switch("splash") - end + Timer.clear() + if button == "START" then + Gamestate.switch("splash") + return true + else + Gamestate.switch("select") + end end function state:update(dt) @@ -54,9 +53,9 @@ end function state:draw() --background colour - love.graphics.setColor( 60, 86, 173, 255 ) - love.graphics.rectangle( 'fill', 0, 0, love.graphics:getWidth(), love.graphics:getHeight() ) - love.graphics.setColor( 255, 255, 255, 255 ) + love.graphics.setColor( 60, 86, 173, 255 ) + love.graphics.rectangle( 'fill', 0, 0, love.graphics:getWidth(), love.graphics:getHeight() ) + love.graphics.setColor( 255, 255, 255, 255 ) -- coloured backgrounds local width = window.width @@ -171,7 +170,7 @@ function state:refresh() state.pierceanimation = anim8.newAnimation('once', g16('4, 3', '2-5, 1', '1-5, 2', '1-3, 3', '4, 3'), stime/12, {[1]=6*ctime}) -- animation runs for rtime secs - Timer.add(rtime, function() Gamestate.switch("splash") end) + Timer.add(rtime, function() Gamestate.switch("select") end) end diff --git a/src/select.lua b/src/select.lua index fe4c46e09..062cce93f 100644 --- a/src/select.lua +++ b/src/select.lua @@ -3,214 +3,266 @@ local Level = require 'level' local window = require 'window' local fonts = require 'fonts' local background = require 'selectbackground' -local state = Gamestate.new() local sound = require 'vendor/TEsound' -local Character = require 'character' -local characters = Character.characters +local character = require 'character' local controls = require('inputcontroller').get() -local character_selections = {} -character_selections[1] = {} -- main characters -character_selections[1][0] = {} -- left -character_selections[1][1] = {} -- right -character_selections[1][1][0] = characters['troy'] -character_selections[1][1][1] = characters['shirley'] -character_selections[1][1][2] = characters['pierce'] -character_selections[1][0][0] = characters['jeff'] -character_selections[1][0][1] = characters['britta'] -character_selections[1][0][2] = characters['abed'] -character_selections[1][0][3] = characters['annie'] - -character_selections[2] = {} -- page 2 -character_selections[2][0] = {} -- left -character_selections[2][1] = {} -- right -character_selections[2][1][0] = characters['chang'] -character_selections[2][1][1] = characters['fatneil'] -character_selections[2][1][2] = characters['vicedean'] -character_selections[2][0][0] = characters['dean'] -character_selections[2][0][1] = characters['guzman'] -character_selections[2][0][2] = characters['buddy'] -character_selections[2][0][3] = characters['leonard'] - -character_selections[3] = {} -- page 3 -character_selections[3][0] = {} -- left -character_selections[3][1] = {} -- right -character_selections[3][1][0] = characters['ian'] -character_selections[3][1][1] = characters['rich'] -character_selections[3][1][2] = characters['vicki'] -character_selections[3][0][0] = characters['vaughn'] -character_selections[3][0][1] = characters['garrett'] - -local current_page = 1 -local selections = character_selections[current_page] +local state = Gamestate.new() + +-- The current selected page function state:init() - self.side = 0 -- 0 for left, 1 for right - self.level = 0 -- 0 through 3 for characters + self.side = 0 -- 0 for left, 1 for right + self.level = 0 -- 0 through 3 for characters + self.current_page = 1 - background.init() - self.chartext = "" - self.costtext = "" - self.randomtext = "" + background.init() + self.chartext = "" + self.costtext = "" + self.randomtext = "" end function state:enter(previous) - fonts.set( 'big' ) - self.previous = previous - self.music = sound.playMusic( "opening" ) - background.enter() - background.setSelected( self.side, self.level ) - - self.chartext = "PRESS " .. controls:getKey('JUMP') .. " TO CHOOSE CHARACTER" - self.costtext = "PRESS " .. controls:getKey('ATTACK') .. " or " ..controls:getKey('INTERACT') .. " TO CHANGE COSTUME" - self.randomtext = "PRESS " .. controls:getKey('SELECT') .. " TO GET A RANDOM COSTUME" + self.current_page = 1 + + self.character_selections = {} + self.characters = {} + self.costumes = {} + + self.character_selections[1] = {} -- main characters + self.character_selections[1][0] = {} -- left + self.character_selections[1][1] = {} -- right + self.character_selections[1][1][0] = 'troy' + self.character_selections[1][1][1] = 'shirley' + self.character_selections[1][1][2] = 'pierce' + self.character_selections[1][0][0] = 'jeff' + self.character_selections[1][0][1] = 'britta' + self.character_selections[1][0][2] = 'abed' + self.character_selections[1][0][3] = 'annie' + + self.character_selections[2] = {} -- page 2 + self.character_selections[2][0] = {} -- left + self.character_selections[2][1] = {} -- right + self.character_selections[2][1][0] = 'chang' + self.character_selections[2][1][1] = 'fatneil' + self.character_selections[2][1][2] = 'vicedean' + self.character_selections[2][0][0] = 'dean' + self.character_selections[2][0][1] = 'guzman' + self.character_selections[2][0][2] = 'buddy' + self.character_selections[2][0][3] = 'leonard' + + self.character_selections[3] = {} -- page 3 + self.character_selections[3][0] = {} -- left + self.character_selections[3][1] = {} -- right + self.character_selections[3][1][0] = 'ian' + self.character_selections[3][1][1] = 'rich' + self.character_selections[3][1][2] = 'vicki' + self.character_selections[3][0][0] = 'vaughn' + self.character_selections[3][0][1] = 'garrett' + + self.selections = self.character_selections[self.current_page] + + fonts.set('big') + self.previous = previous + self.music = sound.playMusic("opening") + background.enter() + background.setSelected(self.side, self.level) + + self.chartext = "PRESS " .. controls:getKey('JUMP') .. " TO CHOOSE CHARACTER" + self.costtext = "PRESS " .. controls:getKey('ATTACK') .. " or " ..controls:getKey('INTERACT') .. " TO CHANGE COSTUME" + self.randomtext = "PRESS " .. controls:getKey('SELECT') .. " TO GET A RANDOM COSTUME" end function state:character() - return selections[self.side][self.level] + local name = self.selections[self.side][self.level] + + if not name then + return nil + end + + return self:loadCharacter(name) +end + +function state:loadCharacter(name) + if not self.characters[name] then + self.characters[name] = character.load(name) + self.characters[name].count = 1 + self.characters[name].costume = 'base' + end + + return self.characters[name] end function state:keypressed( button ) - if button == "START" then - Gamestate.switch("splash") - return true - end + if button == "START" then + Gamestate.switch("splash") + return true + end - -- If any input is received while sliding, speed up - if background.slideIn or background.slideOut then - background.speed = 10 - return - end + -- If any input is received while sliding, speed up + if background.slideIn or background.slideOut then + background.speed = 10 + return + end - local level = self.level - local options = 4 + local level = self.level + local options = 4 - if button == 'LEFT' or button == 'RIGHT' then - self.side = (self.side - 1) % 2 - sound.playSfx('click') - elseif button == 'UP' then - level = (self.level - 1) % options - sound.playSfx('click') - elseif button == 'DOWN' then - level = (self.level + 1) % options - sound.playSfx('click') - elseif button == 'ATTACK' then - if self.level == 3 and self.side == 1 then - return - else - local c = self:character() - if c then - c.count = (c.count + 1) - if c.count == (#c.costumes + 1) then - c.count = 1 - end - c.costume = c.costumes[c.count].sheet - sound.playSfx('click') - end - end - return - elseif button == 'INTERACT' then - if self.level == 3 and self.side == 1 then - return - else - local c = self:character() - if c then - c.count = (c.count - 1) - if c.count == 0 then - c.count = #c.costumes - end - c.costume = c.costumes[c.count].sheet - sound.playSfx('click') - end + if button == 'LEFT' or button == 'RIGHT' then + self.side = (self.side - 1) % 2 + sound.playSfx('click') + elseif button == 'UP' then + level = (self.level - 1) % options + sound.playSfx('click') + elseif button == 'DOWN' then + level = (self.level + 1) % options + sound.playSfx('click') + elseif button == 'ATTACK' then + if self.level == 3 and self.side == 1 then + return + else + local c = self:character() + if c then + c.count = (c.count + 1) + if c.count == (#c.costumes + 1) then + c.count = 1 end - return - elseif button == 'SELECT' then - local c = self:character() - if c then - c.count = math.random(#c.costumes) - c.costume = c.costumes[c.count].sheet - sound.playSfx('click') + c.costume = c.costumes[c.count].sheet + sound.playSfx('click') + end + end + return + elseif button == 'INTERACT' then + if self.level == 3 and self.side == 1 then + return + else + local c = self:character() + if c then + c.count = (c.count - 1) + if c.count == 0 then + c.count = #c.costumes end + c.costume = c.costumes[c.count].sheet + sound.playSfx('click') + end end + return + elseif button == 'SELECT' then + local c = self:character() + if c then + c.count = math.random(#c.costumes) + c.costume = c.costumes[c.count].sheet + sound.playSfx('click') + end + end - self.level = level + self.level = level - if ( button == 'JUMP' ) and self.level == 3 and self.side == 1 then - current_page = current_page % #character_selections + 1 - selections = character_selections[current_page] - sound.playSfx('confirm') - elseif button == 'JUMP' then - if self:character() then - -- Tell the background to transition out before changing scenes - background.slideOut = true - end - sound.playSfx('confirm') + if ( button == 'JUMP' ) and self.level == 3 and self.side == 1 then + self.current_page = self.current_page % #self.character_selections + 1 + self.selections = self.character_selections[self.current_page] + sound.playSfx('confirm') + elseif button == 'JUMP' then + if self:character() then + -- Tell the background to transition out before changing scenes + background.slideOut = true end - - background.setSelected( self.side, self.level ) + sound.playSfx('confirm') + end + + background.setSelected(self.side, self.level) end function state:leave() - fonts.reset() + fonts.reset() + background.leave() + + self.character_selections = nil + self.characters = nil + self.costumes = nil + self.selections = nil + self.previous = nil + self.music = nil end function state:update(dt) - -- The background returns 'true' when the slide-out transition is complete - if background.update(dt) then - -- set the selected character and costume - Character:setCharacter( self:character().name ) - Character:setCostume( self:character().costumes[self:character().count].sheet ) - Character.changed = true - - love.graphics.setColor(255, 255, 255, 255) - local level = Gamestate.get('overworld') - level:reset() - Gamestate.switch('flyin') - end + -- The background returns 'true' when the slide-out transition is complete + if background.update(dt) then + -- set the selected character and costume + local currentPick = self:character() + + character.pick(currentPick.name, currentPick.costume) + + -- Probably don't need this anymore + character.changed = true + + love.graphics.setColor(255, 255, 255, 255) + + local level = Gamestate.get('overworld') + level:reset() + + Gamestate.switch('splash') + end end -function state:draw() - background.draw() +function state:drawCharacter(name, x, y, offset) + local char = self:loadCharacter(name) + local key = name .. char.costume - -- Only draw the details on the screen when the background is up - if not background.slideIn then - local name = "" + if not self.costumes[key] then + self.costumes[key] = character.getCostumeImage(name, char.costume) + end - if self:character() then - name = self:character().costumes[self:character().count].name - end + local image = self.costumes[key] - love.graphics.printf(self.chartext, 0, - window.height - 75, window.width, 'center') - love.graphics.printf(self.costtext, 0, - window.height - 55, window.width, 'center') - love.graphics.printf(self.randomtext, 0, - window.height - 35, window.width, 'center') - - love.graphics.printf(name, 0, - 23, window.width, 'center') - - local x, y = background.getPosition(1, 3) - love.graphics.setColor(255, 255, 255, 200) - love.graphics.print("INSUFFICIENT", x, y + 5, 0, 0.5, 0.5, 12, -6) - love.graphics.print( "FRIENDS" , x, y + 5, 0, 0.5, 0.5, -12, -32) - love.graphics.print( current_page .. ' / ' .. #character_selections, x + 60, y + 15, 0, 0.5, 0.5 ) - love.graphics.setColor(255, 255, 255, 255) + if not char.mask then + char.mask = love.graphics.newQuad(0, char.offset, 48, 35, + image:getWidth(), image:getHeight()) + end + + if offset then + love.graphics.drawq(image, char.mask, x, y, 0, -1, 1) + else + love.graphics.drawq(image, char.mask, x, y) + end +end + + +function state:draw() + background.draw() + + -- Only draw the details on the screen when the background is up + if not background.slideIn then + local name = "" + + if self:character() then + name = self:character().costumes[self:character().count].name end - for i=0,1,1 do - for j=0,3,1 do - local character = selections[i][j] - local x, y = background.getPosition(i, j) - if character then - if i == 0 then - love.graphics.drawq( Character:getSheet(character.name, character.costumes[character.count].sheet ), character.mask , x, y, 0, -1, 1 ) - else - love.graphics.drawq( Character:getSheet(character.name, character.costumes[character.count].sheet ), character.mask , x, y ) - end - end - end + love.graphics.printf(self.chartext, 0, window.height - 75, window.width, 'center') + love.graphics.printf(self.costtext, 0, window.height - 55, window.width, 'center') + love.graphics.printf(self.randomtext, 0, window.height - 35, window.width, 'center') + + love.graphics.printf(name, 0, + 23, window.width, 'center') + + local x, y = background.getPosition(1, 3) + love.graphics.setColor(255, 255, 255, 200) + love.graphics.print("INSUFFICIENT", x, y + 5, 0, 0.5, 0.5, 12, -6) + love.graphics.print( "FRIENDS" , x, y + 5, 0, 0.5, 0.5, -12, -32) + love.graphics.print(self.current_page .. ' / ' .. #self.character_selections, x + 60, y + 15, 0, 0.5, 0.5 ) + love.graphics.setColor(255, 255, 255, 255) + end + + for i=0,1,1 do + for j=0,3,1 do + local character_name = self.selections[i][j] + local x, y = background.getPosition(i, j) + if character_name then + self:drawCharacter(character_name, x, y, i == 0) + end end + end end Gamestate.home = state diff --git a/src/selectbackground.lua b/src/selectbackground.lua index a2d607945..c3a689c43 100644 --- a/src/selectbackground.lua +++ b/src/selectbackground.lua @@ -1,105 +1,110 @@ ----------------------------------------------------------------------- --- selectbackground.lua --- The background details in the character select screen. --- Created by tjvezina ----------------------------------------------------------------------- - local window = require 'window' -local slideTime = 0 -local unknownFriend = nil local VerticalParticles = require "verticalparticles" local CharacterStrip = require "characterstrip" + local selectBackground = {} +local slideTime = 0 +local unknownFriend = nil +local strips = nil + function selectBackground.init() - VerticalParticles:init() - unknownFriend = love.graphics.newImage('images/menu/insufficient_friend.png') end function selectBackground.enter() - selectBackground.speed = 1 - selectBackground.slideIn = false - selectBackground.slideOut = false - slideTime = 0; - - strips = {} - - strips[1] = CharacterStrip.new( 81, 73, 149) -- Jeff - strips[2] = CharacterStrip.new(150, 220, 149) -- Britta - strips[3] = CharacterStrip.new(200, 209, 149) -- Abed - strips[4] = CharacterStrip.new(173, 135, 158) -- Annie - strips[5] = CharacterStrip.new(149, 214, 200) -- Troy - strips[6] = CharacterStrip.new(134, 60, 133) -- Shirley - strips[7] = CharacterStrip.new(171, 98, 109) -- Pierce - strips[8] = CharacterStrip.new( 80, 80, 80) -- Insufficient - - for i,s in pairs(strips) do - s.flip = i > 4 - s.pos = (i-1) % 4 - s.ratio = -(s.pos * 2 + 1) - s.x = window.width / 2 + ( ( 7 + (25+15) * s.pos ) * (s.flip and 1 or -1)) - s.y = 66 + (35+15) * s.pos - end - - if not selectBackground.selected then - selectBackground:setSelected(0,0) - end + VerticalParticles.init() + + unknownFriend = love.graphics.newImage('images/menu/insufficient_friend.png') + + selectBackground.speed = 1 + selectBackground.slideIn = false + selectBackground.slideOut = false + slideTime = 0; + + strips = {} + + strips[1] = CharacterStrip.new( 81, 73, 149) -- Jeff + strips[2] = CharacterStrip.new(150, 220, 149) -- Britta + strips[3] = CharacterStrip.new(200, 209, 149) -- Abed + strips[4] = CharacterStrip.new(173, 135, 158) -- Annie + strips[5] = CharacterStrip.new(149, 214, 200) -- Troy + strips[6] = CharacterStrip.new(134, 60, 133) -- Shirley + strips[7] = CharacterStrip.new(171, 98, 109) -- Pierce + strips[8] = CharacterStrip.new( 80, 80, 80) -- Insufficient + + for i,s in pairs(strips) do + s.flip = i > 4 + s.pos = (i-1) % 4 + s.ratio = -(s.pos * 2 + 1) + s.x = window.width / 2 + ( ( 7 + (25+15) * s.pos ) * (s.flip and 1 or -1)) + s.y = 66 + (35+15) * s.pos + end + + if not selectBackground.selected then + selectBackground:setSelected(0,0) + end +end + +function selectBackground.leave() + unknownFriend = nil + strips = nil + VerticalParticles.leave() end -- Renders the starry background and each strip function selectBackground.draw() - love.graphics.setBackgroundColor(0, 0, 0, 0) + love.graphics.setBackgroundColor(0, 0, 0, 0) - VerticalParticles.draw() + VerticalParticles.draw() - for _,strip in ipairs(strips) do strip:draw() end + for _,strip in ipairs(strips) do strip:draw() end - love.graphics.setColor(255, 255, 255, 255) + love.graphics.setColor(255, 255, 255, 255) - local x, y = strips[8]:getCharacterPos() - love.graphics.draw(unknownFriend, x + 14, y + 10) + local x, y = strips[8]:getCharacterPos() + love.graphics.draw(unknownFriend, x + 14, y + 10) end -- Updates the particle system and each strip function selectBackground.update(dt) - VerticalParticles.update(dt) + VerticalParticles.update(dt) - sliding = selectBackground.slideIn or selectBackground.slideOut + sliding = selectBackground.slideIn or selectBackground.slideOut - if not sliding then selectBackground.speed = 1 end + if not sliding then selectBackground.speed = 1 end - if selectBackground.slideOut then - slideTime = slideTime + (dt * selectBackground.speed) - end + if selectBackground.slideOut then + slideTime = slideTime + (dt * selectBackground.speed) + end - for i,strip in ipairs(strips) do - -- Tell the strips to slide out at the proper time - strip.slideOut = (slideTime*4 > (i-1) % 4) - strip:update( dt * selectBackground.speed, strips[8].ratio == 0 ) - end + for i,strip in ipairs(strips) do + -- Tell the strips to slide out at the proper time + strip.slideOut = (slideTime*4 > (i-1) % 4) + strip:update(dt * selectBackground.speed, strips[8].ratio == 0) + end - -- Set 'slideIn' to false when the last strip is fully on-screen - selectBackground.slideIn = (strips[8].ratio < 0) + -- Set 'slideIn' to false when the last strip is fully on-screen + selectBackground.slideIn = (strips[8].ratio < 0) - -- After a set delay, tell the caller it's safe to swap states - return (slideTime > 1.25) + -- After a set delay, tell the caller it's safe to swap states + return (slideTime > 1.25) end -- Returns the postion the strip's character should be drawing at function selectBackground.getPosition(side, level) - return strips[(side * 4) + (level+1)]:getCharacterPos() + return strips[(side * 4) + (level+1)]:getCharacterPos() end function selectBackground.reset() - slideTime = 0 - selectBackground.slideIn = false - selectBackground.slideOut = false + slideTime = 0 + selectBackground.slideIn = false + selectBackground.slideOut = false end function selectBackground.setSelected(side, level) - for _,strip in pairs(strips) do strip.selected = false end - strips[(level+1) + (side == 1 and 4 or 0)].selected = true - selectBackground.selected = (level+1) + (side == 1 and 4 or 0) + for _,strip in pairs(strips) do strip.selected = false end + strips[(level + 1) + (side == 1 and 4 or 0)].selected = true + selectBackground.selected = (level + 1) + (side == 1 and 4 or 0) end diff --git a/src/test/test_character.lua b/src/test/test_character.lua index 4d7285890..bacf6d2f1 100644 --- a/src/test/test_character.lua +++ b/src/test/test_character.lua @@ -16,3 +16,17 @@ end function test_pick_known_combination() character.pick('abed', 'base') end + +function test_load_unknown_character() + assert_error(function() + character.load('unknown') + end, "Unknown character should fail") +end + +function test_load_abed() + local abed = character.load('abed') + assert_equal(abed.name, 'abed') +end + + + diff --git a/src/verticalparticles.lua b/src/verticalparticles.lua index 91b85419a..eb4807bb0 100644 --- a/src/verticalparticles.lua +++ b/src/verticalparticles.lua @@ -1,61 +1,66 @@ ----------------------------------------------------------------------- --- verticalparticles.lua --- Manages the particles for the starry select menu background. --- Created by tjvezina ----------------------------------------------------------------------- +local window = require 'window' -Particle = {} +local Particle = {} Particle.__index = Particle -local window = require 'window' - function Particle:new() - new = {} - setmetatable(new, Particle) + new = {} + setmetatable(new, Particle) - local winWidth = window.width + local winWidth = window.width - new.size = math.random(3) - new.pos = { x = math.random(winWidth), y = math.random(window.height) } + new.size = math.random(3) + new.pos = { x = math.random(winWidth), y = math.random(window.height) } - local ratio = 1.0 - math.cos(math.abs(new.pos.x - winWidth/2) * 2 / winWidth) * 0.6 + local ratio = 1.0 - math.cos(math.abs(new.pos.x - winWidth/2) * 2 / winWidth) * 0.6 - new.speed = 300 * (ratio + math.random()/4) + new.speed = 300 * (ratio + math.random()/4) - return new + return new end -- Loop each particle repeatedly over the screen function Particle:update(dt) - self.pos.y = self.pos.y - (dt * self.speed) + self.pos.y = self.pos.y - (dt * self.speed) - if self.pos.y < 0 then self.pos.y = window.height end + if self.pos.y < 0 then self.pos.y = window.height end end function Particle:draw() - love.graphics.setPoint(self.size, "rough") - love.graphics.point(self.pos.x, self.pos.y) + love.graphics.setPoint(self.size, "rough") + love.graphics.point(self.pos.x, self.pos.y) end -VerticalParticles = {} +local VerticalParticles = {} local particleCount = 100 local particles = {} -- Generate the requested number of particles function VerticalParticles.init() - for i = 1,particleCount do - table.insert(particles, Particle:new()) - end + particles = {} + + for i = 1,particleCount do + table.insert(particles, Particle:new()) + end end function VerticalParticles.update(dt) - for _,particle in ipairs(particles) do particle:update(dt) end + for _,particle in ipairs(particles) do + particle:update(dt) + end end function VerticalParticles.draw() - love.graphics.setColor( 255, 255, 255, 255 ) - for _,particle in ipairs(particles) do particle:draw() end + love.graphics.setColor(255, 255, 255, 255) + for _,particle in ipairs(particles) do + particle:draw() + end end +function VerticalParticles.leave() + particles = {} +end + + return VerticalParticles From 2b17b11d03b4daac946fc2b1dc3d13ee3158f56d Mon Sep 17 00:00:00 2001 From: didory123 Date: Sat, 26 Oct 2013 00:14:06 -0700 Subject: [PATCH 16/32] make health text have a y velocity --- src/player.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/player.lua b/src/player.lua index 7ad4d3297..47b0f6292 100644 --- a/src/player.lua +++ b/src/player.lua @@ -592,9 +592,9 @@ function Player:hurt(damage) self.invulnerable = true if damage ~= nil then - -- self.healthText.x = self.position.x + self.width / 2 - -- self.healthText.y = self.position.y - -- self.healthVel.y = -35 + self.healthText.x = self.position.x + self.width / 2 + self.healthText.y = self.position.y + self.healthVel.y = -35 self.damageTaken = damage self.health = math.max(self.health - damage, 0) end @@ -719,9 +719,11 @@ function Player:draw() self.currently_held:draw() end + local health = self.damageTaken * -1 + if self.rebounding and self.damageTaken > 0 then love.graphics.setColor( 255, 0, 0, 255 ) - love.graphics.print(self.damageTaken * -1, self.position.x + 45, self.position.y, 0, 0.7, 0.7) + love.graphics.print(health, self.healthText.x, self.healthText.y, 0, 0.7, 0.7) end love.graphics.setColor( 255, 255, 255, 255 ) From 10257dcf502ed979859cbdfe52422aff3e73bec8 Mon Sep 17 00:00:00 2001 From: didory123 Date: Sat, 26 Oct 2013 00:15:37 -0700 Subject: [PATCH 17/32] remove weird line spacing --- src/player.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/src/player.lua b/src/player.lua index 47b0f6292..7cbc517e1 100644 --- a/src/player.lua +++ b/src/player.lua @@ -577,7 +577,6 @@ end -- @param damage The amount of damage to deal to the player -- function Player:hurt(damage) - if self.invulnerable or self.godmode then return end From 7e4af71bea6271a485143c5462de27db6e0638ab Mon Sep 17 00:00:00 2001 From: DaNiwa Date: Sat, 26 Oct 2013 11:33:08 -0400 Subject: [PATCH 18/32] Adjust turkey and boss bb also the platform as he now reacts to it --- src/maps/black-caverns-2.tmx | 3 +-- src/nodes/enemies/turkey.lua | 2 +- src/nodes/enemies/turkeyBoss.lua | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/maps/black-caverns-2.tmx b/src/maps/black-caverns-2.tmx index c23b11c44..90deeab68 100644 --- a/src/maps/black-caverns-2.tmx +++ b/src/maps/black-caverns-2.tmx @@ -34,7 +34,7 @@ - eJzt2NEKgjAUBmAhu/U9etTeOoQGQ9Q5bZvm98GBLiKl/Z6503UAAMCv9VE9G98L7cV5AMqY67mPRvdyhF5xD2M2+8yCmuKMAgBALVvPSuatjJbyIh9wP3E/qNkDwnVLXfO1UnPCf/D+Futyc5P6/t552tK6DlFRXstZ/NLznXrmj/w255Zas7X9Yc86y8h1pNY97Bs5+Yj3mhJ9iDpye8HR3iEb17F1reb6Rzf5vOXdVDauK+f9g3uocR515gX4b0PD4vzCu6U8MGc6s5AHplIz8qXZlzMtU+YdAJzZB8eyGDg= + eJzt2FsKgzAQBVCh7a/76FK76yIoBIkm8ZWI58CAH5ZIcx11ug4AADjaO6hP5WuhvjAPwDliPfdV6Vr20CueYcjmu7DgSmFGAQDgKrnfSuatDJbyIh/wPGE/uLIHTOueteZ3pWKm/+A3FutKc5M6f+s8LbWvXKPmLH7p/j4iGzm9g/ak9mzt+bBln2XkPlL73o9Vko/pN13kHNm4j9JesLd3yMZ95O5VrH90s+OwX+xdj/aUvH/wDDn3/B3WAKCevmLRvundUh6Imc8s5IG51Ix8afblm5Y58w4AWvYH1tAYDg== @@ -935,7 +935,6 @@ - diff --git a/src/nodes/enemies/turkey.lua b/src/nodes/enemies/turkey.lua index eb212fc9e..6a4fe715f 100644 --- a/src/nodes/enemies/turkey.lua +++ b/src/nodes/enemies/turkey.lua @@ -11,7 +11,7 @@ return { jumpkill = true, last_jump = 0, bb_width = 50, - bb_height = 50, + bb_height = 30, bb_offset = {x=4, y=22}, velocity = {x = -20, y = 0}, hp = 8, diff --git a/src/nodes/enemies/turkeyBoss.lua b/src/nodes/enemies/turkeyBoss.lua index 80a29d6e6..264a50161 100644 --- a/src/nodes/enemies/turkeyBoss.lua +++ b/src/nodes/enemies/turkeyBoss.lua @@ -20,7 +20,7 @@ return { jumpkill = false, player_rebound = 1200, bb_width = 40, - bb_height = 105, + bb_height = 95, bb_offset = { x = -40, y = 10}, attack_width = 40, attack_offset = { x = -40, y = 10}, From 603167bdde79165468fd0da3f5eb4c84b8a8e77b Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 26 Oct 2013 12:33:35 -0700 Subject: [PATCH 19/32] Reentering a level from pause screen now works. --- src/level.lua | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/level.lua b/src/level.lua index 76c8fe501..56ad6e7b0 100644 --- a/src/level.lua +++ b/src/level.lua @@ -141,6 +141,7 @@ function Level.new(name) local level = {} setmetatable(level, Level) + level.paused = false level.over = false level.state = 'idle' -- TODO: Use state machine level.name = name @@ -271,6 +272,7 @@ end function Level:enter(previous, door, position) + self.paused = false self.respawn = false self.state = 'idle' @@ -534,14 +536,16 @@ end -- Called by Gamestate.switch when changing levels function Level:leave() - for i,node in pairs(self.nodes) do - if node.leave then node:leave() end - if node.collide_end then - node:collide_end(self.player) - end + for i,node in pairs(self.nodes) do + if node.leave then node:leave() end + if node.collide_end then + node:collide_end(self.player) end + end - self.previous = nil + self.previous = nil + + if not self.paused then self.player = nil self.map = nil self.tileset = nil @@ -557,6 +561,7 @@ function Level:leave() self.events = nil self.nodes = nil self.doors = nil + end end function Level:keyreleased( button ) @@ -603,8 +608,9 @@ function Level:keypressed( button ) end if button == 'START' and not self.player.dead and self.player.health > 0 and not self.player.controlState:is('ignorePause') then - Gamestate.switch('pause', self.player) - return true + self.paused = true + Gamestate.switch('pause', self.player) + return true end end From 658d5fb65f2806f56a98ef204c7fde107952d78d Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 26 Oct 2013 15:24:50 -0700 Subject: [PATCH 20/32] Starting a level works --- src/character.lua | 234 ++++++++++++++--------------- src/flyin.lua | 125 ++++++++------- src/hud.lua | 22 +-- src/items/consumables/tacomeat.lua | 6 +- src/player.lua | 20 +-- src/select.lua | 2 +- src/test/test_character.lua | 24 +++ 7 files changed, 230 insertions(+), 203 deletions(-) diff --git a/src/character.lua b/src/character.lua index e48b5f25b..5ac8ce7cf 100644 --- a/src/character.lua +++ b/src/character.lua @@ -4,9 +4,6 @@ local Timer = require 'vendor/timer' local sound = require 'vendor/TEsound' local utils = require 'utils' -local contents, _ = love.filesystem.read('character_map.json') -local sprite_map = json.decode(contents) - local characters = {} local module = {} @@ -14,9 +11,74 @@ local module = {} -- Just for backwards compat module.name = 'abed' +local _loaded_character = nil local _character = 'abed' local _costume = 'base' +local Character = {} +Character.__index = Character + +function Character:reset() + self.state = 'idle' + self.direction = 'right' +end + +-- FIXME: Needed for the taco meat item +-- If a character doesn't have this costume, no effect takes place +function Character:setCostume(costume) + if costume == self.costume then + return + end + + -- Stuff for magic +end + +function Character:sheet() + return self:getSheet(self.costume) +end + +function Character:getSheet(costume) + local path = 'images/characters/' .. self.name .. '/' .. costume .. '.png' + + if not self.sheets[costume] then + self.sheets[costume] = love.graphics.newImage(path) + self.sheets[costume]:setFilter('nearest', 'nearest') + end + + return self.sheets[costume] +end + +function Character:update(dt) + self:animation():update(dt) +end + +function Character:animation() + return self.animations[self.state][self.direction] +end + +function Character:warpUpdate(dt) + self.animations.warp:update(dt) +end + +function Character:respawn() + self.warpin = true + self.animations.warp:gotoFrame(1) + self.animations.warp:resume() + sound.playSfx( "respawn" ) + Timer.add(0.30, function() self.warpin = false end) +end + +function Character:draw() +end + +function Character:getCategory() + return self.costumemap[self.costume].category +end + +function Character:getOverworld() + return self.costumemap[self.costume].ow +end + function module.pick(name, costume) if not love.filesystem.exists("characters/" .. name .. ".json") then error("Unknown character " .. name) @@ -28,6 +90,7 @@ function module.pick(name, costume) _character = name _costume = costume + _loaded_character = nil end function module.load(character) @@ -41,26 +104,29 @@ end -- Load the current character. Do all the crazy stuff too function module.current() - if not love.filesystem.exists("characters/" .. character .. ".json") then - error("Unknown character " .. character) + if _loaded_character then + return _loaded_character end - local contents, _ = love.filesystem.read('characters/' .. character .. ".json") - return json.decode(contents) -end - + local beamPath = 'images/characters/' .. _character .. '/beam.png' + local basePath = 'images/characters/' .. _character .. '/base.png' + local characterPath = "characters/" .. _character .. ".json" -function module.getCostumeImage(character, costume) - local path = "images/characters/" .. character .. "/" .. costume .. ".png" - return love.graphics.newImage(path) -end + if not love.filesystem.exists(characterPath) then + error("Unknown character " .. _character) + end + local contents, _ = love.filesystem.read('character_map.json') + local sprite_map = json.decode(contents) -for i,p in pairs({}) do -- + local contents, _ = love.filesystem.read(characterPath) - -- bring in the data from the character file - local contents, _ = love.filesystem.read('characters/' .. p) local character = json.decode(contents) + setmetatable(character, Character) + + character.name = _character + character.costume = _costume + character.warpin = false if character.animations then --merge local base = utils.deepcopy(character.animations) @@ -73,34 +139,39 @@ for i,p in pairs({}) do -- end -- build the character - character.beam = love.graphics.newImage( 'images/characters/' .. character.name .. '/beam.png') + character.beam = love.graphics.newImage(beamPath) character.beam:setFilter('nearest', 'nearest') character.count = 1 character.sheets = {} - character.sheets.base = love.graphics.newImage( 'images/characters/' .. character.name .. '/base.png') + character.sheets.base = love.graphics.newImage(basePath) character.sheets.base:setFilter('nearest', 'nearest') - character.mask = love.graphics.newQuad(0, character.offset, 48, 35, character.sheets.base:getWidth(), character.sheets.base:getHeight()) + character.mask = love.graphics.newQuad(0, character.offset, 48, 35, + character.sheets.base:getWidth(), + character.sheets.base:getHeight()) - character.positions = require( 'positions/' .. character.name ) + character.positions = require('positions/' .. character.name) - character._grid = anim8.newGrid( 48, 48, character.sheets.base:getWidth(), character.sheets.base:getHeight() ) - character._warp = anim8.newGrid( 36, 300, character.beam:getWidth(), character.beam:getHeight() ) + character._grid = anim8.newGrid(48, 48, + character.sheets.base:getWidth(), + character.sheets.base:getHeight()) - for state, _ in pairs( character.animations ) do - local data = character.animations[ state ] + character._warp = anim8.newGrid(36, 300, character.beam:getWidth(), character.beam:getHeight()) + + for state, _ in pairs(character.animations) do + local data = character.animations[state] if state == 'warp' then - character.animations[ state ] = anim8.newAnimation(data[1], character._warp(unpack(data[2])), data[3]) + character.animations[state] = anim8.newAnimation(data[1], character._warp(unpack(data[2])), data[3]) else if type( data[1] ) == 'string' then -- positionless - character.animations[ state ] = anim8.newAnimation(data[1], character._grid(unpack(data[2])), data[3]) + character.animations[state] = anim8.newAnimation(data[1], character._grid(unpack(data[2])), data[3]) else -- positioned for i, _ in pairs( data ) do - character.animations[ state ][i] = anim8.newAnimation(data[i][1], character._grid(unpack(data[i][2])), data[i][3]) + character.animations[state][i] = anim8.newAnimation(data[i][1], character._grid(unpack(data[i][2])), data[i][3]) end end end @@ -108,117 +179,42 @@ for i,p in pairs({}) do -- character.costumemap = {} character.categorytocostumes = {} - for _,c in pairs( character.costumes ) do - character.costumemap[ c.sheet ] = c - character.categorytocostumes[ c.category ] = character.categorytocostumes[ c.category ] or {} - table.insert( character.categorytocostumes[ c.category ], c ) - end - characters[ character.name ] = character -end - -local Character = {} -Character.__index = Character -Character.characters = characters - -Character.name = 'abed' -Character.costume = 'base' - -Character.warpin = false - -function Character:reset() - self.state = 'idle' - self.direction = 'right' -end - -function Character:setCharacter(name) - if character == self.name then - return + for _,c in pairs(character.costumes) do + character.costumemap[c.sheet] = c + character.categorytocostumes[c.category] = character.categorytocostumes[c.category] or {} + table.insert(character.categorytocostumes[c.category], c) end - if self.characters[name] then - self.name = name - self.costume = 'base' - return - end - - error( "Invalid character ( " .. name .. " ) requested!" ) + _loaded_character = character + return character end -function Character:setCostume(costume) - if costume == self.costume then return end - - if self:hasCostume(costume) then - self.costume = costume - return - end - - error( "Undefined costume ( " .. costume .. " ) requested for character ( " .. self.name .. " )" ) -end -function Character:hasCostume(costume) - for _,c in pairs(self:current().costumes) do - if c.sheet == costume then - return true - end - end - return false +function module.getCostumeImage(character, costume) + local path = "images/characters/" .. character .. "/" .. costume .. ".png" + return love.graphics.newImage(path) end -function Character:current() - return self.characters[self.name] -end -function Character:sheet() - return self:getSheet(self.name, self.costume) -end +function module.characters() + local list = {} -function Character:getSheet(char,costume) - if not self.characters[char].sheets[costume] then - self.characters[char].sheets[costume] = love.graphics.newImage('images/characters/' .. char .. '/' .. costume .. '.png') - self.characters[char].sheets[costume]:setFilter('nearest', 'nearest') + for _, filename in pairs(love.filesystem.enumerate('characters')) do + local name, _ = filename:gsub(".json", "") + table.insert(list, name) end - return self.characters[char].sheets[costume] -end - -function Character:updateAnimation(dt) - self:animation():update(dt) -end - -function Character:animation() - return self.characters[self.name].animations[self.state][self.direction] -end - -function Character:warpUpdate(dt) - self:current().animations.warp:update(dt) -end - -function Character:respawn() - self.warpin = true - self:current().animations.warp:gotoFrame(1) - self:current().animations.warp:resume() - sound.playSfx( "respawn" ) - Timer.add(0.30, function() self.warpin = false end) -end -function Character:draw() -end - -function Character:getCategory() - return self:current().costumemap[ self.costume ].category -end - -function Character:getOverworld() - return self:current().costumemap[ self.costume ].ow + return list end -function Character:findRelatedCostume(char) +-- FIXME: This is broken +function module.findRelatedCostume(char) --returns the requested character's costume that is most similar to the current character local costumes = self.characters[char].categorytocostumes[self:getCategory()] if costumes then return costumes[math.random(#costumes)].sheet end return 'base' end ---Character:reset() return module diff --git a/src/flyin.lua b/src/flyin.lua index e2753d4f7..8d97a5a35 100644 --- a/src/flyin.lua +++ b/src/flyin.lua @@ -3,62 +3,71 @@ local Gamestate = require 'vendor/gamestate' local window = require 'window' local fonts = require 'fonts' local TunnelParticles = require "tunnelparticles" -local flyin = Gamestate.new() local sound = require 'vendor/TEsound' local Timer = require 'vendor/timer' local character = require 'character' local utils = require 'utils' +local flyin = Gamestate.new() + function flyin:init( ) - TunnelParticles:init() + TunnelParticles:init() end function flyin:enter( prev ) - self.flying = {} - self.characterorder = {} - for i,c in pairs(character.characters) do - if c.name ~= character.name then - table.insert(self.characterorder, c.name) - end - end - self.characterorder = utils.shuffle( self.characterorder, 5 ) - table.insert( self.characterorder, character.name ) - local time = 0 - for _,name in pairs( self.characterorder ) do - Timer.add(time, function() - table.insert( self.flying, { - n = name, - c = name == character.name and character.costume or character:findRelatedCostume(name), - x = window.width / 2, - y = window.height / 2, - t = math.random( ( math.pi * 2 ) * 10000 ) / 10000, - r = name == character.name and 0 or ( math.random( 4 ) - 1 ) * ( math.pi / 2 ), - s = 0.1, - show = true - }) - end) - time = time + 0.4 + self.flying = {} + self.characterorder = {} + + local current = character.current() + + -- Only include greendale seven + for _, name in pairs({"abed", "annie", "jeff", "pierce", "troy", "britta", "shirley"}) do + if name ~= current.name then + table.insert(self.characterorder, name) end + end + + self.characterorder = utils.shuffle(self.characterorder, 5) + + table.insert(self.characterorder, current.name) + + local time = 0 + + for _, name in pairs(self.characterorder) do + Timer.add(time, function() + table.insert(self.flying, { + n = name, + c = nil, --name == current.name and current.costume or character.findRelatedCostume(name), + x = window.width / 2, + y = window.height / 2, + t = math.random((math.pi * 2) * 10000) / 10000, + r = name == current.name and 0 or (math.random(4) - 1) * (math.pi / 2), + s = 0.1, + show = true + }) + end) + time = time + 0.4 + end end function flyin:draw() - TunnelParticles.draw() - - love.graphics.circle( 'fill', window.width / 2, window.height / 2, 30 ) - - --draw in reverse order, so the older ones get drawn on top of the newer ones - for i = #flyin.flying, 1, -1 do - local v = flyin.flying[i] - if v.show then - love.graphics.setColor( 255, 255, 255, 255 ) - character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) - -- black mask while coming out of 'tunnel' - if v.s <= 1 then - love.graphics.setColor( 0, 0, 0, 255 * ( 1 - v.s ) ) - character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) - end - end + TunnelParticles.draw() + + love.graphics.circle('fill', window.width / 2, window.height / 2, 30) + + -- draw in reverse order, so the older ones get drawn on top of the newer ones + for i = #flyin.flying, 1, -1 do + local v = flyin.flying[i] + if v.show then + love.graphics.setColor(255, 255, 255, 255) + --character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) + -- black mask while coming out of 'tunnel' + if v.s <= 1 then + love.graphics.setColor(0, 0, 0, 255 * ( 1 - v.s )) + --character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) + end end + end end function flyin:startGame(dt) @@ -68,27 +77,27 @@ function flyin:startGame(dt) end function flyin:keypressed(button) - Timer.clear() - self:startGame() + Timer.clear() + self:startGame() end function flyin:update(dt) - TunnelParticles.update(dt) - for k,v in pairs(flyin.flying) do - if v.n ~= character.name then - v.x = v.x + ( math.cos( v.t ) * dt * v.s * 90 ) - v.y = v.y + ( math.sin( v.t ) * dt * v.s * 90 ) - end - v.s = v.s + dt * 4 - v.r = v.r + dt * 5 - if v.s >= 6 then - v.show = false - end + TunnelParticles.update(dt) + for k,v in pairs(flyin.flying) do + if v.n ~= character.name then + v.x = v.x + ( math.cos( v.t ) * dt * v.s * 90 ) + v.y = v.y + ( math.sin( v.t ) * dt * v.s * 90 ) end - if not flyin.flying[ #flyin.flying ].show then - Timer.clear() - self:startGame() + v.s = v.s + dt * 4 + v.r = v.r + dt * 5 + if v.s >= 6 then + v.show = false end + end + if not flyin.flying[#flyin.flying].show then + Timer.clear() + self:startGame() + end end return flyin diff --git a/src/hud.lua b/src/hud.lua index c30245dd8..053326a10 100644 --- a/src/hud.lua +++ b/src/hud.lua @@ -1,7 +1,6 @@ local window = require 'window' local camera = require 'camera' local fonts = require 'fonts' -local Character = require 'character' local utils = require 'utils' local HUD = {} @@ -20,10 +19,10 @@ function HUD.new(level) local hud = {} setmetatable(hud, HUD) - local character = level.player.character:current() + local character = level.player.character hud.sheet = level.player.character:sheet() - hud.character_quad = love.graphics.newQuad( 0, character.offset or 5, 48, 48, hud.sheet:getWidth(), hud.sheet:getHeight() ) + hud.character_quad = love.graphics.newQuad(0, character.offset or 5, 48, 48, hud.sheet:getWidth(), hud.sheet:getHeight()) hud.character_stencil = function( x, y ) love.graphics.circle( 'fill', x + 31, y + 31, 21 ) @@ -57,21 +56,22 @@ function HUD:draw( player ) 0, 255 ) - love.graphics.draw( energy, self.x - ( player.max_health - player.health ) * 2.8, self.y ) - love.graphics.setStencil( self.character_stencil, self.x, self.y ) - love.graphics.setColor( 255, 255, 255, 255 ) + + love.graphics.draw(energy, self.x - ( player.max_health - player.health ) * 2.8, self.y) + love.graphics.setStencil(self.character_stencil, self.x, self.y) + love.graphics.setColor(255, 255, 255, 255) local currentWeapon = player.inventory:currentWeapon() if currentWeapon and not player.doBasicAttack and not player.currently_held or (player.holdingAmmo and currentWeapon) then local position = {x = self.x + 22, y = self.y + 22} currentWeapon:draw(position, nil,false) else - love.graphics.drawq( self.sheet, self.character_quad, self.x + 7, self.y + 17 ) + love.graphics.drawq(self.sheet, self.character_quad, self.x + 7, self.y + 17) end - love.graphics.setStencil( ) - love.graphics.draw( lens, self.x, self.y) + love.graphics.setStencil() + love.graphics.draw(lens, self.x, self.y) love.graphics.setColor( 0, 0, 0, 255 ) - love.graphics.print( player.money, self.x + 69, self.y + 41,0,0.5,0.5) - love.graphics.print( player.character:current().name, self.x + 60, self.y + 15,0,0.5,0.5) + love.graphics.print(player.money, self.x + 69, self.y + 41,0,0.5,0.5) + love.graphics.print(player.character.name, self.x + 60, self.y + 15,0,0.5,0.5) love.graphics.setColor( 255, 255, 255, 255 ) fonts.revert() diff --git a/src/items/consumables/tacomeat.lua b/src/items/consumables/tacomeat.lua index a06b382f4..6f04e8b85 100644 --- a/src/items/consumables/tacomeat.lua +++ b/src/items/consumables/tacomeat.lua @@ -2,7 +2,7 @@ return{ name = 'tacomeat', type = 'consumable', MAX_ITEMS = 10, - use = function( consumable, player ) + use = function(consumable, player) local Timer = require('vendor/timer') local sound = require('vendor/TEsound') local punchDamage = player.punchDamage @@ -21,9 +21,7 @@ return{ end) end Timer.add(6, function () -- Set costume to zombie and double unarmed player damage. - if player.character:hasCostume('zombie') then - player.character:setCostume('zombie') - end + player.character:setCostume('zombie') player.jumpDamage = player.jumpDamage * 2 player.punchDamage = player.punchDamage * 2 player.slideDamage = player.slideDamage * 2 diff --git a/src/player.lua b/src/player.lua index 25a4cb751..bf600ddb7 100644 --- a/src/player.lua +++ b/src/player.lua @@ -35,7 +35,6 @@ Player.jumpFactor = 1 Player.speedFactor = 1 -- single 'character' object that handles all character switching, costumes and animation -Player.character = character local player = nil --- @@ -67,6 +66,7 @@ function Player.new(collider) plyr.height = 48 plyr.bbox_width = 18 plyr.bbox_height = 44 + plyr.character = character.current() --for damage text plyr.healthText = {x=0, y=0} @@ -528,15 +528,15 @@ function Player:update( dt ) if self.wielding or self.attacked then - self.character:animation():update(dt) + self.character:update(dt) elseif self.jumping then self.character.state = self.jump_state - self.character:animation():update(dt) + self.character:update(dt) elseif self.isJumpState(self.character.state) and not self.jumping then self.character.state = self.walk_state - self.character:animation():update(dt) + self.character:update(dt) elseif not self.isJumpState(self.character.state) and self.velocity.x ~= 0 then if crouching and self.crouch_state == 'crouch' then @@ -545,7 +545,7 @@ function Player:update( dt ) self.character.state = self.walk_state end - self.character:animation():update(dt) + self.character:update(dt) elseif not self.isJumpState(self.character.state) and self.velocity.x == 0 then @@ -559,10 +559,10 @@ function Player:update( dt ) self.character.state = self.idle_state end - self.character:animation():update(dt) + self.character:update(dt) else - self.character:animation():update(dt) + self.character:update(dt) end self.healthText.y = self.healthText.y + self.healthVel.y * dt @@ -708,9 +708,9 @@ function Player:draw() self.frame = animation.frames[animation.position] local x,y,w,h = self.frame:getViewport() self.frame = {x/w+1, y/w+1} - if self.character:current().positions then - self.offset_hand_right = self.character:current().positions.hand_right[self.frame[2]][self.frame[1]] - self.offset_hand_left = self.character:current().positions.hand_left[self.frame[2]][self.frame[1]] + if self.character.positions then + self.offset_hand_right = self.character.positions.hand_right[self.frame[2]][self.frame[1]] + self.offset_hand_left = self.character.positions.hand_left[self.frame[2]][self.frame[1]] else self.offset_hand_right = {0,0} self.offset_hand_left = {0,0} diff --git a/src/select.lua b/src/select.lua index 062cce93f..5d3d40621 100644 --- a/src/select.lua +++ b/src/select.lua @@ -201,7 +201,7 @@ function state:update(dt) local level = Gamestate.get('overworld') level:reset() - Gamestate.switch('splash') + Gamestate.switch('flyin') end end diff --git a/src/test/test_character.lua b/src/test/test_character.lua index bacf6d2f1..61c993662 100644 --- a/src/test/test_character.lua +++ b/src/test/test_character.lua @@ -28,5 +28,29 @@ function test_load_abed() assert_equal(abed.name, 'abed') end +function test_load_abed() + local found = false + for _, name in pairs(character.characters()) do + if name == 'abed' then + found = true + end + end + + assert_true(found, "Couldn't find Abed in characters") +end + +function test_load_current() + local character = character.current() + assert_equal(character.name, 'abed') +end + +function test_load_current() + local character = character.current() + character.state = 'walk' + character.direction = 'left' + character:reset() + assert_equal(character.state, 'idle') + assert_equal(character.direction, 'right') +end From 170fbf2ea8e221543a697ba0e50d7d48b6c5af70 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 26 Oct 2013 16:30:39 -0700 Subject: [PATCH 21/32] Levels work, but the flyin screen is still broken --- src/character.lua | 5 ++- src/flyin.lua | 41 ++++++++++++++++++++++-- src/gameover.lua | 79 ++++++++++++++++++++++++----------------------- src/overworld.lua | 13 +++++--- src/player.lua | 4 +-- 5 files changed, 91 insertions(+), 51 deletions(-) diff --git a/src/character.lua b/src/character.lua index 5ac8ce7cf..0dcd43cd4 100644 --- a/src/character.lua +++ b/src/character.lua @@ -4,8 +4,6 @@ local Timer = require 'vendor/timer' local sound = require 'vendor/TEsound' local utils = require 'utils' -local characters = {} - local module = {} -- Just for backwards compat @@ -68,7 +66,8 @@ function Character:respawn() Timer.add(0.30, function() self.warpin = false end) end -function Character:draw() +function Character:draw(x, y) + self:animation():draw(self:sheet(), x, y) end function Character:getCategory() diff --git a/src/flyin.lua b/src/flyin.lua index 8d97a5a35..aee3fa21a 100644 --- a/src/flyin.lua +++ b/src/flyin.lua @@ -50,6 +50,37 @@ function flyin:enter( prev ) end end +function flyin:drawCharacter(flyer, x, y, r, sx, sy, ox, oy) + local name = flyer.n + local costume = flyer.c + + -- find costume + -- load image + -- load mask + -- draw + + --local char = self:loadCharacter(name) + --local key = name .. char.costume + + --if not self.costumes[key] then + -- self.costumes[key] = character.getCostumeImage(name, char.costume) + --end + + --local image = self.costumes[key] + + --if not char.mask then + -- char.mask = love.graphics.newQuad(0, char.offset, 48, 35, + -- image:getWidth(), image:getHeight()) + --end + + --if offset then + -- love.graphics.drawq(image, char.mask, x, y, 0, -1, 1) + --else + -- love.graphics.drawq(image, char.mask, x, y) + --end +end + + function flyin:draw() TunnelParticles.draw() @@ -60,11 +91,15 @@ function flyin:draw() local v = flyin.flying[i] if v.show then love.graphics.setColor(255, 255, 255, 255) - --character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) + + self:drawCharacter(v, v.x, v.y, v.r - (v.r % (math.pi / 2)), + math.min(v.s, 5), math.min(v.s, 5), 22, 32) -- black mask while coming out of 'tunnel' if v.s <= 1 then - love.graphics.setColor(0, 0, 0, 255 * ( 1 - v.s )) - --character.characters[v.n].animations.flyin:draw( character:getSheet(v.n,v.c), v.x, v.y, v.r - ( v.r % ( math.pi / 2 ) ), math.min(v.s,5), math.min(v.s,5), 22, 32 ) + love.graphics.setColor(0, 0, 0, 255 * (1 - v.s )) + + self:drawCharacter(v, v.x, v.y, v.r - (v.r % (math.pi / 2)), + math.min(v.s, 5), math.min(v.s, 5), 22, 32) end end end diff --git a/src/gameover.lua b/src/gameover.lua index 42b4dcf89..d1d6115d0 100644 --- a/src/gameover.lua +++ b/src/gameover.lua @@ -8,59 +8,62 @@ local part = require 'verticalparticles' local character = require 'character' function state:init() - self.text="G A M E O V E R !" - self.text2="-Press any key to continue-" - part.init() - -- The X coordinates of the columns - self.left = 180 - -- The Y coordinate of the top key - self.top = 93 - -- Vertical spacing between keys - self.spacing = 20 + self.text="G A M E O V E R !" + self.text2="-Press any key to continue-" + part.init() + -- The X coordinates of the columns + self.left = 180 + -- The Y coordinate of the top key + self.top = 93 + -- Vertical spacing between keys + self.spacing = 20 end function state:enter(previous) - fonts.set( 'big' ) - sound.playMusic( "you-just-lost" ) + fonts.set('big') + sound.playMusic("you-just-lost") - character.state = 'dead' - character.direction = 'right' - - self.blink=0 - - camera:setPosition(0, 0) - self.previous = previous + self.character = character:current() + self.character.state = 'dead' + self.character.direction = 'right' + + self.blink = 0 + + camera:setPosition(0, 0) + self.previous = previous end function state:leave() - fonts.reset() + self.character = nil + self.previous = nil + fonts.reset() end -function state:keypressed( button ) - Gamestate.switch("splash") +function state:keypressed(button) + Gamestate.switch("splash") end function state:update(dt) - character:animation():update(dt) - part.update(dt) - self.blink = self.blink + dt - if self.blink > 1 then - self.blink = 0 - end + self.character:update(dt) + part.update(dt) + self.blink = self.blink + dt + if self.blink > 1 then + self.blink = 0 + end end function state:draw() - love.graphics.setColor( 0, 0, 0, 255 ) - love.graphics.rectangle( "fill", 0, 0, 528, 336 ) - part.draw() - fonts.set('big') - love.graphics.print( self.text, self.left - 20, self.top ) - character:animation():draw( character:sheet(), self.left + 60, self.top + 20 ) - fonts.set('ariel') - if self.blink < 0.5 then - love.graphics.print( self.text2, self.left + 5, self.top + 80 ) - end - love.graphics.setColor( 255, 255, 255, 255 ) + love.graphics.setColor(0, 0, 0, 255) + love.graphics.rectangle("fill", 0, 0, 528, 336) + part.draw() + fonts.set('big') + love.graphics.print(self.text, self.left - 20, self.top ) + self.character:draw(self.left + 60, self.top + 20) + fonts.set('ariel') + if self.blink < 0.5 then + love.graphics.print(self.text2, self.left + 5, self.top + 80) + end + love.graphics.setColor(255, 255, 255, 255) end return state diff --git a/src/overworld.lua b/src/overworld.lua index e2ce3c4f3..d4742a419 100644 --- a/src/overworld.lua +++ b/src/overworld.lua @@ -6,9 +6,9 @@ local camera = require 'camera' local sound = require 'vendor/TEsound' local utils = require 'utils' local Player = require 'player' -local state = Gamestate.new() -local Character = require 'character' +local character = require 'character' +local state = Gamestate.new() local map = {} map.tileWidth = 12 @@ -81,7 +81,10 @@ function state:enter(previous) } self.board = love.graphics.newImage('images/overworld/titleboard.png') - self.charactersprites = love.graphics.newImage( 'images/characters/' .. Character.name .. '/overworld.png') + + local current = character.current() + + self.charactersprites = love.graphics.newImage('images/characters/' .. current.name .. '/overworld.png') local g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) @@ -134,8 +137,8 @@ function state:enter(previous) self.previous = previous - local owd = Character:getOverworld() - local charactersprites = love.graphics.newImage('images/characters/' .. Character.name .. '/overworld.png') + local owd = current:getOverworld() + local charactersprites = love.graphics.newImage('images/characters/' .. current.name .. '/overworld.png') g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) diff --git a/src/player.lua b/src/player.lua index d3b8dc20b..3fd3d30d7 100644 --- a/src/player.lua +++ b/src/player.lua @@ -664,8 +664,8 @@ function Player:draw() end if self.character.warpin then - local y = self.position.y - self.character:current().beam:getHeight() + self.height + 4 - self.character:current().animations.warp:draw(self.character:current().beam, self.position.x + 6, y) + local y = self.position.y - self.character.beam:getHeight() + self.height + 4 + self.character.animations.warp:draw(self.character.beam, self.position.x + 6, y) return end From 838ec217083535e414f0fff8d0f65e8a3499e4dd Mon Sep 17 00:00:00 2001 From: DaNiwa Date: Sat, 26 Oct 2013 23:00:51 -0400 Subject: [PATCH 22/32] Correct drawing of floorspace sprites. --- src/nodes/sprite.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nodes/sprite.lua b/src/nodes/sprite.lua index 01eeef41d..c3574ba83 100644 --- a/src/nodes/sprite.lua +++ b/src/nodes/sprite.lua @@ -27,16 +27,17 @@ function Sprite.new(node, collider, level) assert(p.sheet, "'sheet' required for sprite node") sprite.sheet = load_sprite(p.sheet) - + sprite.animation = p.animation or false sprite.foreground = p.foreground == 'true' sprite.flip = p.flip == 'true' + sprite.node = node if p.height and p.width then sprite.height = p.height sprite.width = p.width end - + if sprite.animation then sprite.random = p.random == 'true' sprite.speed = p.speed and tonumber(p.speed) or 0.20 @@ -45,7 +46,7 @@ function Sprite.new(node, collider, level) else sprite.mode = p.mode and p.mode or 'loop' end - + local g = anim8.newGrid(tonumber(p.width), tonumber(p.height), sprite.sheet:getWidth(), sprite.sheet:getHeight()) @@ -63,7 +64,7 @@ function Sprite.new(node, collider, level) sprite.dt = math.random() sprite.x = node.x sprite.y = node.y - + return sprite end From e157461643c399b9ec72ace95158b84031f6ac64 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 10:17:48 -0700 Subject: [PATCH 23/32] Add ability to mark gamestates as paused. Fix #1825 Instead of having to pass around the level, we add a new method `stack` that marks the current gamestate as paused. This will have no effect on non-level gamestates. Nodes also don't have to have a reference to the level to make this work. --- src/level.lua | 3 +-- src/nodes/activenpcs/blacksmith.lua | 4 ++-- src/nodes/cauldron.lua | 2 +- src/nodes/dealer.lua | 2 +- src/vendor/gamestate.lua | 7 +++++++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/level.lua b/src/level.lua index 56ad6e7b0..e15f89892 100644 --- a/src/level.lua +++ b/src/level.lua @@ -608,8 +608,7 @@ function Level:keypressed( button ) end if button == 'START' and not self.player.dead and self.player.health > 0 and not self.player.controlState:is('ignorePause') then - self.paused = true - Gamestate.switch('pause', self.player) + Gamestate.stack('pause', self.player) return true end end diff --git a/src/nodes/activenpcs/blacksmith.lua b/src/nodes/activenpcs/blacksmith.lua index cd9e9ccba..f8e909c69 100644 --- a/src/nodes/activenpcs/blacksmith.lua +++ b/src/nodes/activenpcs/blacksmith.lua @@ -43,10 +43,10 @@ return { player.freeze = false local screenshot = love.graphics.newImage( love.graphics.newScreenshot() ) if result == "Yes" then - Gamestate.switch("shopping", player, screenshot, activenpc.name) + Gamestate.stack("shopping", player, screenshot, activenpc.name) end end player.freeze = true activenpc.prompt = Prompt.new("Would you like to see my wares?",callback, options) end -} \ No newline at end of file +} diff --git a/src/nodes/cauldron.lua b/src/nodes/cauldron.lua index d8d7b1651..538cf6cbd 100644 --- a/src/nodes/cauldron.lua +++ b/src/nodes/cauldron.lua @@ -61,7 +61,7 @@ function Cauldron:keypressed( button, player ) player.freeze = false if result == 'Yes' then local screenshot = love.graphics.newImage(love.graphics.newScreenshot()) - Gamestate.switch('brewing', player, screenshot) + Gamestate.stack('brewing', player, screenshot) end end self.prompt = Prompt.new(message, callback, options) diff --git a/src/nodes/dealer.lua b/src/nodes/dealer.lua index cb070b4c2..1a69d1a77 100644 --- a/src/nodes/dealer.lua +++ b/src/nodes/dealer.lua @@ -47,7 +47,7 @@ function Dealer:keypressed( button, player ) player.freeze = false if result == 'Poker' or result == 'Blackjack' then local screenshot = love.graphics.newImage( love.graphics.newScreenshot() ) - Gamestate.switch(result:lower() .. 'game', player, screenshot) + Gamestate.stack(result:lower() .. 'game', player, screenshot) end end diff --git a/src/vendor/gamestate.lua b/src/vendor/gamestate.lua index a4cb164df..f2bb2c7ce 100644 --- a/src/vendor/gamestate.lua +++ b/src/vendor/gamestate.lua @@ -80,6 +80,13 @@ function GS.switch(to, ...) return current:enter(pre, ...) end +-- Same as GS.switch, but mark the current gamestate as paused +function GS.stack(to, ...) + current.paused = true + return GS.switch(to, ...) +end + + -- holds all defined love callbacks after GS.registerEvents is called -- returns empty function on undefined callback local registry = setmetatable({}, {__index = function() return __NULL__ end}) From 5d5703e86a7c241ab8e204399e03257f412c80b6 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 10:51:09 -0700 Subject: [PATCH 24/32] Fix the flyin. --- src/character.lua | 15 +++++--- src/flyin.lua | 55 +++++++++++++++++++---------- src/test/test_character.lua | 12 +++++++ src/tunnelparticles.lua | 69 +++++++++++++++++++++---------------- 4 files changed, 97 insertions(+), 54 deletions(-) diff --git a/src/character.lua b/src/character.lua index 0dcd43cd4..96105d970 100644 --- a/src/character.lua +++ b/src/character.lua @@ -207,11 +207,16 @@ function module.characters() return list end --- FIXME: This is broken -function module.findRelatedCostume(char) - --returns the requested character's costume that is most similar to the current character - local costumes = self.characters[char].categorytocostumes[self:getCategory()] - if costumes then return costumes[math.random(#costumes)].sheet end +--returns the requested character's costume that is most similar to the current character +function module.findRelatedCostume(name, category) + local char = module.load(name) + + for _, costume in pairs(char.costumes) do + if costume.category == category then + return costume.sheet + end + end + return 'base' end diff --git a/src/flyin.lua b/src/flyin.lua index aee3fa21a..a4f97f5ce 100644 --- a/src/flyin.lua +++ b/src/flyin.lua @@ -16,32 +16,40 @@ end function flyin:enter( prev ) self.flying = {} + self.images = {} + self.masks = {} self.characterorder = {} - local current = character.current() + self.current = character.current() -- Only include greendale seven for _, name in pairs({"abed", "annie", "jeff", "pierce", "troy", "britta", "shirley"}) do - if name ~= current.name then + if name ~= self.current.name then table.insert(self.characterorder, name) end end self.characterorder = utils.shuffle(self.characterorder, 5) - table.insert(self.characterorder, current.name) + table.insert(self.characterorder, self.current.name) local time = 0 for _, name in pairs(self.characterorder) do + local costume_name = self.current.costume + + if name ~= self.current.name then + costume_name = character.findRelatedCostume(name, self.current:getCategory()) + end + Timer.add(time, function() table.insert(self.flying, { n = name, - c = nil, --name == current.name and current.costume or character.findRelatedCostume(name), + c = costume_name, x = window.width / 2, y = window.height / 2, t = math.random((math.pi * 2) * 10000) / 10000, - r = name == current.name and 0 or (math.random(4) - 1) * (math.pi / 2), + r = name == self.current.name and 0 or (math.random(4) - 1) * (math.pi / 2), s = 0.1, show = true }) @@ -50,9 +58,20 @@ function flyin:enter( prev ) end end +function flyin:leave() + self.current = nil + self.flying = {} + self.images = {} + self.masks = {} + self.characterorder = {} + TunnelParticles.leave() +end + function flyin:drawCharacter(flyer, x, y, r, sx, sy, ox, oy) local name = flyer.n local costume = flyer.c + local key = name .. costume + -- find costume -- load image @@ -62,22 +81,20 @@ function flyin:drawCharacter(flyer, x, y, r, sx, sy, ox, oy) --local char = self:loadCharacter(name) --local key = name .. char.costume - --if not self.costumes[key] then - -- self.costumes[key] = character.getCostumeImage(name, char.costume) - --end + if not self.images[key] then + self.images[key] = character.getCostumeImage(name, costume) + end - --local image = self.costumes[key] + local image = self.images[key] + + if not self.masks[key] then + self.masks[key] = love.graphics.newQuad(11 * 48, 4 * 48, 48, 48, + image:getWidth(), image:getHeight()) + end - --if not char.mask then - -- char.mask = love.graphics.newQuad(0, char.offset, 48, 35, - -- image:getWidth(), image:getHeight()) - --end + local mask = self.masks[key] - --if offset then - -- love.graphics.drawq(image, char.mask, x, y, 0, -1, 1) - --else - -- love.graphics.drawq(image, char.mask, x, y) - --end + love.graphics.drawq(image, mask, x, y, r, sx, sy, ox, oy) end @@ -119,7 +136,7 @@ end function flyin:update(dt) TunnelParticles.update(dt) for k,v in pairs(flyin.flying) do - if v.n ~= character.name then + if v.n ~= self.current.name then v.x = v.x + ( math.cos( v.t ) * dt * v.s * 90 ) v.y = v.y + ( math.sin( v.t ) * dt * v.s * 90 ) end diff --git a/src/test/test_character.lua b/src/test/test_character.lua index 61c993662..3bd587fd4 100644 --- a/src/test/test_character.lua +++ b/src/test/test_character.lua @@ -54,3 +54,15 @@ function test_load_current() assert_equal(character.state, 'idle') assert_equal(character.direction, 'right') end + +function test_find_unrelated_costume() + local c = character.findRelatedCostume('abed', 'unknown_category') + assert_equal(c, 'base') +end + +function test_find_related_costume() + local c = character.findRelatedCostume('abed', 's1e7') + assert_equal(c, 'batman') +end + + diff --git a/src/tunnelparticles.lua b/src/tunnelparticles.lua index 64aaf29b8..6a2a20e62 100644 --- a/src/tunnelparticles.lua +++ b/src/tunnelparticles.lua @@ -8,42 +8,42 @@ TunnelParticle = {} TunnelParticle.__index = TunnelParticle local window = require 'window' -local maxDistance = math.sqrt( ( window.height / 2 ) ^ 2 + ( window.width / 2 ) ^ 2 ) +local maxDistance = math.sqrt((window.height / 2) ^ 2 + (window.width / 2) ^ 2) function TunnelParticle:new() - new = {} - setmetatable(new, TunnelParticle) + new = {} + setmetatable(new, TunnelParticle) - -- r = radius is the angle - -- d = distance from origin - -- s = speed is constant + -- r = radius is the angle + -- d = distance from origin + -- s = speed is constant - new.startSpeed = math.random( 200, 500 ) - new.radius = math.random( ( math.pi * 2 ) * 10000 ) / 10000 - new.distance = math.random( 30, maxDistance ) - new.speed = new.startSpeed * ( new.distance / maxDistance ) - new.spin = ( ( new.startSpeed - 200 ) / 500 ) + new.startSpeed = math.random(200, 500) + new.radius = math.random((math.pi * 2 ) * 10000) / 10000 + new.distance = math.random(30, maxDistance) + new.speed = new.startSpeed * (new.distance / maxDistance) + new.spin = ((new.startSpeed - 200) / 500) - return new + return new end -- Loop each particle repeatedly over the screen function TunnelParticle:update(dt) - self.speed = self.startSpeed * ( self.distance / maxDistance ) - self.distance = self.distance - dt * self.speed - self.radius = self.radius + self.spin * dt - - if self.distance <= 30 then - self.distance = maxDistance - end + self.speed = self.startSpeed * (self.distance / maxDistance) + self.distance = self.distance - dt * self.speed + self.radius = self.radius + self.spin * dt + + if self.distance <= 30 then + self.distance = maxDistance + end end function TunnelParticle:draw() - love.graphics.setPoint( ( self.startSpeed / 50 ) * ( self.distance / maxDistance ), "rough") - love.graphics.point( - ( window.width / 2 ) + ( math.cos( self.radius ) * self.distance ), - ( window.height / 2 ) + ( math.sin( self.radius ) * self.distance ) - ) + love.graphics.setPoint((self.startSpeed / 50) * (self.distance / maxDistance), "rough") + love.graphics.point( + (window.width / 2) + (math.cos(self.radius) * self.distance), + (window.height / 2) + (math.sin(self.radius) * self.distance) + ) end TunnelParticles = {} @@ -53,18 +53,27 @@ local particles = {} -- Generate the requested number of particles function TunnelParticles.init() - for i = 1,particleCount do - table.insert(particles, TunnelParticle:new()) - end + for i = 1,particleCount do + table.insert(particles, TunnelParticle:new()) + end end function TunnelParticles.update(dt) - for _,particle in ipairs(particles) do particle:update(dt) end + for _, particle in ipairs(particles) do + particle:update(dt) + end end function TunnelParticles.draw() - love.graphics.setColor( 255, 255, 255, 255 ) - for _,particle in ipairs(particles) do particle:draw() end + love.graphics.setColor( 255, 255, 255, 255 ) + for _, particle in ipairs(particles) do + particle:draw() + end +end + +function TunnelParticles.leave() + particles = {} end + return TunnelParticles From d80a1270498c9e27de943075eb8a78d1da9a1f83 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 11:40:06 -0700 Subject: [PATCH 25/32] The overworld now works. --- src/overworld.lua | 52 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/overworld.lua b/src/overworld.lua index d4742a419..83b40274c 100644 --- a/src/overworld.lua +++ b/src/overworld.lua @@ -86,7 +86,7 @@ function state:enter(previous) self.charactersprites = love.graphics.newImage('images/characters/' .. current.name .. '/overworld.png') - local g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) + local g = anim8.newGrid(36, 36, self.charactersprites:getWidth(), self.charactersprites:getHeight()) --flags self.flag_image = love.graphics.newImage('images/overworld/flag.png') @@ -94,8 +94,8 @@ function state:enter(previous) -- free_ride_ferry self.wheelchair = love.graphics.newImage('images/overworld/free_ride_ferry.png') - local wc_x1, wc_x2, wc_y1, wc_y2 = 1685, 1956, 816, 680 - local offset_x, offset_y = math.floor( wheelchair:getHeight() / 2 ) - 10, math.floor( wheelchair:getWidth() / 2 ) + self.wc_x1, self.wc_x2, self.wc_y1, self.wc_y2 = 1685, 1956, 816, 680 + self.offset_x, self.offset_y = math.floor(self.wheelchair:getHeight() / 2 ) - 10, math.floor(self.wheelchair:getWidth() / 2 ) -- animated water self.watersprite = love.graphics.newImage('images/overworld/world_water.png') @@ -109,7 +109,7 @@ function state:enter(previous) -- gay sparkles self.sparklesprite = love.graphics.newImage('images/overworld/gay_sparkle.png') - self.bling = anim8.newGrid(24, 24, sparklesprite:getWidth(), sparklesprite:getHeight()) + self.bling = anim8.newGrid(24, 24, self.sparklesprite:getWidth(), self.sparklesprite:getHeight()) self.sparkles = { {1028,456},{1089,442},{1403,440},{1348,591},{1390,633},{1273,698},{1160,657},{1088,702},{1048,665},{1072,604}, {1060,552},{1104,548},{1172,555},{1199,727},{1263,735},{1313,505},{1337,459},{1358,429},{1270,617},{1289,571}, @@ -117,22 +117,22 @@ function state:enter(previous) } for _,_sp in pairs(self.sparkles) do - _sp[3] = anim8.newAnimation('loop', bling('1-4,1','1-4,2'), (math.random(15) / 100) + 0.15) + _sp[3] = anim8.newAnimation('loop', self.bling('1-4,1','1-4,2'), (math.random(15) / 100) + 0.15) _sp[3]:gotoFrame(math.random( 8 )) end -- overworld clouds self.cloudquads = { - love.graphics.newQuad( 0, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --small - love.graphics.newQuad( 100, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --medium - love.graphics.newQuad( 200, 0, 100, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ), --large - love.graphics.newQuad( 300, 0, 200, 67, cloudpuffsprite:getWidth(), cloudpuffsprite:getHeight() ) --x-large + love.graphics.newQuad( 0, 0, 100, 67, self.cloudpuffsprite:getWidth(), self.cloudpuffsprite:getHeight() ), --small + love.graphics.newQuad( 100, 0, 100, 67, self.cloudpuffsprite:getWidth(), self.cloudpuffsprite:getHeight() ), --medium + love.graphics.newQuad( 200, 0, 100, 67, self.cloudpuffsprite:getWidth(), self.cloudpuffsprite:getHeight() ), --large + love.graphics.newQuad( 300, 0, 200, 67, self.cloudpuffsprite:getWidth(), self.cloudpuffsprite:getHeight() ) --x-large } self.clouds = {} for i=0,15 do - insertrandomcloud(true) + self:insertrandomcloud(map, true) end self.previous = previous @@ -140,7 +140,7 @@ function state:enter(previous) local owd = current:getOverworld() local charactersprites = love.graphics.newImage('images/characters/' .. current.name .. '/overworld.png') - g = anim8.newGrid(36, 36, charactersprites:getWidth(), charactersprites:getHeight()) + g = anim8.newGrid(36, 36, self.charactersprites:getWidth(), self.charactersprites:getHeight()) camera:scale(scale, scale) camera.max.x = map.width * map.tileWidth - (window.width * 2) @@ -230,7 +230,7 @@ function state:update(dt) --check for out of bounds if cloud.x + 200 < 0 or cloud.x > map.width * map.tileWidth then self.clouds[i] = false - self:insertrandomcloud() + self:insertrandomcloud(map) end end end @@ -272,7 +272,7 @@ function state:update(dt) -- release a new spunk local rand = math.random(3) table.insert(self.spunks, { - spunk = anim8.newAnimation('once', spunk_image('1-3,1'), 0.2), + spunk = anim8.newAnimation('once', self.spunk_image('1-3,1'), 0.2), x = self.spunk_x, y = self.spunk_y, dx = ( rand == 3 and self.spunk_dx or ( rand == 2 and 0 or -self.spunk_dx ) ), @@ -370,9 +370,9 @@ function state:draw() love.graphics.setBackgroundColor(133, 185, 250) - for x=math.floor( camera.x / 36 ), math.floor( ( camera.x + camera:getWidth() ) / 36 ) do - for y=math.floor( camera.y / 36 ), math.floor( ( camera.y + camera:getHeight() ) / 36 ) do - water:draw(watersprite, x * 36, y * 36 ) + for x=math.floor(camera.x / 36), math.floor((camera.x + camera:getWidth()) / 36) do + for y=math.floor(camera.y / 36), math.floor((camera.y + camera:getHeight()) / 36) do + self.water:draw(self.watersprite, x * 36, y * 36 ) end end @@ -403,22 +403,22 @@ function state:draw() local face_offset = self.facing == -1 and 36 or 0 if self.moving then - self.walk:draw(charactersprites, math.floor(self.tx) + face_offset - 7, math.floor(self.ty) - 15,0,self.facing,1) + self.walk:draw(self.charactersprites, math.floor(self.tx) + face_offset - 7, math.floor(self.ty) - 15,0,self.facing,1) else - self.stand:draw(charactersprites, math.floor(self.tx) + face_offset - 7, math.floor(self.ty) - 15,0,self.facing,1) + self.stand:draw(self.charactersprites, math.floor(self.tx) + face_offset - 7, math.floor(self.ty) - 15,0,self.facing,1) end - if (self.ty == wc_y1 and self.tx > wc_x1 and self.tx <= wc_x2) or - (self.tx == wc_x2 and self.ty > wc_y2 and self.ty <= wc_y1) then + if (self.ty == self.wc_y1 and self.tx > self.wc_x1 and self.tx <= self.wc_x2) or + (self.tx == self.wc_x2 and self.ty > self.wc_y2 and self.ty <= self.wc_y1) then -- follow the player - love.graphics.draw(self.wheelchair, self.tx - offset_x, self.ty - offset_y) + love.graphics.draw(self.wheelchair, self.tx - self.offset_x, self.ty - self.offset_y) elseif self.zone == self.zones['caverns'] or - ( self.tx == wc_x2 and self.ty <= wc_y2 ) then + ( self.tx == self.wc_x2 and self.ty <= self.wc_y2 ) then -- cavern dock - love.graphics.draw(self.wheelchair, wc_x2 - offset_x, wc_y2 - offset_y) + love.graphics.draw(self.wheelchair, self.wc_x2 - self.offset_x, self.wc_y2 - self.offset_y) else -- island dock - love.graphics.draw(self.wheelchair, wc_x1 - offset_x, wc_y1 - offset_y) + love.graphics.draw(self.wheelchair, self.wc_x1 - self.offset_x, self.wc_y1 - self.offset_y) end for i, image in ipairs(self.overlay) do @@ -439,8 +439,8 @@ function state:draw() end end - love.graphics.draw(self.board, camera.x + window.width - board:getWidth() / 2, - camera.y + window.height + board:getHeight() * 2) + love.graphics.draw(self.board, camera.x + window.width - self.board:getWidth() / 2, + camera.y + window.height + self.board:getHeight() * 2) love.graphics.printf(self:title(), camera.x + window.width - self.board:getWidth() / 2, From 5d6259940fbc2cc3c42b50c5eb91a3c8bd9e2886 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 11:55:07 -0700 Subject: [PATCH 26/32] Fix global flags reference --- src/overworld.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/overworld.lua b/src/overworld.lua index 83b40274c..96d10002b 100644 --- a/src/overworld.lua +++ b/src/overworld.lua @@ -159,10 +159,10 @@ function state:enter(previous) for _,mapInfo in pairs(self.zones) do if mapInfo.level == level then mapInfo.visited = true - table.insert( flags, { + table.insert(self.flags, { x = mapInfo.x, y = mapInfo.y - } ) + }) break end end From 1018585b802eef8b97e48ed443b869573f73708f Mon Sep 17 00:00:00 2001 From: edisonout Date: Mon, 28 Oct 2013 21:08:55 +0000 Subject: [PATCH 27/32] reduce player's health --- src/hud.lua | 2 +- src/player.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hud.lua b/src/hud.lua index c30245dd8..508aa68cb 100644 --- a/src/hud.lua +++ b/src/hud.lua @@ -57,7 +57,7 @@ function HUD:draw( player ) 0, 255 ) - love.graphics.draw( energy, self.x - ( player.max_health - player.health ) * 2.8, self.y ) + love.graphics.draw( energy, self.x - ( player.max_health - player.health ) * 5.6, self.y ) love.graphics.setStencil( self.character_stencil, self.x, self.y ) love.graphics.setColor( 255, 255, 255, 255 ) local currentWeapon = player.inventory:currentWeapon() diff --git a/src/player.lua b/src/player.lua index 7cbc517e1..0f82121ec 100644 --- a/src/player.lua +++ b/src/player.lua @@ -18,8 +18,8 @@ local Inventory = require('inventory') local healthbarq = {} -for i=20,0,-1 do - table.insert(healthbarq, love.graphics.newQuad(28 * i, 0, 28, 27, +for i=10,0,-1 do + table.insert(healthbarq, love.graphics.newQuad(28 * i * 2, 0, 28, 27, healthbar:getWidth(), healthbar:getHeight())) end @@ -69,7 +69,7 @@ function Player.new(collider) --for damage text plyr.healthText = {x=0, y=0} plyr.healthVel = {x=0, y=0} - plyr.max_health = 20 + plyr.max_health = 10 plyr.health = plyr.max_health plyr.jumpDamage = 3 From b7ac3cda013c04ad24c7b6e6c5b27c47407485d5 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 15:38:51 -0700 Subject: [PATCH 28/32] Require is bad --- src/character.lua | 2 +- src/level.lua | 44 ++++++++++--------- src/nodes/cutscenes/welcome_to_hawkthorne.lua | 25 ++++++----- src/nodes/enemy.lua | 3 +- src/utils.lua | 4 ++ src/vendor/tmx.lua | 2 +- 6 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/character.lua b/src/character.lua index 96105d970..19b766586 100644 --- a/src/character.lua +++ b/src/character.lua @@ -151,7 +151,7 @@ function module.current() character.sheets.base:getWidth(), character.sheets.base:getHeight()) - character.positions = require('positions/' .. character.name) + character.positions = utils.require('positions/' .. character.name) character._grid = anim8.newGrid(48, 48, character.sheets.base:getWidth(), diff --git a/src/level.lua b/src/level.lua index e15f89892..fa6f7864e 100644 --- a/src/level.lua +++ b/src/level.lua @@ -15,28 +15,17 @@ local HUD = require 'hud' local utils = require 'utils' local music = {} -local node_cache = {} -local tile_cache = {} - local Player = require 'player' local Floorspace = require 'nodes/floorspace' local Floorspaces = require 'floorspaces' local Platform = require 'nodes/platform' +local Sprite = require 'nodes/sprite' local Block = require 'nodes/block' local function limit( x, min, max ) return math.min(math.max(x,min),max) end -local function load_tileset(name) - if tile_cache[name] then - return tile_cache[name] - end - - local tileset = tmx.load(require("maps/" .. name)) - tile_cache[name] = tileset - return tileset -end local function on_collision(dt, shape_a, shape_b, mtv_x, mtv_y) if shape_a.player and shape_b.player then return end @@ -154,8 +143,9 @@ function Level.new(name) "Check the documentation for more info." ) - level.map = require("maps/" .. name) - level.tileset = load_tileset(name) + level.node_cache = {} + level.map = utils.require("maps/" .. name) + level.tileset = tmx.load(level.map) level.collider = HC(100, on_collision, collision_stop) level.offset = getCameraOffset(level.map) level.music = getSoundtrack(level.map) @@ -182,13 +172,9 @@ function Level.new(name) for k,v in pairs(level.map.objectgroups.nodes.objects) do local nodePath = 'nodes/' .. v.type - local ok, NodeClass = pcall(require, nodePath) + local ok, NodeClass = level:loadNode(nodePath) - if not ok then - - print("WARNING: Can't load " .. nodePath) - - else + if ok then local node if NodeClass and v.type == 'scenetrigger' then @@ -215,7 +201,6 @@ function Level.new(name) end end - end if level.map.objectgroups.floorspace then @@ -253,6 +238,23 @@ function Level.new(name) return level end +-- Return the node from the filesystem +function Level:loadNode(path) + if self.node_cache[path] then + return true, self.node_cache[path] + end + + local ok, class = pcall(utils.require, path) + + if not ok then + print("WARNING: Can't load " .. nodePath) + end + + self.node_cache[path] = class + + return true, class +end + function Level:restartLevel() assert(self.name ~= "overworld","level's name cannot be overworld") assert(Gamestate.currentState() ~= Gamestate.get("overworld"),"level cannot be overworld") diff --git a/src/nodes/cutscenes/welcome_to_hawkthorne.lua b/src/nodes/cutscenes/welcome_to_hawkthorne.lua index 9cce6d7de..b9a7c4fd3 100644 --- a/src/nodes/cutscenes/welcome_to_hawkthorne.lua +++ b/src/nodes/cutscenes/welcome_to_hawkthorne.lua @@ -6,10 +6,6 @@ local sound = require 'vendor/TEsound' local camera = require 'camera' local dialog = require 'dialog' -local head = love.graphics.newImage('images/cornelius_head.png') -local lightning = love.graphics.newImage('images/lightning.png') -local oval = love.graphics.newImage('images/corn_circles.png') -local sparkle = love.graphics.newImage('images/cornelius_sparkles.png') local Scene = {} Scene.__index = Scene @@ -34,6 +30,11 @@ function Scene.new(node, collider, layer) scene.y = node.y scene.finised = false + scene.head = love.graphics.newImage('images/cornelius_head.png') + scene.lightning = love.graphics.newImage('images/lightning.png') + scene.ovalImg = love.graphics.newImage('images/corn_circles.png') + scene.sparkle = love.graphics.newImage('images/cornelius_sparkles.png') + scene.nodes = nametable(layer) scene.nodes.head.opacity = 0 scene.nodes.lightning.opacity = 0 @@ -58,14 +59,14 @@ function Scene.new(node, collider, layer) sy = 1, } - local g = anim8.newGrid(144, 192, head:getWidth(), head:getHeight()) + local g = anim8.newGrid(144, 192, scene.head:getWidth(), scene.head:getHeight()) scene.talking = anim8.newAnimation('loop', g('1,1', '2,1', '3,1', '2,1', '1,1'), 0.15) - local h = anim8.newGrid(72, 312, lightning:getWidth(), lightning:getHeight()) + local h = anim8.newGrid(72, 312, scene.lightning:getWidth(), scene.lightning:getHeight()) scene.electric = anim8.newAnimation('once', h('1-5,1', '4-5,1'), 0.1) - local j = anim8.newGrid(192, 264, oval:getWidth(), oval:getHeight()) + local j = anim8.newGrid(192, 264, scene.ovalImg:getWidth(), scene.ovalImg:getHeight()) scene.circle = anim8.newAnimation('once', j('1-6,1'), 0.15) scene.pulse = anim8.newAnimation('loop', j('5-6,1'), 0.7) - local s = anim8.newGrid(24, 24, sparkle:getWidth(), sparkle:getHeight()) + local s = anim8.newGrid(24, 24, scene.sparkle:getWidth(), scene.sparkle:getHeight()) for spark in pairs(scene.sparkles) do local anim = anim8.newAnimation('loop', s('1-4,1'), 0.22 + math.random() / 10) @@ -157,19 +158,19 @@ function Scene:draw(player) love.graphics.setColor(255, 255, 255, 255) love.graphics.setColor(255, 255, 255, self.nodes.lightning.opacity) - self.electric:draw(lightning, self.nodes.lightning.x, self.nodes.lightning.y) + self.electric:draw(self.lightning, self.nodes.lightning.x, self.nodes.lightning.y) love.graphics.setColor(255, 255, 255, self.nodes.oval.opacity) - self.oval:draw(oval, self.nodes.oval.x, self.nodes.oval.y) + self.oval:draw(self.ovalImg, self.nodes.oval.x, self.nodes.oval.y) love.graphics.setColor(255, 255, 255, self.nodes.head.opacity) - self.talking:draw(head, self.nodes.head.x, self.nodes.head.y) + self.talking:draw(self.head, self.nodes.head.x, self.nodes.head.y) love.graphics.setColor(255, 255, 255, 255) for i, s in pairs(self.sparkle_animations) do local spark = self.sparkles[i] love.graphics.setColor(255, 255, 255, self.sparkle_opacity) - s:draw(sparkle, self.nodes[spark].x, self.nodes[spark].y) + s:draw(self.sparkle, self.nodes[spark].x, self.nodes[spark].y) love.graphics.setColor(255, 255, 255, 255) end diff --git a/src/nodes/enemy.lua b/src/nodes/enemy.lua index 041e32134..fde58731a 100644 --- a/src/nodes/enemy.lua +++ b/src/nodes/enemy.lua @@ -16,6 +16,7 @@ local cheat = require 'cheat' local sound = require 'vendor/TEsound' local token = require 'nodes/token' local game = require 'game' +local utils = require 'utils' local Enemy = {} Enemy.__index = Enemy @@ -33,7 +34,7 @@ function Enemy.new(node, collider, enemytype) enemy.type = type - enemy.props = require( 'nodes/enemies/' .. type ) + enemy.props = utils.require('nodes/enemies/' .. type) local sprite_sheet if node.properties.sheet then sprite_sheet = 'images/enemies/' .. node.properties.sheet .. '.png' diff --git a/src/utils.lua b/src/utils.lua index 7fc17600b..5d1eefa30 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -271,4 +271,8 @@ function utils.setMode(width, height, fullscreen, vsync, fsaa) end end +function utils.require(path) + return love.filesystem.load(path .. ".lua")() +end + return utils diff --git a/src/vendor/tmx.lua b/src/vendor/tmx.lua index 94e62bfaf..5523e7da2 100644 --- a/src/vendor/tmx.lua +++ b/src/vendor/tmx.lua @@ -120,7 +120,7 @@ function tmx.load(level) end end - table.sort( map.layers, function(a,b) return a.parallax < b.parallax end ) + table.sort(map.layers, function(a,b) return a.parallax < b.parallax end) return map end From a8565078cefc4980220a9e360dcbc231fe501aa6 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 17:08:49 -0700 Subject: [PATCH 29/32] Fix variable typo --- Makefile | 7 +++---- osx/Info.plist | 4 ---- src/level.lua | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 1980510fa..9aee96953 100644 --- a/Makefile +++ b/Makefile @@ -42,11 +42,10 @@ bin/tmx2lua: bin/love.app/Contents/MacOS/love: mkdir -p bin - $(wget) https://bitbucket.org/kyleconroy/love/downloads/love-sparkle.zip - unzip -q love-sparkle.zip - rm -f love-sparkle.zip + $(wget) https://bitbucket.org/kyleconroy/love/downloads/love-osx-mavericks-fixed.zip + unzip -q love-osx-mavericks-fixed.zip + rm -f love-osx-mavericks-fixed.zip mv love.app bin - cp osx/dsa_pub.pem bin/love.app/Contents/Resources cp osx/Info.plist bin/love.app/Contents /usr/bin/love: diff --git a/osx/Info.plist b/osx/Info.plist index 8a67a798c..04fdf64fd 100644 --- a/osx/Info.plist +++ b/osx/Info.plist @@ -40,9 +40,5 @@ 2006-2012 Hawkthorne Development Team NSPrincipalClass NSApplication - SUFeedURL - http://example.com - SUPublicDSAKeyFile - dsa_pub.pem diff --git a/src/level.lua b/src/level.lua index fa6f7864e..5ef718e03 100644 --- a/src/level.lua +++ b/src/level.lua @@ -247,7 +247,7 @@ function Level:loadNode(path) local ok, class = pcall(utils.require, path) if not ok then - print("WARNING: Can't load " .. nodePath) + print("WARNING: Can't load " .. path) end self.node_cache[path] = class From 17121c2d0e76198d98da35517dfd96fdec625386 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 17:49:40 -0700 Subject: [PATCH 30/32] Add support for OS X 10.9 --- Makefile | 7 +++---- src/main.lua | 4 +++- src/test/test_utils.lua | 14 ++++++++++++++ src/utils.lua | 8 ++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 1980510fa..9aee96953 100644 --- a/Makefile +++ b/Makefile @@ -42,11 +42,10 @@ bin/tmx2lua: bin/love.app/Contents/MacOS/love: mkdir -p bin - $(wget) https://bitbucket.org/kyleconroy/love/downloads/love-sparkle.zip - unzip -q love-sparkle.zip - rm -f love-sparkle.zip + $(wget) https://bitbucket.org/kyleconroy/love/downloads/love-osx-mavericks-fixed.zip + unzip -q love-osx-mavericks-fixed.zip + rm -f love-osx-mavericks-fixed.zip mv love.app bin - cp osx/dsa_pub.pem bin/love.app/Contents/Resources cp osx/Info.plist bin/love.app/Contents /usr/bin/love: diff --git a/src/main.lua b/src/main.lua index 9e19ccd15..407a4cd67 100644 --- a/src/main.lua +++ b/src/main.lua @@ -42,7 +42,9 @@ function love.load(arg) error("Love 0.8.0 is required") end - table.remove(arg, 1) + -- The Mavericks builds of Love adds too many arguements + arg = utils.cleanarg(arg) + local state, door, position = 'update', nil, nil -- SCIENCE! diff --git a/src/test/test_utils.lua b/src/test/test_utils.lua index 39a825bd1..83a1d9853 100644 --- a/src/test/test_utils.lua +++ b/src/test/test_utils.lua @@ -31,3 +31,17 @@ function test_split_string() assert_equal(output[1], "a") assert_equal(output[2], "a") end + +--should remove first and last values if they are the same +function test_remove_duplicate_args() + local output = utils.cleanarg({"src", "--level=forest", "src"}) + assert_equal(output[1], "--level=forest") + assert_equal(#output, 1) +end + +--should remove first value +function test_remove_first_args() + local output = utils.cleanarg({"src", "--level=forest"}) + assert_equal(output[1], "--level=forest") + assert_equal(#output, 1) +end diff --git a/src/utils.lua b/src/utils.lua index 7fc17600b..3413aea43 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -271,4 +271,12 @@ function utils.setMode(width, height, fullscreen, vsync, fsaa) end end +function utils.cleanarg(args) + local first = table.remove(args, 1) + if first == args[#args] then + table.remove(args) + end + return args +end + return utils From 5bfb52bd8fa2faba83917067141798f25c1f0914 Mon Sep 17 00:00:00 2001 From: Nick Scripp Date: Mon, 28 Oct 2013 13:09:22 -0600 Subject: [PATCH 31/32] Nerf the Giant Turkey --- src/maps/black-caverns-2.tmx | 6 +++--- src/nodes/enemies/turkeyBoss.lua | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/maps/black-caverns-2.tmx b/src/maps/black-caverns-2.tmx index 90deeab68..0e82c9378 100644 --- a/src/maps/black-caverns-2.tmx +++ b/src/maps/black-caverns-2.tmx @@ -683,7 +683,7 @@ - + @@ -830,7 +830,7 @@ - + @@ -839,7 +839,7 @@ - + diff --git a/src/nodes/enemies/turkeyBoss.lua b/src/nodes/enemies/turkeyBoss.lua index 264a50161..d51e9ec2f 100644 --- a/src/nodes/enemies/turkeyBoss.lua +++ b/src/nodes/enemies/turkeyBoss.lua @@ -25,7 +25,7 @@ return { attack_width = 40, attack_offset = { x = -40, y = 10}, velocity = {x = 0, y = 1}, - hp = 200, + hp = 100, tokens = 15, hand_x = -40, hand_y = 70, @@ -115,7 +115,7 @@ return { love.graphics.rectangle( 'fill', x + 11, y + 27, 59, 9 ) end love.graphics.setStencil(energy_stencil, x, y) - local max_hp = 200 + local max_hp = 100 local rate = 55/max_hp love.graphics.setColor( math.min(utils.map(enemy.hp, max_hp, max_hp / 2 + 1, 0, 255 ), 255), -- green to yellow @@ -220,9 +220,10 @@ return { Timer.add(0.75, function() enemy.direction = direction == -1 and 'right' or 'left' end) elseif enemy.last_attack > pause and enemy.state ~= 'jump' then - if math.random() > 0.9 and enemy.hp < 80 then + local rand = math.random() + if enemy.hp < 80 and rand > 0.9 then enemy.props.spawn_minion(enemy, direction) - elseif math.random() > 0.6 then + elseif rand > 0.6 then enemy.props.wing_attack(enemy, player, enemy.props.attackDelay) else enemy.props.attackBasketball(enemy) From 336f7fbd036a0bd67c8cd1e7a5a5d84c7325bdeb Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 28 Oct 2013 18:56:34 -0700 Subject: [PATCH 32/32] Change leave to make tables empty instead of nil --- src/level.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/level.lua b/src/level.lua index 5ef718e03..e91944a9f 100644 --- a/src/level.lua +++ b/src/level.lua @@ -561,8 +561,8 @@ function Level:leave() self.boundary = nil self.transition = nil self.events = nil - self.nodes = nil - self.doors = nil + self.nodes = {} + self.doors = {} end end