local function state(songName, songDifficulty) local state = {} -- Returns needed functions for the state to work after loading it properly -- I NEED THEM IMPORTS local myMath = require("modules.math") local myTypes = require("modules.types") local conductor = require("modules.conductor") local json = require("modules.json") local files = require("modules.files") local logger = require("modules.logging") local socket = require("socket") local logging= require("modules.logging") -- I NEED THEM IMPORTS local startTime = 0 local chartString = files.read_file(string.format("charts/%s/%s-%s.json", songName, songName, songDifficulty)) if not chartString then error("Chart couldn't be loaded!") end local chart = json.parse(chartString).song local inst = love.audio.newSource(string.format("songs/%s/Inst.ogg", chart.song), "stream") local voices = love.audio.newSource(string.format("songs/%s/Voices.ogg", chart.song), "stream") local miss = love.audio.newSource("sounds/missnote1.ogg", "static") conductor:setBpm(chart.bpm) conductor:mapBpmChanges(chart) local step = 0 local beat = 0 local zoom = 1 local volume = 100 local playing = false local stage = json.parse(files.read_file("stages/stage.json")) local unspawnedNotes = {} local notes = {} local characters = {} local settings = {} local ratings = { sick = 0, good = 0, bad = 0, shit = 0, miss = 0, } local hitWindows = { [135] = "bad", [90] = "good", [45] = "sick" } local directions = { "LEFT", "DOWN", "UP", "RIGHT" } local singVectors = { singLEFT = myTypes.Vector2(20, 0), singDOWN = myTypes.Vector2(0, -20), singUP = myTypes.Vector2(0, 20), singRIGHT = myTypes.Vector2(-20, 0) } local pressed = { false, false, false, false, false } local holded = { false, false, false, false } local font = love.graphics.newFont("fonts/Phantomuff.ttf", 15) local fps = 60 -- for the counter local receptors = {} local keyBinds = {} -- loaded from settings.json, if anything's wrong then check your settings.json local paused = false local elapsed = 0 local pauseTime = 0 -- the global amount of time the song has been paused local pauseStart = 0 -- the start of the latest pause (for pauseTime calculation) local function quit() playing = false inst:stop() inst:release() inst = nil voices:stop() voices:release() voices = nil miss:stop() miss:release() state.quit() end local function checkNote(dir) local closestNote local closestIndex for index, note in next, notes do if note.position - conductor.songPosition < 200 then if note.mustPress and not note.pressed and note.direction == dir and (not closestNote or closestNote and note.position < closestNote.position) then closestNote = note closestIndex = index end end end if closestNote then local rating = "shit" for window, rate in next, hitWindows do if math.abs(closestNote.position - elapsed) <= window then rating = rate end end ratings[rating] = ratings[rating] + 1 characters.bf:PlayAnimation("sing"..directions[closestNote.direction]) closestNote.pressed = true receptors[dir]:PlayAnimation(string.lower(directions[dir]).." confirm", 5, false) notes[closestIndex] = nil closestNote:destroy() end end function state.update(dt) if not playing then return end -- playing isn't supposed to work like "paused", it's there to keep the game from working during loading if paused then goto continue end -- if paused then skip this cycle local currentTime = socket.gettime() elapsed = (currentTime - startTime) * 1000 - pauseTime conductor.songPosition = elapsed local oldStep = step local oldBeat = beat step = conductor:getStepRounded(elapsed) beat = conductor:getBeatRounded(elapsed) if beat ~= oldBeat then if beat % 2 == 0 then -- gf:PlayAnimation("BF NOTE LEFT", 30, false) for name, character in next, characters do if not character.singing then if name == "gf" then if beat % character.beats == 0 then character:PlayAnimation("danceLeft") end else character:PlayAnimation("idle") end end end if beat % 4 == 0 then zoom = zoom + .1 end end end local section = chart.notes[math.floor(step / 16) + 1] if section.mustHitSection then local currentSingVector = singVectors[characters.bf.animation] or myTypes.Vector2() myTypes.cameraTarget = myTypes.Vector2(-stage.camera_boyfriend[1], -stage.camera_boyfriend[2]):Add(characters.bf.stageCamera:Negate()):Add(myTypes.Vector2(0, -200)):Add(currentSingVector) else local currentSingVector = singVectors[characters.dad.animation] or myTypes.Vector2() myTypes.cameraTarget = myTypes.Vector2(stage.camera_opponent[1], stage.camera_opponent[2]):Add(characters.dad.stageCamera:Negate()):Add(myTypes.Vector2(0, -200)):Add(currentSingVector) end myTypes.updateSprites(dt) for name, character in next, characters do if name ~= "gf" and character.sprite.animation ~= "idle" and character.sprite.ended then character:PlayAnimation("idle") end end for index, note in next, unspawnedNotes do if note.position - elapsed < 600 and note.mustPress then note:spawn() unspawnedNotes[index] = nil notes[#notes+1] = note elseif note.position - elapsed < 600 and not note.mustPress then note:spawn() unspawnedNotes[index] = nil notes[#notes+1] = note end end for index, note in next, notes do if note.mustPress then note.sprite.position = myTypes.Vector2(600 + (79 * (note.direction - 1)), settings.Downscroll and 430 - (note.position-elapsed) or note.position - elapsed) if note.position - elapsed < -150 then note:destroy() miss:stop() miss:play() characters.bf:PlayAnimation("sing"..directions[note.direction].."miss") notes[index] = nil ratings.miss = ratings.miss + 1 end else note.sprite.position = myTypes.Vector2(20 + 79 * (note.direction - 1), settings.Downscroll and 430 - (note.position-elapsed) or note.position - elapsed) if note.position - elapsed < 10 then notes[index] = nil if section.altAnim or note.altAnim then characters.dad:PlayAnimation("sing"..directions[note.direction].."-alt") else characters.dad:PlayAnimation("sing"..directions[note.direction]) end note:destroy() note = nil end end end for index, receptor in next, receptors do if receptor.ended and receptor.animation ~= "arrow"..directions[index] and receptor.animation ~= "" then receptor:PlayAnimation("arrow"..directions[index], 25, false) end end if inst and not inst:isPlaying() then quit() end zoom = myMath.lerp(zoom, 1, .05) fps = 1000 / dt if inst:getVolume() ~= volume / 100 then inst:setVolume(volume / 100) voices:setVolume(volume / 100) end ::continue:: end local mainCanvas = love.graphics.newCanvas(1920, 1080) function state.draw() love.graphics.setCanvas(mainCanvas) love.graphics.clear() love.graphics.circle("fill", 960, 59, 50000) myTypes.drawSprites() love.graphics.setCanvas() love.graphics.draw(mainCanvas, (love.graphics.getWidth() - (love.graphics.getWidth() * zoom)) / 2, (love.graphics.getHeight() - love.graphics.getHeight() * zoom) / 2, 0, love.graphics.getWidth()/1920 * zoom, (love.graphics.getHeight()/1080 * zoom)) love.graphics.print({{0,0,0,1}, string.format("FPS: %s \nVolume: %s", love.timer.getFPS(), volume)}, font) love.graphics.print({{0,0,0,1}, string.format("Sick: %s \nGood: %s \nBad: %s \nShit: %s \nMiss: %s", ratings.sick, ratings.good, ratings.bad, ratings.shit, ratings.miss)}, font, 0, 20) end love.window.setMode(1280, 720, { fullscreen = false , resizable = false}) function state.load() -- GF first so she is below other chars if chart.gfVersion ~= "none" then characters.gf = myTypes.character(chart.gfVersion) characters.gf.stagePosition = myTypes.Vector2(stage.girlfriend[1], stage.girlfriend[2]) end characters.bf = myTypes.character(chart.player1) characters.bf.stagePosition = myTypes.Vector2(stage.boyfriend[1], stage.boyfriend[2]) characters.dad = myTypes.character(chart.player2) characters.dad.stagePosition = myTypes.Vector2(stage.opponent[1], stage.opponent[2]) for index, section in next, chart.notes do for index, note in next, section.sectionNotes do local newNote = myTypes.note(note, section.mustHitSection) unspawnedNotes[#unspawnedNotes+1] = newNote end end for i = 0, 3 do local receptor = myTypes.Sprite("sprites/NOTE_assets.png", "sprites/NOTE_assets.json") receptor:PlayAnimation("arrow"..directions[i+1], 25, false) receptor.position = myTypes.Vector2(600 + (79* i), settings.Downscroll and 0 or 430) receptor.ui = true -- So it doesnt move with the camera. receptors[i + 1] = receptor end for i = 0, 3 do -- opponent receptors, purely graphics local receptor = myTypes.Rect("sprites/NOTE_assets.png", "sprites/NOTE_assets.json") receptor:Frame("arrow"..directions[i+1], 0) receptor.position = myTypes.Vector2(20 + 79 * i, settings.Downscroll and 0 or 430) receptor.ui = true -- So it doesnt move with the camera. end myTypes.cameraTarget = myTypes.Vector2() settings = json.parse(files.read_file("settings.json")) if not settings then error("Failed to load settings") end keyBinds = settings.Keybinds state.loaded = true end function state.finish() inst:play() voices:play() while not inst:isPlaying() do end --waiting till the song actually plays. elapsed = 0 actualElapsed = 0 playing = true startTime = socket.gettime() end function state.keypressed(key, un, is) if key == "space" then paused = not paused if paused then inst:pause() voices:pause() pauseStart = socket.gettime() * 1000 else inst:play() voices:play() pauseTime = pauseTime + (socket.gettime() * 1000 - pauseStart) end elseif key == "escape" then quit() elseif key == keyBinds[1] then checkNote(1) elseif key == keyBinds[2] then checkNote(2) elseif key == keyBinds[3] then checkNote(3) elseif key == keyBinds[4] then checkNote(4) elseif key == "-" then volume = volume ~= 0 and volume - 10 or 0 elseif key == "+" or key == "=" then volume = volume ~= 100 and volume + 10 or 100 end end return state end return state -- vscode doesnt like this but idrc