diff --git a/buildnumber.txt b/buildnumber.txt index a3ff295b..9e7afa3a 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -2286 \ No newline at end of file +2313 \ No newline at end of file diff --git a/source/funkin/chart/Chart.hx b/source/funkin/chart/Chart.hx index c7c28ecc..a088a840 100644 --- a/source/funkin/chart/Chart.hx +++ b/source/funkin/chart/Chart.hx @@ -6,17 +6,23 @@ import openfl.utils.Assets; class Chart { public static function parse(songName:String, difficulty:String = "normal"):ChartData { + var chartPath = Paths.chart(songName, difficulty); var base:ChartData = { strumLines: [], + noteTypes: [], events: [], meta: { name: songName, - bpm: 0 + bpm: 0, + beatsPerMesure: 4, + stepsPerBeat: 4 }, + scrollSpeed: 2, stage: "stage", - codenameChart: true + codenameChart: true, + fromMods: Paths.assetsTree.existsSpecific(chartPath, "TEXT", MODS) }; - var chartPath = Paths.chart(songName, difficulty); + if (!Assets.exists(chartPath)) { Logs.trace('Chart for song ${songName} ($difficulty) at "$chartPath" was not found.', ERROR, RED); return base; @@ -39,6 +45,7 @@ class Chart { data = field; } + base.scrollSpeed = data.speed; base.stage = data.stage; base.meta.name = data.song; base.meta.bpm = data.bpm; @@ -46,13 +53,25 @@ class Chart { base.strumLines.push({ characters: [data.player2], opponent: true, + position: "dad", notes: [] }); base.strumLines.push({ characters: [data.player1], opponent: false, + position: "boyfriend", notes: [] }); + if (data.gf != "none") { + base.strumLines.push({ + characters: [data.gf != null ? data.gf : "gf"], + opponent: true, + position: "girlfriend", + notes: [], + visible: false, + strumLinePos: 0.5 + }); + } var camFocusedBF:Bool = false; var beatsPerMesure:Float = data.beatsPerMesure.getDefault(4); @@ -61,10 +80,12 @@ class Chart { var curCrochet:Float = ((60 / curBPM) * 1000); if (data.notes != null) for(section in data.notes) { - if (section == null) continue; // Yoshi Engine charts crash fix + if (section == null) { + curTime += curCrochet * beatsPerMesure; + continue; // Yoshi Engine charts crash fix + } if (camFocusedBF != (camFocusedBF = section.mustHitSection)) { - // TODO: auto-add event for cam movement base.events.push({ time: curTime, type: CAM_MOVEMENT, @@ -73,10 +94,22 @@ class Chart { } if (section.sectionNotes != null) for(note in section.sectionNotes) { + if (note[1] < 0) continue; + var daStrumTime:Float = note[0]; var daNoteData:Int = Std.int(note[1] % 8); var daNoteType:Int = Std.int(note[1] / 8); var gottaHitNote:Bool = daNoteData >= 4 ? !section.mustHitSection : section.mustHitSection; + + if (note.length > 2) { + if (note[3] is Int) + daNoteType = addNoteType(base, data.noteTypes[Std.int(note[3])-1]); + else if (note[3] is String) + daNoteType = addNoteType(base, note[3]); + } else { + daNoteType = addNoteType(base, data.noteTypes[daNoteType-1]); + } + base.strumLines[gottaHitNote ? 1 : 0].notes.push({ time: daStrumTime, @@ -88,7 +121,7 @@ class Chart { if (section.changeBPM && section.bpm != curBPM) { // TODO: BPM CHANGE EVENT - curCrochet = ((60 / section.bpm) * 1000); + curCrochet = ((60 / (curBPM = section.bpm)) * 1000); } curTime += curCrochet * beatsPerMesure; @@ -96,6 +129,19 @@ class Chart { } return base; } + + private static function addNoteType(chart:ChartData, noteTypeName:String):Int { + switch(noteTypeName.trim()) { + case "Default Note" | null | "": + return 0; + default: + var index = chart.noteTypes.indexOf(noteTypeName); + if (index > -1) + return index+1; + chart.noteTypes.push(noteTypeName); + return chart.noteTypes.length; + } + } } typedef ChartData = { @@ -104,11 +150,17 @@ typedef ChartData = { public var meta:ChartMetaData; public var codenameChart:Bool; public var stage:String; + public var scrollSpeed:Float; + public var fromMods:Bool; + public var noteTypes:Array; } typedef ChartMetaData = { public var name:String; public var bpm:Float; + public var ?beatsPerMesure:Float; + public var ?stepsPerBeat:Float; + public var ?needsVoices:Bool; public var ?icon:String; public var ?color:Dynamic; public var ?difficulties:Array; @@ -120,7 +172,9 @@ typedef ChartStrumLine = { var characters:Array; var opponent:Bool; var notes:Array; + var position:String; var ?strumLinePos:Float; // 0.25 = default opponent pos, 0.75 = default boyfriend pos + var ?visible:Null; } typedef ChartNote = { diff --git a/source/funkin/editors/ChartingState.hx b/source/funkin/editors/ChartingState.hx deleted file mode 100644 index b9b1c3fe..00000000 --- a/source/funkin/editors/ChartingState.hx +++ /dev/null @@ -1,1006 +0,0 @@ -package funkin.editors; - -import funkin.system.Conductor.BPMChangeEvent; -import funkin.system.Section.SwagSection; -import funkin.system.Conductor; -import funkin.system.Song.SwagSong; -import funkin.system.Song; -import flixel.FlxG; -import flixel.FlxSprite; -import flixel.addons.display.FlxGridOverlay; -import flixel.addons.ui.FlxInputText; -import flixel.addons.ui.FlxUI9SliceSprite; -import flixel.addons.ui.FlxUI; -import flixel.addons.ui.FlxUICheckBox; -import flixel.addons.ui.FlxUIDropDownMenu; -import flixel.addons.ui.FlxUIInputText; -import flixel.addons.ui.FlxUINumericStepper; -import flixel.addons.ui.FlxUITabMenu; -import flixel.addons.ui.FlxUITooltip.FlxUITooltipStyle; -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.group.FlxGroup; -import flixel.math.FlxMath; -import flixel.math.FlxPoint; -import flixel.system.FlxSound; -import flixel.text.FlxText; -import flixel.ui.FlxButton; -import flixel.ui.FlxSpriteButton; -import flixel.util.FlxColor; -import haxe.Json; -import haxe.io.Path; -import lime.utils.Assets; -import openfl.events.Event; -import openfl.events.IOErrorEvent; -import openfl.media.Sound; -import openfl.net.FileReference; -import openfl.utils.ByteArray; -import funkin.system.Conductor; -import funkin.game.HealthIcon; - -import funkin.game.Note; - -using StringTools; - -class ChartingState extends MusicBeatState -{ - var _file:FileReference; - - var UI_box:FlxUITabMenu; - - /** - * Array of notes showing when each section STARTS in STEPS - * Usually rounded up?? - */ - var curSection:Int = 0; - - public static var lastSection:Int = 0; - - var bpmTxt:FlxText; - - var strumLine:FlxSprite; - - var GRID_SIZE:Int = 40; - - var dummyArrow:FlxSprite; - - var curRenderedNotes:FlxTypedGroup; - var curRenderedSustains:FlxTypedGroup; - - var gridBG:FlxSprite; - - var _song:SwagSong; - - var typingShit:FlxInputText; - /* - * WILL BE THE CURRENT / LAST PLACED NOTE - **/ - var curSelectedNote:Array; - - var characterList:Array; - - var tempBpm:Float = 0; - - var vocals:FlxSound; - - var leftIcon:HealthIcon; - var rightIcon:HealthIcon; - - override function create() - { - curSection = lastSection; - characterList = [for(e in Paths.getFolderContent('data/characters/')) if (Path.extension(e).toLowerCase() == "xml") e.substr(0, e.length-4)]; - - gridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 8, GRID_SIZE * 16); - add(gridBG); - - leftIcon = new HealthIcon('bf'); - rightIcon = new HealthIcon('dad'); - leftIcon.scrollFactor.set(1, 1); - rightIcon.scrollFactor.set(1, 1); - - leftIcon.setGraphicSize(0, 45); - rightIcon.setGraphicSize(0, 45); - - add(leftIcon); - add(rightIcon); - - leftIcon.setPosition(0, -100); - rightIcon.setPosition(gridBG.width / 2, -100); - - var gridBlackLine:FlxSprite = new FlxSprite(gridBG.x + gridBG.width / 2).makeGraphic(2, Std.int(gridBG.height), FlxColor.BLACK); - add(gridBlackLine); - - curRenderedNotes = new FlxTypedGroup(); - curRenderedSustains = new FlxTypedGroup(); - - if (PlayState.SONG != null) - _song = PlayState.SONG; - else - { - _song = { - song: 'Test', - notes: [], - noteTypes: [], - bpm: 150, - needsVoices: true, - player1: 'bf', - player2: 'dad', - speed: 1, - validScore: false, - stage: 'stage', - gf: 'gf' - }; - } - - FlxG.mouse.visible = true; - - tempBpm = _song.bpm; - - addSection(); - - // sections = _song.notes; - - updateGrid(); - - loadSong(_song.song); - Conductor.changeBPM(_song.bpm); - Conductor.mapBPMChanges(_song); - - bpmTxt = new FlxText(1000, 50, 0, "", 16); - bpmTxt.scrollFactor.set(); - add(bpmTxt); - - strumLine = new FlxSprite(0, 50).makeGraphic(Std.int(FlxG.width / 2), 4); - add(strumLine); - - dummyArrow = new FlxSprite().makeGraphic(GRID_SIZE, GRID_SIZE); - add(dummyArrow); - - var tabs = [ - {name: "Song", label: 'Song'}, - {name: "Section", label: 'Section'}, - {name: "Note", label: 'Note'} - ]; - - UI_box = new FlxUITabMenu(null, tabs, true); - - UI_box.resize(300, 400); - UI_box.x = FlxG.width / 2; - UI_box.y = 20; - add(UI_box); - - addSongUI(); - addSectionUI(); - addNoteUI(); - - add(curRenderedNotes); - add(curRenderedSustains); - - super.create(); - } - - function addSongUI():Void - { - var UI_songTitle = new FlxUIInputText(10, 10, 70, _song.song, 8); - typingShit = UI_songTitle; - - var check_voices = new FlxUICheckBox(10, 25, null, null, "Has voice track", 100); - check_voices.checked = _song.needsVoices; - // _song.needsVoices = check_voices.checked; - check_voices.callback = function() - { - _song.needsVoices = check_voices.checked; - trace('CHECKED!'); - }; - - var check_mute_inst = new FlxUICheckBox(10, 200, null, null, "Mute Instrumental (in editor)", 100); - check_mute_inst.checked = false; - check_mute_inst.callback = function() - { - var vol:Float = 1; - - if (check_mute_inst.checked) - vol = 0; - - FlxG.sound.music.volume = vol; - }; - - var saveButton:FlxButton = new FlxButton(110, 8, "Save", function() - { - saveLevel(); - }); - - var reloadSong:FlxButton = new FlxButton(saveButton.x + saveButton.width + 10, saveButton.y, "Reload Audio", function() - { - loadSong(_song.song); - }); - - var reloadSongJson:FlxButton = new FlxButton(reloadSong.x, saveButton.y + 30, "Reload JSON", function() - { - loadJson(_song.song.toLowerCase()); - }); - - var loadAutosaveBtn:FlxButton = new FlxButton(reloadSongJson.x, reloadSongJson.y + 30, 'load autosave', loadAutosave); - - var stepperSpeed:FlxUINumericStepper = new FlxUINumericStepper(10, 80, 0.1, 1, 0.1, 10, 1); - stepperSpeed.value = _song.speed; - stepperSpeed.name = 'song_speed'; - - var stepperBPM:FlxUINumericStepper = new FlxUINumericStepper(10, 65, 1, 1, 1, 339, 0); - stepperBPM.value = Conductor.bpm; - stepperBPM.name = 'song_bpm'; - - var player1DropDown = new FlxUIDropDownMenu(10, 100, FlxUIDropDownMenu.makeStrIdLabelArray(characterList, true), function(character:String) - { - _song.player1 = characterList[Std.parseInt(character)]; - }); - player1DropDown.selectedLabel = _song.player1; - - var player2DropDown = new FlxUIDropDownMenu(140, 100, FlxUIDropDownMenu.makeStrIdLabelArray(characterList, true), function(character:String) - { - _song.player2 = characterList[Std.parseInt(character)]; - }); - - player2DropDown.selectedLabel = _song.player2; - - var tab_group_song = new FlxUI(null, UI_box); - tab_group_song.name = "Song"; - tab_group_song.add(UI_songTitle); - - tab_group_song.add(check_voices); - tab_group_song.add(check_mute_inst); - tab_group_song.add(saveButton); - tab_group_song.add(reloadSong); - tab_group_song.add(reloadSongJson); - tab_group_song.add(loadAutosaveBtn); - tab_group_song.add(stepperBPM); - tab_group_song.add(stepperSpeed); - tab_group_song.add(player1DropDown); - tab_group_song.add(player2DropDown); - - UI_box.addGroup(tab_group_song); - UI_box.scrollFactor.set(); - - FlxG.camera.follow(strumLine); - } - - var stepperLength:FlxUINumericStepper; - var check_mustHitSection:FlxUICheckBox; - var check_changeBPM:FlxUICheckBox; - var stepperSectionBPM:FlxUINumericStepper; - var check_altAnim:FlxUICheckBox; - - function addSectionUI():Void - { - var tab_group_section = new FlxUI(null, UI_box); - tab_group_section.name = 'Section'; - - stepperLength = new FlxUINumericStepper(10, 10, 4, 0, 0, 999, 0); - stepperLength.value = _song.notes[curSection].lengthInSteps; - stepperLength.name = "section_length"; - - stepperSectionBPM = new FlxUINumericStepper(10, 80, 1, Conductor.bpm, 0, 999, 0); - stepperSectionBPM.value = Conductor.bpm; - stepperSectionBPM.name = 'section_bpm'; - - var stepperCopy:FlxUINumericStepper = new FlxUINumericStepper(110, 130, 1, 1, -999, 999, 0); - - var copyButton:FlxButton = new FlxButton(10, 130, "Copy last section", function() - { - copySection(Std.int(stepperCopy.value)); - }); - - var clearSectionButton:FlxButton = new FlxButton(10, 150, "Clear", clearSection); - - var swapSection:FlxButton = new FlxButton(10, 170, "Swap section", function() - { - for (i in 0..._song.notes[curSection].sectionNotes.length) - { - var note = _song.notes[curSection].sectionNotes[i]; - note[1] = (note[1] + 4) % 8; - _song.notes[curSection].sectionNotes[i] = note; - updateGrid(); - } - }); - - check_mustHitSection = new FlxUICheckBox(10, 30, null, null, "Must hit section", 100); - check_mustHitSection.name = 'check_mustHit'; - check_mustHitSection.checked = true; - // _song.needsVoices = check_mustHit.checked; - - check_altAnim = new FlxUICheckBox(10, 400, null, null, "Alt Animation", 100); - check_altAnim.name = 'check_altAnim'; - - check_changeBPM = new FlxUICheckBox(10, 60, null, null, 'Change BPM', 100); - check_changeBPM.name = 'check_changeBPM'; - - tab_group_section.add(stepperLength); - tab_group_section.add(stepperSectionBPM); - tab_group_section.add(stepperCopy); - tab_group_section.add(check_mustHitSection); - tab_group_section.add(check_altAnim); - tab_group_section.add(check_changeBPM); - tab_group_section.add(copyButton); - tab_group_section.add(clearSectionButton); - tab_group_section.add(swapSection); - - UI_box.addGroup(tab_group_section); - } - - var stepperSusLength:FlxUINumericStepper; - - function addNoteUI():Void - { - var tab_group_note = new FlxUI(null, UI_box); - tab_group_note.name = 'Note'; - - stepperSusLength = new FlxUINumericStepper(10, 10, Conductor.stepCrochet / 2, 0, 0, Conductor.stepCrochet * 16); - stepperSusLength.value = 0; - stepperSusLength.name = 'note_susLength'; - - var applyLength:FlxButton = new FlxButton(100, 10, 'Apply'); - - tab_group_note.add(stepperSusLength); - tab_group_note.add(applyLength); - - UI_box.addGroup(tab_group_note); - } - - function loadSong(daSong:String):Void - { - if (FlxG.sound.music != null) - { - FlxG.sound.music.stop(); - // vocals.stop(); - } - - FlxG.sound.playMusic(Paths.inst(daSong), 0.6); - - // WONT WORK FOR TUTORIAL OR TEST SONG!!! REDO LATER - vocals = new FlxSound().loadEmbedded(Paths.voices(daSong)); - FlxG.sound.list.add(vocals); - - FlxG.sound.music.pause(); - vocals.pause(); - - FlxG.sound.music.onComplete = function() - { - vocals.pause(); - vocals.time = 0; - FlxG.sound.music.pause(); - FlxG.sound.music.time = 0; - changeSection(); - }; - } - - override function getEvent(id:String, sender:Dynamic, data:Dynamic, ?params:Array) - { - if (id == FlxUICheckBox.CLICK_EVENT) - { - var check:FlxUICheckBox = cast sender; - var label = check.getLabel().text; - switch (label) - { - case 'Must hit section': - _song.notes[curSection].mustHitSection = check.checked; - - updateHeads(); - - case 'Change BPM': - _song.notes[curSection].changeBPM = check.checked; - FlxG.log.add('changed bpm shit'); - case "Alt Animation": - _song.notes[curSection].altAnim = check.checked; - } - } - else if (id == FlxUINumericStepper.CHANGE_EVENT && (sender is FlxUINumericStepper)) - { - var nums:FlxUINumericStepper = cast sender; - var wname = nums.name; - FlxG.log.add(wname); - if (wname == 'section_length') - { - _song.notes[curSection].lengthInSteps = Std.int(nums.value); - updateGrid(); - } - else if (wname == 'song_speed') - { - _song.speed = nums.value; - } - else if (wname == 'song_bpm') - { - tempBpm = Std.int(nums.value); - Conductor.mapBPMChanges(_song); - Conductor.changeBPM(Std.int(nums.value)); - } - else if (wname == 'note_susLength') - { - curSelectedNote[2] = nums.value; - updateGrid(); - } - else if (wname == 'section_bpm') - { - _song.notes[curSection].bpm = Std.int(nums.value); - updateGrid(); - } - } - - // FlxG.log.add(id + " WEED " + sender + " WEED " + data + " WEED " + params); - } - - var updatedSection:Bool = false; - - /* this function got owned LOL - function lengthBpmBullshit():Float - { - if (_song.notes[curSection].changeBPM) - return _song.notes[curSection].lengthInSteps * (_song.notes[curSection].bpm / _song.bpm); - else - return _song.notes[curSection].lengthInSteps; - }*/ - function sectionStartTime():Float - { - var daBPM:Float = _song.bpm; - var daPos:Float = 0; - for (i in 0...curSection) - { - if (_song.notes[i].changeBPM) - { - daBPM = _song.notes[i].bpm; - } - daPos += 4 * (1000 * 60 / daBPM); - } - return daPos; - } - - override function update(elapsed:Float) - { - Conductor.songPosition = FlxG.sound.music.time; - _song.song = typingShit.text; - - strumLine.y = getYfromStrum((Conductor.songPosition - sectionStartTime()) % (Conductor.stepCrochet * _song.notes[curSection].lengthInSteps)); - - if (curBeat % 4 == 0 && curStep >= 16 * (curSection + 1)) - { - trace(curStep); - trace((_song.notes[curSection].lengthInSteps) * (curSection + 1)); - trace('DUMBSHIT'); - - if (_song.notes[curSection + 1] == null) - { - addSection(); - } - - changeSection(curSection + 1, false); - } - - FlxG.watch.addQuick('daBeat', curBeat); - FlxG.watch.addQuick('daStep', curStep); - - if (FlxG.mouse.justPressed) - { - if (FlxG.mouse.overlaps(curRenderedNotes)) - { - curRenderedNotes.forEach(function(note:Note) - { - if (FlxG.mouse.overlaps(note)) - { - if (FlxG.keys.pressed.CONTROL) - { - selectNote(note); - } - else - { - trace('tryin to delete note...'); - deleteNote(note); - } - } - }); - } - else - { - if (FlxG.mouse.x > gridBG.x - && FlxG.mouse.x < gridBG.x + gridBG.width - && FlxG.mouse.y > gridBG.y - && FlxG.mouse.y < gridBG.y + (GRID_SIZE * _song.notes[curSection].lengthInSteps)) - { - FlxG.log.add('added note'); - addNote(); - } - } - } - - if (FlxG.mouse.x > gridBG.x - && FlxG.mouse.x < gridBG.x + gridBG.width - && FlxG.mouse.y > gridBG.y - && FlxG.mouse.y < gridBG.y + (GRID_SIZE * _song.notes[curSection].lengthInSteps)) - { - dummyArrow.x = Math.floor(FlxG.mouse.x / GRID_SIZE) * GRID_SIZE; - if (FlxG.keys.pressed.SHIFT) - dummyArrow.y = FlxG.mouse.y; - else - dummyArrow.y = Math.floor(FlxG.mouse.y / GRID_SIZE) * GRID_SIZE; - } - - if (FlxG.keys.justPressed.ENTER) - { - lastSection = curSection; - - PlayState.SONG = _song; - FlxG.sound.music.stop(); - vocals.stop(); - FlxG.switchState(new PlayState()); - } - - if (FlxG.keys.justPressed.E) - { - changeNoteSustain(Conductor.stepCrochet); - } - if (FlxG.keys.justPressed.Q) - { - changeNoteSustain(-Conductor.stepCrochet); - } - - if (FlxG.keys.justPressed.TAB) - { - if (FlxG.keys.pressed.SHIFT) - { - UI_box.selected_tab -= 1; - if (UI_box.selected_tab < 0) - UI_box.selected_tab = 2; - } - else - { - UI_box.selected_tab += 1; - if (UI_box.selected_tab >= 3) - UI_box.selected_tab = 0; - } - } - - if (!typingShit.hasFocus) - { - if (FlxG.keys.justPressed.SPACE) - { - if (FlxG.sound.music.playing) - { - FlxG.sound.music.pause(); - vocals.pause(); - } - else - { - vocals.play(); - FlxG.sound.music.play(); - } - } - - if (FlxG.keys.justPressed.R) - { - if (FlxG.keys.pressed.SHIFT) - resetSection(true); - else - resetSection(); - } - - if (FlxG.mouse.wheel != 0) - { - FlxG.sound.music.pause(); - vocals.pause(); - - FlxG.sound.music.time -= (FlxG.mouse.wheel * Conductor.stepCrochet * 0.4); - vocals.time = FlxG.sound.music.time; - } - - if (!FlxG.keys.pressed.SHIFT) - { - if (FlxG.keys.pressed.W || FlxG.keys.pressed.S) - { - FlxG.sound.music.pause(); - vocals.pause(); - - var daTime:Float = 700 * FlxG.elapsed; - - if (FlxG.keys.pressed.W) - { - FlxG.sound.music.time -= daTime; - } - else - FlxG.sound.music.time += daTime; - - vocals.time = FlxG.sound.music.time; - } - } - else - { - if (FlxG.keys.justPressed.W || FlxG.keys.justPressed.S) - { - FlxG.sound.music.pause(); - vocals.pause(); - - var daTime:Float = Conductor.stepCrochet * 2; - - if (FlxG.keys.justPressed.W) - { - FlxG.sound.music.time -= daTime; - } - else - FlxG.sound.music.time += daTime; - - vocals.time = FlxG.sound.music.time; - } - } - } - - _song.bpm = tempBpm; - - /* if (FlxG.keys.justPressed.UP) - Conductor.changeBPM(Conductor.bpm + 1); - if (FlxG.keys.justPressed.DOWN) - Conductor.changeBPM(Conductor.bpm - 1); */ - - var shiftThing:Int = 1; - if (FlxG.keys.pressed.SHIFT) - shiftThing = 4; - if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.D) - changeSection(curSection + shiftThing); - if (FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.A) - changeSection(curSection - shiftThing); - - bpmTxt.text = bpmTxt.text = Std.string(FlxMath.roundDecimal(Conductor.songPosition / 1000, 2)) - + " / " - + Std.string(FlxMath.roundDecimal(FlxG.sound.music.length / 1000, 2)) - + "\nSection: " - + curSection; - super.update(elapsed); - } - - function changeNoteSustain(value:Float):Void - { - if (curSelectedNote != null) - { - if (curSelectedNote[2] != null) - { - curSelectedNote[2] += value; - curSelectedNote[2] = Math.max(curSelectedNote[2], 0); - } - } - - updateNoteUI(); - updateGrid(); - } - - function resetSection(songBeginning:Bool = false):Void - { - updateGrid(); - - FlxG.sound.music.pause(); - vocals.pause(); - - // Basically old shit from changeSection??? - FlxG.sound.music.time = sectionStartTime(); - - if (songBeginning) - { - FlxG.sound.music.time = 0; - curSection = 0; - } - - vocals.time = FlxG.sound.music.time; - - updateGrid(); - updateSectionUI(); - } - - function changeSection(sec:Int = 0, ?updateMusic:Bool = true):Void - { - trace('changing section' + sec); - - if (_song.notes[sec] != null) - { - curSection = sec; - - updateGrid(); - - if (updateMusic) - { - FlxG.sound.music.pause(); - vocals.pause(); - - /*var daNum:Int = 0; - var daLength:Float = 0; - while (daNum <= sec) - { - daLength += lengthBpmBullshit(); - daNum++; - }*/ - - FlxG.sound.music.time = sectionStartTime(); - vocals.time = FlxG.sound.music.time; - } - - updateGrid(); - updateSectionUI(); - } - } - - function copySection(?sectionNum:Int = 1) - { - var daSec = FlxMath.maxInt(curSection, sectionNum); - - for (note in _song.notes[daSec - sectionNum].sectionNotes) - { - var strum = note[0] + Conductor.stepCrochet * (_song.notes[daSec].lengthInSteps * sectionNum); - - var copiedNote:Array = [strum, note[1], note[2]]; - _song.notes[daSec].sectionNotes.push(copiedNote); - } - - updateGrid(); - } - - function updateSectionUI():Void - { - var sec = _song.notes[curSection]; - - stepperLength.value = sec.lengthInSteps; - check_mustHitSection.checked = sec.mustHitSection; - check_altAnim.checked = sec.altAnim; - check_changeBPM.checked = sec.changeBPM; - stepperSectionBPM.value = sec.bpm; - - updateHeads(); - } - - function updateHeads():Void - { - if (check_mustHitSection.checked) - { - leftIcon.animation.play('bf'); - rightIcon.animation.play('dad'); - } - else - { - leftIcon.animation.play('dad'); - rightIcon.animation.play('bf'); - } - } - - function updateNoteUI():Void - { - if (curSelectedNote != null) - stepperSusLength.value = curSelectedNote[2]; - } - - function updateGrid():Void - { - while (curRenderedNotes.members.length > 0) - { - curRenderedNotes.remove(curRenderedNotes.members[0], true); - } - - while (curRenderedSustains.members.length > 0) - { - curRenderedSustains.remove(curRenderedSustains.members[0], true); - } - - var sectionInfo:Array = _song.notes[curSection].sectionNotes; - - if (_song.notes[curSection].changeBPM && _song.notes[curSection].bpm > 0) - { - Conductor.changeBPM(_song.notes[curSection].bpm); - FlxG.log.add('CHANGED BPM!'); - } - else - { - // get last bpm - var daBPM:Float = _song.bpm; - for (i in 0...curSection) - if (_song.notes[i].changeBPM) - daBPM = _song.notes[i].bpm; - Conductor.changeBPM(daBPM); - } - - /* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE - for (sec in 0..._song.notes.length) - { - for (notesse in 0..._song.notes[sec].sectionNotes.length) - { - if (_song.notes[sec].sectionNotes[notesse][2] == null) - { - trace('SUS NULL'); - _song.notes[sec].sectionNotes[notesse][2] = 0; - } - } - } - */ - - for (i in sectionInfo) - { - var daNoteInfo = i[1]; - var daStrumTime = i[0]; - var daSus = i[2]; - - var note:Note = new Note(daStrumTime, daNoteInfo % 4); - note.sustainLength = daSus; - note.setGraphicSize(GRID_SIZE, GRID_SIZE); - note.updateHitbox(); - note.x = Math.floor(daNoteInfo * GRID_SIZE); - note.y = Math.floor(getYfromStrum((daStrumTime - sectionStartTime()) % (Conductor.stepCrochet * _song.notes[curSection].lengthInSteps))); - - curRenderedNotes.add(note); - - if (daSus > 0) - { - var sustainVis:FlxSprite = new FlxSprite(note.x + (GRID_SIZE / 2), - note.y + GRID_SIZE).makeGraphic(8, Math.floor(FlxMath.remapToRange(daSus, 0, Conductor.stepCrochet * 16, 0, gridBG.height))); - curRenderedSustains.add(sustainVis); - } - } - } - - private function addSection(lengthInSteps:Int = 16):Void - { - var sec:SwagSection = { - lengthInSteps: lengthInSteps, - bpm: _song.bpm, - changeBPM: false, - mustHitSection: true, - sectionNotes: [], - altAnim: false - }; - - _song.notes.push(sec); - } - - function selectNote(note:Note):Void - { - var swagNum:Int = 0; - - for (i in _song.notes[curSection].sectionNotes) - { - if (i.strumTime == note.strumTime && i.noteData % 4 == note.noteData) - { - curSelectedNote = _song.notes[curSection].sectionNotes[swagNum]; - } - - swagNum += 1; - } - - updateGrid(); - updateNoteUI(); - } - - function deleteNote(note:Note):Void - { - for (i in _song.notes[curSection].sectionNotes) - { - if (i[0] == note.strumTime && i[1] % 4 == note.noteData) - { - FlxG.log.add('FOUND EVIL NUMBER'); - _song.notes[curSection].sectionNotes.remove(i); - } - } - - updateGrid(); - } - - function clearSection():Void - { - _song.notes[curSection].sectionNotes = []; - - updateGrid(); - } - - function clearSong():Void - { - for (daSection in 0..._song.notes.length) - { - _song.notes[daSection].sectionNotes = []; - } - - updateGrid(); - } - - private function addNote():Void - { - var noteStrum = getStrumTime(dummyArrow.y) + sectionStartTime(); - var noteData = Math.floor(FlxG.mouse.x / GRID_SIZE); - var noteSus = 0; - - _song.notes[curSection].sectionNotes.push([noteStrum, noteData, noteSus]); - - curSelectedNote = _song.notes[curSection].sectionNotes[_song.notes[curSection].sectionNotes.length - 1]; - - if (FlxG.keys.pressed.CONTROL) - { - _song.notes[curSection].sectionNotes.push([noteStrum, (noteData + 4) % 8, noteSus]); - } - - trace(noteStrum); - trace(curSection); - - updateGrid(); - updateNoteUI(); - - autosaveSong(); - } - - function getStrumTime(yPos:Float):Float - { - return FlxMath.remapToRange(yPos, gridBG.y, gridBG.y + gridBG.height, 0, 16 * Conductor.stepCrochet); - } - - function getYfromStrum(strumTime:Float):Float - { - return FlxMath.remapToRange(strumTime, 0, 16 * Conductor.stepCrochet, gridBG.y, gridBG.y + gridBG.height); - } - - function loadJson(song:String):Void - { - PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.difficulty); - FlxG.resetState(); - } - - function loadAutosave():Void - { - PlayState.SONG = Song.parseJSONshit(FlxG.save.data.autosave); - FlxG.resetState(); - } - - function autosaveSong():Void - { - FlxG.save.data.autosave = Json.stringify({ - "song": _song - }); - FlxG.save.flush(); - } - - private function saveLevel() - { - var json = { - "song": _song - }; - - var data:String = Json.stringify(json); - - if ((data != null) && (data.length > 0)) - { - _file = new FileReference(); - _file.addEventListener(Event.COMPLETE, onSaveComplete); - _file.addEventListener(Event.CANCEL, onSaveCancel); - _file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file.save(data.trim(), _song.song.toLowerCase() + ".json"); - } - } - - function onSaveComplete(_):Void - { - _file.removeEventListener(Event.COMPLETE, onSaveComplete); - _file.removeEventListener(Event.CANCEL, onSaveCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file = null; - FlxG.log.notice("Successfully saved LEVEL DATA."); - } - - /** - * Called when the save file dialog is cancelled. - */ - function onSaveCancel(_):Void - { - _file.removeEventListener(Event.COMPLETE, onSaveComplete); - _file.removeEventListener(Event.CANCEL, onSaveCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file = null; - } - - /** - * Called if there is an error while saving the gameplay recording. - */ - function onSaveError(_):Void - { - _file.removeEventListener(Event.COMPLETE, onSaveComplete); - _file.removeEventListener(Event.CANCEL, onSaveCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file = null; - FlxG.log.error("Problem saving Level data"); - } -} diff --git a/source/funkin/game/GameOverSubstate.hx b/source/funkin/game/GameOverSubstate.hx index c7afa65b..169b9c3b 100644 --- a/source/funkin/game/GameOverSubstate.hx +++ b/source/funkin/game/GameOverSubstate.hx @@ -57,7 +57,7 @@ class GameOverSubstate extends MusicBeatSubstate lossSFX = FlxG.sound.play(Paths.sound(lossSFXName)); Conductor.changeBPM(100); - DiscordUtil.changePresence('Game Over', PlayState.SONG.song + " (" + PlayState.difficulty + ")"); + DiscordUtil.changePresence('Game Over', PlayState.SONG.meta.name + " (" + PlayState.difficulty + ")"); } override function update(elapsed:Float) diff --git a/source/funkin/game/Note.hx b/source/funkin/game/Note.hx index afe512fa..f6d502f5 100644 --- a/source/funkin/game/Note.hx +++ b/source/funkin/game/Note.hx @@ -1,5 +1,6 @@ package funkin.game; +import funkin.chart.Chart.ChartNote; import openfl.utils.Assets; import flixel.FlxG; import flixel.FlxCamera; @@ -19,17 +20,21 @@ class Note extends FlxSprite public var strumTime:Float = 0; public var mustPress(get, never):Bool; - public var strumLine(get, never):StrumLine; - private function get_strumLine() { - if (PlayState.instance != null) - return PlayState.instance.players[strumLineID]; - return null; + public var strumLine(default, set):StrumLine; + private function set_strumLine(strLine:StrumLine) { + if (this.strumLine != null) { + if (this.strumLine.notes != null) + this.strumLine.notes.remove(this, true); + strLine.notes.add(this); + strLine.notes.sortNotes(); + } + return strumLine = strLine; } private function get_mustPress():Bool { - if (PlayState.instance != null) - return PlayState.instance.players[strumLineID] != null && !PlayState.instance.players[strumLineID].cpu; - return strumLineID == 1; + // if (PlayState.instance != null) + // return PlayState.instance.players[strumLineID] != null && !PlayState.instance.players[strumLineID].cpu; + return false; } public var noteData:Int = 0; public var canBeHit:Bool = false; @@ -72,7 +77,6 @@ class Note extends FlxSprite public var flipSustain:Bool = true; public var noteTypeID:Int = 0; - public var strumLineID:Int = 0; // TO APPLY THOSE ON A SINGLE NOTE public var scrollSpeed:Null = null; @@ -93,27 +97,25 @@ class Note extends FlxSprite public var animSuffix:String = ""; - public function new(strumTime:Float, noteData:Int, noteType:Int = 0, strumLineID:Int = 1, ?prevNote:Note, ?sustainNote:Bool = false, animSuffix:String = "") + public function new(strumLine:StrumLine, noteData:ChartNote, isSustainNote:Bool) { super(); - if (prevNote == null) - prevNote = this; - - this.prevNote = prevNote; - this.strumLineID = strumLineID; - this.noteTypeID = noteType; - isSustainNote = sustainNote; + // TODO: Sustain note + this.prevNote = strumLine.notes.members.last(); + this.noteTypeID = noteData.type.getDefault(0); + this.isSustainNote = false; + this.strumLine = strumLine; x += 50; // MAKE SURE ITS DEFINITELY OFF SCREEN? y -= 2000; - this.strumTime = strumTime; + this.strumTime = noteData.time.getDefault(0); - this.noteData = noteData; + this.noteData = noteData.id.getDefault(0); var customType = Paths.image('game/notes/${this.noteType}'); - var event = EventManager.get(NoteCreationEvent).recycle(this, strumID, this.noteType, noteTypeID, strumLineID, mustPress, Assets.exists(customType) ? 'game/notes/${this.noteType}' : 'game/notes/default', 0.7, animSuffix); + var event = EventManager.get(NoteCreationEvent).recycle(this, strumID, this.noteType, noteTypeID, PlayState.instance.players.indexOf(strumLine), mustPress, Assets.exists(customType) ? 'game/notes/${this.noteType}' : 'game/notes/default', 0.7, animSuffix); if (PlayState.instance != null) event = PlayState.instance.scripts.event("onNoteCreation", event); diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 45779a8c..86dd34e4 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1,7 +1,7 @@ package funkin.game; +import funkin.chart.Chart; import funkin.game.SplashHandler; -import funkin.editors.CharacterEditor; import funkin.scripting.DummyScript; import funkin.menus.StoryMenuState.WeekData; import funkin.ui.FunkinText; @@ -9,49 +9,26 @@ import flixel.group.FlxSpriteGroup; import funkin.options.Options; import funkin.scripting.Script; import flixel.util.FlxDestroyUtil; -import funkin.system.Section.SwagSection; -import funkin.system.Song.SwagSong; import funkin.scripting.ScriptPack; -import funkin.shaders.WiggleEffect.WiggleEffectType; -import flixel.FlxBasic; import flixel.FlxCamera; import flixel.FlxG; -import flixel.FlxGame; import flixel.FlxObject; import flixel.FlxSprite; -import flixel.FlxState; import flixel.FlxSubState; -import flixel.addons.display.FlxGridOverlay; -import flixel.addons.effects.FlxTrail; -import flixel.addons.effects.FlxTrailArea; -import flixel.addons.effects.chainable.FlxEffectSprite; -import flixel.addons.effects.chainable.FlxWaveEffect; import flixel.addons.transition.FlxTransitionableState; -import flixel.graphics.atlas.FlxAtlas; -import flixel.graphics.frames.FlxAtlasFrames; -import flixel.group.FlxGroup.FlxTypedGroup; import flixel.math.FlxMath; import flixel.math.FlxPoint; -import flixel.math.FlxRect; import flixel.system.FlxSound; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.ui.FlxBar; -import flixel.util.FlxCollision; import flixel.util.FlxColor; import flixel.util.FlxSort; -import flixel.util.FlxStringUtil; import flixel.util.FlxTimer; -import haxe.Json; import haxe.io.Path; import lime.utils.Assets; -import openfl.display.BlendMode; -import openfl.display.StageQuality; -import openfl.filters.ShaderFilter; import funkin.system.Conductor; -import funkin.system.Song; -import funkin.editors.ChartingState; import funkin.cutscenes.*; import funkin.menus.*; @@ -60,6 +37,7 @@ import funkin.scripting.events.*; using StringTools; @:access(flixel.text.FlxText.FlxTextFormatRange) +@:access(funkin.game.StrumLine) class PlayState extends MusicBeatState { /** @@ -68,9 +46,9 @@ class PlayState extends MusicBeatState public static var instance:PlayState = null; /** - * SONG METADATA + * SONG DATA (Chart, Metadata) */ - public static var SONG:SwagSong; + public static var SONG:ChartData; /** * Whenever the song is being played in Story Mode. */ @@ -127,6 +105,10 @@ class PlayState extends MusicBeatState * Current Stage. */ public var stage:Stage; + /** + * Current Stage. + */ + public var validScore:Bool = true; /** * Whenever the player can die. */ @@ -164,20 +146,15 @@ class PlayState extends MusicBeatState /** * Dad character */ - public var dad(default, set):Character; + public var dad(get, set):Character; /** * Girlfriend character */ - public var gf:Character; + public var gf(get, set):Character; /** * Boyfriend character */ - public var boyfriend(default, set):Character; - - /** - * Group of all of the notes. Using `forEach` on this group will only loop through the first notes. - */ - public var notes:NoteGroup; + public var boyfriend(get, set):Character; /** * Strum line position @@ -197,19 +174,14 @@ class PlayState extends MusicBeatState * Previous cam follow. */ private static var smoothTransitionData:PlayStateTransitionData; - - /** - * All of the strum line notes. - */ - public var strumLineNotes:FlxTypedGroup; /** * Player strums. */ - public var playerStrums:StrumLine; + public var playerStrums(get, null):StrumLine; /** * CPU strums. */ - public var cpuStrums:StrumLine; + public var cpuStrums(get, null):StrumLine; /** * Shortcut to `playerStrums`. */ @@ -393,7 +365,14 @@ class PlayState extends MusicBeatState * Timer for the start countdown */ public var startTimer:FlxTimer; - + /** + * Remaining events + */ + public var events:Array = []; + /** + * Current camera target. -1 means no automatic camera targetting. + */ + public var curCameraTarget:Int = 0; /** * Length of the intro countdown. */ @@ -420,10 +399,6 @@ class PlayState extends MusicBeatState */ public var canPause:Bool = true; - /** - * Current section (can be `null` when using YoshiCrafter Engine charts). - */ - public var curSection(get, null):SwagSection; /** * Format for the accuracy rating. */ @@ -524,17 +499,17 @@ class PlayState extends MusicBeatState persistentDraw = true; if (SONG == null) - SONG = Song.loadFromJson('tutorial', 'normal'); + SONG = Chart.parse('tutorial', 'normal'); - scrollSpeed = SONG.speed; + scrollSpeed = SONG.scrollSpeed; Conductor.setupSong(SONG); detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay"; // Checks if cutscene files exists - var cutscenePath = Paths.script('songs/${SONG.song.toLowerCase()}/cutscene'); - var endCutscenePath = Paths.script('songs/${SONG.song.toLowerCase()}/cutscene-end'); + var cutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene'); + var endCutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene-end'); if (Assets.exists(cutscenePath)) cutscene = cutscenePath; if (Assets.exists(endCutscenePath)) endCutscene = endCutscenePath; @@ -544,29 +519,7 @@ class PlayState extends MusicBeatState // CHARACTER INITIALISATION #if REGION - // Updating Discord Rich Presence. - dad = new Character(100, 100, SONG.player2); - - - if (dad != null && dad.isGF) { - dad.setPosition(400, 130); - gf = dad; - dad.scrollFactor.set(0.95, 0.95); - } else { - var gfVersion = SONG.gf; - if (gfVersion == null) gfVersion = "gf"; - if (gfVersion != "none") { - gf = new Character(400, 130, gfVersion); - gf.scrollFactor.set(0.95, 0.95); - } - } - - var dadMidpoint = dad.getGraphicMidpoint(); - - comboGroup = new FlxSpriteGroup(FlxG.width * 0.55, (FlxG.height * 0.5) - 60); - - boyfriend = new Character(770, 100, SONG.player1, true); #end // SCRIPTS & STAGE INITIALISATION @@ -574,16 +527,17 @@ class PlayState extends MusicBeatState if (SONG.stage == null || SONG.stage.trim() == "") SONG.stage = "stage"; add(stage = new Stage(SONG.stage)); - var camPos:FlxPoint = new FlxPoint(dadMidpoint.x, dadMidpoint.y); - dadMidpoint.put(); + // var camPos:FlxPoint = new FlxPoint(dadMidpoint.x, dadMidpoint.y); + // dadMidpoint.put(); + var camPos:FlxPoint = new FlxPoint(0, 0); - switch(SONG.song) { + switch(SONG.meta.name) { // case "": // ADD YOUR HARDCODED SCRIPTS HERE! default: for(content in [ - Paths.getFolderContent('songs/${SONG.song.toLowerCase()}/scripts', true, fromMods ? MODS : SOURCE), + Paths.getFolderContent('songs/${SONG.meta.name.toLowerCase()}/scripts', true, fromMods ? MODS : SOURCE), Paths.getFolderContent('data/charts/', true, fromMods ? MODS : SOURCE)]) { for(file in content) { var ext = Path.extension(file).toLowerCase(); @@ -593,18 +547,7 @@ class PlayState extends MusicBeatState } } - scripts.load(); - scripts.call("create"); - - if (gf != null) { - gf.danceOnBeat = false; - add(gf); - } - add(comboGroup); - - if (dad != null) add(dad); - if (boyfriend != null) add(boyfriend); #end // PRECACHING @@ -617,17 +560,48 @@ class PlayState extends MusicBeatState #if REGION strumLine = new FlxObject(0, 50, FlxG.width, 10); strumLine.scrollFactor.set(); - - strumLineNotes = new FlxTypedGroup(); - add(strumLineNotes); - players.push(cpuStrums = new StrumLine([dad], 0.25, !coopMode && !opponentMode, true, coopMode ? controlsP2 : controls)); - players.push(playerStrums = new StrumLine([boyfriend], 0.75, !coopMode && opponentMode, false, coopMode ? controlsP1 : controls)); + generateSong(SONG); + + trace(SONG.noteTypes); + for(noteType in SONG.noteTypes) { + var scriptPath = Paths.script('data/notes/${noteType}'); + if (Assets.exists(scriptPath) && !scripts.contains(scriptPath)) { + var script = Script.create(scriptPath); + if (!(script is DummyScript)) { + scripts.add(script); + script.load(); + } + } + } + + for(strumLine in SONG.strumLines) { + if (strumLine == null) continue; + + var chars = []; + var charPosName:String = strumLine.position == null ? (strumLine.opponent ? "dad" : "boyfriend") : strumLine.position; + if (strumLine.characters != null) for(k=>charName in strumLine.characters) { + var char = new Character(0, 0, charName, !strumLine.opponent); + stage.applyCharStuff(char, charPosName); + char.x += k * 50; + chars.push(char); + } + + var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.opponent ? 0.25 : 0.75) : strumLine.strumLinePos; + var strLine = new StrumLine(chars, strOffset, !coopMode && (strumLine.opponent != opponentMode), strumLine.opponent, coopMode ? (strumLine.opponent ? controlsP2 : controlsP1) : controls); + strLine.cameras = [camHUD]; + players.push(strLine); + if (strumLine.visible != false) + add(strLine); + + strLine.generate(strumLine); + } splashHandler = new SplashHandler(); add(splashHandler); - generateSong(SONG); + scripts.load(); + scripts.call("create"); #end // CAMERA & HUD INITIALISATION @@ -686,7 +660,7 @@ class PlayState extends MusicBeatState missesTxt.alignment = CENTER; accuracyTxt.alignment = LEFT; - for(e in [strumLineNotes, notes, healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt]) + for(e in [healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt]) e.cameras = [camHUD]; #end @@ -709,7 +683,7 @@ class PlayState extends MusicBeatState * This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts. */ public dynamic function updateDiscordPresence() - DiscordUtil.changeSongPresence(detailsText, (paused ? "Paused - " : "") + SONG.song + " (" + difficulty + ")", inst, getIconRPC()); + DiscordUtil.changeSongPresence(detailsText, (paused ? "Paused - " : "") + SONG.meta.name + " (" + difficulty + ")", inst, getIconRPC()); /** * Starts a cutscene. @@ -721,13 +695,13 @@ class PlayState extends MusicBeatState if (callback == null) callback = startCountdown; if (cutsceneScriptPath == null) - cutsceneScriptPath = Paths.script('songs/${SONG.song.toLowerCase()}/${prefix}cutscene'); + cutsceneScriptPath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/${prefix}cutscene'); if (playCutscenes) { inCutscene = true; - var videoCutscene = Paths.video('${PlayState.SONG.song.toLowerCase()}-${prefix}cutscene'); - var videoCutsceneAlt = Paths.file('songs/${PlayState.SONG.song.toLowerCase()}/${prefix}cutscene.mp4'); - var dialogue = Paths.file('songs/${PlayState.SONG.song.toLowerCase()}/${prefix}dialogue.xml'); + var videoCutscene = Paths.video('${PlayState.SONG.meta.name.toLowerCase()}-${prefix}cutscene'); + var videoCutsceneAlt = Paths.file('songs/${PlayState.SONG.meta.name.toLowerCase()}/${prefix}cutscene.mp4'); + var dialogue = Paths.file('songs/${PlayState.SONG.meta.name.toLowerCase()}/${prefix}dialogue.xml'); persistentUpdate = true; if (cutsceneScriptPath != null) { openSubState(new ScriptedCutscene(cutsceneScriptPath, function() { @@ -870,131 +844,35 @@ class PlayState extends MusicBeatState instance = null; } - public function generateNotes(songData:SwagSong) { - // TODO: redo with proper chart parser & codename chart format - - if (songData == null) return; - if (songData.noteTypes == null) songData.noteTypes = []; - - var noteData:Array = songData.notes; - var stepCrochet = Conductor.stepCrochet; - - for (section in noteData) - { - if (section == null || section.sectionNotes == null) continue; - - if (section.changeBPM && section.bpm > 0) - stepCrochet = ((60 / section.bpm) * 250); - - for (songNotes in section.sectionNotes) - { - if (songNotes == null) continue; - var daStrumTime:Float = songNotes[0]; - var daNoteData:Int = Std.int(songNotes[1] % 8); - - if (daNoteData < 0) continue; - var daNoteType:Int = Std.int(songNotes[1] / 8); - var gottaHitNote:Bool = daNoteData >= 4 ? !section.mustHitSection : section.mustHitSection; - - if (songNotes.length > 2) { - if (songNotes[3] is Int) - daNoteType = addNoteType(songData.noteTypes[Std.int(songNotes[3])-1], songNotes[3] == 0); - else if (songNotes[3] is String) - daNoteType = addNoteType(songNotes[3]); - } else { - daNoteType = addNoteType(songData.noteTypes[daNoteType-1], daNoteType == 0); - } - - var swagNote:Note; - if (notes.length > 0) - swagNote = notes.members[Std.int(notes.members.length - 1)]; - else - swagNote = null; - - swagNote.nextNote = (swagNote = new Note(daStrumTime, daNoteData % 4, daNoteType, gottaHitNote ? 1 : 0, swagNote, false, section.altAnim ? "-alt" : "")); - swagNote.sustainLength = songNotes[2]; - swagNote.scrollFactor.set(0, 0); - swagNote.stepLength = stepCrochet; - notes.add(swagNote); - - // calculate sustain length and fix - var susLength:Float = swagNote.sustainLength / stepCrochet; - if (susLength > 0.75) susLength++; - - // create sustains - for (susNote in 0...Math.floor(susLength)) - { - swagNote = new Note(daStrumTime + (stepCrochet * susNote), daNoteData % 4, daNoteType, gottaHitNote ? 1 : 0, swagNote, true, section.altAnim ? "-alt" : ""); - swagNote.scrollFactor.set(); - swagNote.stepLength = stepCrochet; - notes.add(swagNote); - } - - // preload splash - if (Options.splashesEnabled) - splashHandler.getSplashGroup(swagNote.splash); - } - } - notes.sortNotes(); - } - - @:dox(hide) private function generateSong(?songData:SwagSong):Void + @:dox(hide) private function generateSong(?songData:ChartData):Void { if (songData == null) songData = SONG; - if (songData.maxHealth != null && songData.maxHealth > 0) - maxHealth = songData.maxHealth; + events = songData.events != null ? [for(e in songData.events) e] : []; + events.sort(function(p1, p2) { + return FlxSort.byValues(FlxSort.ASCENDING, p1.time, p2.time); + }); - camZoomingInterval = cast songData.beatsPerMesure.getDefault(4); + camZoomingInterval = cast songData.meta.beatsPerMesure.getDefault(4); - Conductor.changeBPM(songData.bpm); + Conductor.changeBPM(songData.meta.bpm); - curSong = songData.song.toLowerCase(); + curSong = songData.meta.name.toLowerCase(); - inst = FlxG.sound.load(Paths.inst(SONG.song, difficulty)); + inst = FlxG.sound.load(Paths.inst(SONG.meta.name, difficulty)); vocals = FlxG.sound.list.recycle(FlxSound); @:privateAccess { vocals.reset(); vocals.exists = true; } - if (SONG.needsVoices) - vocals.loadEmbedded(Paths.voices(SONG.song, difficulty)); + if (SONG.meta.needsVoices != false) // null or true + vocals.loadEmbedded(Paths.voices(SONG.meta.name, difficulty)); FlxG.sound.list.add(vocals); inst.persist = vocals.persist = false; - notes = new NoteGroup(); - add(notes); - - generateNotes(songData); - generatedMusic = true; } - - public function addNoteType(name:String, addAsDefaultIfUnknown:Bool = false):Int { - if (name == null) { - if (addAsDefaultIfUnknown) - return 0; - else - name = "Unknown"; - } - - for(k=>e in noteTypesArray) - if (e == name) return k; - - noteTypesArray.push(name); - - // loads script - var scriptPath = Paths.script('data/notes/${name}'); - if (Assets.exists(scriptPath) && !scripts.contains(scriptPath)) { - var script = Script.create(scriptPath); - if (!(script is DummyScript)) { - scripts.add(script); - script.load(); - } - } - return noteTypesArray.length-1; - } @:dox(hide) function sortByShit(Obj1:Note, Obj2:Note):Int { return FlxSort.byValues(FlxSort.ASCENDING, Obj1.strumTime, Obj2.strumTime); @@ -1057,7 +935,7 @@ class PlayState extends MusicBeatState * Returns the Discord RPC icon. */ public inline function getIconRPC():String - return (dad != null ? (dad.icon != null ? dad.icon : dad.curCharacter) : SONG.player2); + return (dad != null ? (dad.icon != null ? dad.icon : dad.curCharacter) : null); var __songPlaying:Bool = false; var __wasAutoPause:Bool = false; @@ -1105,11 +983,6 @@ class PlayState extends MusicBeatState scripts.call("onVocalsResync"); } - @:dox(hide) - public function get_curSection() { - return PlayState.SONG.notes[curMeasure]; - } - /** * Pauses the game. */ @@ -1166,19 +1039,11 @@ class PlayState extends MusicBeatState pauseGame(); if (canAccessDebugMenus) { - if (FlxG.keys.justPressed.SEVEN) - { - FlxG.switchState(new ChartingState()); - } if (FlxG.keys.justPressed.F5) { Logs.trace('Reloading scripts...', WARNING, YELLOW); scripts.reload(); Logs.trace('Song scripts successfully reloaded.', WARNING, GREEN); } - if (FlxG.keys.justPressed.EIGHT) - FlxG.switchState(new CharacterEditor(SONG.player2)); - if (FlxG.keys.justPressed.NINE) - FlxG.switchState(new CharacterEditor(SONG.player1)); } iconP1.scale.set(lerp(iconP1.scale.x, 1, 0.33), lerp(iconP1.scale.y, 1, 0.33)); @@ -1213,28 +1078,31 @@ class PlayState extends MusicBeatState } } - if (generatedMusic && curSection != null) + while(events.length > 0 && events[0].time <= Conductor.songPosition) + executeEvent(events.shift()); + + if (generatedMusic && players[curCameraTarget] != null) { - var dadPos = dad != null ? dad.getCameraPosition() : FlxPoint.get(0,0); - var bfPos = boyfriend != null ? boyfriend.getCameraPosition() : FlxPoint.get(0,0); - var section = curSection; - - // from dad to bf - var ratio:Float = 0; - if (section.camTarget != null) - ratio = section.camTarget; - else - ratio = section.mustHitSection ? 1 : 0; - - var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(FlxPoint.get(FlxMath.lerp(dadPos.x, bfPos.x, ratio), FlxMath.lerp(dadPos.y, bfPos.y, ratio)), bfPos, dadPos, ratio, ratio >= 0.5)); - - if (!event.cancelled) { - camFollow.setPosition( - event.position.x, event.position.y - ); + var strumLine = players[curCameraTarget]; + var pos = FlxPoint.get(); + var r = 0; + for(c in strumLine.characters) { + if (c == null) continue; + var cpos = c.getCameraPosition(); + pos.x += cpos.x; + pos.y += cpos.y; + r++; + cpos.put(); } - for(e in [event.position, event.bfCamPos, event.dadCamPos]) - e.put(); + if (r > 0) { + pos.x /= r; + pos.y /= r; + + var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLine, r)); + if (!event.cancelled) + camFollow.setPosition(pos.x, pos.y); + } + pos.put(); } if (camZooming) @@ -1253,10 +1121,6 @@ class PlayState extends MusicBeatState gameOver(dad); - if (generatedMusic) { - __updateNote_event = EventManager.get(NoteUpdateEvent); - notes.forEachAlive(updateNote); - } if (!inCutscene) keyShit(); @@ -1269,49 +1133,28 @@ class PlayState extends MusicBeatState scripts.call("postUpdate", [elapsed]); } - var __updateNote_strum:Strum = null; - var __updateNote_event:NoteUpdateEvent = null; - @:dox(hide) - function updateNote(daNote:Note) - { - for(e in players[daNote.strumLineID].members) { - if (e.ID == daNote.noteData % 4) { - __updateNote_strum = e; - break; //ing bad - } + public function executeEvent(event:ChartEvent) { + if (event == null) return; + if (event.params == null) return; + + switch(event.type) { + case CUSTOM: + var d = [for(d in event.params) d]; + scripts.call(d.shift(), d); + case CAM_MOVEMENT: + if (event.params.length > 0 && event.params[0] is Int) + curCameraTarget = event.params[0]; + case BPM_CHANGE: + // todo!! + case ALT_ANIM_TOGGLE: + // todo!!! + default: } - PlayState.instance.scripts.event("onNoteUpdate", __updateNote_event.recycle(daNote, FlxG.elapsed, __updateNote_strum)); - if (__updateNote_event.cancelled) return; - - if (__updateNote_event.__updateHitWindow) { - daNote.canBeHit = (daNote.strumTime > Conductor.songPosition - (hitWindow * daNote.latePressWindow) - && daNote.strumTime < Conductor.songPosition + (hitWindow * daNote.earlyPressWindow)); - - if (daNote.strumTime < Conductor.songPosition - hitWindow && !daNote.wasGoodHit) - daNote.tooLate = true; - } - - if (__updateNote_event.__autoCPUHit && (daNote.strumLine == null || daNote.strumLine.cpu) && !daNote.wasGoodHit && daNote.strumTime < Conductor.songPosition) goodNoteHit(daNote); - - if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (daNote.stepLength) < Conductor.songPosition) { - deleteNote(daNote); - return; - } - - if (daNote.tooLate && !(daNote.strumLine != null && daNote.strumLine.cpu)) { - noteMiss(daNote); - return; - } - - - if (__updateNote_event.strum == null) return; - - if (__updateNote_event.__reposNote) __updateNote_event.strum.updateNotePosition(daNote); - __updateNote_event.strum.updateSustain(daNote); - - PlayState.instance.scripts.event("onNotePostUpdate", __updateNote_event); } + @:dox(hide) + public var __updateNote_event:NoteUpdateEvent = null; + /** * Forces a game over. * @param character Character which died. Default to `boyfriend`. @@ -1353,10 +1196,10 @@ class PlayState extends MusicBeatState inst.pause(); vocals.pause(); - if (SONG.validScore) + if (validScore) { #if !switch - Highscore.saveScore(SONG.song, { + Highscore.saveScore(SONG.meta.name, { score: songScore, misses: misses, accuracy: accuracy @@ -1380,7 +1223,7 @@ class PlayState extends MusicBeatState { FlxG.switchState(new StoryMenuState()); - if (SONG.validScore) + if (validScore) { // TODO: more week info saving Highscore.saveWeekScore(storyWeek.name, { @@ -1425,7 +1268,13 @@ class PlayState extends MusicBeatState var __justReleased:Array = []; private function keyShit():Void { + if (generatedMusic) { + __updateNote_event = EventManager.get(NoteUpdateEvent); + } + for(id=>p in players) { + p.updateNotes(); + if (p.cpu) continue; __funcsToExec.clear(); @@ -1447,8 +1296,8 @@ class PlayState extends MusicBeatState if (__pressed.contains(true)) { __funcsToExec.push(function(note:Note) { - if (note.strumLineID == id && __pressed[note.strumID] && note.isSustainNote && note.canBeHit && !note.wasGoodHit) { - goodNoteHit(note); + if (__pressed[note.strumID] && note.isSustainNote && note.canBeHit && !note.wasGoodHit) { + goodNoteHit(p, note); } }); } @@ -1456,25 +1305,25 @@ class PlayState extends MusicBeatState var notePerStrum = [for(_ in 0...4) null]; if (__justPressed.contains(true)) { __funcsToExec.push(function(note:Note) { - if (__justPressed[note.strumID] && !note.isSustainNote && note.strumLineID == id && !note.wasGoodHit && note.canBeHit) { + if (__justPressed[note.strumID] && !note.isSustainNote && !note.wasGoodHit && note.canBeHit) { if (notePerStrum[note.strumID] == null) notePerStrum[note.strumID] = note; - else if (Math.abs(notePerStrum[note.strumID].strumTime - note.strumTime) <= 2) deleteNote(note); + else if (Math.abs(notePerStrum[note.strumID].strumTime - note.strumTime) <= 2) p.deleteNote(note); else if (note.strumTime < notePerStrum[note.strumID].strumTime) notePerStrum[note.strumID] = note; } }); } if (__funcsToExec.length > 0) { - notes.forEachAlive(function(note:Note) { + p.notes.forEachAlive(function(note:Note) { for(e in __funcsToExec) e(note); }); } - if (!p.ghostTapping) for(k=>p in __justPressed) if (p && notePerStrum[k] == null) { + if (!p.ghostTapping) for(k=>pr in __justPressed) if (pr && notePerStrum[k] == null) { // FUCK YOU - noteMiss(null, k, id); + noteMiss(p, null, k, id); } - for(e in notePerStrum) if (e != null) goodNoteHit(e); + for(e in notePerStrum) if (e != null) goodNoteHit(p, e); p.forEach(function(str:Strum) { str.updatePlayerInput(__pressed[str.ID], __justPressed[str.ID], __justReleased[str.ID]); @@ -1489,18 +1338,17 @@ class PlayState extends MusicBeatState * @param direction Specify a custom direction in case note is null. * @param player Specify a custom player in case note is null. */ - public function noteMiss(note:Note, ?direction:Int, ?player:Int):Void + public function noteMiss(strumLine:StrumLine, note:Note, ?direction:Int, ?player:Int):Void { - var playerID:Null = note == null ? player : note.strumLineID; + var playerID:Null = note == null ? player : players.indexOf(strumLine); var directionID:Null = note == null ? direction : note.strumID; if (playerID == null || directionID == null) return; - var event:NoteHitEvent = scripts.event("onPlayerMiss", EventManager.get(NoteHitEvent).recycle(true, false, false, note, players[player].characters, true, note != null ? note.noteType : null, "", "", "", directionID, -10, 0, -0.04, "shit")); - + var event:NoteHitEvent = scripts.event("onPlayerMiss", EventManager.get(NoteHitEvent).recycle(true, false, false, note, players[playerID].characters, true, note != null ? note.noteType : null, "", "", "", directionID, -10, 0, -0.04, "shit")); if (event.cancelled) return; - if (event.note != null && event.note.strumLine != null) event.note.strumLine.addHealth(event.healthGain); + if (event.note != null && strumLine != null) strumLine.addHealth(event.healthGain); if (gf != null && combo > 5 && gf.hasAnimation('sad')) gf.playAnim('sad', event.forceAnim, MISS); combo = 0; @@ -1525,21 +1373,22 @@ class PlayState extends MusicBeatState char.stunned = true; char.playSingAnim(directionID, "miss", MISS); } - // boyfriend.stunned = true; - deleteNote(note); + if (strumLine != null && note != null) { + strumLine.deleteNote(note); + } } @:dox(hide) public function getNoteType(id:Int):String { - return noteTypesArray[id]; + return SONG.noteTypes[id-1]; } /** * Hits a note * @param note Note to hit. */ - public function goodNoteHit(note:Note):Void + public function goodNoteHit(strumLine:StrumLine, note:Note):Void { if(note == null || note.wasGoodHit) return; @@ -1573,10 +1422,10 @@ class PlayState extends MusicBeatState } var event:NoteHitEvent; - if (note.strumLine != null && !note.strumLine.cpu) - event = scripts.event("onPlayerHit", EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, note, note.strumLine.characters, true, note.noteType, note.animSuffix, "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, note.noteData > 0 ? 0.023 : 0.004, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick")); + if (strumLine != null && !strumLine.cpu) + event = scripts.event("onPlayerHit", EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, note, strumLine.characters, true, note.noteType, note.animSuffix, "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, note.noteData > 0 ? 0.023 : 0.004, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick")); else - event = scripts.event("onDadHit", EventManager.get(NoteHitEvent).recycle(false, false, false, note, note.strumLine.characters, false, note.noteType, note.animSuffix, "game/score/", "", note.strumID, 0, null, 0, daRating, false)); + event = scripts.event("onDadHit", EventManager.get(NoteHitEvent).recycle(false, false, false, note, strumLine.characters, false, note.noteType, note.animSuffix, "game/score/", "", note.strumID, 0, null, 0, daRating, false)); scripts.event("onNoteHit", event); if (!event.cancelled) { @@ -1664,7 +1513,7 @@ class PlayState extends MusicBeatState ratingNum += 1; } - if (event.note.strumLine != null) event.note.strumLine.addHealth(event.healthGain); + if (strumLine != null) strumLine.addHealth(event.healthGain); if (!event.animCancelled) for(char in event.characters) @@ -1686,24 +1535,13 @@ class PlayState extends MusicBeatState } } - if (event.deleteNote && !note.isSustainNote) deleteNote(note); - } - - /** - * Deletes a note. - * @param note Note to delete - */ - public function deleteNote(note:Note) { - if (note == null) return; - var event:SimpleNoteEvent = scripts.event("onNoteDelete", EventManager.get(SimpleNoteEvent).recycle(note)); - if (!event.cancelled) { - scripts.call("onNoteDelete", [note]); - note.kill(); - notes.remove(note, true); - note.destroy(); - } + if (event.deleteNote && !note.isSustainNote) strumLine.deleteNote(note); } + public inline function deleteNote(note:Note) + if (note.strumLine != null) + note.strumLine.deleteNote(note); + @:dox(hide) override function stepHit(curStep:Int) { @@ -1752,16 +1590,40 @@ class PlayState extends MusicBeatState private inline function set_cpu(s:StrumLine):StrumLine return cpuStrums = s; + private function get_boyfriend():Character { + if (players != null && players[1] != null) + return players[1].characters[0]; + return null; + } private function set_boyfriend(bf:Character):Character { if (players != null && players[1] != null) players[1].characters = [bf]; - return this.boyfriend = bf; + return bf; + } + private function get_dad():Character { + if (players != null && players[0] != null) + return players[0].characters[0]; + return null; } private function set_dad(dad:Character):Character { if (players != null && players[0] != null) players[0].characters = [dad]; - return this.dad = dad; + return dad; } + private function get_gf():Character { + if (players != null && players[2] != null) + return players[2].characters[0]; + return null; + } + private function set_gf(gf:Character):Character { + if (players != null && players[2] != null) + players[2].characters = [gf]; + return gf; + } + private inline function get_cpuStrums():StrumLine + return players[0]; + private inline function get_playerStrums():StrumLine + return players[1]; } class ComboRating { diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index e9514ac1..8f0ed9ef 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -19,6 +19,8 @@ class Stage extends FlxBasic implements IBeatReceiver { public var stagePath:String; public var stageSprites:Map = []; public var stageScript:Script; + public var state:FlxState; + public var characterPoses:Map = []; public function getSprite(name:String) { return stageSprites[name]; @@ -29,6 +31,7 @@ class Stage extends FlxBasic implements IBeatReceiver { if (state == null) state = PlayState.instance; if (state == null) state = FlxG.state; + this.state = state; stagePath = Paths.xml('stages/$stage'); try { @@ -89,15 +92,18 @@ class Stage extends FlxBasic implements IBeatReceiver { state.add(spr); spr; case "boyfriend" | "bf": - if (PlayState.instance == null || PlayState.instance.boyfriend == null) continue; - doCharNodeShit(PlayState.instance.boyfriend, node); - PlayState.instance.add(PlayState.instance.boyfriend); - PlayState.instance.boyfriend; + addCharPos("boyfriend", node); + null; case "girlfriend" | "gf": - if (PlayState.instance == null || PlayState.instance.gf == null) continue; - doCharNodeShit(PlayState.instance.gf, node); - PlayState.instance.add(PlayState.instance.gf); - PlayState.instance.gf; + addCharPos("girlfriend", node); + null; + case "dad" | "opponent": + addCharPos("dad", node); + null; + case "character": + if (!node.has.name) continue; + addCharPos(node.att.name, node); + null; case "ratings" | "combo": if (PlayState.instance == null) continue; PlayState.instance.comboGroup.setPosition( @@ -106,11 +112,6 @@ class Stage extends FlxBasic implements IBeatReceiver { ); PlayState.instance.add(PlayState.instance.comboGroup); PlayState.instance.comboGroup; - case "dad" | "opponent": - if (PlayState.instance == null || PlayState.instance.dad == null || PlayState.instance.dad.isGF) continue; - doCharNodeShit(PlayState.instance.dad, node); - PlayState.instance.add(PlayState.instance.dad); - PlayState.instance.dad; default: null; } if (sprite != null) { @@ -120,6 +121,24 @@ class Stage extends FlxBasic implements IBeatReceiver { } } + if (characterPoses["girlfriend"] == null) + addCharPos("girlfriend", null, { + x: 400, + y: 130 + }); + + if (characterPoses["dad"] == null) + addCharPos("dad", null, { + x: 100, + y: 100 + }); + + if (characterPoses["boyfriend"] == null) + addCharPos("boyfriend", null, { + x: 770, + y: 100 + }); + if (PlayState.instance == null) return; stageScript = Script.create(Paths.script('data/stages/$stage')); for(k=>e in stageSprites) { @@ -128,7 +147,32 @@ class Stage extends FlxBasic implements IBeatReceiver { PlayState.instance.scripts.add(stageScript); } + public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo) { + var dummyBasic:FlxBasic = new FlxBasic(); + dummyBasic.active = false; + state.add(dummyBasic); + characterPoses[name] = { + node: node, + nonXMLInfo: nonXMLInfo, + dummy: dummyBasic + }; + + } + + public function applyCharStuff(char:Character, posName:String) { + if (characterPoses[posName] == null) + state.add(char); + else { + var charPos = characterPoses[posName]; + if (charPos.node != null) { + doCharNodeShit(char, charPos.node); + } else if (charPos.nonXMLInfo != null) { + char.setPosition(charPos.nonXMLInfo.x, charPos.nonXMLInfo.y); + } + state.insert(state.members.indexOf(charPos.dummy), char); + } + } private static function doCharNodeShit(char:Character, node:Access) { if (node.has.x) { var x:Null = Std.parseFloat(node.att.x); @@ -157,4 +201,15 @@ class Stage extends FlxBasic implements IBeatReceiver { public function stepHit(curStep:Int) {} public function measureHit(curMeasure:Int) {} +} + +typedef StageCharPos = { + var node:Access; + var dummy:FlxBasic; + var ?nonXMLInfo:StageCharPosInfo; +} + +typedef StageCharPosInfo = { + var x:Float; + var y:Float; } \ No newline at end of file diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx index 6c46fe72..4a745720 100644 --- a/source/funkin/game/StrumLine.hx +++ b/source/funkin/game/StrumLine.hx @@ -1,5 +1,8 @@ package funkin.game; +import funkin.scripting.events.SimpleNoteEvent; +import funkin.system.Conductor; +import funkin.chart.Chart.ChartStrumLine; import funkin.system.Controls; import funkin.scripting.events.StrumCreationEvent; import flixel.group.FlxGroup.FlxTypedGroup; @@ -28,6 +31,10 @@ class StrumLine extends FlxTypedGroup { * Whenever Ghost Tapping is enabled. */ @:isVar public var ghostTapping(get, set):Null = null; + /** + * Group of all of the notes in this strumline. Using `forEach` on this group will only loop through the first notes for performance reasons. + */ + public var notes:NoteGroup; private function get_ghostTapping() { if (this.ghostTapping != null) return this.ghostTapping; @@ -47,6 +54,70 @@ class StrumLine extends FlxTypedGroup { this.cpu = cpu; this.opponentSide = opponentSide; this.controls = controls; + this.notes = new NoteGroup(); + } + + public function generate(strumLine:ChartStrumLine) { + if (strumLine.notes != null) for(note in strumLine.notes) { + notes.add(new Note(this, note, false)); + } + notes.sortNotes(); + } + + public override function update(elapsed:Float) { + super.update(elapsed); + notes.update(elapsed); + } + + public override function draw() { + super.draw(); + notes.cameras = cameras; + notes.draw(); + } + + public function updateNotes() { + notes.forEach(updateNote); + } + + var __updateNote_strum:Strum; + public function updateNote(daNote:Note) { + + for(e in members) { + if (e.ID == daNote.noteData % 4) { + __updateNote_strum = e; + break; //ing bad + } + } + PlayState.instance.scripts.event("onNoteUpdate", PlayState.instance.__updateNote_event.recycle(daNote, FlxG.elapsed, __updateNote_strum)); + if (PlayState.instance.__updateNote_event.cancelled) return; + + if (PlayState.instance.__updateNote_event.__updateHitWindow) { + daNote.canBeHit = (daNote.strumTime > Conductor.songPosition - (PlayState.instance.hitWindow * daNote.latePressWindow) + && daNote.strumTime < Conductor.songPosition + (PlayState.instance.hitWindow * daNote.earlyPressWindow)); + + if (daNote.strumTime < Conductor.songPosition - PlayState.instance.hitWindow && !daNote.wasGoodHit) + daNote.tooLate = true; + } + + if (PlayState.instance.__updateNote_event.__autoCPUHit && cpu && !daNote.wasGoodHit && daNote.strumTime < Conductor.songPosition) PlayState.instance.goodNoteHit(this, daNote); + + if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (daNote.stepLength) < Conductor.songPosition) { + deleteNote(daNote); + return; + } + + if (daNote.tooLate && !cpu) { + PlayState.instance.noteMiss(this, daNote); + return; + } + + + if (PlayState.instance.__updateNote_event.strum == null) return; + + if (PlayState.instance.__updateNote_event.__reposNote) PlayState.instance.__updateNote_event.strum.updateNotePosition(daNote); + PlayState.instance.__updateNote_event.strum.updateSustain(daNote); + + PlayState.instance.scripts.event("onNotePostUpdate", PlayState.instance.__updateNote_event); } public inline function addHealth(health:Float) @@ -103,12 +174,25 @@ class StrumLine extends FlxTypedGroup { } add(babyArrow); - PlayState.instance.strumLineNotes.add(babyArrow); babyArrow.playAnim('static'); } } + /** + * Deletes a note from this strumline. + * @param note Note to delete + */ + public function deleteNote(note:Note) { + if (note == null) return; + var event:SimpleNoteEvent = PlayState.instance.scripts.event("onNoteDelete", EventManager.get(SimpleNoteEvent).recycle(note)); + if (!event.cancelled) { + note.kill(); + notes.remove(note, true); + note.destroy(); + } + } + /** * SETTERS & GETTERS */ diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index 3feae795..602743ad 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -108,6 +108,12 @@ class FreeplayState extends MusicBeatState */ public var iconArray:Array = []; + /** + * FlxInterpolateColor object for smooth transition between Freeplay colors. + */ + public var interpColor:FlxInterpolateColor; + + override function create() { songList = FreeplaySonglist.get(); @@ -165,6 +171,7 @@ class FreeplayState extends MusicBeatState changeDiff(0, true); changeCoopMode(0, true); + interpColor = new FlxInterpolateColor(bg.color); super.create(); } @@ -213,7 +220,8 @@ class FreeplayState extends MusicBeatState scoreText.x = coopText.x = scoreBG.x + 4; diffText.x = Std.int(scoreBG.x + ((scoreBG.width - diffText.width) / 2)); - bg.color = CoolUtil.lerpColor(bg.color, songs[curSelected].color, 0.0625); + interpColor.fpsLerpTo(songs[curSelected].color, 0.0625); + bg.color = interpColor.color; var dontPlaySongThisFrame = false; diff --git a/source/funkin/menus/PauseSubState.hx b/source/funkin/menus/PauseSubState.hx index 59ab994a..5d76728f 100644 --- a/source/funkin/menus/PauseSubState.hx +++ b/source/funkin/menus/PauseSubState.hx @@ -65,7 +65,7 @@ class PauseSubState extends MusicBeatSubstate bg.scrollFactor.set(); add(bg); - var levelInfo:FlxText = new FlxText(20, 15, 0, PlayState.SONG.song, 32); + var levelInfo:FlxText = new FlxText(20, 15, 0, PlayState.SONG.meta.name, 32); var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, PlayState.difficulty.toUpperCase(), 32); var multiplayerText:FlxText = new FlxText(20, 15 + 32 + 32, 0, PlayState.opponentMode ? 'OPPONENT MODE' : (PlayState.coopMode ? 'CO-OP MODE' : ''), 32); diff --git a/source/funkin/scripting/events/CamMoveEvent.hx b/source/funkin/scripting/events/CamMoveEvent.hx index 2d05f9d3..dd7c4a43 100644 --- a/source/funkin/scripting/events/CamMoveEvent.hx +++ b/source/funkin/scripting/events/CamMoveEvent.hx @@ -1,5 +1,6 @@ package funkin.scripting.events; +import funkin.game.StrumLine; import flixel.math.FlxPoint; class CamMoveEvent extends CancellableEvent { @@ -9,25 +10,12 @@ class CamMoveEvent extends CancellableEvent { public var position:FlxPoint; /** - * Boyfriend's camera position. + * Currently focused strumline. */ - public var bfCamPos:FlxPoint; - - /** - * Dad's camera position. - */ - public var dadCamPos:FlxPoint; + public var strumLine:StrumLine; /** - * Ratio for lerping between Dad and BF. - * 0 = Camera focuses dad - * 0.5 = Duet Camera - * 1 = Camera focuses BF + * Number of focused characters */ - public var ratio:Float = 0; - - /** - * Whenever the camera focuses BF more than dad. - */ - public var focusBF:Bool = false; + public var focusedCharacters:Int; } \ No newline at end of file diff --git a/source/funkin/system/Conductor.hx b/source/funkin/system/Conductor.hx index 4e021514..6da4cedb 100644 --- a/source/funkin/system/Conductor.hx +++ b/source/funkin/system/Conductor.hx @@ -1,5 +1,6 @@ package funkin.system; +import funkin.chart.Chart; import flixel.math.FlxMath; import openfl.Lib; import flixel.FlxState; @@ -121,10 +122,10 @@ class Conductor changeBPM(0); } - public static function setupSong(SONG:SwagSong) { + public static function setupSong(SONG:ChartData) { reset(); - mapBPMChanges(SONG); - changeBPM(SONG.bpm, cast SONG.beatsPerMesure.getDefault(4), cast SONG.stepsPerBeat.getDefault(4)); + // mapBPMChanges(SONG); // TODO!!! + changeBPM(SONG.meta.bpm, cast SONG.meta.beatsPerMesure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4)); } /** * Maps BPM changes from a song. diff --git a/source/funkin/utils/CoolUtil.hx b/source/funkin/utils/CoolUtil.hx index bca274d7..abedfb4e 100644 --- a/source/funkin/utils/CoolUtil.hx +++ b/source/funkin/utils/CoolUtil.hx @@ -24,6 +24,7 @@ import flixel.util.FlxAxes; import flixel.graphics.frames.FlxFrame; import flixel.graphics.frames.FlxFramesCollection; import flash.geom.ColorTransform; +import funkin.chart.Chart; using StringTools; @@ -294,7 +295,8 @@ class CoolUtil public static function __loadSong(name:String, difficulty:String) { PlayState.difficulty = difficulty; - PlayState.SONG = Song.loadFromJson(name, difficulty); + PlayState.SONG = Chart.parse(name, difficulty); + PlayState.fromMods = PlayState.SONG.fromMods; } public static function setSpriteSize(sprite:FlxSprite, width:Float, height:Float) { sprite.scale.set(width / sprite.frameWidth, height / sprite.frameHeight); diff --git a/source/funkin/utils/FlxInterpolateColor.hx b/source/funkin/utils/FlxInterpolateColor.hx index d33f15eb..a0373606 100644 --- a/source/funkin/utils/FlxInterpolateColor.hx +++ b/source/funkin/utils/FlxInterpolateColor.hx @@ -3,6 +3,10 @@ package funkin.utils; import flixel.FlxG; import flixel.util.FlxColor; +/** + * Additional class for FlxColor lerping. + * Gets rid of precision issues with `FlxColor.interpolate` + */ class FlxInterpolateColor { public var red:Float = 0; public var green:Float = 0;