@@ -1,142 +1,51 @@
local inspector = lib.object.create()

inspector:include(lib.inspector)

inspector.config = {
width = 140
width = 140,
objects = {'muju', 'shrine', 'dirt'},
initialObject = 'muju'
}

function inspector:bind()
function offset(x, ...)
local rest = {...}
return function()
return self.x + x, unpack(rest)
end
end
inspector.state = function()
local state = {
active = false,
editing = 'muju',
x = -inspector.config.width,
hand = love.mouse.getSystemCursor('hand'),
gooey = lib.gooey.controller:new()
}

self.gooey = lib.gooey.create():bind()
self.dropdown = self.gooey:add(lib.dropdown, 'inspector.editing')
self.dropdown.geometry = offset(6, 8, 100, 20)
self.dropdown.choices = {'muju', 'shrine', 'dirt'}
state.dropdown = state.gooey:add(lib.gooey.dropdown, 'inspector.editing')

return state
end

function inspector:bind()
self.dropdown.geometry = self:createOffsetFunction(6, 8, 100, 20)
self.dropdown.choices = self.config.objects
self.dropdown.padding = 6
self.dropdown.label = 'subject'
self.components = self.dropdown.value:map(self.setupComponents(self))
self.components = self.dropdown.value:map(self:wrap(self.setupComponents))

love.keypressed
:filter(f.eq('`'))
:subscribe(self.toggleActive(self))
:subscribe(self:wrap(self.toggleActive))

love.update:subscribe(function()
self.x = math.lerp(self.x, self:getTargetX(), 16 * lib.tick.rate)
end)
love.update
:subscribe(self:wrap(self.smoothX))

love.mousemoved
:pack()
:combine(self.components)
:subscribe(self.updateCursor(self))
:subscribe(self:wrap(self.updateCursor))

app.scene.view.hud
app.context.view.hud
:with(self.components)
:subscribe(self.render(self))

self.dropdown.value:onNext('muju')
end

function inspector:getTargetX()
return self.active and 0 or -self.config.width
end

function inspector:toggleActive()
return function()
self.active = not self.active
end
end

function inspector:updateCursor()
local hand = love.mouse.getSystemCursor('hand')
return function(mouse, components)
local mx, my = unpack(mouse)
local contains = false
contains = contains or self.dropdown:contains(mx, my)
for i = 1, #components do
contains = contains or f.try(components[i].contains, components[i], mx, my)
end
love.mouse.setCursor(contains and hand or nil)
end
end

function inspector:setupComponents()
return function(editing)
local subject = table.get(app, editing)
if subject.editor then
local components = {}
local y = 44

for i = 1, #subject.editor.sections do
local section = subject.editor.sections[i]
local header = self.gooey:add(lib.label, 'prop.' .. section.title)
header.geometry = offset(8, y)
header.label = section.title
table.insert(components, header)

y = y + 20

for j = 1, #section do
local prop = section[j]
local editor = self.gooey:add(lib.editor, 'prop.' .. prop)
editor.label = prop:gsub('[A-Z]', function(x) return ' ' .. x:lower() end)
editor.value = subject.config[prop]
editor.valueSubject:onNext(editor.value)
editor.geometry = offset(8, y, self.config.width - 16)
editor.valueSubject:subscribe(function(newValue)
subject.config[prop] = tonumber(newValue) or newValue
end)

y = y + 20
table.insert(components, editor)
end

y = y + 20
end

return components
else
local config = subject.config or subject
return table.map(table.keys(config), function(prop, i)
local editor = self.gooey:add(lib.editor, 'config.' .. prop)
editor.label = prop:gsub('[A-Z]', function(x) return ' ' .. x:lower() end)
editor.value = config[prop]
editor.valueSubject:onNext(editor.value)
editor.geometry = offset(8, 24 + 20 * i, self.config.width - 16)
editor.valueSubject:subscribe(function(newValue)
subject.config[prop] = tonumber(newValue) or newValue
end)
return editor
end)
end
end
end

function inspector:render()
return function(_, editors)
local height = love.graphics.getHeight()

g.setColor(35, 35, 35, 220)
g.rectangle('fill', self.x, 0, self.config.width, height)

if editors then
g.setColor(255, 255, 255)
for i = 1, #editors do
self.gooey:render(editors[i])
end
end

self.gooey:render(self.dropdown)
:subscribe(self:wrap(self.draw))

return -10000
end
self.dropdown.value:onNext(self.config.initialObject)
end

return inspector:new({
active = false,
editing = 'muju',
x = -inspector.config.width
})
return inspector

This file was deleted.

@@ -1,11 +1,13 @@
local muju = lib.object.create()

muju:include(lib.muju)

muju.config = app.muju.config

muju.state = function()
local state = {
position = {
x = app.scene.width / 2,
x = app.context.scene.width / 2,
y = 301
},
speed = {
@@ -26,100 +28,60 @@ muju.state = function()

state.animation = state.animations.muju

state.abilities = lib.abilities.create()
state.abilities = lib.abilities:new()
state.abilities:add('blink')

return state
end

function muju:bind()
lib.input:subscribe(app.muju.actions.move(self))
self:tint(.5, .2, .7)

app.muju.actions.tint(self, .5, .2, .7)
lib.input
:subscribe(self:wrap(self.move))

lib.input
:filter(app.muju.actions.canShapeshift(self))
:filter(self:wrap(self.canShapeshift))
:pluck('shapeshift')
:changes()
:filter(f.eq(true))
:subscribe(function()
self.form = self.form == 'muju' and 'thuju' or 'muju'
self.animation = self.animations[self.form]
if self.form == 'thuju' then
self.animation:clear()
self.animation:reset('spawn')
self.animation:add('idle')
end
self.lastShapeshift = lib.tick.index
end)
:subscribe(self:wrap(self.shapeshift))

lib.input
:pluck('attack')
:changes()
:filter(f.eq(true))
:subscribe(function()
self.animation:set('attack')
self.animation:add('idle')
end)
:subscribe(self:wrap(self.attack))

lib.input
:subscribe(self:wrap(self.animate))

love.update
:subscribe(function()
local speed = math.sqrt((self.speed.x ^ 2) + (self.speed.y ^ 2)) / self.config.speed
self.shuffle:setVolume(speed * self.config.shuffleVolume)
end)
:subscribe(self:wrap(self.flipAnimation))

love.update:subscribe(app.muju.actions.flip(self))
love.update
:subscribe(self:wrap(self.setShuffleVolume))

self.animations.thuju.events
:pluck('data', 'name')
:filter(f.eq('spawn'))
:subscribe(function()
local x = self.position.x
local y = self.position.y
app.scene.particles:emit('thujustep', x, y, 30, function()
return { direction = love.math.random() < .5 and math.pi or 0 }
end)
end)
:subscribe(self:wrap(lib.thuju.createSpawnParticles))

self.animations.muju.events
:pluck('data', 'name')
:filter(f.eq('step'))
:subscribe(app.muju.actions.footstep(self))
:subscribe(self:wrap(self.eventFootstep))

self.animations.muju.events
:pluck('data', 'name')
:filter(f.eq('staff'))
:subscribe(app.muju.actions.limp(self))
:subscribe(self:wrap(self.eventLimp))

love.update
:with(lib.input)
:subscribe(app.muju.actions.animate(self))

for _, object in ipairs({'shrine', 'dirt'}) do
self:subscribeCollision(object, app.muju.actions.resolveCollision(self, app.scene.objects[object]))
for _, object in pairs(table.filter(app.context.objects, 'solid')) do
self:resolveCollisionsWith(object)
end

app.scene.view.draw:subscribe(app.muju.actions.draw(self))

return self
end

function muju:subscribeCollision(name, fn)
local other = app.scene.objects[name]
return love.update
:map(function()
local distance = math.distance(self.position.x, self.position.y, other.position.x, other.position.y)
local direction = math.direction(self.position.x, self.position.y, other.position.x, other.position.y)
return distance, direction
end)
:filter(function(distance, direction)
return distance < self.config.radius + other.config.radius * math.abs(math.cos(direction))
end)
:map(function(distance, direction)
local delta = (self.config.radius + other.config.radius) - distance
return delta * math.cos(direction), delta * math.sin(direction) * math.abs(math.cos(direction))
end)
:subscribe(fn)
app.context.view.draw:subscribe(self:wrap(self.draw))
end

return muju

This file was deleted.

This file was deleted.

@@ -1,20 +1,19 @@
local dust = {}
return {
image = app.particles.images.smoke,
max = 32,
blendMode = 'additive',

dust.image = app.particles.images.smoke
dust.max = 32
dust.blendMode = 'additive'

dust.options = {}
dust.options.particleLifetime = {.5}
dust.options.colors = {{255, 200, 150, 5}, {255, 200, 150, 12}, {255, 200, 150, 0}}
dust.options.sizes = .6
dust.options.sizeVariation = .5
dust.options.areaSpread = {'normal', 4, 8}
dust.options.linearAcceleration = {0, -150, 0, -350}
dust.options.linearDamping = 10
dust.options.rotation = {0, 2 * math.pi}
dust.options.spin = {-10, 10}
dust.options.speed = {0, 300}
dust.options.spread = math.pi / 10

return dust
options = {
particleLifetime = {.5},
colors = {{255, 200, 150, 5}, {255, 200, 150, 12}, {255, 200, 150, 0}},
sizes = .6,
sizeVariation = .5,
areaSpread = {'normal', 4, 8},
linearAcceleration = {0, -150, 0, -350},
linearDamping = 10,
rotation = {0, 2 * math.pi},
spin = {-10, 10},
speed = {0, 300},
spread = math.pi / 10
}
}
@@ -1,20 +1,19 @@
local thujustep = {}
return {
image = app.particles.images.smoke,
max = 32,
blendMode = 'additive',

thujustep.image = app.particles.images.smoke
thujustep.max = 32
thujustep.blendMode = 'additive'

thujustep.options = {}
thujustep.options.particleLifetime = {.75}
thujustep.options.colors = {{255, 200, 150, 5}, {255, 200, 150, 12}, {255, 200, 150, 0}}
thujustep.options.sizes = .65
thujustep.options.sizeVariation = .5
thujustep.options.areaSpread = {'normal', 4, 8}
thujustep.options.linearAcceleration = {0, -150, 0, -350}
thujustep.options.linearDamping = 10
thujustep.options.rotation = {0, 2 * math.pi}
thujustep.options.spin = {-10, 10}
thujustep.options.speed = {100, 500}
thujustep.options.spread = math.pi / 10

return thujustep
options = {
particleLifetime = {.75},
colors = {{255, 200, 150, 5}, {255, 200, 150, 12}, {255, 200, 150, 0}},
sizes = .65,
sizeVariation = .5,
areaSpread = {'normal', 4, 8},
linearAcceleration = {0, -150, 0, -350},
linearDamping = 10,
rotation = {0, 2 * math.pi},
spin = {-10, 10},
speed = {100, 500},
spread = math.pi / 10
}
}

This file was deleted.

@@ -0,0 +1,37 @@
local overgrowth = {}

overgrowth.width = 800
overgrowth.height = 600

local w, h = overgrowth.width, overgrowth.height

overgrowth.objects = {
{ 'environment' },
{ 'environment.patch',
x = w / 2,
y = h / 2,
angle = 0,
texture = app.environment.textures.dirt
},
{ 'buildings.dirt',
position = {
x = w / 2 - 100,
y = h / 2
}
},
{ 'buildings.shrine',
position = {
x = w / 2 + 100,
y = h / 2
}
},
{ 'muju',
key = 'muju',
position = {
x = w / 2,
y = h / 2
}
}
}

return overgrowth

This file was deleted.

@@ -1,12 +1,8 @@
local abilities = {}
local abilities = lib.object.create()

function abilities.create()
local self = {
list = {}
}

return setmetatable(self, {__index = abilities})
end
abilities.state = {
list = {}
}

function abilities:add(ability, position)
position = position or (#self.list + 1)
@@ -1,7 +1,7 @@
local ability = {}
local spell = {}

function ability:canUse()
return true
function spell:decayTimer()
self.timer = self.timer - math.min(self.timer, lib.tick.rate)
end

return ability
return spell
@@ -66,4 +66,11 @@ function funk.self(f, self)
end
end

-- funk key fresh
function funk.key(key)
return function(t)
return t[key]
end
end

return funk
@@ -1,4 +1,4 @@
local button = setmetatable({}, {__index = lib.component})
local button = setmetatable({}, {__index = lib.gooey.component})

getmetatable(button).__call = function()
return setmetatable({}, {__index = button})
@@ -1,4 +1,4 @@
local checkbox = setmetatable({}, {__index = lib.component})
local checkbox = setmetatable({}, {__index = lib.gooey.component})

getmetatable(checkbox).__call = function()
return setmetatable({}, {__index = checkbox})
File renamed without changes.
@@ -1,13 +1,13 @@
local gooey = {}
local gooey = lib.object.create()

gooey.font = fonts['04B_03'](8)

function gooey.create()
local self = {
gooey.state = function()
return {
font = fonts['04B_03'](8),
components = {},
focused = nil
}
return setmetatable(self, {__index = gooey})
end

function gooey:bind()
@@ -1,4 +1,4 @@
local dropdown = setmetatable({}, {__index = lib.component})
local dropdown = setmetatable({}, {__index = lib.gooey.component})

getmetatable(dropdown).__call = function()
return setmetatable({}, {__index = dropdown})
@@ -1,4 +1,4 @@
local editor = setmetatable({}, {__index = lib.component})
local editor = setmetatable({}, {__index = lib.gooey.component})

getmetatable(editor).__call = function()
return setmetatable({}, {__index = editor})
@@ -34,6 +34,8 @@ function editor:render()
local focusFactor = math.lerp(self.prevFocusFactor, self.focusFactor, lib.tick.accum / lib.tick.rate)
local errorFactor = math.lerp(self.prevErrorFactor, self.errorFactor, lib.tick.accum / lib.tick.rate)

x = x + math.round(4 * hoverFactor)

g.setFont(self.gooey.font)
g.setColor(255, 255, 255, 180 + (75 * hoverFactor))
g.print(self.label, x, y)
@@ -1,4 +1,4 @@
local label = setmetatable({}, {__index = lib.component})
local label = setmetatable({}, {__index = lib.gooey.component})

getmetatable(label).__call = function()
return setmetatable({}, {__index = label})
@@ -1,4 +1,4 @@
local slider = setmetatable({}, {__index = lib.component})
local slider = setmetatable({}, {__index = lib.gooey.component})

getmetatable(slider).__call = function()
return setmetatable({}, {__index = slider})
@@ -0,0 +1,47 @@
local hud = {}

function hud:drawShapeshiftCooldown(u, v)
local u, v = self.u, self.v
local muju = app.context.objects.muju
local margin = self.config.margin * v
local padding = self.config.padding * v

local w = self.font:getWidth('SHAPESHIFT') + 2 * padding
g.setColor(0, 0, 0, 40)
g.rectangle('fill', margin, margin, w, self.font:getHeight() + 2 * padding)

g.setColor(255, 255, 255)
g.print('SHAPESHIFT', margin + padding, margin + padding)

local muju = app.context.objects.muju
local percent = math.clamp((lib.tick.index - muju.lastShapeshift) / (muju.config.shapeshiftCooldown / lib.tick.rate), 0, 1)
if percent < 1 then
g.setColor(0, 0, 0, 255 * (.5 - percent / 2))
g.rectangle('fill', margin + w * percent, margin, w * (1 - percent), self.font:getHeight() + 2 * padding)
end
end

function hud:drawAbilities(u, v)
local u, v = self.u, self.v
local muju = app.context.objects.muju
local margin = self.config.margin * v
local padding = self.config.padding * v

local y = 2 * margin + self.font:getHeight() + 2 * padding
local size = .06 * v
for i = 1, 3 do
local ability = muju.abilities.list[i]

g.setColor(0, 0, 0, 40)
g.rectangle('fill', margin, y, size, size)

if muju.abilities.list[i] then
g.setColor(255, 255, 255, 150 / ((f.try(ability.canCast, ability)) and 1 or 2))
g.rectangle('line', margin, y, size, size)
end

y = y + size + margin / 2
end
end

return hud
@@ -2,7 +2,7 @@ local joystick = love.joystick.getJoysticks()[1]

return love.update
:map(function()
if app.inspector.gooey.focused then
if app.context.inspector.gooey.focused then
return {
x = 0,
y = 0,
@@ -0,0 +1,97 @@
local inspector = {}

function inspector:createOffsetFunction(x, ...)
local rest = {...}
return function()
return self.x + x, unpack(rest)
end
end

function inspector:toggleActive()
self.active = not self.active
end

function inspector:smoothX()
local targetX = self.active and 0 or -self.config.width
self.x = math.lerp(self.x, targetX, 16 * lib.tick.rate)
end

function inspector:updateCursor(mouse, components)
local mx, my = unpack(mouse)
local contains = false
contains = contains or self.dropdown:contains(mx, my)
for i = 1, #components do
contains = contains or f.try(components[i].contains, components[i], mx, my)
end
love.mouse.setCursor(contains and self.hand or nil)
end

function inspector:setupComponents(editing)
local subject = table.get(app, editing)
if subject.editor then
local components = {}
local y = 44

for i = 1, #subject.editor.sections do
local section = subject.editor.sections[i]
local header = self.gooey:add(lib.gooey.label, 'prop.' .. section.title)
header.geometry = self:createOffsetFunction(8, y)
header.label = section.title
table.insert(components, header)

y = y + 20

for j = 1, #section do
local prop = section[j]
local editor = self.gooey:add(lib.gooey.editor, 'prop.' .. prop)
editor.label = prop:gsub('[A-Z]', function(x) return ' ' .. x:lower() end)
editor.value = subject.config[prop]
editor.valueSubject:onNext(editor.value)
editor.geometry = self:createOffsetFunction(8, y, self.config.width - 16)
editor.valueSubject:subscribe(function(newValue)
subject.config[prop] = tonumber(newValue) or newValue
end)

y = y + 20
table.insert(components, editor)
end

y = y + 20
end

return components
else
local config = subject.config or subject
return table.map(table.keys(config), function(prop, i)
local editor = self.gooey:add(lib.gooey.editor, 'config.' .. prop)
editor.label = prop:gsub('[A-Z]', function(x) return ' ' .. x:lower() end)
editor.value = config[prop]
editor.valueSubject:onNext(editor.value)
editor.geometry = self:createOffsetFunction(8, 24 + 20 * i, self.config.width - 16)
editor.valueSubject:subscribe(function(newValue)
subject.config[prop] = tonumber(newValue) or newValue
end)
return editor
end)
end
end

function inspector:draw(_, editors)
local height = love.graphics.getHeight()

g.setColor(35, 35, 35, 220)
g.rectangle('fill', self.x, 0, self.config.width, height)

if editors then
g.setColor(255, 255, 255)
for i = 1, #editors do
self.gooey:render(editors[i])
end
end

self.gooey:render(self.dropdown)

return -10000
end

return inspector
@@ -0,0 +1,130 @@
local muju = {}

function muju:move(input)
local x, y = input.x, input.y
local config = self.config
local direction = math.atan2(y, x)
local length = math.min(math.distance(0, 0, x, y), 1)

if x == 0 and y == 0 then
self.speed.x = math.lerp(self.speed.x, 0, math.min(config.deceleration * lib.tick.rate, 1))
self.speed.y = math.lerp(self.speed.y, 0, math.min(config.deceleration * lib.tick.rate, 1))
else
self.speed.x = math.lerp(self.speed.x, config.speed * math.cos(direction) * length, config.acceleration * lib.tick.rate)
self.speed.y = math.lerp(self.speed.y, config.speed * math.sin(direction) * length, config.acceleration * lib.tick.rate)
end

self.position.x = self.position.x + self.speed.x * lib.tick.rate
self.position.y = self.position.y + self.speed.y * lib.tick.rate
end

function muju:canShapeshift()
return lib.tick.index - self.lastShapeshift > self.config.shapeshiftCooldown / lib.tick.rate
end

function muju:shapeshift()
self.form = self.form == 'muju' and 'thuju' or 'muju'
self.animation = self.animations[self.form]

if self.form == 'thuju' then
self.animation:clear()
self.animation:reset('spawn')
self.animation:add('idle')
end

self.lastShapeshift = lib.tick.index
end

function muju:attack()
self.animation:set('attack')
self.animation:add('idle')
end

function muju:animate(input)
self.animation.speed = 1

local moving = math.abs(input.x) > .5 or math.abs(input.y) > .5
local speed = math.sqrt((self.speed.x ^ 2) + (self.speed.y ^ 2)) / self.config.speed

if moving then
self.animation:set('walk')
elseif self.animation.state == self.animation.config.states.walk and speed < 1 then
self.animation:set('stop')
self.animation:add('idle')
end
end

function muju:flipAnimation()
local x = self.speed.x
if x ~= 0 then
self.animation.flipped = x > 0
end
end

function muju:setShuffleVolume()
local speed = math.sqrt((self.speed.x ^ 2) + (self.speed.y ^ 2)) / self.config.speed
self.shuffle:setVolume(speed * self.config.shuffleVolume)
end

function muju:eventFootstep()
local sound = love.audio.play(app.muju.sound['footstep' .. love.math.random(1, 2)])
sound:setVolume(self.config.footstepVolume)
sound:setPitch(.9 + love.math.random() * .2)
end

function muju:eventLimp()
local sound = love.audio.play(app.muju.sound.staff)
sound:setVolume(self.config.staffVolume)
sound:setPitch(.8 + love.math.random() * .6)

local x = self.position.x + (self.animation.flipped and 40 or -40)
local y = self.position.y
app.context.particles:emit('dust', x, y, 25, function()
return { direction = love.math.random() < .5 and math.pi or 0 }
end)
end

function muju:tint(r, g, b)
for _, slot in pairs({'robebottom', 'torso', 'front_upper_arm', 'rear_upper_arm', 'front_bracer', 'rear_bracer'}) do
local slot = self.animation.skeleton:findSlot(slot)
slot.r, slot.g, slot.b = r, g, b
end
end

function muju:resolveCollisionsWith(other)
return love.update
:map(function()
local distance = math.distance(self.position.x, self.position.y, other.position.x, other.position.y)
local direction = math.direction(self.position.x, self.position.y, other.position.x, other.position.y)
return distance, direction
end)
:filter(function(distance, direction)
return distance < self.config.radius + other.config.radius * math.abs(math.cos(direction))
end)
:map(function(distance, direction)
local delta = (self.config.radius + other.config.radius) - distance
return delta * math.cos(direction), delta * math.sin(direction) * math.abs(math.cos(direction))
end)
:subscribe(function(dx, dy)
self.position.x = math.lerp(self.position.x, self.position.x - dx / 2, 8 * lib.tick.rate)
self.position.y = math.lerp(self.position.y, self.position.y - dy / 2, 8 * lib.tick.rate)

other.position.x = math.lerp(other.position.x, other.position.x + dx / 2, 12 * lib.tick.rate)
other.position.y = math.lerp(other.position.y, other.position.y + dy / 2, 12 * lib.tick.rate)
end)
end

function muju:draw()
local image = app.art.shadow
local scale = 70 / image:getWidth()
g.setColor(255, 255, 255, 120)
g.draw(image, self.position.x, self.position.y, 0, scale, scale / 2, image:getWidth() / 2, image:getHeight() / 2)

g.setColor(255, 255, 255)
self.animation:tick(lib.tick.delta)
self.animation:draw(self.position.x, self.position.y)

return -self.position.y
end

return muju
@@ -4,14 +4,20 @@ function object.create()
return setmetatable({}, {__index = object})
end

function object:include(source)
table.merge(source, self)
end

function object:wrap(fn)
return f.self(fn, self)
end

function object:new(state)
local baseState = type(self.state) == 'function' and self.state() or self.state
state = table.merge(state, baseState)
local instance = table.merge(state, {})

setmetatable(instance, {
__index = self
})
setmetatable(instance, { __index = self })

f.try(instance.bind, instance)

@@ -0,0 +1,28 @@
local obstacle = {}

function obstacle:setSolid()
self.solid = true
end

function obstacle:setStartPosition()
self.position.initial = {
x = self.position.x,
y = self.position.y
}
end

function obstacle:revertToStartPosition()
self.position.x = math.lerp(self.position.x, self.position.initial.x, 16 * lib.tick.rate)
self.position.y = math.lerp(self.position.y, self.position.initial.y, 16 * lib.tick.rate)
end

function obstacle:draw()
g.setColor(255, 255, 255, 120)
g.drawCenter(app.art.shadow, 70, self.position.initial.x, self.position.initial.y, 0, 1, .5)

g.setColor(255, 255, 255)
g.drawCenter(self.image, self.config.size, self.position.x, self.position.y)
return -self.position.y
end

return obstacle
@@ -1,15 +1,18 @@
local particles = {}
local particles = lib.object.create()

function particles.create()
local self = {
particles.config = {
list = {'dust', 'thujustep'}
}

particles.state = function()
return {
systems = {}
}
end

setmetatable(self, {__index = particles})

local particleList = {'dust', 'thujustep'}
for i = 1, #particleList do
local name = particleList[i]
function particles:bind()
for i = 1, #self.config.list do
local name = self.config.list[i]
local particle = app.particles[name]
local system = g.newParticleSystem(particle.image, particle.max or 1024)
system:setOffset(particle.image:getWidth() / 2, particle.image:getHeight() / 2)
@@ -19,11 +22,7 @@ function particles.create()
end
end

return self
end

function particles:bind()
app.scene.view.draw:subscribe(function()
app.context.view.draw:subscribe(function()
g.setColor(255, 255, 255)
for code, system in pairs(self.systems) do
system:update(lib.tick.delta)
@@ -1,37 +1,39 @@
local quilt = {}

function quilt:init()
self.threads = {}
self.delays = {}
function quilt.init()
quilt.threads = {}
quilt.delays = {}

love.update:subscribe(quilt.update)
end

function quilt:add(thread)
self.threads[thread] = coroutine.create(thread)
self.delays[thread] = 0
function quilt.add(thread)
quilt.threads[thread] = coroutine.create(thread)
quilt.delays[thread] = 0
return thread
end

function quilt:remove(thread)
self.threads[thread] = nil
function quilt.remove(thread)
quilt.threads[thread] = nil
return thread
end

function quilt:reset(thread)
self.delays[thread] = 0
function quilt.reset(thread)
quilt.delays[thread] = 0
return thread
end

function quilt:update(dt)
for thread, cr in pairs(self.threads) do
if self.delays[thread] <= dt then
function quilt.update()
for thread, cr in pairs(quilt.threads) do
if quilt.delays[thread] <= lib.tick.rate then
local _, delay = coroutine.resume(cr)
self.delays[thread] = delay or 0
quilt.delays[thread] = delay or 0

if coroutine.status(cr) == 'dead' then
self:remove(thread)
quilt:remove(thread)
end
else
self.delays[thread] = self.delays[thread] - dt
quilt.delays[thread] = quilt.delays[thread] - lib.tick.rate
end
end
end

This file was deleted.

@@ -0,0 +1,9 @@
local thuju = {}

function thuju:createSpawnParticles()
app.context.particles:emit('thujustep', self.position.x, self.position.y, 30, function()
return { direction = love.math.random() < .5 and math.pi or 0 }
end)
end

return thuju
@@ -59,6 +59,7 @@ end

function table.filter(t, fn, iterator)
iterator = iterator or pairs
if type(fn) == 'string' then fn = f.key(fn) end
local res = {}
for k, v in iterator(t) do
if fn(v, k) then
@@ -108,7 +109,9 @@ function table.merge(t1, t2)
return t2
end

function g.drawCenter(image, size, x, y)
function g.drawCenter(image, size, x, y, a, sx, sy)
local scale = size / image:getWidth()
g.draw(image, x, y, 0, scale, scale, image:getWidth() / 2, image:getHeight() / 2)
sx = sx or 1
sy = sy or 1
g.draw(image, x, y, a, scale * sx, scale * sy, image:getWidth() / 2, image:getHeight() / 2)
end
@@ -1,78 +1,44 @@
local view = lib.object.create()

view.state = {
x = 0,
y = 0,
width = 800,
height = 600,
xmin = 0,
ymin = 0,
xmax = 800,
ymax = 600,
frame = {
view.state = function()
return {
x = 0,
y = 0,
width = g.getWidth(),
height = g.getHeight()
},

viewId = 0,
draws = {},
guis = {},
effects = {},
toRemove = {},
target = nil,

prevx = 0,
prevy = 0,
prevscale = 1,
shake = 0
}
width = 800,
height = 600,
xmin = 0,
ymin = 0,
xmax = 800,
ymax = 600,
frame = {
x = 0,
y = 0,
width = g.getWidth(),
height = g.getHeight()
},

viewId = 0,
draws = {},
guis = {},
effects = {},
toRemove = {},
target = nil,

prevx = 0,
prevy = 0,
prevscale = 1,
shake = 0
}
end

function view:bind()
self:resize()

self.draw = lib.rx.Subject.create()
self.hud = lib.rx.Subject.create()

self.draw.onNext = function(subject, ...)
local w, h = g.getDimensions()
local state = self.state
local source, target = state.sourceCanvas, state.targetCanvas

table.sort(subject.observers, function(a, b)
return (a.depth or 0) > (b.depth or 0)
end)

self:worldPush()

g.setCanvas(source)

for i = 1, #subject.observers do
subject.observers[i].depth = subject.observers[i]:onNext(...) or 0
end

g.setCanvas()
g.pop()

g.setCanvas()
g.setColor(255, 255, 255)
g.draw(source)
end

self.hud.onNext = function(subject, ...)
local w, h = g.getDimensions()
local state = self.state
local source, target = state.sourceCanvas, state.targetCanvas

table.sort(subject.observers, function(a, b)
return (a.depth or 0) > (b.depth or 0)
end)

for i = 1, #subject.observers do
subject.observers[i].depth = subject.observers[i]:onNext(...) or 0
end
end
self.draw.onNext = f.self(self.doDraw, self)
self.hud.onNext = f.self(self.doHud, self)

love.update
:subscribe(function()
@@ -87,65 +53,75 @@ function view:bind()
end

function view:update()
local state = self.state

state.prevx = state.x
state.prevy = state.y
state.prevscale = state.scale
self.prevx = self.x
self.prevy = self.y
self.prevscale = self.scale

self:contain()

state.shake = math.lerp(state.shake, 0, 8 * lib.tick.rate)
self.shake = math.lerp(self.shake, 0, 8 * lib.tick.rate)
end

while #state.toRemove > 0 do
local x = state.toRemove[1]
function view:doDraw()
local w, h = g.getDimensions()
local subject = self.draw
local source, target = self.sourceCanvas, self.targetCanvas

if x.draw then
for i = 1, #state.draws do
if state.draws[i] == x then table.remove(state.draws, i) i = #state.draws + 1 end
end
end
table.sort(subject.observers, function(a, b)
return (a.depth or 0) > (b.depth or 0)
end)

if x.gui then
for i = 1, #state.guis do
if state.guis[i] == x then table.remove(state.guis, i) i = #state.guis + 1 end
end
end
self:worldPush()

for i = 1, #state.effects do
if state.effects[i] == x then table.remove(state.effects, i) i = #state.effects + 1 end
end
g.setCanvas(source)

table.remove(state.toRemove, 1)
for i = 1, #subject.observers do
subject.observers[i].depth = subject.observers[i]:onNext() or 0
end

table.sort(state.draws, function(a, b)
return a.depth == b.depth and a.viewId < b.viewId or a.depth > b.depth
g.setCanvas()
g.pop()

g.setCanvas()
g.setColor(255, 255, 255)
g.draw(source)
end

function view:doHud()
local w, h = g.getDimensions()
local subject = self.hud
local source, target = self.sourceCanvas, self.targetCanvas

table.sort(subject.observers, function(a, b)
return (a.depth or 0) > (b.depth or 0)
end)

for i = 1, #subject.observers do
subject.observers[i].depth = subject.observers[i]:onNext() or 0
end
end

function view:resize()
local state = self.state
local w, h = g.getDimensions()
local ratio = w / h

state.frame.x, state.frame.y, state.frame.width, state.frame.height = 0, 0, state.width, state.height
if (state.width / state.height) > (w / h) then
state.scale = w / state.width
local margin = math.max(math.round(((h - w * (state.height / state.width)) / 2)), 0)
state.frame.y = margin
state.frame.height = h - 2 * margin
state.frame.width = w
self.frame.x, self.frame.y, self.frame.width, self.frame.height = 0, 0, self.width, self.height
if (self.width / self.height) > (w / h) then
self.scale = w / self.width
local margin = math.max(math.round(((h - w * (self.height / self.width)) / 2)), 0)
self.frame.y = margin
self.frame.height = h - 2 * margin
self.frame.width = w
else
state.scale = h / state.height
local margin = math.max(math.round(((w - h * (state.width / state.height)) / 2)), 0)
state.frame.x = margin
state.frame.width = w - 2 * margin
state.frame.height = h
self.scale = h / self.height
local margin = math.max(math.round(((w - h * (self.width / self.height)) / 2)), 0)
self.frame.x = margin
self.frame.width = w - 2 * margin
self.frame.height = h
end

state.sourceCanvas = g.newCanvas(w, h)
state.targetCanvas = g.newCanvas(w, h)
self.sourceCanvas = g.newCanvas(w, h)
self.targetCanvas = g.newCanvas(w, h)
end

function view:register(x, action)
@@ -182,14 +158,13 @@ function view:threeDepth(x, y, z)
end

function view:contain()
local state = self.state
state.x = math.clamp(state.x, 0, state.xmax - state.width)
state.y = math.clamp(state.y, 0, state.ymax - state.height)
self.x = math.clamp(self.x, 0, self.xmax - self.width)
self.y = math.clamp(self.y, 0, self.ymax - self.height)
end

function view:worldPoint(x, y)
x = math.round(((x - state.frame.x) / self.scale) + self.x)
if y then y = math.round(((y - state.frame.y) / self.scale) + self.y) end
x = math.round(((x - self.frame.x) / self.scale) + self.x)
if y then y = math.round(((y - self.frame.y) / self.scale) + self.y) end
return x, y
end

@@ -201,19 +176,19 @@ function view:screenPoint(x, y)
end

function view:worldMouseX()
return math.round(((love.mouse.getX() - self.state.frame.x) / self.scale) + self.x)
return math.round(((love.mouse.getX() - self.frame.x) / self.scale) + self.x)
end

function view:worldMouseY()
return math.round(((love.mouse.getY() - self.state.frame.y) / self.scale) + self.y)
return math.round(((love.mouse.getY() - self.frame.y) / self.scale) + self.y)
end

function view:frameMouseX()
return love.mouse.getX() - self.state.frame.x
return love.mouse.getX() - self.frame.x
end

function view:frameMouseY()
return love.mouse.getY() - self.state.frame.y
return love.mouse.getY() - self.frame.y
end

function view:screenshake(amount)
@@ -222,22 +197,21 @@ function view:screenshake(amount)
end

function view:worldPush()
local state = self.state
local x, y, s = unpack(table.interpolate({state.prevx, state.prevy, state.prevscale}, {state.x, state.y, state.scale}, lib.tick.accum / lib.tick.rate))
local shakex = 1 - (2 * love.math.noise(state.shake + x + lib.tick.accum))
local shakey = 1 - (2 * love.math.noise(state.shake + y + lib.tick.accum))
x = x + (shakex * state.shake)
y = y + (shakey * state.shake)
local x, y, s = unpack(table.interpolate({self.prevx, self.prevy, self.prevscale}, {self.x, self.y, self.scale}, lib.tick.accum / lib.tick.rate))
local shakex = 1 - (2 * love.math.noise(self.shake + x + lib.tick.accum))
local shakey = 1 - (2 * love.math.noise(self.shake + y + lib.tick.accum))
x = x + (shakex * self.shake)
y = y + (shakey * self.shake)

g.push()
g.translate(state.frame.x, state.frame.y)
g.translate(self.frame.x, self.frame.y)
g.scale(s)
g.translate(-x, -y)
end

function view:guiPush()
g.push()
g.translate(self.state.frame.x, self.state.frame.y)
g.translate(self.self.frame.x, self.self.frame.y)
end

return view
@@ -18,7 +18,7 @@ f = lib.funk
g = love.graphics
require 'lib/util'

app.scene.load('overgrowth')
app.context.load('overgrowth')

love.keypressed
:filter(f.eq('escape'))