diff --git a/source/funkin/graphics/FlxRepeatSprite.hx b/source/funkin/graphics/FlxRepeatSprite.hx index 0de6ae1a..f92ca92c 100644 --- a/source/funkin/graphics/FlxRepeatSprite.hx +++ b/source/funkin/graphics/FlxRepeatSprite.hx @@ -185,6 +185,7 @@ class FlxRepeatSprite extends FlxSpriteExt { function setupTile(tileX:Int, tileY:Int, baseFrame:FlxFrame) { __tempPoint.set(baseFrame.frame.width * scale.y, baseFrame.frame.height * scale.y); _frame.frame.copyFrom(baseFrame.frame); + _frame.angle = baseFrame.angle; return __tempPoint; } diff --git a/source/funkin/util/song/Song.hx b/source/funkin/util/song/Song.hx index 8d355cb5..80fcab1e 100644 --- a/source/funkin/util/song/Song.hx +++ b/source/funkin/util/song/Song.hx @@ -72,7 +72,7 @@ class Song { switch (format) { case 'json': return checkSong(parseJson(chartPath), meta); // Funkin chart case 'osu': return checkSong(OsuFormat.convertSong(chartPath), meta); // Osu chart - case 'sm' | 'ssc': return checkSong(SmFormat.convertSong(chartPath, diff), meta); // Stepmania chart + case 'sm' | 'ssc': return checkSong(SmFormat.convert(chartPath, diff), meta); // Stepmania chart case 'qua': return checkSong(QuaFormat.convertSong(chartPath), meta); // Quaver chart case 'chart': return checkSong(GhFormat.convertSong(chartPath), meta); // Guitar hero chart } diff --git a/source/funkin/util/song/formats/BasicParser.hx b/source/funkin/util/song/formats/BasicParser.hx new file mode 100644 index 00000000..b0883caa --- /dev/null +++ b/source/funkin/util/song/formats/BasicParser.hx @@ -0,0 +1,78 @@ +package funkin.util.song.formats; + +import haxe.ds.Vector; + +typedef BasicBpmChange = { + time:Float, + bpm:Float +} + +typedef ChartVar = { + name:String, + value:String +} + +typedef BasicSection = { + notes:Array>, + bpm:Float // set to -1 if no changes +} + +class BasicParser { + var variables:Map = []; + var bpmChanges:Array = []; + + var map:Array = []; + var fnfMap:SwagSong; + + public function new() {} + + public function convertSong(path:String, diff:String):SwagSong { + map = CoolUtil.getFileContent(path).split('\n'); + fnfMap = Song.getDefaultSong(); + + __initVars(); + applyVars(variables, fnfMap); + parseBpmChanges(map, bpmChanges); + + fnfMap.bpm = bpmChanges[0]?.bpm ?? 100.0; + + final sections = parseNotes(); + if (sections != null) { + for (section in sections) { + final newSec:SwagSection = Song.getDefaultSection(); + newSec.sectionNotes = section.notes; + newSec.changeBPM = section.bpm != -1; + newSec.bpm = section.bpm; + + fnfMap.notes.push(newSec); + } + } + + return fnfMap; + } + + function parseBpmChanges(map:Array, bpmChanges:Array):Void {} + + function applyVars(variables:Map, fnfMap:SwagSong):Void {} + + function parseNotes():Vector { + return null; + } + + var doop:Int = 0; + + @:noCompletion + function __initVars():Void { + for (i in 0...map.length) { + final _var = __resolveVar(map[i], i); + if (_var != null) { + variables.set(variables.exists(_var.name) ? _var.name + (doop++) : _var.name, _var.value); + } + } + } + + @:noCompletion + function __resolveVar(line:String, index:Int):ChartVar { + return null; + } +} \ No newline at end of file diff --git a/source/funkin/util/song/formats/SmFormat.hx b/source/funkin/util/song/formats/SmFormat.hx index 79526290..dfcce77e 100644 --- a/source/funkin/util/song/formats/SmFormat.hx +++ b/source/funkin/util/song/formats/SmFormat.hx @@ -1,185 +1,153 @@ package funkin.util.song.formats; +import funkin.util.song.formats.BasicParser.ChartVar; +import funkin.util.song.formats.BasicParser.BasicSection; +import funkin.util.song.formats.BasicParser.BasicBpmChange; +import haxe.ds.Vector; + /* Custom made sm (stepmania) to fnf json format for mau engin - DOES NOT CONVERT ZIP PACK FILES YET, ONLY .SM CHART FILES!!!!!! + TODO: Rewrite this bitch!!! (and make a general format for other converters) */ -typedef BpmChanges = Array<{measure:Int, bpm:Float}>; -typedef SmSection = {notes:Array>, changeBpm:Bool, bpm:Float}; +class SmFormat extends BasicParser { + public static function convert(path:String, diff:String):SwagSong { + return new SmFormat().convertSong(path, diff); + } -class SmFormat { - public static function convertSong(path:String, diff:String):SwagSong { - var smMap:Array = CoolUtil.getFileContent(path).split('\n'); - var fnfMap:SwagSong = Song.getDefaultSong(); + override function applyVars(variables:Map, fnfMap:SwagSong) { + fnfMap.song = variables.get("TITLE"); + fnfMap.offsets = [Std.int(Std.parseFloat(variables.get('OFFSET')) * -1000), 0]; + } - var title = getMapVar(smMap, 'TITLE'); - var offset = Std.int(Std.parseFloat(getMapVar(smMap, 'OFFSET'))*1000); - var bpm = 0.0; - - var bpmChanges:BpmChanges = []; - for (i in getMapVar(smMap, 'BPMS').split(",")) { + override function parseBpmChanges(map:Array, bpmChanges:Array) { + for (i in variables.get("BPMS").split(",")) { final data = i.split("="); bpmChanges.push({ - measure: Std.int(Std.parseFloat(data[0]) * 0.25), - bpm: Std.parseFloat(data[1]) + time: Std.parseFloat(data[0]), // Calculated in beats + bpm: Std.parseFloat(data[1]) }); } - bpm = bpmChanges[0].bpm; - - var notes = getMapNotes(smMap, bpmChanges, diff); - var sections:Array = []; - for (i in 0...Lambda.count(notes)) { - final newSec:SwagSection = Song.getDefaultSection(); - final data = notes.get(i); - if (notes.get(i) != null) { - newSec.sectionNotes = data.notes; - newSec.changeBPM = data.changeBpm; - newSec.bpm = data.bpm; - } - sections.push(newSec); - } - - fnfMap.song = title; - fnfMap.notes = sections; - fnfMap.bpm = bpm; - //trace(offset); - fnfMap.offsets = [offset,0]; - return fnfMap; } - private static function getMapVar(map:Array, mapVar:String):String { - for (l in 0...map.length) { - final line = map[l]; - if (line.startsWith('#$mapVar')) { - var retVar:String = line.split('#$mapVar:')[1].trim().replace('\r','').replace('\n',''); - if (!retVar.endsWith(";")) { - var i:Int = l + 1; - while (!map[i].endsWith(";") && !map[i].startsWith(";")) { - retVar += map[i].trim().replace('\r','').replace('\n',''); - i++; + override function __resolveVar(line:String, index:Int):ChartVar { + if (line.trim().startsWith("#")) { + var varName:String = line.split(":")[0]; + varName = varName.substring(1, varName.length); + + var retVar:String = line.split('$varName:')[1].trim().replace('\r','').replace('\n',''); + if (!retVar.endsWith(";")) { + index++; + while (!map[index].endsWith(";") && !map[index].startsWith(";")) { + var lineValue = map[index].trim().replace('\r','').replace('\n',''); + if (!lineValue.contains("// measure")) retVar += lineValue; // I have no idea why this exists, only makes parsing harder + else retVar += ","; + + if (lineValue.length == 4) { + retVar += "-"; + } + + index++; + + if (index > map.length) { + throw("Couldnt get variable for " + varName); + break; } } + } - retVar = (retVar.endsWith(';')) ? retVar.substring(0, retVar.length - 1) : retVar; - return retVar; + return { + name: varName, + value: (retVar.endsWith(';')) ? retVar.substring(0, retVar.length - 1) : retVar } } + return null; } - static inline function cleanStr(s:String):String - return Std.string(s).trim().replace('\r','').replace('\n',''); + var diffNotes:Map> = []; - private static function getMapNotes(map:Array, bpmChanges:BpmChanges, diff:String):Map { - var bpm = bpmChanges[0].bpm; - bpmChanges.remove(bpmChanges[0]); - - var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds - var stepCrochet:Float = crochet / 4; // steps in milliseconds - var sectionCrochet:Float = crochet * 4; // sections in milliseconds - - var returnMap:Map = []; - var noteMeasures:Map> = []; - - // Get the line measures actually start - var notesLine:Int = 0; - var _diff:String = null; - for (l in 0...map.length) { - if (map[l].startsWith("#NOTES:")) { - for (i in l...map.length) { - if (map[i].trim().toLowerCase().startsWith(diff)) // Find diff - _diff = diff; - - if (cleanStr(map[i]).length == 4) { - if (_diff == diff) { // STARTED NOTES, WOW!! (cries) - notesLine = i; - break; - } - } - } - break; + override function parseNotes():Vector { + var baseNotes:Array = []; + for (name => variable in variables) { + if (name.startsWith("NOTES")) { + baseNotes.push(variable); } } - var measure:Int = 0; - for (l in notesLine...map.length) { - var noteLine = map[l].trim(); - if (noteLine.length <= 0) continue; - if (noteLine == ';') break; - if (noteLine.startsWith(",")) { // new measure - measure++; - } else { // Push notes to measure - var lastMeasureData:Array = noteMeasures.get(measure) ?? []; - lastMeasureData.push(noteLine); - noteMeasures.set(measure, lastMeasureData); - } + for (chart in baseNotes) { + var parts = chart.split(":"); + + var diff:String = parts[2]; + var sections:Vector = parseSm(parts[5].substr(1, parts[5].length)); + + diffNotes.set(diff, sections); } + + return null; + } - var strumTime:Float = 0; - for (i in 0...Lambda.count(noteMeasures)) { // Measures - if (noteMeasures.get(i) == null) continue; - final measureArray:Array = noteMeasures.get(i); - final stepsPerLine = 16 / measureArray.length; + function parseSm(notes:String):Vector { + // For ease of use, diving it + var sections:Array> = []; + for (sec in notes.split(",")) { + sections.push(sec.split("-")); + } - final smSec:SmSection = { + var sectionsVector:Array = []; + for (i in 0...sections.length) { + sectionsVector.push({ notes: [], - changeBpm: false, - bpm: 0 - } - - for (l in 0...measureArray.length) { // Lines - final measurePerc = i + (l + 1) / measureArray.length; + bpm: -1 + }); + } - var lastChange = null; - for (change in bpmChanges) { - if (change.measure <= measurePerc) { - lastChange = change; - bpmChanges.remove(change); - } - } + var curBpm:Float = bpmChanges[0].bpm; + bpmChanges.remove(bpmChanges[0]); - if (lastChange != null) { - crochet = ((60 / lastChange.bpm) * 1000); - stepCrochet = crochet / 4; - sectionCrochet = crochet * 4; + var position:Float = 0.0; + var beatPosition:Float = 0.0; + var crochet:Float = 0.0; - smSec.changeBpm = true; - smSec.bpm = lastChange.bpm; - } + var recalc = function (index:Int) { + crochet = (60 / curBpm) * (1 / sections[index].length) * 1000; + } + for (i in 0...sections.length) { + recalc(i); - strumTime += stepCrochet * stepsPerLine; - final line = measureArray[l].split(''); - for (n in 0...line.length) { // Notes - switch (line[n]) { + for (line in sections[i]) { + var lineSplit = line.split(""); + for (n in 0...lineSplit.length) { + switch (lineSplit[n]) { case '1':// Normal note - smSec.notes.push([strumTime,n,0]); + sectionsVector[i].notes.push([position, n, 0.0]); case '2':// Hold head - var susLengthInt = findSusLength(measureArray, [l,n]); - var susLength = stepCrochet * stepsPerLine * susLengthInt; - smSec.notes.push([strumTime,n,susLength]); + //var susLengthInt = findSusLength(measureArray, [l,n]); + //var susLength = stepCrochet * stepsPerLine * susLengthInt; + //smSec.notes.push([strumTime,n,susLength]); //case '4':// Roll head + //case '3': // Hold / Roll tail //case 'M':// Mine default: } } - } - - returnMap.set(i, smSec); - } + + position += crochet; + beatPosition += 1 / sections[i].length; - return returnMap; - } + if (bpmChanges[0] != null) { + while (bpmChanges[0].time <= beatPosition) { + curBpm = bpmChanges[0].bpm; + bpmChanges.remove(bpmChanges[0]); + recalc(i); - inline private static function findSusLength(measure:Array, startSus:Array):Int { - var steps:Int = 0; - for (i in startSus[0]...measure.length) { - if (measure[i].split('')[startSus[1]] == '3') { - break; + sectionsVector[i].bpm = curBpm; + } + } } - steps++; } - return steps; + + return Vector.fromArrayCopy(sectionsVector); } } \ No newline at end of file