Browse Source

Congration you done it;

bjorn 10 years ago
parent
commit
98159375fe

+ 14 - 0
data/particle/dust.lua

@@ -0,0 +1,14 @@
+local Dust = class()
+Dust.image = data.media.graphics.particles.smoke
+Dust.max = 64
+Dust.blendMode = 'additive'
+
+Dust.options = {}
+Dust.options.particleLifetime = {.5}
+Dust.options.colors = {{80, 40, 0, 100}, {80, 40, 0, 0}}
+Dust.options.sizes = {.6, 1}
+Dust.options.sizeVariation = .1
+Dust.options.areaSpread = {'normal', 24, 16}
+Dust.options.rotation = {0, 2 * math.pi}
+
+return Dust

+ 16 - 0
data/particle/smoke.lua

@@ -0,0 +1,16 @@
+local Smoke = class()
+Smoke.image = data.media.graphics.particles.smoke
+Smoke.max = 64
+Smoke.blendMode = 'alpha'
+
+Smoke.options = {}
+Smoke.options.particleLifetime = {.5}
+Smoke.options.colors = {{0, 0, 0, 15}, {0, 0, 0, 0}}
+Smoke.options.sizes = {1, 1.5}
+Smoke.options.sizeVariation = .4
+Smoke.options.areaSpread = {'normal', 10, 10}
+Smoke.options.rotation = {0, 2 * math.pi}
+Smoke.options.spin = {-10, 10}
+Smoke.options.speed = {100, 200}
+
+return Smoke

+ 80 - 0
deps/particles.lua

@@ -0,0 +1,80 @@
+local g = love.graphics
+Particles = extend(Manager)
+
+Particles.depth = -5
+
+function Particles:init()
+  self.active = true
+  if not self.active then return end
+
+  self.systems = {}
+  self.modes = {draw = {}, gui = {}}
+
+  for i = 1, #data.particle do
+    local particle = data.particle[i]
+    local system = g.newParticleSystem(particle.image, particle.max or 1024)
+    system:setOffset(particle.image:getWidth() / 2, particle.image:getHeight() / 2)
+    self.systems[particle.code] = system
+    self.modes[particle.mode or 'draw'][particle.code] = system
+    for option, value in pairs(particle.options) do
+      self:apply(particle.code, option, value)
+    end
+  end
+
+  ctx.event:emit('view.register', {object = self})
+  ctx.event:emit('view.register', {object = self, mode = 'gui'})
+end
+
+function Particles:draw()
+  if ctx.ded or not self.active then return end
+  g.setColor(255, 255, 255)
+  table.each(self.modes.draw, function(system, code)
+    system:update(ls.dt)
+    g.setBlendMode(data.particle[code].blendMode or 'alpha')
+    g.draw(system)
+    g.setBlendMode('alpha')
+  end)
+end
+
+function Particles:gui()
+  if not self.active then return end
+  g.setColor(255, 255, 255)
+  table.each(self.modes.gui, function(system, code)
+    system:update(ls.dt)
+    g.setBlendMode(data.particle[code].blendMode or 'alpha')
+    g.draw(system)
+    g.setBlendMode('alpha')
+  end)
+end
+
+function Particles:emit(code, x, y, count, options)
+  if not self.active or not data.particle[code] then return end
+
+  if type(count) == 'table' then
+    options = count
+    count = 1
+  end
+
+  options = options or {}
+
+  table.each(options, function(value, option)
+    self:apply(code, option, value)
+  end)
+
+  self.systems[code]:setPosition(x, y)
+  self.systems[code]:emit(count)
+
+  table.each(options, function(value, option)
+    if data.particle[code].options[option] then
+      self:apply(code, option, data.particle[code].options[option])
+    end
+  end)
+end
+
+function Particles:apply(code, option, value)
+  if not self.active then return end
+  local system = self.systems[code]
+  local setter = system['set' .. option:capitalize()]
+  if type(value) == 'table' then setter(system, unpack(value))
+  else setter(system, value) end
+end

+ 89 - 0
deps/shine/boxblur.lua

@@ -0,0 +1,89 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Box blur shader with support for different horizontal and vertical blur size",
+
+new = function(self)
+	self.radius_h, self.radius_v = 3, 3
+	self.canvas_h, self.canvas_v = love.graphics.newCanvas(), love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+		extern vec2 direction;
+		extern number radius;
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{
+			vec4 c = vec4(0.0f);
+
+			for (float i = -radius; i <= radius; i += 1.0f)
+			{
+				c += Texel(texture, tc + i * direction);
+			}
+			return c / (2.0f * radius + 1.0f) * color;
+		}
+	]]
+	self.shader:send("direction",{1.0,0.0}) --Not needed but may fix some errors if the shader is used somewhere else
+end,
+
+draw = function(self, func, ...)
+	local s = love.graphics.getShader()
+	local co = {love.graphics.getColor()}
+
+	-- draw scene
+	self:_render_to_canvas(self.canvas_h, func, ...)
+
+	love.graphics.setColor(co)
+	love.graphics.setShader(self.shader)
+
+	local b = love.graphics.getBlendMode()
+	love.graphics.setBlendMode('premultiplied')
+
+	-- first pass (horizontal blur)
+	self.shader:send('direction', {1 / love.graphics.getWidth(), 0})
+	self.shader:send('radius', math.floor(self.radius_h + .5))
+	self:_render_to_canvas(self.canvas_v,
+	                       love.graphics.draw, self.canvas_h, 0,0)
+
+	-- second pass (vertical blur)
+	self.shader:send('direction', {0, 1 / love.graphics.getHeight()})
+	self.shader:send('radius', math.floor(self.radius_v + .5))
+	love.graphics.draw(self.canvas_v, 0,0)
+
+	-- restore blendmode, shader and canvas
+	love.graphics.setBlendMode(b)
+	love.graphics.setShader(s)
+end,
+
+set = function(self, key, value)
+	local sz = math.floor(assert(tonumber(value), "Not a number: "..tostring(value)) + .5)
+	if key == "radius" then
+		self.radius_h, self.radius_v = sz, sz
+	elseif key == "radius_h" or key == "radius_v" then
+		self[key] = sz
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+	return self
+end
+}

+ 54 - 0
deps/shine/colorgradesimple.lua

@@ -0,0 +1,54 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Simple linear color grading of red, green and blue channel",
+
+new = function(self)
+	self.canvas = love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+		extern vec3 grade;
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{
+			return vec4(grade, 1.0f) * Texel(texture, tc) * color;
+		}
+	]]
+	self.shader:send("grade",{1.0,1.0,1.0})
+end,
+
+draw = function(self, func, ...)
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "grade" then
+		self.shader:send(key, value)
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 142 - 0
deps/shine/crt.lua

@@ -0,0 +1,142 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Daniel Oaks
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "CRT-like barrel distortion",
+
+new = function(self)
+	self._x_distortion, self._y_distortion = 0.06, 0.065
+	self._outline = {25, 25, 26}
+	self._draw_outline = true
+
+	self.canvas = love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+	// How much we distort on the x and y axis.
+	//   from 0 to 1
+	extern float x_distortion;
+	extern float y_distortion;
+
+	vec2 distort_coords(vec2 point)
+	{
+		// convert to coords we use for barrel distort function
+		//   turn 0 -> 1 into -1 -> 1
+		point.x = ((point.x * 2.0) - 1.0);
+		point.y = ((point.y * -2.0) + 1.0);
+
+		// distort
+		point.x = point.x + (point.y * point.y) * point.x * x_distortion;
+		point.y = point.y + (point.x * point.x) * point.y * y_distortion;
+
+		// convert back to coords glsl uses
+		//   turn -1 -> 1 into 0 -> 1
+		point.x = ((point.x + 1.0) / 2.0);
+		point.y = ((point.y - 1.0) / -2.0);
+
+		return point;
+	}
+
+	vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+	{
+		vec2 working_coords = distort_coords(tc);
+		return Texel(texture, working_coords);
+	}
+	]]
+	self.shader:send("x_distortion", self._x_distortion)
+	self.shader:send("y_distortion", self._y_distortion)
+end,
+
+distort = function(self, x, y)
+	local w = love.graphics.getWidth()
+	local h = love.graphics.getHeight()
+
+	-- turn 0 -> w/h into -1 -> 1
+	local distorted_x = (x / (w / 2)) - 1
+	local distorted_y = (y / (h / 2)) - 1
+
+	distorted_x = distorted_x + (distorted_y * distorted_y) * distorted_x * self._x_distortion
+	distorted_y = distorted_y + (distorted_x * distorted_x) * distorted_y * self._y_distortion
+
+	-- turn -1 -> 1 into 0 -> w/h
+	distorted_x = (distorted_x + 1) * (w / 2)
+	distorted_y = (distorted_y + 1) * (h / 2)
+
+	return distorted_x, distorted_y
+end,
+
+draw = function(self, func, ...)
+	local s = love.graphics.getShader()
+	local co = {love.graphics.getColor()}
+	local b = love.graphics.getBlendMode()
+
+	-- draw scene to canvas
+	self:_render_to_canvas(self.canvas, func, ...)
+
+	-- draw outline if required
+	if self._draw_outline then
+		love.graphics.setBlendMode('replace')
+		local width = love.graphics.getLineWidth()
+		love.graphics.setLineWidth(1)
+		self.canvas:renderTo(function()
+			local w = love.graphics.getWidth()
+			local h = love.graphics.getHeight()
+			love.graphics.setColor(self._outline)
+			love.graphics.line(0,0, w,0, w,h, 0,h, 0,0)
+		end)
+		love.graphics.setLineWidth(width)
+	end
+
+	-- apply shader to canvas
+	love.graphics.setColor(co)
+	love.graphics.setShader(self.shader)
+	love.graphics.setBlendMode('premultiplied')
+	love.graphics.draw(self.canvas, 0,0)
+	love.graphics.setBlendMode(b)
+
+	-- reset shader and canvas
+	love.graphics.setShader(s)
+end,
+
+set = function(self, key, value)
+	if key == "x" then
+		assert(type(value) == "number")
+		self._x_distortion = value
+		self.shader:send("x_distortion", value)
+	elseif key == "y" then
+		assert(type(value) == "number")
+		self._y_distortion = value
+		self.shader:send("y_distortion", value)
+	elseif key == "draw_outline" then
+		assert(type(value) == "boolean")
+		self._draw_outline = value
+	elseif key == "outline" then
+		assert(type(value) == "table")
+		self._outline = value
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 61 - 0
deps/shine/desaturate.lua

@@ -0,0 +1,61 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Desaturation/tint effect",
+
+new = function(self)
+	self.canvas = love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+		extern vec4 tint;
+		extern number strength;
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{
+			color = Texel(texture, tc);
+			number luma = dot(vec3(0.299f, 0.587f, 0.114f), color.rgb);
+			return mix(color, tint * luma, strength);
+		}
+	]]
+	self.shader:send("tint",{1.0,1.0,1.0,1.0})
+	self.shader:send("strength",0.5)
+end,
+
+draw = function(self, func, ...)
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "tint" then
+		assert(type(value) == "table")
+		self.shader:send("tint", {value[1]/255, value[2]/255, value[3]/255, 1})
+	elseif key == "strength" then
+		self.shader:send("strength", math.max(0, math.min(1, tonumber(value) or 0)))
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 172 - 0
deps/shine/dmg.lua

@@ -0,0 +1,172 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Josef Patoprsty
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+local palettes = {
+	-- Default color palette. Source:
+	-- http://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Original_Game_Boy
+	{
+		name = "default",
+		colors = {
+			{ 15/255, 56/255, 15/255},
+			{ 48/255, 98/255, 48/255},
+			{139/255,172/255, 15/255},
+			{155/255,188/255, 15/255}
+		}
+	},
+	-- Hardcore color profiles. Source:
+	-- http://www.hardcoregaming101.net/gbdebate/gbcolours.htm
+	{
+		name = "dark_yellow",
+		colors = {
+			{33/255,32/255,16/255},
+			{107/255,105/255,49/255},
+			{181/255,174/255,74/255},
+			{255/255,247/255,123/255}
+		}
+	},
+	{
+		name = "light_yellow",
+		colors = {
+			{102/255,102/255,37/255},
+			{148/255,148/255,64/255},
+			{208/255,208/255,102/255},
+			{255/255,255/255,148/255}
+		}
+	},
+	{
+		name = "green",
+		colors = {
+			{8/255,56/255,8/255},
+			{48/255,96/255,48/255},
+			{136/255,168/255,8/255},
+			{183/255,220/255,17/255}
+		}
+	},
+	{
+		name = "greyscale",
+		colors = {
+			{56/255,56/255,56/255},
+			{117/255,117/255,117/255},
+			{178/255,178/255,178/255},
+			{239/255,239/255,239/255}
+		}
+	},
+	{
+		name = "stark_bw",
+		colors = {
+			{0/255,0/255,0/255},
+			{117/255,117/255,117/255},
+			{178/255,178/255,178/255},
+			{255/255,255/255,255/255}
+		}
+	},
+	{
+		name = "pocket",
+		colors = {
+			{108/255,108/255,78/255},
+			{142/255,139/255,87/255},
+			{195/255,196/255,165/255},
+			{227/255,230/255,201/255}
+		}
+	}
+}
+
+local lookup_palette = function(name)
+	for _,palette in pairs(palettes) do
+		if palette.name == name then
+			return palette
+		end
+	end
+end
+
+return {
+	requires = {'canvas', 'shader'},
+	description = "DMG Color Emulation",
+
+	new = function(self)
+		self.canvas = love.graphics.newCanvas()
+		self.shader = love.graphics.newShader[[
+			extern number value;
+			uniform vec3 palette[ 4 ];
+			vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords){
+				vec4 pixel = Texel(texture, texture_coords);
+				float avg = min(0.9999,max(0.0001,(pixel.r + pixel.g + pixel.b)/3));
+				int index = int(avg*4);
+				pixel.r = palette[index][0];
+				pixel.g = palette[index][1];
+				pixel.b = palette[index][2];
+				return  pixel;
+			}
+		]]
+		self.shader:send('palette',unpack(palettes[1].colors))
+	end,
+
+	draw = function(self, func, ...)
+		self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+	end,
+
+	set = function(self, key, value)
+		if key == "palette" then
+			local palette
+			if type(value) == "number" and palettes[math.floor(value)] then -- Check if value is an index
+				palette = palettes[math.floor(value)]
+			elseif type(value) == "string" then -- Check if value is a named palette
+				palette = lookup_palette(value)
+			elseif type(value) == "table" then -- Check if value is a custom palette.
+				-- Needs to match: {{R,G,B},{R,G,B},{R,G,B},{R,G,B}}
+				local valid = true
+				-- Table needs to have four indexes of tables
+				for color_2bit = 1,4 do
+					if value[color_2bit] and type(value[color_2bit]) == "table" then
+						-- Table needs to have three indexes of floats 0..1
+						for color_channel = 1,3 do
+							if value[color_2bit][color_channel] and type(value[color_2bit][color_channel]) == "number" then
+								if value[color_2bit][color_channel] < 0 or value[color_2bit][color_channel] > 1 then
+									-- Number is not a float 0..1
+									valid = false
+								end
+							else
+								-- Table does not have three indexes of numbers
+								valid = false
+							end
+						end
+					else
+						-- Table does not have four indexes of tables
+						valid = false
+					end
+				end
+				-- Fall back on failure
+				palette = valid and {colors=value} or palettes[1]
+			else
+				-- Fall back to default
+				palette = palettes[1]
+			end
+			self.shader:send(key,unpack(palette.colors))
+		else
+			error("Unknown property: " .. tostring(key))
+		end
+
+		return self
+	end
+}

+ 75 - 0
deps/shine/filmgrain.lua

@@ -0,0 +1,75 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Film grain overlay",
+
+new = function(self)
+	self.canvas = love.graphics.newCanvas()
+	self.noisetex = love.image.newImageData(100,100)
+	self.noisetex:mapPixel(function()
+		local l = love.math.random() * 255
+		return l,l,l,l
+	end)
+	self.noisetex = love.graphics.newImage(self.noisetex)
+	self.shader = love.graphics.newShader[[
+		extern number opacity;
+		extern number grainsize;
+		extern number noise;
+		extern Image noisetex;
+		extern vec2 tex_ratio;
+		float rand(vec2 co)
+		{
+			return Texel(noisetex, mod(co * tex_ratio / vec2(grainsize), vec2(1.0))).r;
+		}
+
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{
+			return color * Texel(texture, tc) * mix(1.0, rand(tc+vec2(noise)), opacity);
+		}
+	]]
+	self.shader:send("opacity",.3)
+	self.shader:send("grainsize",1)
+
+	self.shader:send("noise",0)
+	self.shader:send("noisetex", self.noisetex)
+	self.shader:send("tex_ratio", {love.graphics.getWidth() / self.noisetex:getWidth(), love.graphics.getHeight() / self.noisetex:getHeight()})
+end,
+
+draw = function(self, func, ...)
+	self.shader:send("noise", love.math.random())
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "opacity" or key == "grainsize" then
+		self.shader:send(key, math.max(0, tonumber(value) or 0))
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 96 - 0
deps/shine/gaussianblur.lua

@@ -0,0 +1,96 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+-- unroll convolution loop
+local function build_shader(sigma)
+	local support = math.max(1, math.floor(3*sigma + .5))
+	local one_by_sigma_sq = sigma > 0 and 1 / (sigma * sigma) or 1
+	local norm = 0
+
+	local code = {[[
+		extern vec2 direction;
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{ vec4 c = vec4(0.0f);
+	]]}
+	local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);"
+
+	for i = -support,support do
+		local coeff = math.exp(-.5 * i*i * one_by_sigma_sq)
+		norm = norm + coeff
+		code[#code+1] = blur_line:format(coeff, i)
+	end
+
+	code[#code+1] = ("return c * vec4(%f) * color;}"):format(norm > 0 and 1/norm or 1)
+
+	return love.graphics.newShader(table.concat(code))
+end
+
+return {
+requires = {'canvas', 'shader'},
+description = "Fast Gaussian blur shader",
+
+new = function(self)
+	self.canvas_h, self.canvas_v = love.graphics.newCanvas(), love.graphics.newCanvas()
+	self.shader = build_shader(1)
+	self.shader:send("direction",{1.0,0.0})
+end,
+
+draw = function(self, func, ...)
+	local c = love.graphics.getCanvas()
+	local s = love.graphics.getShader()
+	local co = {love.graphics.getColor()}
+
+	-- draw scene
+	self:_render_to_canvas(self.canvas_h, func, ...)
+
+	love.graphics.setColor(co)
+	love.graphics.setShader(self.shader)
+
+	local b = love.graphics.getBlendMode()
+	love.graphics.setBlendMode('premultiplied')
+
+	-- first pass (horizontal blur)
+	self.shader:send('direction', {1 / love.graphics.getWidth(), 0})
+	self:_render_to_canvas(self.canvas_v,
+	                       love.graphics.draw, self.canvas_h, 0,0)
+
+	-- second pass (vertical blur)
+	self.shader:send('direction', {0, 1 / love.graphics.getHeight()})
+	love.graphics.draw(self.canvas_v, 0,0)
+
+	-- restore blendmode, shader and canvas
+	love.graphics.setBlendMode(b)
+	love.graphics.setShader(s)
+	love.graphics.setCanvas(c)
+end,
+
+set = function(self, key, value)
+	if key == "sigma" then
+		self.shader = build_shader(tonumber(value))
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+	return self
+end
+}

+ 109 - 0
deps/shine/glowsimple.lua

@@ -0,0 +1,109 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+-- unroll convolution loop
+local function build_blur_shader(sigma)
+	local support = math.max(1, math.floor(3*sigma + .5))
+	local one_by_sigma_sq = sigma > 0 and 1 / (sigma * sigma) or 1
+	local norm = 0
+
+	local code = {[[
+		extern vec2 direction;
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{ vec4 c = vec4(0.0f);
+	]]}
+	local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);"
+
+	for i = -support,support do
+		local coeff = math.exp(-.5 * i*i * one_by_sigma_sq)
+		norm = norm + coeff
+		code[#code+1] = blur_line:format(coeff, i)
+	end
+
+	code[#code+1] = ("return c * vec4(%f) * color;}"):format(1 / norm)
+
+	return love.graphics.newShader(table.concat(code))
+end
+
+return {
+requires = {'canvas', 'shader'},
+description = "Simple glow shader based on gassian blurring",
+
+new = function(self)
+	self.canvas  = {love.graphics.newCanvas(), love.graphics.newCanvas()}
+	self.shader_blur = build_blur_shader(5)
+	self.shader_thresh = love.graphics.newShader[[
+	extern number min_luma;
+	vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+	{
+		vec4 c = Texel(texture, tc);
+		number luma = dot(vec3(0.299, 0.587, 0.114), c.rgb);
+		return c * color * step(min_luma, luma);
+	}
+	]]
+	self.shader_thresh:send('min_luma', 0.7)
+end,
+
+draw = function(self, func, ...)
+	local s = love.graphics.getShader()
+	local co = {love.graphics.getColor()}
+
+	-- draw scene to screen
+	func()
+
+	-- draw scene with brigthness treshold
+	love.graphics.setShader(self.shader_thresh)
+	self:_render_to_canvas(self.canvas[1], func, ...)
+
+	love.graphics.setColor(co)
+	local b = love.graphics.getBlendMode()
+	love.graphics.setBlendMode('premultiplied')
+
+	love.graphics.setShader(self.shader_blur)
+	-- first pass (horizontal blur)
+	self.shader_blur:send('direction', {1 / love.graphics.getWidth(), 0})
+	self:_render_to_canvas(self.canvas[2],
+	                       love.graphics.draw, self.canvas[1], 0,0)
+
+	-- second pass (vertical blur)
+	love.graphics.setBlendMode('additive')
+	self.shader_blur:send('direction', {0, 1 / love.graphics.getHeight()})
+	love.graphics.draw(self.canvas[2], 0,0)
+
+	-- restore blendmode, shader and canvas
+	love.graphics.setBlendMode(b)
+	love.graphics.setShader(s)
+end,
+
+set = function(self, key, value)
+	if key == "sigma" then
+		self.shader_blur = build_blur_shader(tonumber(value))
+	elseif key == "min_luma" then
+		self.shader_thresh:send("min_luma", tonumber(value))
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+	return self
+end
+}

+ 94 - 0
deps/shine/godsray.lua

@@ -0,0 +1,94 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Josef Patoprsty
+
+Based on work by: ioxu
+
+https://www.love2d.org/forums/viewtopic.php?f=4&t=3733&start=120#p71099
+
+Based on work by: Fabien Sanglard
+
+http://fabiensanglard.net/lightScattering/index.php
+
+Based on work from: 
+
+[Mitchell]: Kenny Mitchell "Volumetric Light Scattering as a Post-Process" GPU Gems 3 (2005). 
+[Mitchell2]: Jason Mitchell "Light Shaft Rendering" ShadersX3 (2004). 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+local x,y
+
+return {
+	requires = {'canvas', 'shader'},
+	description = "Realtime Light Scattering",
+
+	new = function(self)
+		self.canvas = love.graphics.newCanvas()
+		self.shader = love.graphics.newShader[[
+			extern number exposure = 0.3;
+			extern number decay = .95;
+			extern number density = .4;
+			extern number weight = .3;
+			extern vec2 light_position= vec2(0.5,0.5);
+			extern number samples = 70.0 ;
+
+			vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
+				vec2 deltaTextCoord = vec2( texture_coords - light_position.xy );
+				vec2 textCoo = texture_coords.xy;
+				deltaTextCoord *= 1.0 / float(samples) * density;
+				float illuminationDecay = 1.0;
+				vec4 cc = vec4(0.0, 0.0, 0.0, 1.0);
+
+				for(int i=0; i < samples ; i++) {
+					textCoo -= deltaTextCoord;
+					vec4 sample = Texel( texture, textCoo );
+					sample *= illuminationDecay * weight;
+					cc += sample;
+					illuminationDecay *= decay;
+				}
+				cc *= exposure;
+				return cc;
+			}
+		]]
+	end,
+
+	draw = function(self, func, ...)
+		self.shader:send("light_position",{x or 0.5,y or 0.5})
+		self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+	end,
+
+	set = function(self, key, value)
+		if key == "exposure" or key == "decay" or key == "density" or key == "weight" then
+			self.shader:send(key,math.min(1,math.max(0,tonumber(value) or 0)))
+		elseif key == "positionx" then
+			x = math.min(1,math.max(0,tonumber(value) or 0.5))
+		elseif key == "positiony" then
+			y = math.min(1,math.max(0,tonumber(value) or 0.5))
+		elseif key == "samples" then
+			self.shader:send(key,math.max(1,tonumber(value) or 1))
+		else
+			error("Unknown property: " .. tostring(key))
+		end
+
+		return self
+	end
+}

+ 122 - 0
deps/shine/init.lua

@@ -0,0 +1,122 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+local BASE = ...
+
+local shine = {}
+shine.__index = shine
+
+-- commonly used utility function
+function shine._render_to_canvas(_, canvas, func, ...)
+	local old_canvas = love.graphics.getCanvas()
+
+	canvas:clear()
+	love.graphics.setCanvas(canvas)
+	func(...)
+
+	love.graphics.setCanvas(old_canvas)
+end
+
+function shine._apply_shader_to_scene(_, shader, canvas, func, ...)
+	local s = love.graphics.getShader()
+	local co = {love.graphics.getColor()}
+
+	-- draw scene to canvas
+	shine._render_to_canvas(_, canvas, func, ...)
+
+	-- apply shader to canvas
+	love.graphics.setColor(co)
+	love.graphics.setShader(shader)
+	local b = love.graphics.getBlendMode()
+	love.graphics.setBlendMode('premultiplied')
+	love.graphics.draw(canvas, 0,0)
+	love.graphics.setBlendMode(b)
+
+	-- reset shader and canvas
+	love.graphics.setShader(s)
+end
+
+-- effect chaining
+function shine.chain(first, second)
+	local effect = {}
+	function effect:set(k, v)
+		local ok = pcall(first.set, first, k, v)
+		ok = pcall(second.set, second, k, v) or ok
+		if not ok then
+			error("Unknown property: " .. tostring(k))
+		end
+	end
+	function effect:draw(func, ...)
+		local args = {n = select('#',...), ...}
+		second(function() first(func, unpack(args, 1, args.n)) end)
+	end
+
+	return setmetatable(effect, {__newindex = shine.__newindex, __index = shine, __call = effect.draw})
+end
+
+-- guards
+function shine.draw()
+	error("Incomplete effect: draw(func) not implemented", 2)
+end
+function shine.set()
+	error("Incomplete effect: set(key, value) not implemented", 2)
+end
+
+function shine.__newindex(self, k, v)
+	if k == "parameters" then
+		assert(type(v) == "table")
+		for k,v in pairs(v) do
+			self:set(k,v)
+		end
+	else
+		self:set(k, v)
+	end
+end
+
+return setmetatable({}, {__index = function(self, key)
+	local ok, effect = pcall(require, BASE .. "." .. key)
+	if not ok then
+		error("No such effect: "..key, 2)
+	end
+
+	setmetatable(effect, shine)
+	for _, v in ipairs(effect.requires) do
+		if not love.graphics.isSupported(v) then
+			error(v.." not supported by the graphics card", 2)
+		end
+	end
+
+	local constructor = function(t)
+		local instance = {}
+		effect.new(instance)
+		setmetatable(instance, {__newindex = shine.__newindex, __index = effect, __call = effect.draw})
+		if t and type(t) == "table" then
+			instance.parameters = t
+		end
+		return instance
+	end
+
+	self[key] = constructor
+	return constructor
+end})

+ 190 - 0
deps/shine/pixelate.lua

@@ -0,0 +1,190 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Daniel Oaks
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+local function build_shader(add_original, samples)
+	local code = {[[
+	extern float pixel_size;
+
+	vec4 effect(vec4 vcolor, Image texture, vec2 texture_coords, vec2 pixel_coords)
+	{
+	]]}
+
+	if (add_original) then
+		code[#code+1] = [[
+			vec4 original_rgb = Texel(texture, texture_coords);
+
+			float r = original_rgb.r;
+			float g = original_rgb.g;
+			float b = original_rgb.b;
+			int count = 1;
+		]]
+	else
+		code[#code+1] = [[
+			float r = 0;
+			float g = 0;
+			float b = 0;
+			int count = 0;
+		]]
+	end
+
+	code[#code+1] = [[
+		float pix_x = floor(float(pixel_coords.x) / pixel_size);
+		float start_of_pixel_x = (pix_x + 0.2) * float(pixel_size) / float(love_ScreenSize.x);
+		float end_of_pixel_x = (pix_x + 0.8) * float(pixel_size) / float(love_ScreenSize.x);
+		float mid_of_pixel_x = (start_of_pixel_x + end_of_pixel_x) / 2.0;
+
+		float pix_y = floor(float(pixel_coords.y) / pixel_size);
+		float start_of_pixel_y = 1 - (pix_y + 0.2) * float(pixel_size) / float(love_ScreenSize.y);
+		float end_of_pixel_y = 1 - (pix_y + 0.8) * float(pixel_size) / float(love_ScreenSize.y);
+		float mid_of_pixel_y = (start_of_pixel_y + end_of_pixel_y) / 2.0;
+
+		// go through and add it all together!
+		// XXX - can r/g/b overflow?
+		vec4 working_pix;
+		vec2 working_coords;
+	]]
+
+	if (samples >= 1) then
+		code[#code+1] = [[
+			working_coords.x = mid_of_pixel_x;
+			working_coords.y = mid_of_pixel_y;
+			working_pix = Texel(texture, working_coords);
+			r += working_pix.r; g += working_pix.g; b += working_pix.b;
+			count++;
+		]]
+	end
+	if (samples >= 2) then
+		code[#code+1] = [[
+			working_coords.x = start_of_pixel_x;
+			working_coords.y = start_of_pixel_y;
+			working_pix = Texel(texture, working_coords);
+			r += working_pix.r; g += working_pix.g; b += working_pix.b;
+			count++;
+		]]
+	end
+	if (samples >= 3) then
+		code[#code+1] = [[
+			working_coords.x = end_of_pixel_x;
+			working_coords.y = end_of_pixel_y;
+			working_pix = Texel(texture, working_coords);
+			r += working_pix.r; g += working_pix.g; b += working_pix.b;
+			count++;
+		]]
+	end
+	if (samples >= 4) then
+		code[#code+1] = [[
+			working_coords.x = start_of_pixel_x;
+			working_coords.y = mid_of_pixel_y;
+			working_pix = Texel(texture, working_coords);
+			r += working_pix.r; g += working_pix.g; b += working_pix.b;
+			count++;
+		]]
+	end
+	if (samples >= 5) then
+		code[#code+1] = [[
+			working_coords.x = end_of_pixel_x;
+			working_coords.y = start_of_pixel_y;
+			working_pix = Texel(texture, working_coords);
+			r += working_pix.r; g += working_pix.g; b += working_pix.b;
+			count++;
+		]]
+	end
+	if (samples >= 6) then
+		code[#code+1] = [[
+			working_coords.x = start_of_pixel_x;
+			working_coords.y = end_of_pixel_y;
+			working_pix = Texel(texture, working_coords);
+			r += working_pix.r; g += working_pix.g; b += working_pix.b;
+			count++;
+		]]
+	end
+	if (samples >= 7) then
+		code[#code+1] = [[
+			working_coords.x = mid_of_pixel_x;
+			working_coords.y = start_of_pixel_y;
+			working_pix = Texel(texture, working_coords);
+			r += working_pix.r; g += working_pix.g; b += working_pix.b;
+			count++;
+		]]
+	end
+
+	code[#code+1] = [[
+		// average
+		r /= count; g /= count; b /= count;
+
+		// assemble output rgb
+		vec4 rgb_out;
+
+		rgb_out.r = r;
+		rgb_out.b = b;
+		rgb_out.g = g;
+
+		return rgb_out;
+	}
+	]]
+
+	return love.graphics.newShader(table.concat(code))
+end
+
+return {
+requires = {'canvas', 'shader'},
+description = "Pixelation",
+
+new = function(self)
+	self._pixel_size = 3
+	self._add_original = true
+	self._samples = 7
+
+	self.canvas = love.graphics.newCanvas()
+	self.shader = build_shader(self._add_original, self._samples)
+
+	self.shader:send("pixel_size", self._pixel_size)
+end,
+
+draw = function(self, func, ...)
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "pixel_size" then
+		assert(type(value) == "number")
+		self._pixel_size = value
+		self.shader:send("pixel_size", value)
+	elseif key == "samples" then
+		assert(type(value) == "number")
+		self._samples = value
+		self.shader = build_shader(self._add_original, self._samples)
+		self.shader:send("pixel_size", self._pixel_size)
+	elseif key == "add_original" then
+		assert(type(value) == "boolean")
+		self._add_original = value
+		self.shader = build_shader(self._add_original, self._samples)
+		self.shader:send("pixel_size", self._pixel_size)
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 74 - 0
deps/shine/posterize.lua

@@ -0,0 +1,74 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Posterize effect to quantize color bands",
+
+new = function(self)
+	self.canvas = love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+		extern number num_bands;
+		vec3 rgb2hsv(vec3 c)
+		{
+			vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+			vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+			vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+			float d = q.x - min(q.w, q.y);
+			float e = 1.0e-10;
+			return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+		}
+
+		vec3 hsv2rgb(vec3 c)
+		{
+			vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+			vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+			return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+		}
+
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{
+			color = Texel(texture, tc);
+			vec3 hsv = floor((rgb2hsv(color.rgb) * num_bands) + 0.5) / num_bands;
+			return vec4(hsv2rgb(hsv), color.a);
+		}
+	]]
+	self.shader:send("num_bands",1)
+end,
+
+draw = function(self, func, ...)
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "num_bands" then
+		self.shader:send(key, math.max(0, tonumber(value) or 0))
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 131 - 0
deps/shine/scanlines.lua

@@ -0,0 +1,131 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Daniel Oaks
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Horizontal Scanlines",
+
+new = function(self)
+	self._pixel_size = 3
+	self._opacity = 0.3
+	self._center_fade = 0.44
+	self._line_height = 0.35
+
+	self.canvas = love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+	#define PI (3.14159265)
+
+	extern float pixel_size;
+
+	// Opacity of the scanlines, 0 to 1.
+	extern float opacity;
+	// How much scanlines 'fade out' in the center of the screen
+	extern float center_fade;
+	// How much of each pixel is scanline, 0 to 1.
+	extern float scanline_height;
+
+	// input coords 0 -> 1,
+	//   return coords -1 -> 1
+	vec2 coords_glsl_to_neg1_1(vec2 point) {
+		point.x = ((point.x * 2.0) - 1.0);
+		point.y = ((point.y * -2.0) + 1.0);
+
+		return point;
+	}
+
+	vec4 desaturate(vec4 color, float amount)
+	{
+		vec4 gray = vec4(dot(vec4(0.2126,0.7152,0.0722,0.2), color));
+		return vec4(mix(color, gray, amount));
+	}
+
+	vec4 effect(vec4 vcolor, Image texture, vec2 texture_coords, vec2 pixel_coords)
+	{
+		vec4 working_rgb = Texel(texture, texture_coords);
+
+		// horizontal scanlines
+		float current_pixel_v = pixel_coords.y / pixel_size;
+		float scanline_is_active = cos(current_pixel_v * 2.0 * PI) - 1.0 + scanline_height * 3.0;
+
+		// clamp
+		if (scanline_is_active > 1.0) {
+			scanline_is_active = 1.0;
+		}
+
+		// fading towards the center, looks much better
+		vec2 fade_coords = coords_glsl_to_neg1_1(texture_coords);
+		float fade_value = ((1.0 - abs(fade_coords.x)) + (1.0 - abs(fade_coords.y))) / 2.0;
+
+		scanline_is_active -= fade_value * center_fade;
+
+		// clamp
+		if (scanline_is_active < 0.0) {
+			scanline_is_active = 0.0;
+		}
+
+		// eh, just implement as lowering alpha for now
+		// see if we should be modifying rgb values instead
+		// possibly by making it darker and less saturated in the scanlines?
+		working_rgb = desaturate(working_rgb, scanline_is_active * 0.07);
+		working_rgb.r = working_rgb.r - (scanline_is_active * opacity);
+		working_rgb.g = working_rgb.g - (scanline_is_active * opacity);
+		working_rgb.b = working_rgb.b - (scanline_is_active * opacity);
+
+		return working_rgb;
+	}
+	]]
+	self.shader:send("pixel_size", self._pixel_size)
+	self.shader:send("opacity", self._opacity)
+	self.shader:send("center_fade", self._center_fade)
+	self.shader:send("scanline_height", self._line_height)
+end,
+
+draw = function(self, func, ...)
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "pixel_size" then
+		assert(type(value) == "number")
+		self._pixel_size = value
+		self.shader:send("pixel_size", value)
+	elseif key == "opacity" then
+		assert(type(value) == "number")
+		self._opacity = value
+		self.shader:send("opacity", value)
+	elseif key == "center_fade" then
+		assert(type(value) == "number")
+		self._center_fade = value
+		self.shader:send("center_fade", value)
+	elseif key == "line_height" then
+		assert(type(value) == "number")
+		self._line_height = value
+		self.shader:send("line_height", value)
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 62 - 0
deps/shine/separate_chroma.lua

@@ -0,0 +1,62 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Separates red, green and blue components",
+
+new = function(self)
+	self.angle, self.radius = 0, 0
+	self.canvas = love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+		extern vec2 direction;
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{
+			return color * vec4(
+				Texel(texture, tc - direction).r,
+				Texel(texture, tc).g,
+				Texel(texture, tc + direction).b,
+				1.0);
+		}
+	]]
+	self.shader:send("direction",{0,0})
+end,
+
+draw = function(self, func, ...)
+	local dx = math.cos(self.angle) * self.radius / love.graphics.getWidth()
+	local dy = math.sin(self.angle) * self.radius / love.graphics.getHeight()
+	self.shader:send("direction", {dx,dy})
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "radius" or key == "angle" then
+		self[key] = tonumber(value) or 0
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 84 - 0
deps/shine/sketch.lua

@@ -0,0 +1,84 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Martin Felis
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Sketched drawing style",
+
+new = function(self)
+	self.canvas = love.graphics.newCanvas()
+	self.noisetex = love.image.newImageData(100,100)
+	self.noisetex:mapPixel(function()
+		local l = love.math.random() * 255
+		return l,l,l,l
+	end)
+	self.noisetex = love.graphics.newImage(self.noisetex)
+	self.noisetex:setWrap ("repeat", "repeat")
+	self.noisetex:setFilter("nearest", "nearest")
+
+	self.shader = love.graphics.newShader[[
+		extern number amp;
+		extern number screen_center_x;
+		extern number screen_center_y;
+
+		extern Image noisetex;
+
+		vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
+		{
+			vec2 screen_center = vec2 (screen_center_x, screen_center_y);
+			vec4 noise;
+
+			noise = Texel (noisetex, texture_coords + screen_center);
+			noise = normalize (noise * 2.0 - vec4 (1.0, 1.0, 1.0, 1.0));
+			noise *= amp;
+
+			return Texel(texture, texture_coords + noise.xy);
+		}
+	]]
+	self.shader:send("amp", 0.001 )
+
+	-- Set the screen_center positions when the camera moves but the
+	-- noise texture should stay fixed in world coordinates to reduce
+	-- aliasing effects.
+	self.shader:send("screen_center_x",love.window.getWidth() * 0.5)
+	self.shader:send("screen_center_y",love.window.getHeight() * 0.5)
+
+	self.shader:send("noisetex", self.noisetex)
+end,
+
+draw = function(self, func, ...)
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "amp" or key == "screen_center_x" or key == "screen_center_y" then
+		self.shader:send(key, math.max(0, tonumber(value) or 0))
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 62 - 0
deps/shine/vignette.lua

@@ -0,0 +1,62 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2015 Matthias Richter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+return {
+requires = {'canvas', 'shader'},
+description = "Vignette overlay",
+
+new = function(self)
+	self.canvas = love.graphics.newCanvas()
+	self.shader = love.graphics.newShader[[
+		extern number radius;
+		extern number softness;
+		extern number opacity;
+		extern number aspect;
+		vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
+		{
+			color = Texel(texture, tc);
+			number v = smoothstep(radius, radius-softness, length((tc - vec2(0.5f)) * aspect));
+			return mix(color, color * v, opacity);
+		}
+	]]
+	self.shader:send("radius",1)
+	self.shader:send("softness",.45)
+	self.shader:send("opacity",.5)
+	self.shader:send("aspect", love.graphics.getWidth() / love.graphics.getHeight())
+end,
+
+draw = function(self, func, ...)
+	self:_apply_shader_to_scene(self.shader, self.canvas, func, ...)
+end,
+
+set = function(self, key, value)
+	if key == "radius" or key == "softness" or key == "opacity" then
+		self.shader:send(key, math.max(0, tonumber(value) or 0))
+	else
+		error("Unknown property: " .. tostring(key))
+	end
+
+	return self
+end
+}

+ 6 - 1
deps/view.lua

@@ -37,6 +37,8 @@ function View:init()
 
 
   self.shake = 0
   self.shake = 0
 
 
+  self.effect = shine.scanlines()
+
   ctx.event:on('view.register', function(data)
   ctx.event:on('view.register', function(data)
     self:register(data.object, data.mode)
     self:register(data.object, data.mode)
   end)
   end)
@@ -116,7 +118,10 @@ function View:draw()
 
 
   g.setCanvas()
   g.setCanvas()
   g.setColor(255, 255, 255)
   g.setColor(255, 255, 255)
-  g.draw(source)
+  love.math.setRandomSeed(ls.tick * 100)
+  self.effect(function()
+    g.draw(source)
+  end)
 
 
   g.push()
   g.push()
 
 

+ 1 - 1
dinoland.lua

@@ -1,5 +1,5 @@
 Dinoland = extend(Map)
 Dinoland = extend(Map)
-Dinoland.width = 6400
+Dinoland.width = 6600
 Dinoland.height = 900
 Dinoland.height = 900
 Dinoland.groundHeight = 100
 Dinoland.groundHeight = 100
 
 

+ 1 - 0
game.lua

@@ -20,6 +20,7 @@ function Game:load()
   self.projectiles = Manager()
   self.projectiles = Manager()
   self.hud = Hud()
   self.hud = Hud()
   self.goal = Goal()
   self.goal = Goal()
+  self.particles = Particles()
 
 
   self.map:spawnHuts()
   self.map:spawnHuts()
 
 

+ 79 - 12
hud.lua

@@ -1,5 +1,23 @@
 Hud = class()
 Hud = class()
 
 
+Hud.bonuses = {
+  wreckingBall = {
+    name = 'Wrecking Ball',
+    description = 'Kill all buildings',
+    score = 100000,
+    check = function()
+      local aliveBuilding = false
+      table.each(ctx.buildings.objects, function(building)
+        if not building.destroyed then
+          aliveBuilding = true
+        end
+      end)
+
+      return not aliveBuilding
+    end
+  }
+}
+
 function Hud:init()
 function Hud:init()
   ctx.event:emit('view.register', {object = self, mode = 'gui'})
   ctx.event:emit('view.register', {object = self, mode = 'gui'})
 
 
@@ -12,6 +30,13 @@ function Hud:init()
   self.rainbowShitCounter = 0
   self.rainbowShitCounter = 0
   self.rainbowShitDisplay = 0
   self.rainbowShitDisplay = 0
   self.prevRainbowShitDisplay = 0
   self.prevRainbowShitDisplay = 0
+
+  self.win = {}
+  self.win.active = false
+  self.win.width = 800
+  self.win.height = 500
+  self.win.x = -400
+  self.win.prevx = self.win.x
 end
 end
 
 
 function Hud:update()
 function Hud:update()
@@ -33,6 +58,11 @@ function Hud:update()
     end)
     end)
   end
   end
 
 
+  if self.win.active then
+    self.win.prevx = self.win.x
+    self.win.x = math.lerp(self.win.x, love.graphics.getWidth() / 2, 8 * ls.tickrate)
+  end
+
   self.rainbowShitDisplay = math.lerp(self.rainbowShitDisplay, self.rainbowShitCounter, 8 * ls.tickrate)
   self.rainbowShitDisplay = math.lerp(self.rainbowShitDisplay, self.rainbowShitCounter, 8 * ls.tickrate)
 
 
   self.scoreDisplay = math.lerp(self.scoreDisplay, self.score, 5 * ls.tickrate)
   self.scoreDisplay = math.lerp(self.scoreDisplay, self.score, 5 * ls.tickrate)
@@ -53,14 +83,16 @@ function Hud:gui()
     g.line(0, y, gw, y)
     g.line(0, y, gw, y)
   end
   end
 
 
-  g.setFont('media/fonts/BebasNeueBold.otf', 24)
-  g.setColor(0, 0, 0, 150)
-  local str = math.round(self.scoreDisplay)
-  local w = math.max(g.getFont():getWidth(str), 80)
-  local h = g.getFont():getHeight()
-  g.rectangle('fill', 0, 0, w + 8, h + 8)
-  g.setColor(255, 255, 255)
-  g.print(str, 4, 4)
+  if not self.win.active then
+    g.setFont('media/fonts/BebasNeueBold.otf', 24)
+    g.setColor(0, 0, 0, 150)
+    local str = math.round(self.scoreDisplay)
+    local w = math.max(g.getFont():getWidth(str), 80)
+    local h = g.getFont():getHeight()
+    g.rectangle('fill', 0, 0, w + 8, h + 8)
+    g.setColor(255, 255, 255)
+    g.print(str, 4, 4)
+  end
 
 
   if self.bubble.active then
   if self.bubble.active then
     local y, scale = math.lerp(self.bubble.prevy, self.bubble.y, ls.accum / ls.tickrate), math.lerp(self.bubble.prevScale, self.bubble.scale, ls.accum / ls.tickrate)
     local y, scale = math.lerp(self.bubble.prevy, self.bubble.y, ls.accum / ls.tickrate), math.lerp(self.bubble.prevScale, self.bubble.scale, ls.accum / ls.tickrate)
@@ -79,7 +111,7 @@ function Hud:gui()
   local baseHeight = 100
   local baseHeight = 100
 
 
   if ctx.pigeon.rainbowShitTimer > 0 then
   if ctx.pigeon.rainbowShitTimer > 0 then
-    love.math.setRandomSeed(love.timer.getTime() * ctx.pigeon.rainbowShitTimer - self.scoreDisplay)
+    love.math.setRandomSeed(math.max(love.timer.getTime() * ctx.pigeon.rainbowShitTimer - self.scoreDisplay, 1))
     local prc = math.min(ctx.pigeon.rainbowShitTimer / 5, 1)
     local prc = math.min(ctx.pigeon.rainbowShitTimer / 5, 1)
     g.setColor(128 + love.math.random() * 127, 128 + love.math.random() * 127, 128 + love.math.random() * 127)
     g.setColor(128 + love.math.random() * 127, 128 + love.math.random() * 127, 128 + love.math.random() * 127)
     g.rectangle('fill', 2, 50 + baseHeight * (1 - prc), baseWidth, baseHeight * prc)
     g.rectangle('fill', 2, 50 + baseHeight * (1 - prc), baseWidth, baseHeight * prc)
@@ -93,6 +125,28 @@ function Hud:gui()
   local prc = math.lerp(self.prevRainbowShitDisplay, self.rainbowShitDisplay, ls.accum / ls.tickrate) / Pigeon.rainbowShitThreshold
   local prc = math.lerp(self.prevRainbowShitDisplay, self.rainbowShitDisplay, ls.accum / ls.tickrate) / Pigeon.rainbowShitThreshold
   g.setColor(255, 0, 0)
   g.setColor(255, 0, 0)
   g.rectangle('fill', 2, 50 + baseHeight * (1 - prc), baseWidth, baseHeight * prc)
   g.rectangle('fill', 2, 50 + baseHeight * (1 - prc), baseWidth, baseHeight * prc)
+
+  if self.win.active then
+    g.setColor(0, 0, 0, 200)
+    local x = math.lerp(self.win.prevx, self.win.x, ls.accum / ls.tickrate)
+    local w, h = self.win.width, self.win.height
+    g.rectangle('fill', x - w / 2, gh / 2 - h / 2, w, h)
+    g.setColor(255, 255, 255)
+    g.setFont('media/fonts/BebasNeueBold.otf', 32)
+    local str = 'Congration you done it'
+    g.print(str, x - g.getFont():getWidth(str) / 2, gh / 2 - h / 2 + 32)
+
+    local yy = gh / 2 - h / 2 + 80
+    local str = math.round(self.scoreDisplay)
+    g.print(str, x - g.getFont():getWidth(str) / 2, yy)
+    yy = yy + g.getFont():getHeight() + 1
+
+    for i = 1, #self.win.bonuses do
+      local name = self.win.bonuses[i]
+      g.print(self.bonuses[name].name .. ': ' .. self.bonuses[name].description .. ' (+' .. self.bonuses[name].score .. ')', x - w / 2 + 32, yy)
+      yy = yy + g.getFont():getHeight() + 1
+    end
+  end
 end
 end
 
 
 function Hud:resetBubble()
 function Hud:resetBubble()
@@ -100,7 +154,7 @@ function Hud:resetBubble()
   self.bubble.amount = 0
   self.bubble.amount = 0
   self.bubble.timer = 0
   self.bubble.timer = 0
   self.bubble.multiplier = 0
   self.bubble.multiplier = 0
-  self.bubble.targetY = 300
+  self.bubble.targetY = 200
   self.bubble.y = self.bubble.targetY
   self.bubble.y = self.bubble.targetY
   self.bubble.prevy = self.bubble.y
   self.bubble.prevy = self.bubble.y
   self.bubble.targetScale = 1
   self.bubble.targetScale = 1
@@ -114,9 +168,9 @@ function Hud:addScore(amount, kind)
   self.bubble.amount = self.bubble.amount + amount
   self.bubble.amount = self.bubble.amount + amount
   self.bubble.amountDisplay = self.bubble.amount
   self.bubble.amountDisplay = self.bubble.amount
   self.bubble.multiplier = self.bubble.multiplier + 1
   self.bubble.multiplier = self.bubble.multiplier + 1
-  --self.bubble.scale = self.bubble.scale + .2
+  self.bubble.scale = self.bubble.scale + .2
   self.bubble.targetScale = self.bubble.targetScale + .1
   self.bubble.targetScale = self.bubble.targetScale + .1
-  self.bubble.targetY = math.lerp(self.bubble.targetY, 100, .2)
+  self.bubble.targetY = math.lerp(self.bubble.targetY, 50, .15)
   self.bubble.prevTimer = self.bubble.timer
   self.bubble.prevTimer = self.bubble.timer
 
 
   if kind == 'person' then
   if kind == 'person' then
@@ -127,3 +181,16 @@ function Hud:addScore(amount, kind)
     end
     end
   end
   end
 end
 end
+
+function Hud:activateWin()
+  self.win.active = true
+  self.win.bonuses = {}
+  collectgarbage()
+  table.each(self.bonuses, function(bonus, name)
+    if bonus.check() then
+      table.insert(self.win.bonuses, name)
+      self.score = self.score + bonus.score
+    end
+  end)
+  self.scoreDisplay = 0
+end

+ 2 - 0
loader.lua

@@ -112,5 +112,7 @@ data.load = function()
 
 
     return animation
     return animation
   end)
   end)
+
+  load('data/particle', 'particle')
 end
 end
 
 

BIN
media/graphics/particles/line.png


BIN
media/graphics/particles/linering.png


BIN
media/graphics/particles/ring.png


BIN
media/graphics/particles/smoke.png


BIN
media/graphics/particles/snowflake.png


BIN
media/graphics/particles/softCircle.png


BIN
media/graphics/particles/star.png


+ 33 - 2
pigeon.lua

@@ -6,7 +6,7 @@ Pigeon = class()
 Pigeon.walkForce = 600
 Pigeon.walkForce = 600
 Pigeon.maxSpeed = 350
 Pigeon.maxSpeed = 350
 Pigeon.jumpForce = 3000
 Pigeon.jumpForce = 3000
-Pigeon.flySpeed = 100
+Pigeon.flySpeed = 75
 Pigeon.rocketForce = 500
 Pigeon.rocketForce = 500
 Pigeon.maxFlySpeed = 300
 Pigeon.maxFlySpeed = 300
 Pigeon.maxFuel = 25
 Pigeon.maxFuel = 25
@@ -62,6 +62,12 @@ function Pigeon:init()
       self.drop = nil
       self.drop = nil
       if self.walk.firstShake == false then
       if self.walk.firstShake == false then
         ctx.view:screenshake(10)
         ctx.view:screenshake(10)
+
+        local skeleton = self.animation.spine.skeleton
+        local bone = skeleton:findBone('rightfoot')
+        local x = skeleton.x + bone.worldX
+        local y = skeleton.y + bone.worldY
+        ctx.particles:emit('dust', x, y, 15)
       else
       else
         self.walk.firstShake = false
         self.walk.firstShake = false
       end
       end
@@ -70,6 +76,12 @@ function Pigeon:init()
       self.drop = nil
       self.drop = nil
       if self.walk.firstShake == false then
       if self.walk.firstShake == false then
         ctx.view:screenshake(10)
         ctx.view:screenshake(10)
+
+        local skeleton = self.animation.spine.skeleton
+        local bone = skeleton:findBone('leftfoot')
+        local x = skeleton.x + bone.worldX
+        local y = skeleton.y + bone.worldY
+        ctx.particles:emit('dust', x, y, 15)
       else
       else
         self.walk.firstShake = false
         self.walk.firstShake = false
       end
       end
@@ -120,6 +132,17 @@ function Pigeon:update()
     self.downDirty = .1
     self.downDirty = .1
   end
   end
 
 
+  local skeleton = self.animation.spine.skeleton
+  skeleton.flipY = true
+  skeleton:updateWorldTransform()
+  local bone = skeleton:findBone('leftwing')
+  local x, y = skeleton.x + bone.worldX, skeleton.y + bone.worldY
+  local dir = (-bone.worldRotation * math.pi / 180) - .2
+  x = x + math.cos(dir) * (bone.data.length + 100) * self.animation.scale
+  y = y + math.sin(dir) * (bone.data.length + 100) * self.animation.scale
+  ctx.particles:emit('smoke', x, y, 3, {direction = -bone.worldRotation * math.pi / 180})
+  skeleton.flipY = false
+
   self.crushGrace = timer.rot(self.crushGrace)
   self.crushGrace = timer.rot(self.crushGrace)
 
 
   self:updateBeak()
   self:updateBeak()
@@ -127,6 +150,10 @@ function Pigeon:update()
 
 
   if self.body:getX() > ctx.goal.x then
   if self.body:getX() > ctx.goal.x then
     self:changeState('idle')
     self:changeState('idle')
+    self.animation:set('flyLoop')
+    if not ctx.hud.win.active then
+      ctx.hud:activateWin()
+    end
   end
   end
 end
 end
 
 
@@ -187,7 +214,7 @@ function Pigeon:collideWith(other, myFixture)
       other:changeState('dead')
       other:changeState('dead')
     elseif self.state == self.walk and self.drop and myFixture == self.feet[self.drop].fixture then
     elseif self.state == self.walk and self.drop and myFixture == self.feet[self.drop].fixture then
       other:changeState('dead')
       other:changeState('dead')
-    elseif self.state == self.air and select(2, self.body:getLinearVelocity()) > 0 and (myFixture == self.feet.left.fixture or myFixture == self.feet.right.fixture) then
+    elseif self.crushGrace > 0 and (myFixture == self.feet.left.fixture or myFixture == self.feet.right.fixture) then
       other:changeState('dead')
       other:changeState('dead')
     end
     end
   elseif isa(other, Building) and not other.destroyed and self.state == self.peck and (myFixture == self.beak.top.fixture or myFixture == self.beak.bottom.fixture) then
   elseif isa(other, Building) and not other.destroyed and self.state == self.peck and (myFixture == self.beak.top.fixture or myFixture == self.beak.bottom.fixture) then
@@ -459,6 +486,10 @@ end
 function Pigeon.air:exit()
 function Pigeon.air:exit()
   if self.air.lastVelocity > 800 then
   if self.air.lastVelocity > 800 then
     ctx.view:screenshake(15 + (self.air.lastVelocity / 100))
     ctx.view:screenshake(15 + (self.air.lastVelocity / 100))
+    local skeleton = self.animation.spine.skeleton
+    local x = skeleton.x
+    local y = skeleton.y
+    ctx.particles:emit('dust', x, y, 15 + (self.air.lastVelocity / 100))
   end
   end
 end
 end
 
 

+ 2 - 0
require.lua

@@ -4,6 +4,7 @@ flux = require 'deps/flux'
 lume = require 'deps/lume'
 lume = require 'deps/lume'
 lurker = require 'deps/lurker'
 lurker = require 'deps/lurker'
 ls = require 'deps/lovestep/lovestep'
 ls = require 'deps/lovestep/lovestep'
+shine = require 'deps/shine'
 require 'deps/lutil/util'
 require 'deps/lutil/util'
 require 'deps/util'
 require 'deps/util'
 require 'deps/animation'
 require 'deps/animation'
@@ -12,6 +13,7 @@ require 'deps/view'
 require 'deps/manager'
 require 'deps/manager'
 require 'deps/physicsinterpolator'
 require 'deps/physicsinterpolator'
 require 'deps/typo'
 require 'deps/typo'
+require 'deps/particles'
 
 
 require 'context'
 require 'context'
 require 'game'
 require 'game'