using Cryville.Common; using Cryville.Common.Math; using Cryville.Crtr.Browsing; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; using System.IO; namespace Cryville.Crtr.Extensions.Bestdori { public class BestdoriChartConverter : ResourceConverter { static readonly string[] SUPPORTED_FORMATS = { ".json" }; public override string[] GetSupportedFormats() { return SUPPORTED_FORMATS; } public override IEnumerable ConvertFrom(FileInfo file) { List result = new List(); List src; using (var reader = new StreamReader(file.FullName)) { src = JsonConvert.DeserializeObject>(reader.ReadToEnd()); } var group = new Chart.Group() { tracks = new List(), notes = new List(), motions = new List(), }; Chart chart = new Chart { format = 2, time = new BeatTime(0, 0, 1), ruleset = "bang_dream_girls_band_party", sigs = new List(), sounds = new List(), motions = new List(), groups = new List { group }, }; string bgm = null; double? cbpm = null; double pbeat = 0, ctime = 0; double endbeat = 0; foreach (var ev in src) { double cbeat = ev.StartBeat; ctime += cbpm == null ? 0 : (cbeat - pbeat) / cbpm.Value * 60; pbeat = cbeat; if (cbeat > endbeat) endbeat = cbeat; if (ev is BestdoriChartEvent.System) { if (bgm != null) continue; var tev = (BestdoriChartEvent.System)ev; bgm = StringUtils.TrimExt(tev.data); var name = "bang_dream_girls_band_party__" + bgm; result.Add(new SongResource(name, new FileInfo(Path.Combine(file.Directory.FullName, tev.data)))); chart.sounds.Add(new Chart.Sound { time = ToBeatTime(tev.beat), id = name }); } else if (ev is BestdoriChartEvent.BPM) { var tev = (BestdoriChartEvent.BPM)ev; cbpm = tev.bpm; chart.sigs.Add(new Chart.Signature { time = ToBeatTime(tev.beat), tempo = (float)tev.bpm }); } else if (ev is BestdoriChartEvent.Single) { var tev = (BestdoriChartEvent.Single)ev; group.notes.Add(new Chart.Note { time = ToBeatTime(tev.beat), judges = new List { new Chart.Judge { name = tev.flick ? "single_flick" : "single" } }, motions = new List { new Chart.Motion { motion = "track:" + tev.lane.ToString(CultureInfo.InvariantCulture) } }, }); } else if (ev is BestdoriChartEvent.Long) { var tev = (BestdoriChartEvent.Long)ev; var c1 = tev.connections[tev.connections.Count - 1]; var note = new Chart.Note { time = ToBeatTime(tev.connections[0].beat), endtime = ToBeatTime(c1.beat), judges = new List(), }; for (int i = 0; i < tev.connections.Count; i++) { BestdoriChartEvent.Connection c = tev.connections[i]; note.motions.Add(new Chart.Motion { motion = "track:" + c.lane.ToString(CultureInfo.InvariantCulture) }); if (i == 0) note.judges.Add(new Chart.Judge { name = "single" }); else if (i == tev.connections.Count - 1) note.judges.Add(new Chart.Judge { time = ToBeatTime(c.beat), name = c.flick ? "longend_flick" : "longend" }); else if (!c.hidden) note.judges.Add(new Chart.Judge { time = ToBeatTime(c.beat), name = "longnode" }); } if (c1.beat > endbeat) endbeat = c1.beat; group.notes.Add(note); } else throw new NotImplementedException("Unsupported event: " + ev.type); } if (bgm == null) throw new FormatException("Chart contains no song"); chart.endtime = ToBeatTime(endbeat); result.Add(new RawChartResource(string.Format("bang_dream_girls_band_party__{0}__{1}", bgm, StringUtils.TrimExt(file.Name)), chart, new ChartMeta { name = string.Format("Bandori {0} {1}", bgm, StringUtils.TrimExt(file.Name)), author = "©BanG Dream! Project ©Craft Egg Inc. ©bushiroad", ruleset = "bang_dream_girls_band_party", note_count = group.notes.Count, length = (float)ctime, song = new SongMetaInfo { name = bgm, author = "©BanG Dream! Project ©Craft Egg Inc. ©bushiroad", } })); return result; } BeatTime ToBeatTime(double beat, double error = 1e-4) { int i, n, d; FractionUtils.ToFraction(beat, error, out n, out d); i = n / d; n %= d; return new BeatTime(i, n, d); } } #pragma warning disable IDE1006 [JsonConverter(typeof(BestdoriChartEventCreator))] abstract class BestdoriChartEvent { public abstract string type { get; } public abstract double StartBeat { get; } public abstract class InstantEvent : BestdoriChartEvent { public double beat; public override double StartBeat { get { return beat; } } } public class BPM : InstantEvent { public override string type { get { return "BPM"; } } public double bpm; } public class System : InstantEvent { public override string type { get { return "System"; } } public string data; } public abstract class SingleBase : InstantEvent { public double lane; public bool skill; public bool flick; } public class Single : SingleBase { public override string type { get { return "Single"; } } } public class Directional : SingleBase { public override string type { get { return "Directional"; } } public string direction; public int width; } public class Connection : SingleBase { public override string type { get { return null; } } public bool hidden; } public class Long : BestdoriChartEvent { public override string type { get { return "Long"; } } public List connections; public override double StartBeat { get { return connections[0].beat; } } } public class Slide : Long { public override string type { get { return "Slide"; } } } } #pragma warning restore IDE1006 class BestdoriChartEventCreator : CustomCreationConverter { string _currentType; public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var obj = JObject.ReadFrom(reader); var type = obj["type"]; if (type == null) _currentType = null; else _currentType = obj["type"].ToObject(); return base.ReadJson(obj.CreateReader(), objectType, existingValue, serializer); } public override BestdoriChartEvent Create(Type objectType) { switch (_currentType) { case "BPM": return new BestdoriChartEvent.BPM(); case "System": return new BestdoriChartEvent.System(); case "Single": return new BestdoriChartEvent.Single(); case "Directional": return new BestdoriChartEvent.Directional(); case null: return new BestdoriChartEvent.Connection(); case "Long": return new BestdoriChartEvent.Long(); case "Slide": return new BestdoriChartEvent.Slide(); default: throw new ArgumentException("Unknown event type: " + _currentType); } } } }