-- I NEED THEM IMPORTS local conductor = require("modules.conductor") local json = require("modules.json") local files = require("modules.files") local socket = require("socket") local logging = require("modules.logging") local tween = require("modules.tween") local character = require("modules.types.character") -- I NEED THEM IMPORTS local rankWindows = { { rating = "sick", hitWindow = 45, spawnSplash = true, score = 300 }, { rating = "good", hitWindow = 90, spawnSplash = false, score = 200 }, { rating = "bad", hitWindow = 135, spawnSplash = false, score = 100 }, { rating = "shit", hitWindow = 300, spawnSplash = false, score = 50 } } local receptorOffsets = { arrow = Vector2(0, 0), press = Vector2(-5, -5), confirm = Vector2(35, 35) } local receptorAnims = { "arrow", "arrow", "arrow", "arrow" } local directions = { "LEFT", "DOWN", "UP", "RIGHT" } local colors = { "purple", "blue", "green", "red" } local covers = { "Purple", "Blue", "Green", "Red" } local function state(songName, songDifficulty, show) ---@type engine.state local state = {} -- Returns needed functions for the state to work after loading it properly print(curChar) local startTime = 0 local chartString if curChar == "bf" and not Erect then chartString = files.read_file(string.format("charts/%s/%s-chart.json", songName, songName)) else chartString = files.read_file(string.format("charts/%s/%s-chart-%s.json", songName, songName, Erect and "erect" or curChar)) end print(chartString) local metadata if curChar == "bf" and not Erect then metadata = files.read_file(string.format("charts/%s/%s-metadata.json", songName, songName)) else metadata = files.read_file(string.format("charts/%s/%s-metadata-%s.json", songName, songName, Erect and "erect" or curChar)) end print(metadata) metadata = json.parse(metadata) if not chartString then error("Chart couldn't be loaded!") end local chart = json.parse(chartString) _G.inst = love.audio.newSource(string.format("songs/%s/Inst.ogg", metadata.songName), "stream") _G.voices = love.filesystem.getInfo(string.format("songs/%s/Voices.ogg", metadata.songName)) if voices then voices = love.audio.newSource(string.format("songs/%s/Voices.ogg", metadata.songName), "stream") end local speed = chart.scrollSpeed[songDifficulty] and chart.scrollSpeed[songDifficulty] / 2 or 0.5 local miss = love.audio.newSource("sounds/missnote1.ogg", "static") local loss = love.audio.newSource("sounds/fnf_loss_sfx.ogg", "static") local gameOver = love.audio.newSource("sounds/gameOver.ogg", "stream") local gameOverEnd = love.audio.newSource("sounds/gameOverEnd.ogg", "static") conductor:setBpm(metadata.timeChanges[1].bpm) -- conductor:mapBpmChanges(chart) local step = 0 local beat = 0 local zoom = 1 local uiZoom = 1 local mustZoom = 2 local iconZoom = 1 local combo = 0 local highestCombo = 0 _G.modules = {} local playing = false local stageName = metadata.playData.stage local stageString = files.read_file(string.format("stages/%s.json", stageName)) or files.read_file("stages/stage.json") _G.stage = json.parse(stageString) local unspawnedNotes = {} local notes = {} local defaultZoom = 1 local unspawnedHoldNotes = {} local holdNotes = {} local events = {} local characters = {} local icons = {} local data = love.filesystem.getSaveDirectory() local ui = { healthIcons = true, -- If halth is false it wont render either way health = true, score = true, } local settings = {} local ratings = { sick = 0, good = 0, bad = 0, shit = 0, miss = 0, } local score = 0 local totalScore = 0 -- If you hit ALL sicks local accuracy = 100 local font = love.graphics.newFont("fonts/Phantomuff.ttf", 15) local biggerFont = love.graphics.newFont("fonts/Phantomuff.ttf", 30) local evenBiggerFont = love.graphics.newFont("fonts/FridayNightFunkin-Regular.ttf", 50) local big = love.graphics.newFont(30) local receptors = {} local opponentReceptors = {} local holdCovers = {} local opponentHoldCovers = {} local splashes = {} local keyBinds = {} -- loaded from settings.json, if anything's wrong then try rebinding in the menu local offset = 0 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 health = 1 -- 0 to 2, 1 starters local dead = false local deadBF local restart = false local countDownAudio = { love.audio.newSource("sounds/countdown/introTHREE.ogg", "static"), love.audio.newSource("sounds/countdown/introTWO.ogg", "static"), love.audio.newSource("sounds/countdown/introONE.ogg", "static"), love.audio.newSource("sounds/countdown/introGO.ogg", "static") } local curCD = 0 local counting = true -- Before the round starts it will be true local cdLength = 0 for i, audio in next, countDownAudio do cdLength = cdLength + audio:getDuration() * 1000 end --- @class engine.sharedvars --- @field canStart boolean --- @field screenSize engine.vector2 --- @field canvasSize engine.vector2 --- @field settings table --- @field receptors table --- @field splashes table --- @field opponentReceptors table --- @field health number --- @field speed number --- @field ui table --- @field notes table --- @field characters table _G.sharedVars = { canStart = true, screenSize = Vector2(1280, 720), canvasSize = Vector2(3840, 2160), settings = settings, receptors = receptors, splashes = splashes, opponentReceptors = opponentReceptors, health = health, speed = speed, ui = ui, zoom = zoom, defaultZoom = defaultZoom, notes = notes, -- only spawned notes holds = holdNotes, characters = characters, shouldCountdown = true, } local cameraTween = tween.new(1, render.cameraPosition, { x = 0, y = 0 }, tween.easing.inOutQuad) local zoomTween = tween.new(32, sharedVars, { defaultZoom = defaultZoom }, tween.easing.outExpo) local function quit(save) if restart then return end playing = false if modules then for index, module in next, modules do if module.onClose then module.onClose() modules[index] = nil end end end modules = {} if not dead then inst:stop() inst:release() inst = nil if voices then voices:stop() voices:release() voices = nil end miss:stop() miss:release() else loss:stop() gameOver:stop() gameOverEnd:stop() end if save then local oldSave = files.read_file(data .. "/Data.json") if not oldSave then oldSave = { songs = {} } else oldSave = json.parse(oldSave) end if not oldSave.songs[curChar] then oldSave.songs[curChar] = {} end if not oldSave.songs[curChar][songName] then oldSave.songs[curChar][songName] = {} end local rankingWindows = { { name = "Bad", window = 50 }, { name = "Good", window = 70 }, { name = "Sick", window = 90 }, { name = "Perfect", window = 100 }, } if oldSave.songs[curChar][songName][songDifficulty] and oldSave.songs[curChar][songName][songDifficulty].score < score or not oldSave.songs[curChar][songName][songDifficulty] then local rank = "Ass" for index, newRank in next, rankingWindows do if accuracy >= newRank.window then rank = newRank.name end end oldSave.songs[curChar][songName][songDifficulty] = { accuracy = tostring(accuracy):sub(1, 5), score = score, rank = rank } files.write_file(data .. "/Data.json", json.stringify(oldSave)) end end render.offset = Vector2() if gameMode == "storymode" then currentSong = currentSong + 1 state.changeState(save and songOrder[currentSong] and "playstate" or "weekstate", songOrder[currentSong], songDifficulty) else state.changeState(save and "resultsstate" or "freeplaystate", score, accuracy, ratings, combo) end end local function die() dead = true playing = false if modules then for index, module in next, modules do if module.onDeath then module.onDeath() end if module.onClose then module.onClose() modules[index] = nil end end end modules = {} inst:stop() inst:release() inst = nil if voices then voices:stop() voices:release() voices = nil end miss:stop() miss:release() render.destroyAllSprites() deadBF = Character(string.format("%s-dead", chart.player1)) deadBF:PlayAnimation("firstDeath") loss:play() end local function checkNote(dir) local closestNote local closestIndex for index, note in next, notes do if note.position - conductor.songPosition < 230 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 for index, module in next, modules do if module.noteHit then module.noteHit(closestNote) end end local rating = conductor:judgeNote(rankWindows, math.abs(closestNote.position - elapsed)) if rating.spawnSplash then splashes[closestNote.direction]:PlayAnimation( string.format("note impact %s %s", math.random(1, 2), colors[closestNote.direction]), 24, false) end ratings[rating.rating] = ratings[rating.rating] + 1 characters.bf:PlayAnimation("sing" .. directions[closestNote.direction]) closestNote.pressed = true health = health + closestNote.hitHealth notes[closestIndex] = nil closestNote:Destroy() score = score + rating.score totalScore = totalScore + rankWindows[1].score accuracy = (score / totalScore) * 100 combo = combo + 1 if combo > highestCombo then highestCombo = combo end receptors[dir]:PlayAnimation(string.format("%s confirm", string.lower(directions[dir])), 24, false) receptorAnims[dir] = "confirm" else receptors[dir]:PlayAnimation(string.format("%s press", string.lower(directions[dir])), 24, false) receptorAnims[dir] = "press" end end function state.update(dt) if not playing then for index, module in next, modules do if module.onUpdate then module.onUpdate(dt, 0) end end if sharedVars.canStart then sharedVars.canStart = false -- already started -- inst:play() -- if voices then -- voices:play() -- end -- while not inst:isPlaying() do -- end --waiting till the song actually plays. elapsed = 0 playing = true --countdown now startTime = socket.gettime() end if dead then render.cameraPosition = deadBF.stageCamera if not restart then if deadBF and deadBF.sprite.ended then deadBF:PlayAnimation("deathLoop") end if loss and not loss:isPlaying() and not gameOverEnd:isPlaying() and not gameOver:isPlaying() then gameOver:play() end else if not gameOverEnd:isPlaying() then deadBF = nil loss:release() loss = nil gameOverEnd:release() gameOverEnd = nil gameOver:release() gameOver = nil state.restart(songName, songDifficulty) end end render.updateSprites(dt) end return end -- playing isn't supposed to work like "paused", it's there to keep the game from working during loading if counting and sharedVars.shouldCountdown then if not countDownAudio[curCD] or not countDownAudio[curCD]:isPlaying() then if not countDownAudio[curCD + 1] then counting = false -- Ended the countdown inst:play() inst:setLooping(false) if voices then voices:play() end elapsed = 0 playing = true --We can truly play now startTime = socket.gettime() cdLength = 0 render.cameraPosition = Vector2(stage.camera_opponent[1], stage.camera_opponent[2]):Add(characters .dad and characters.dad.stageCamera:Negate() or characters.bf.stageCamera:Negate()):Add(Vector2( 0, -200)) else curCD = curCD + 1 countDownAudio[curCD]:play() end end elseif not sharedVars.shouldCountdown then counting = false end if paused then goto continue end -- if paused then skip this cycle local currentTime = socket.gettime() elapsed = (currentTime - startTime) * 1000 - pauseTime - cdLength 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" or character.animInfo.danceLeft then character:PlayAnimation("danceLeft") else character:PlayAnimation("idle") end end end if beat % 4 == 0 then zoom = zoom + .02 uiZoom = uiZoom + .05 end else for name, character in next, characters do if not character.singing then if name == "gf" or character.animInfo.danceLeft then character:PlayAnimation("danceRight") end end end end for index, module in next, modules do if module.onBeat then module.onBeat(beat) end end iconZoom = 1.2 end render.updateSprites(dt) if cameraTween then local finished = cameraTween:update(dt) if finished then cameraTween = nil end end if zoomTween then local finsihed = zoomTween:update(dt) if finsihed then zoomTween = nil end end for name, character in next, characters do if name ~= "gf" and character.animInfo.idle and character.sprite.animation ~= "idle" and character.sprite.ended then character:PlayAnimation("idle") elseif (name == "gf" or character.animInfo.danceLeft) and character.singing and character.sprite.animation ~= "danceLeft" and character.sprite.ended then character:PlayAnimation("danceLeft") end end for index, module in next, modules do if module.onUpdate then module.onUpdate(dt, elapsed) -- elapsed is for special stuff i guess end end -- Spawn holds before normal notes so they are below them for index, holdNote in next, unspawnedHoldNotes do if (holdNote.position - elapsed) * speed < 1500 then holdNote:Spawn() unspawnedHoldNotes[index] = nil holdNotes[#holdNotes + 1] = holdNote end end for index, note in next, unspawnedNotes do if (note.position - elapsed) * speed < 1500 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 = Vector2(receptors[note.direction].position.x + note.offset.x, settings.Downscroll and receptors[note.direction].position.y - (note.position - elapsed) * speed or receptors[note.direction].position.y + (note.position - elapsed) * speed) if (note.position - elapsed) * speed < -150 then note:Destroy() miss:stop() miss:play() characters.bf:PlayAnimation("sing" .. directions[note.direction] .. "miss") notes[index] = nil ratings.miss = ratings.miss + 1 health = health - note.missHealth score = score - 200 if combo > highestCombo then highestCombo = combo end end else note.sprite.position = Vector2(opponentReceptors[note.direction].position.x + note.offset.x, settings.Downscroll and opponentReceptors[note.direction].position.y - (note.position - elapsed) * speed or opponentReceptors[note.direction].position.y + (note.position - elapsed) * speed) if (note.position - elapsed) * speed < 10 then notes[index] = nil characters[note.character or "dad"]:PlayAnimation("sing" .. directions[note.direction]) for index, module in next, modules do if module.opponentNoteHit then module.opponentNoteHit(note) end end note:Destroy() note = nil end end end for index, hold in next, holdNotes do if hold.mustPress then hold.sprite.position = Vector2(receptors[hold.direction].position.x + hold.offset.x, settings.Downscroll and receptors[hold.direction].position.y - (hold.position - elapsed) * speed or receptors[hold.direction].position.y + (hold.position - elapsed) * speed) if (hold.position - elapsed) * speed < 0 then if love.keyboard.isDown(keyBinds[hold.direction]) then if characters.bf.animInfo["sing" .. directions[hold.direction] .. "-alt"] then characters.bf:PlayAnimation("sing" .. directions[hold.direction] .. "-alt") else characters.bf:PlayAnimation("sing" .. directions[hold.direction]) end hold:Destroy() holdNotes[index] = nil health = health + hold.hitHealth * 0.2 receptors[hold.direction]:PlayAnimation( string.format("%s confirm", string.lower(directions[hold.direction])), 24, false) receptorAnims[hold.direction] = "confirm" if holdCovers[hold.direction].frame > 2 or not holdCovers[hold.direction].animation or holdCovers[hold.direction].alpha == 0 then holdCovers[hold.direction]:PlayAnimation(string.format("holdCover%s", covers[hold.direction]), 24, false) end holdCovers[hold.direction].alpha = 1 holdCovers[hold.direction].ende = false elseif (hold.position - elapsed) * speed < -150 then hold:Destroy() characters.bf:PlayAnimation("sing" .. directions[hold.direction] .. "miss") holdNotes[index] = nil health = health - hold.missHealth * 0.2 end end else hold.sprite.position = Vector2(opponentReceptors[hold.direction].position.x + hold.offset.x, settings.Downscroll and opponentReceptors[hold.direction].position.y - (hold.position - elapsed) * speed or opponentReceptors[hold.direction].position.y + (hold.position - elapsed) * speed) if (hold.position - elapsed) * speed < 10 then characters[hold.character or "dad"]:PlayAnimation("sing" .. directions[hold.direction]) for index, module in next, modules do if module.opponentNoteHit then module.opponentNoteHit(hold) end end hold:Destroy() holdNotes[index] = nil end end end for index, event in next, events do if event.time <= elapsed then events[index] = nil if event.name == "focuscamera" then local char = tonumber(type(event.vars) == "table" and event.vars.char or event.vars) local cameraPosition = char == 1 and Vector2(stage.camera_opponent[1], stage.camera_opponent[2]):Add(characters.dad.stageCamera :Negate()):Add(Vector2(0, -200)) or char == 2 and Vector2(stage.camera_girlfriend[1], stage.camera_girlfriend[2]):Add(characters.gf.stageCamera :Negate()):Add(Vector2(0, -200)) or char == 0 and Vector2(-stage.camera_boyfriend[1], -stage.camera_boyfriend[2]):Add(characters.bf.stageCamera :Negate()):Add(Vector2(0, -200)) local ease = tween.easing.outQuad local easetime = 1.5 if event.vars and type(event.vars) == "table" then if event.vars.ease and event.vars.ease ~= "CLASSIC" then easetime = event.vars.duration or 1.5 end end cameraTween = tween.new(easetime, render.cameraPosition, { x = cameraPosition.x, y = cameraPosition.y }, ease) elseif event.name == "zoomcamera" then zoomTween = tween.new(event.vars.duration or 32, sharedVars, { defaultZoom = event.vars.zoom }, tween.easing.outExpo) elseif event.name == "playanimation" or event.name == "play animation" then local chars = { dad = "dad", boyfriend = "bf", girlfriend = "gf" } if not characters[chars[event.vars.target] or event.vars.target].animInfo[event.vars.anim] then return end characters[chars[event.vars.target] or event.vars.target]:PlayAnimation(event.vars.anim) elseif event.name == "change character" then local chars = { dad = "dad", boyfriend = "bf", girlfriend = "gf" } characters[chars[event.vars.char] or event.vars.char]:Destroy() characters[chars[event.vars.char] or event.vars.char] = Character(event.vars.to) end for index, module in next, modules do if module.onEvent then module.onEvent(event) end end end end zoom = Lerp(zoom, (stage.defaultZoom or 1) * sharedVars.defaultZoom, .05) uiZoom = Lerp(uiZoom, 1, .05) iconZoom = Lerp(iconZoom, 1, .1) for index, splash in next, splashes do if splash.animation and splash.ended then splash:StopAnimation() end end for index, receptor in next, receptors do if receptor.ended then receptor:PlayAnimation(string.format("arrow%s", directions[index]), 24, true) receptorAnims[index] = "arrow" end receptor.extraOffset = receptorOffsets[receptorAnims[index]] end for index, holdCover in next, holdCovers do if holdCover.ended then if holdCover.ende then holdCover.alpha = 0 else holdCover:PlayAnimation(string.format("holdCoverEnd%s", covers[index]), 24, false) holdCover.ende = true end end end if health <= 0 then die() elseif health > 2 then health = 2 end if inst and not inst:isPlaying() and not counting then quit(true) end ::continue:: end local mainCanvas local uiCanvas function state.draw() love.graphics.setCanvas(mainCanvas) love.graphics.clear() render.drawSprites() for i, module in next, modules do if module.drawBelowUI then module.drawBelowUI() --mainly for cutscenes i guess end end love.graphics.setCanvas(uiCanvas) love.graphics.clear() render.drawUI() -- HEALTH BAR if playing and ui.health then love.graphics.setColor(characters.dad and characters.dad.colors[1] / 255 or 0, characters.dad and characters.dad.colors[2] / 255 or 1, characters.dad and characters.dad.colors[1] / 255 or 0 ) love.graphics.rectangle("fill", 560 + render.offset.x, (settings.Downscroll and 30 or 960) + render.offset.y, 800, 30) love.graphics.setColor(characters.bf.colors[1] / 255, characters.bf.colors[2] / 255, characters.bf.colors[3] / 255) love.graphics.rectangle("fill", (1360 - health * 400) + render.offset.x, (settings.Downscroll and 30 or 960) + render.offset.y, health * 400, 30) love.graphics.setColor(0, 0, 0) love.graphics.rectangle("line", 560 + render.offset.x, (settings.Downscroll and 30 or 960) + render.offset.y, 800, 30) love.graphics.setColor(1, 1, 1) if ui.healthIcons then love.graphics.draw(icons.bf.image, health > .2 and icons.bf.alive or icons.bf.dead, (1510 - health * 400) + render.offset.x - (75 - 150 * iconZoom / 2), (settings.Downscroll and 0 or 930) + render.offset.y + (75 - 150 * iconZoom / 2), 0, -iconZoom, iconZoom) if characters.dad then love.graphics.draw(icons.dad.image, health < 1.8 and icons.dad.alive or icons.dad.dead, (1210 - health * 400) + render.offset.x + (75 - 150 * iconZoom / 2), (settings.Downscroll and 0 or 930) + render.offset.y + (75 - 150 * iconZoom / 2), 0, iconZoom, iconZoom) end end end if playing and ui.score then love.graphics.print( { { 0, 0, 0, 1 }, string.format("Score: %s Accuracy: %s", score, tostring(accuracy):sub(1, 5)) }, big, 760, settings.Downscroll and 70 or 940) end for i, module in next, modules do if module.onDraw then module.onDraw() --mainly for cutscenes i guess end end love.graphics.setCanvas() if sharedVars.globalShader then love.graphics.setShader(sharedVars.globalShader) end love.graphics.draw(mainCanvas, (love.graphics.getWidth() - (love.graphics.getWidth() * zoom * mustZoom)) / 2, (love.graphics.getHeight() - love.graphics.getHeight() * zoom * mustZoom) / 2, 0, (love.graphics.getWidth() / sharedVars.canvasSize.x) * zoom * mustZoom, (love.graphics.getHeight() / sharedVars.canvasSize.y) * zoom * mustZoom ) love.graphics.draw(uiCanvas, (love.graphics.getWidth() - (love.graphics.getWidth() * uiZoom * mustZoom)) / 2, (love.graphics.getHeight() - love.graphics.getHeight() * uiZoom * mustZoom) / 2, 0, (love.graphics.getWidth() / sharedVars.canvasSize.x) * uiZoom * mustZoom, (love.graphics.getHeight() / sharedVars.canvasSize.y) * uiZoom * mustZoom ) love.graphics.setShader() love.graphics.print({ { 0, 0, 0, 1 }, string.format("FPS: %s", love.timer.getFPS()) }, font) end function state.load() love.window.setTitle(string.format("TaggedEngine - %s", metadata.songName)) settings = json.parse(files.read_file(data .. "/Settings.json")) if not settings then error("Failed to load settings") end local stageScript = string.format("stages/%s.lua", metadata.playData.stage) local stageModule local opened = files.read_file(stageScript) if opened then stageModule = require(string.format("stages.%s", metadata.playData.stage)) modules[#modules + 1] = stageModule end local songScriptExists = love.filesystem.getInfo(string.format("charts/%s/script.lua", songName)) print(songScriptExists) if songScriptExists then print("hey") local songScript = require(string.format("charts.%s.script", songName)) modules[#modules + 1] = songScript end -- GF first so she is below other chars if metadata.playData.characters.girlfriend then characters.gf = Character(metadata.playData.characters.girlfriend) characters.gf:PlayAnimation("danceLeft") characters.gf.sprite.layer = 0 if not characters.gf.hasStagePosition then characters.gf.stagePosition = Vector2(stage.girlfriend[1], stage.girlfriend[2]) end end characters.bf = Character(metadata.playData.characters.player) characters.bf:PlayAnimation("idle") characters.bf.sprite.layer = 1 if not characters.bf.hasStagePosition then characters.bf.stagePosition = Vector2(stage.boyfriend[1], stage.boyfriend[2]) end local image = love.graphics.newImage(string.format("images/icons/icon-%s.png", characters.bf.icon)) icons.bf = { image = image, alive = love.graphics.newQuad(0, 0, 150, 150, image), dead = love.graphics.newQuad( 150, 0, 150, 150, image) } if metadata.playData.characters.opponent and metadata.playData.characters.opponent ~= "none" then -- you can have no player2 but always player1 characters.dad = Character(metadata.playData.characters.opponent) characters.dad:PlayAnimation(characters.dad.animInfo.idle and "idle" or "danceLeft") characters.dad.sprite.layer = 1 if not characters.dad.hasStagePosition then characters.dad.stagePosition = Vector2(stage.opponent[1], stage.opponent[2]) end local image = love.graphics.newImage(string.format("images/icons/icon-%s.png", characters.dad.icon)) icons.dad = { image = image, alive = love.graphics.newQuad(0, 0, 150, 150, image), dead = love.graphics .newQuad(150, 0, 150, 150, image) } end conductor.stepCrochet = conductor:calculateCrochet(metadata.timeChanges[1].bpm) / 4 local noteSkin = chart.noteSkin or "NOTE_assets" --will do this sometiems local noteSplash = chart.splashSkin or "noteSplashes" for i = 0, 3 do local receptor = Sprite("sprites/NOTE_assets.png", "sprites/NOTE_assets.xml") receptor:PlayAnimation("arrow" .. directions[i + 1], 24, true) receptor.layer = 9 receptor.position = Vector2(1200 + (158 * i), settings.Downscroll and 860 or 0) receptor.ui = true -- So it doesnt move with the camera. -- for index, anim in next, receptor.quads do -- for index, quad in next, anim do -- quad.offset = quad.offset -- end -- end receptors[i + 1] = receptor local splash = Sprite("sprites/noteSplashes.png", "sprites/noteSplashes.json") splash.layer = 11 splash.position = Vector2(1140 + (158 * i), settings.Downscroll and 800 or -100) splash.ui = true splashes[i + 1] = splash local noteCover = Sprite(string.format("sprites/holdCovers/holdCover%s.png", covers[i + 1]), string.format("sprites/holdCovers/holdCover%s.xml", covers[i + 1])) noteCover.layer = 12 noteCover.position = Vector2(1120 + (158 * i), settings.Downscroll and 780 or -100) noteCover.ui = true noteCover.starterFrame = 1 holdCovers[i + 1] = noteCover end for i = 0, 3 do -- opponent receptors, purely graphics local receptor = Sprite("sprites/NOTE_assets.png", "sprites/NOTE_assets.xml") receptor:PlayAnimation("arrow" .. directions[i + 1], 24, true) receptor.position = Vector2(100 + 158 * i, settings.Downscroll and 860 or 0) receptor.layer = 9 receptor.ui = true -- So it doesnt move with the camera. opponentReceptors[i + 1] = receptor end -- Load the notes AFTER the receptors to make sure they are always above them for index, note in next, chart.notes[songDifficulty] do local newNote = Note(note) for index, module in next, modules do if module.processNote then module.processNote(newNote) end end unspawnedNotes[#unspawnedNotes + 1] = newNote if note.l and note.l > 0 then local length = math.floor(note.l / conductor.stepCrochet) local ogT = note.t for i = 0, length - .1, .1 do note.t = ogT + i * conductor.stepCrochet local newHold = Note(note, true) for index, module in next, modules do if module.processNote then module.processNote(newHold) end end unspawnedHoldNotes[#unspawnedHoldNotes + 1] = newHold end note.t = ogT + length * conductor.stepCrochet local newHold = Note(note, true, true) unspawnedHoldNotes[#unspawnedHoldNotes + 1] = newHold newHold.holdEnd = true if settings.Downscroll then newHold.flipY = true end newHold.speed = speed for index, module in next, modules do if module.processNote then module.processNote(newHold) end end end end if chart.events then for index, event in next, chart.events do local newEvent = { time = event.t, name = string.lower(event.e), vars = event.v } logging.log(newEvent.name) events[#events + 1] = newEvent if newEvent.name == "change character" then character.preload(newEvent.vars.to) end end end keyBinds = settings.Keybinds offset = settings.Offset state.loaded = true for i, module in next, modules do if type(module) ~= "boolean" then if module.onCreate then module.onCreate(metadata.songName) end end end local canvasSize = sharedVars.canvasSize or Vector2(3840, 2160) mainCanvas = love.graphics.newCanvas(canvasSize.x, canvasSize.y) uiCanvas = love.graphics.newCanvas(canvasSize.x, canvasSize.y) local screenSize = sharedVars.screenSize or Vector2(1280, 720) love.window.setMode(screenSize.x, screenSize.y, { fullscreen = false, resizable = false }) render.offset = Vector2(960, 540) end function state.finish() if sharedVars.canStart then sharedVars.canStart = false -- already started -- inst:play() -- if voices then -- voices:play() -- end -- while not inst:isPlaying() do -- end --waiting till the song actually plays. elapsed = 0 playing = true --countdown now startTime = socket.gettime() end render.cameraPosition = Vector2(stage.camera_girlfriend[1], stage.camera_girlfriend[2]):Add(characters.gf .stageCamera:Negate()):Add(Vector2(0, -200)) end function state.keypressed(key, un, is) if key == "space" then if playing then paused = not paused if paused then inst:pause() if voices then voices:pause() end pauseStart = socket.gettime() * 1000 for index, module in next, modules do if module.onPause then module.onPause(elapsed) end end else inst:play() inst:setLooping(false) if voices then voices:play() end for index, module in next, modules do if module.onUnpause then module.onUnpause((socket.gettime() * 1000 - pauseStart), elapsed) end end pauseTime = pauseTime + (socket.gettime() * 1000 - pauseStart) local currentTime = socket.gettime() elapsed = (currentTime - startTime) * 1000 - pauseTime - cdLength inst:seek((elapsed + offset) / 1000 > 0 and (elapsed + offset) / 1000 or 0, "seconds") if voices then voices:seek((elapsed + offset) / 1000 > 0 and (elapsed + offset) / 1000 or 0, "seconds") end end elseif dead then gameOverEnd:play() gameOver:stop() loss:stop() restart = true deadBF:PlayAnimation("deathConfirm") 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 == "9" then for index, note in next, notes do if note.position < 150000 then notes[index] = nil note = nil end end for index, note in next, holdNotes do if note.position < 150000 then holdNotes[index] = nil note = nil end end for index, note in next, unspawnedHoldNotes do if note.position < 150000 then unspawnedHoldNotes[index] = nil note = nil end end for index, note in next, unspawnedNotes do if note.position < 150000 then unspawnedNotes[index] = nil note = nil end end startTime = socket.gettime() - 150 inst:seek(150) voices:seek(150) end end return state end return state -- vscode doesnt like this but idrc