417 lines
9.7 KiB
Lua
417 lines
9.7 KiB
Lua
local love = love
|
|
local step = 1.0 / 120
|
|
local sitelenpona
|
|
local text
|
|
local marble
|
|
local wave
|
|
local particles
|
|
local audio
|
|
local recorder
|
|
local DetectCollision
|
|
local OnImpact
|
|
local OnVictory
|
|
local BeatScore
|
|
local Draw
|
|
local Update
|
|
local ExtrapolateBeatScore
|
|
local _ExtrapolateBeatScore
|
|
local mouseControl
|
|
local scores
|
|
|
|
local state
|
|
state = {
|
|
|
|
Reset = function()
|
|
state.tick = 0
|
|
state.beat = {}
|
|
state.currentBeat = 1
|
|
state.timeToSimulate = 0.0
|
|
state.longestStreak = 0
|
|
state.currentStreak = 0
|
|
state.lastCollisionTick = 0
|
|
state.lastCollisionX = 0
|
|
state.lastCollisionY = 0
|
|
end,
|
|
|
|
timeToSimulate = 0.0,
|
|
lastBeatScore = 0.0,
|
|
currentBeat = 1,
|
|
longestStreak = 0,
|
|
currentStreak = 0,
|
|
|
|
beat = {
|
|
t = nil,
|
|
mu = nil,
|
|
},
|
|
}
|
|
|
|
local function UpdateWindowTransform( w, h )
|
|
local d = math.min( w, h )
|
|
local r = love.graphics.get
|
|
local tf = love.math.newTransform()
|
|
|
|
local size = 3
|
|
|
|
tf:translate( w / 2, h / 2)
|
|
tf:scale( d / size, -d / size )
|
|
transform = tf
|
|
end
|
|
|
|
OnImpact = function( impact )
|
|
if not impact then return end
|
|
local score = BeatScore( impact.t )
|
|
--DEBUG
|
|
state.lastBeatScore = score
|
|
state.lastCollisionTick = state.tick
|
|
local pass = false
|
|
|
|
if score > 1.0 then
|
|
|
|
pass = true
|
|
|
|
state.currentStreak = state.currentStreak + 1
|
|
if state.currentStreak > state.longestStreak then
|
|
state.longestStreak = state.currentStreak
|
|
end
|
|
|
|
state.currentBeat = state.currentBeat + 1
|
|
if state.currentBeat >= 119 then
|
|
return OnVictory()
|
|
end
|
|
|
|
else
|
|
state.currentStreak = 0
|
|
end
|
|
|
|
scores.OnImpact( state.tick, pass )
|
|
|
|
|
|
local x, y = impact.r * math.cos(impact.th), impact.r * math.sin(impact.th)
|
|
particles:setPosition( x, y )
|
|
particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true )
|
|
particles:emit( 50 * score * score )
|
|
|
|
state.lastCollisionX, state.lastCollisionY = transform:transformPoint( 1.2 * x, 1.2 * y )
|
|
|
|
audio.OnImpact( impact, score, pass, state.currentBeat )
|
|
marble.OnImpact( impact, state.currentBeat )
|
|
wave.OnImpact( impact, state.currentBeat )
|
|
end
|
|
local _OnImpact = OnImpact
|
|
|
|
local function NewGame( demoName )
|
|
love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink.
|
|
OnImpact = _OnImpact
|
|
ExtrapolateBeatScore = _ExtrapolateBeatScore
|
|
love.draw = Draw
|
|
love.update = Update
|
|
|
|
do --particle shit
|
|
particles:reset()
|
|
particles:setSizes( 0.0007, 0.0001, 0.0003 )
|
|
--particles:setSizes( 0.0007, 0.0001, 0.0003 )
|
|
particles:setSizeVariation( 1 )
|
|
particles:setRadialAcceleration( 0, 0.5 )
|
|
particles:setSpeed( 0.2, 1 )
|
|
particles:setLinearDamping( 1, 10 )
|
|
particles:setRelativeRotation( true )
|
|
particles:setEmitterLifetime( -1 )
|
|
particles:setParticleLifetime( 0, 5 )
|
|
particles:setSpread( 0.05 )
|
|
particles:setColors(
|
|
1, 1, 1, 1,
|
|
91 / 255, 206 / 255, 250 / 255, 1,
|
|
245 / 255, 169 / 255, 184 / 255, 1,
|
|
1,1,1,0,
|
|
245 / 255, 169 / 255, 184 / 255, 1,
|
|
1,1,1,0,
|
|
245 / 255, 169 / 255, 184 / 255, 1,
|
|
1,1,1,0)
|
|
end
|
|
mouseControl.Reset()
|
|
state.Reset()
|
|
marble.Reset()
|
|
wave.Reset()
|
|
text.Reset()
|
|
audio.Reset()
|
|
recorder.Reset()
|
|
scores.Reset()
|
|
|
|
state.isDemo = demoName
|
|
if demoName then
|
|
recorder.Load( demoName )
|
|
end
|
|
end
|
|
|
|
function love.load()
|
|
love.mouse.setRelativeMode( true )
|
|
|
|
UpdateWindowTransform( love.graphics.getDimensions() )
|
|
|
|
|
|
|
|
do--particle system setup
|
|
particles = love.graphics.newParticleSystem(
|
|
love.graphics.newImage( "prideflag.png" ),
|
|
2048)
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
options = assert( require( "options" ) )
|
|
mouseControl = assert( require "mousecontrols" )
|
|
sitelenpona = assert( require "sitelenpona" )
|
|
text = assert( require "text" )
|
|
marble = assert( require "marble" )
|
|
wave = assert( require "wave" )
|
|
DetectCollision = assert ( require "collision" )
|
|
audio = assert( require "audio" )
|
|
recorder = assert( require "recorder" )
|
|
scores = assert( require "scores" )
|
|
return NewGame()
|
|
|
|
end
|
|
|
|
ExtrapolateBeatScore = function( )
|
|
local t = state.tick / 120.0
|
|
local beat = state.beat
|
|
if not beat.t then return 2.0 end
|
|
if not beat.mu then return 2.0 end
|
|
if beat.mu < 0.001 then return 2.0 end
|
|
|
|
local pow = 1 / math.max( beat.mu, 0.2 )
|
|
t = 1.05 * math.sin( 0.5 * math.pi * ( t - beat.t ) / beat.mu )
|
|
return math.pow( t * t, pow )
|
|
end
|
|
_ExtrapolateBeatScore = ExtrapolateBeatScore
|
|
|
|
BeatScore = function( t )
|
|
local beat = state.beat
|
|
|
|
--Base case 1: first tap.
|
|
if not beat.t then
|
|
beat.t = t
|
|
return 2.0
|
|
end
|
|
|
|
local dt = t - beat.t
|
|
beat.t = t
|
|
|
|
--Base case 2: second tap.
|
|
if not beat.mu then
|
|
beat.mu = dt
|
|
return 2.0
|
|
end
|
|
|
|
local pow = 1 / math.max( beat.mu, 0.1 )
|
|
local score = 1.1 * math.sin( 0.5 * math.pi * dt / beat.mu )
|
|
score = math.pow( score * score, pow )
|
|
|
|
--General case: update average beat length.
|
|
|
|
--Debounce: max BPM 200
|
|
if dt > 60.0 / 200.0 then
|
|
|
|
local WEIGHT = 0.75 --High number makes the last beat more significant.
|
|
local mu = ( 1.0 - WEIGHT ) * beat.mu + WEIGHT * dt
|
|
beat.mu = mu
|
|
|
|
else
|
|
score = 0
|
|
end
|
|
|
|
return score
|
|
end
|
|
|
|
OnVictory = function()
|
|
|
|
if state.isDemo then
|
|
state.isDemo = false
|
|
else
|
|
scores.Save()
|
|
recorder.Save()
|
|
end
|
|
|
|
particles:setParticleLifetime( 0, 30 )
|
|
particles:setSizes( 0.001, 0.0001, 0.0005 )
|
|
particles:setColors(
|
|
245 / 255, 169 / 255, 184 / 255, 1,
|
|
1, 1, 1, 1,
|
|
245 / 255, 169 / 255, 184 / 255, 1,
|
|
1, 1, 1, 0,
|
|
245 / 255, 169 / 255, 184 / 255, 1,
|
|
1, 1, 1, 0
|
|
)
|
|
particles:setPosition( 0, 0 )
|
|
particles:setEmissionArea( "normal", 0.5, 0.5, 0, true )
|
|
particles:emit( 500 )
|
|
particles:setEmissionArea( "normal", 0.01, 0.01, 0, true )
|
|
|
|
love.graphics.setCanvas( marble.Canvas() )
|
|
Draw()
|
|
love.graphics.setCanvas()
|
|
|
|
local totalTime = state.tick / 120.0
|
|
OnImpact = function() end
|
|
love.update = function( dt )
|
|
while dt > step do
|
|
marble.Integrate( step )
|
|
wave.Integrate( step )
|
|
marble.Update()
|
|
wave.Update()
|
|
|
|
dt = dt - step
|
|
end
|
|
|
|
local marblePos = marble.Current()
|
|
particles:emit( 1 )
|
|
particles:update( dt )
|
|
particles:moveTo( marblePos.x, marblePos.y )
|
|
end
|
|
love.draw = function()
|
|
love.graphics.push( "transform" )
|
|
love.graphics.applyTransform( transform )
|
|
love.graphics.setColor( 1, 1, 1, 1 )
|
|
love.graphics.draw( particles )
|
|
love.graphics.pop()
|
|
|
|
text.Draw( 119 )
|
|
scores.RenderHighScores()
|
|
|
|
local score = state.longestStreak * state.longestStreak / totalTime
|
|
|
|
marble.Draw()
|
|
end
|
|
marble.OnVictory()
|
|
love.graphics.setBackgroundColor( 91 / 255, 206 / 255, 250 / 255 ) --Trans blue.
|
|
end
|
|
|
|
Draw = function()
|
|
|
|
|
|
|
|
local score = ExtrapolateBeatScore()
|
|
|
|
|
|
love.graphics.push( "transform" )
|
|
|
|
love.graphics.applyTransform( transform )
|
|
love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 )
|
|
love.graphics.draw(particles, 0, 0)
|
|
wave.Draw( score )
|
|
|
|
|
|
|
|
love.graphics.pop()
|
|
|
|
|
|
sitelenpona.Draw( text.words[state.currentBeat] )
|
|
marble.Draw()
|
|
text.Draw( state.currentBeat )
|
|
|
|
love.graphics.setColor( 0.3, 0.3, 0.3, 1.0 - score )
|
|
local s = "+"..state.currentStreak..(state.currentStreak > 50 and "!" or "")
|
|
love.graphics.print( s, state.lastCollisionX, state.lastCollisionY, 0, 0.5 - 0.1 * score, 0.5 - 0.1 * score)
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
Update = function( dt )
|
|
|
|
audio.Update( ExtrapolateBeatScore(), state.currentBeat )
|
|
particles:update( dt )
|
|
dt = dt + state.timeToSimulate
|
|
|
|
--Physics tick.
|
|
while dt > step do
|
|
|
|
state.tick = state.tick + 1
|
|
|
|
if state.isDemo then
|
|
local ddx, ddy = recorder.NextTick()
|
|
if ddx then marble.SetAcceleration( ddx, ddy )
|
|
else state.isDemo = false end
|
|
else
|
|
recorder.Update( marble.GetAcceleration() ) --For savegames.
|
|
end
|
|
|
|
marble.Integrate( step )
|
|
wave.Integrate( step )
|
|
|
|
OnImpact(
|
|
DetectCollision(
|
|
marble.Current(), marble.Next(),
|
|
wave.Current(), wave.Next() ))
|
|
|
|
marble.Update()
|
|
wave.Update()
|
|
|
|
dt = dt - step
|
|
end
|
|
state.timeToSimulate = dt
|
|
end
|
|
_Update = Update
|
|
|
|
local function OptionsMenu()
|
|
if not options then options = require( "options" ) end
|
|
return options.EnterMenu()
|
|
end
|
|
|
|
local function LoadGame() -- Load game screen.
|
|
if not loadGame then loadGame = assert( require "loadgame" ) end
|
|
local Click, Press = assert(loadGame.OnClick), assert(loadGame.KeyPress)
|
|
love.mousepressed = function( x, y, button, istouch, presses)
|
|
local demoName = Click( x, y, button, istouch, presses )
|
|
if demoName then return NewGame( demoName ) end
|
|
end
|
|
|
|
love.keypressed = function( key, code, isRepeat)
|
|
local demoName = Press( key, code, isRepeat )
|
|
if demoName then return NewGame( demoName ) end
|
|
end
|
|
|
|
loadGame.LoadGameMenu()
|
|
end
|
|
|
|
function love.keypressed( key, code, isRepeat )
|
|
if code == "escape" then return love.event.quit() end
|
|
if code == "space" then return NewGame() end
|
|
if code == "return" then return LoadGame() end --Play demo.
|
|
if code == "o" then return OptionsMenu() end
|
|
if state.isDemo then return end
|
|
|
|
return marble.OnKey(
|
|
love.keyboard.isScancodeDown( options.up.value ),
|
|
love.keyboard.isScancodeDown( options.left.value ),
|
|
love.keyboard.isScancodeDown( options.down.value ),
|
|
love.keyboard.isScancodeDown( options.right.value )
|
|
)
|
|
end
|
|
|
|
function love.keyreleased( key, code )
|
|
return marble.OnKey(
|
|
love.keyboard.isScancodeDown( options.up.value ),
|
|
love.keyboard.isScancodeDown( options.left.value ),
|
|
love.keyboard.isScancodeDown( options.down.value ),
|
|
love.keyboard.isScancodeDown( options.right.value ) )
|
|
end
|
|
|
|
function love.resize( w, h )
|
|
UpdateWindowTransform( w, h )
|
|
if marble then marble.Resize() end
|
|
end
|
|
|
|
--TODO: make this feel better.
|
|
function love.mousemoved( x, y, dx, dy, number, istouch )
|
|
dx, dy = mouseControl.mousemoved( dx, dy )
|
|
if dx then return marble.SetAcceleration( dx, dy ) end
|
|
end
|
|
|
|
function love.mousepressed( x, y, button, istouch, presses )
|
|
return NewGame()
|
|
end |