using Cryville.Crtr.Browsing; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; namespace Cryville.Crtr.Extensions.Malody { public class MalodyChartConverter : ResourceConverter { static readonly string[] SUPPORTED_FORMATS = { ".mc", ".mcz" }; static readonly string[] MODES = { "key", "step", "dj", "catch", "pad", "taiko", "ring", "slide", "live" }; public override string[] GetSupportedFormats() { return SUPPORTED_FORMATS; } public override IEnumerable ConvertFrom(FileInfo file) { List result = new List(); MalodyChart src; if (file.Extension != ".mc") throw new NotImplementedException("mcz file is not supported"); using (var reader = new StreamReader(file.FullName)) { src = JsonConvert.DeserializeObject(reader.ReadToEnd()); } if (src.meta.mode != 0) throw new NotImplementedException("The chart mode is not supported"); var ruleset = "malody!" + MODES[src.meta.mode]; if (src.meta.mode == 0) { ruleset += "." + src.meta.mode_ext.column.ToString(CultureInfo.InvariantCulture) + "k"; } ChartMeta meta = new ChartMeta() { name = src.meta.version, author = src.meta.creator, song = new SongMetaInfo() { name = src.meta.song.titleorg != null ? src.meta.song.titleorg : src.meta.song.title, author = src.meta.song.artistorg != null ? src.meta.song.artistorg : src.meta.song.artist, }, ruleset = ruleset, }; Chart chart = new Chart { format = 2, time = new BeatTime(-4, 0, 1), ruleset = ruleset, sigs = new List(), sounds = new List(), motions = new List(), groups = new List(), }; var group = new Chart.Group() { tracks = new List(), notes = new List(), motions = new List(), }; chart.groups.Add(group); int col = src.meta.mode_ext.column; IEnumerable events = src.time.Cast(); if (src.effect != null) events = events.Concat(src.effect.Cast()); events = events.Concat(src.note.Cast()); List endEvents = new List(); foreach (var ev in events) if (ev.endbeat != null) endEvents.Add(new MalodyChart.EndEvent { beat = ev.endbeat, StartEvent = ev, }); events = events.Concat(endEvents) .OrderBy(e => e.beat[0] + (float)e.beat[1] / e.beat[2]) .ThenBy(e => e.Priority); Dictionary longEvents = new Dictionary(); float? baseBpm = null, cbpm = null; float pbeat = 0f, ctime = 0f; int[] endbeat = new int[] { 0, 0, 1 }; foreach (var ev in events) { float cbeat = ConvertBeat(ev.beat); ctime += cbpm == null ? 0 : (cbeat - pbeat) / cbpm.Value * 60f; pbeat = cbeat; if (ev is MalodyChart.Time) { var tev = (MalodyChart.Time)ev; if (baseBpm == null) baseBpm = tev.bpm; cbpm = tev.bpm; chart.sigs.Add(new Chart.Signature { time = new BeatTime(ev.beat[0], ev.beat[1], ev.beat[2]), tempo = tev.bpm, }); chart.motions.Add(new Chart.Motion { time = new BeatTime(ev.beat[0], ev.beat[1], ev.beat[2]), motion = "svm:" + (tev.bpm / baseBpm.Value).ToString(CultureInfo.InvariantCulture) }); } else if (ev is MalodyChart.Effect) { var tev = (MalodyChart.Effect)ev; if (tev.scroll != null) group.motions.Add(new Chart.Motion { time = new BeatTime(ev.beat[0], ev.beat[1], ev.beat[2]), motion = "svm:" + tev.scroll.Value.ToString(CultureInfo.InvariantCulture) }); } else if (ev is MalodyChart.Note) { var tev = (MalodyChart.Note)ev; if (tev.type == 1) { if (tev.beat[0] == 0 && tev.beat[1] == 0) { var res = new SongResource(meta.song.name, new FileInfo(file.DirectoryName + "/" + tev.sound)); result.Add(res); chart.sounds.Add(new Chart.Sound { time = new BeatTime(0, 0, 1), id = res.Name, offset = -tev.offset / 1000f, }); } else throw new NotImplementedException(); } else { if (ConvertBeat(tev.beat) > ConvertBeat(endbeat)) endbeat = tev.beat; var rn = new Chart.Note() { time = new BeatTime(tev.beat[0], tev.beat[1], tev.beat[2]), motions = new List { new Chart.Motion() { motion = "track:" + tev.column.ToString(CultureInfo.InvariantCulture) } }, }; if (tev.endbeat != null) { if (ConvertBeat(tev.endbeat) > ConvertBeat(endbeat)) endbeat = tev.endbeat; rn.endtime = new BeatTime(tev.endbeat[0], tev.endbeat[1], tev.endbeat[2]); longEvents.Add(ev, new StartEventState { Destination = rn, Time = ctime, }); } group.notes.Add(rn); } } else if (ev is MalodyChart.EndEvent) { var tev = (MalodyChart.EndEvent)ev; if (tev.StartEvent is MalodyChart.Note) { var sev = tev.StartEvent; longEvents.Remove(sev); } else throw new NotSupportedException(); } else throw new NotSupportedException(); } chart.endtime = new BeatTime(endbeat[0] + 4, endbeat[1], endbeat[2]); meta.length = ctime; meta.note_count = group.notes.Count; string chartName = string.Format("{0} - {1}", meta.song.name, meta.name); if (src.meta.background != null) { meta.cover = src.meta.background; } result.Add(new RawChartResource(chartName, chart, meta)); return result; } struct StartEventState { public float Time { get; set; } public ChartEvent Destination { get; set; } } float ConvertBeat(int[] beat) { return beat[0] + (float)beat[1] / beat[2]; } } #pragma warning disable IDE1006 struct MalodyChart { public interface IEvent { int[] beat { get; set; } int[] endbeat { get; set; } int Priority { get; } } public struct EndEvent : IEvent { public int[] beat { get; set; } public int[] endbeat { get; set; } public IEvent StartEvent { get; set; } public int Priority { get { return StartEvent.Priority - 1; } } } public Meta meta; public struct Meta { public SongInfo song; public struct SongInfo { public string title; public string artist; public string titleorg; public string artistorg; } public string background; public string creator; public string version; public int mode; public ModeExt mode_ext; public struct ModeExt { public int column; } } public List