using Cryville.Common; using Cryville.Common.Collections.Specialized; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using UnsafeIL; namespace Cryville.Crtr { [JsonConverter(typeof(BeatTimeConverter))] public struct BeatTime : IComparable, IEquatable { [JsonConstructor()] public BeatTime(int _b, int _n, int _d) { b = _b; n = _n; d = _d; } public BeatTime(int _n, int _d) { b = _n / _d; n = _n % _d; d = _d; } [JsonIgnore] public int b; [JsonIgnore] public int n; [JsonIgnore] public int d; [JsonIgnore] public double Decimal { get { return b + (double)n / d; } } public int CompareTo(BeatTime other) { var c = b.CompareTo(other.b); if (c != 0) return c; return ((double)n / d).CompareTo((double)other.n / other.d); } public override bool Equals(object obj) { if (!(obj is BeatTime)) return false; return Equals((BeatTime)obj); } public bool Equals(BeatTime other) { return b.Equals(other.b) && ((double)n / d).Equals((double)other.n / other.d); } public override int GetHashCode() { return Decimal.GetHashCode(); } public static bool operator ==(BeatTime left, BeatTime right) { return left.Equals(right); } public static bool operator !=(BeatTime left, BeatTime right) { return !left.Equals(right); } } public class BeatTimeConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(int[]); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { int b = (int)reader.ReadAsInt32(); int n = (int)reader.ReadAsInt32(); int d = (int)reader.ReadAsInt32(); reader.Read(); return new BeatTime(b, n, d); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { BeatTime obj = (BeatTime)value; writer.WriteStartArray(); writer.WriteValue(obj.b); writer.WriteValue(obj.n); writer.WriteValue(obj.d); writer.WriteEndArray(); } } public abstract class ChartEvent { public BeatTime? time; [JsonIgnore] public float BeatPosition { get { return (float)time.Value.Decimal + BeatOffset; } } public BeatTime? endtime; [JsonIgnore] public float EndBeatPosition { get { if (endtime == null) return BeatPosition; return (float)endtime.Value.Decimal + BeatOffset; } } [JsonIgnore] public float BeatOffset; [JsonIgnore] public abstract int Priority { get; } [JsonIgnore] public virtual bool Standalone { get { return false; } } public ChartEvent Clone() { return (ChartEvent)MemberwiseClone(); } [JsonIgnore] public float Duration { get { if (endtime == null) return 0; return EndBeatPosition - BeatPosition; } } [JsonIgnore] public bool IsLong { get { return Duration > 0; } } private ReleaseEvent relev = null; [JsonIgnore] public ReleaseEvent ReleaseEvent { get { if (relev == null) relev = new ReleaseEvent(this); return relev; } } [JsonIgnore] public IntKeyedDictionary PropSrcs { get; private set; } protected void SubmitPropSrc(string name, PropSrc property) { PropSrcs.Add(IdentifierManager.SharedInstance.Request(name), property); } [JsonIgnore] public IntKeyedDictionary PropOps { get; private set; } protected void SubmitPropOp(string name, PropOp property) { PropOps.Add(IdentifierManager.SharedInstance.Request(name), property); } protected ChartEvent() { PropSrcs = new IntKeyedDictionary(); PropOps = new IntKeyedDictionary(); SubmitPropSrc("event", new PropSrc.Float(() => { int hash = GetHashCode(); return Unsafe.As(ref hash); })); SubmitPropSrc("long", new PropSrc.Boolean(() => IsLong)); SubmitPropSrc("time", new PropSrc.BeatTime(() => time.Value)); SubmitPropSrc("endtime", new PropSrc.BeatTime(() => endtime.Value)); SubmitPropOp("time", new PropOp.BeatTime(v => time = v)); SubmitPropOp("endtime", new PropOp.BeatTime(v => endtime = v)); } } public class ReleaseEvent : ChartEvent { public readonly ChartEvent Original; public ReleaseEvent(ChartEvent orig) { Original = orig; time = orig.endtime; } public override int Priority { get { return Original.Priority + 1; } } } public abstract class EventContainer : ChartEvent { public List motions = new List(); [JsonIgnore] public Clip Clip { get; private set; } public EventContainer() { SubmitPropOp("clip", new PropOp.Clip(v => Clip = v)); } [JsonIgnore] public virtual IEnumerable Events { get { return motions.Cast(); } } public virtual EventList GetEventsOfType(string type) { switch (type) { case "motions": return new EventList(motions); default: throw new ArgumentException(string.Format("Unknown event type \"{0}\"", type)); } } } public abstract class EventList : ChartEvent { public IList Events { get; private set; } protected EventList(IList events) { Events = events; SubmitPropSrc("count", new PropSrc.Float(() => Events.Count)); SubmitPropOp("count", new ListCountOp(() => Events)); } public abstract ChartEvent Create(); public override int Priority { get { throw new NotSupportedException("Fake event"); } } class ListCountOp : PropOp { readonly Func> _cb; public ListCountOp(Func> cb) { _cb = cb; } protected override void Execute() { int ac = _cb().Count; int ec = (int)Math.Round(GetOperand(0).AsNumber()); if (ac != ec) { throw new RulesetViolationException(string.Format( "Event count not matched, expected {0}, got {1}", ec, ac )); } } } } public class EventList : EventList where T : ChartEvent, new() { public EventList(List events) : base(new CastedList(events)) { } public override ChartEvent Create() { return new T(); } } public class Chart : EventContainer { [JsonRequired] public long format; // Format Version public string ruleset; public List groups = new List(); public override IEnumerable Events { get { return base.Events .Concat(groups.Cast()) .Concat(sigs.Cast()) .Concat(sounds.Cast()); } } public override EventList GetEventsOfType(string type) { switch (type) { case "groups": return new EventList(groups); default: return base.GetEventsOfType(type); } } public override int Priority { get { return 10; } } public class Group : EventContainer { public List tracks = new List(); public List notes = new List(); public override IEnumerable Events { get { return base.Events .Concat(notes.Cast() .Concat(tracks.Cast() )); } } public override EventList GetEventsOfType(string type) { switch (type) { case "tracks": return new EventList(tracks); case "notes": return new EventList(notes); default: return base.GetEventsOfType(type); } } public override int Priority { get { return 10; } } } public class Track : EventContainer { public override int Priority { get { return 10; } } } public class Motion : ChartEvent { #pragma warning disable IDE1006 [JsonRequired] public string motion { get { return ToString(); } set { LoadFromString(value); } } #pragma warning restore IDE1006 private void LoadFromString(string s) { if (RelativeNode != null || AbsoluteValue != null) throw new InvalidOperationException("The motion property can only be set at initialization"); Match m = Regex.Match(s, @"^(.+?)(#(\d+))?(@(.+?))?(\^(.+?))?(\*(.+?))?(:(.+))?$"); if (!m.Success) throw new ArgumentException("Invalid motion string format"); name = new Identifier(m.Groups[1].Value); var registry = ChartPlayer.motionRegistry[name]; if (m.Groups[3].Success) { ushort id = ushort.Parse(m.Groups[3].Value); Vec1 time = m.Groups[5].Success ? new Vec1(m.Groups[5].Value) : null; byte? trs = m.Groups[7].Success ? byte.Parse(m.Groups[7].Value) : null; Vec1 rate = m.Groups[9].Success ? new Vec1(m.Groups[9].Value) : null; Vector value = m.Groups[11].Success ? Vector.Construct(registry.Type, m.Groups[11].Value) : null; RelativeNode = new MotionNode() { Id = id, Time = time, Transition = (TransitionType?)trs, Rate = rate, Value = value }; } else { AbsoluteValue = Vector.Construct(registry.Type, m.Groups[11].Value); } SubmitPropSrc("value", new VectorSrc(() => { if (RelativeNode != null) return RelativeNode.Value; else return AbsoluteValue; })); } public override string ToString() { string result = Name.ToString(); if (RelativeNode != null) { var node = RelativeNode; result += "#" + node.Id; if (node.Time != null) result += "@" + node.Time.ToString(); if (node.Transition != null) result = "^" + ((byte)node.Transition).ToString(CultureInfo.InvariantCulture); if (node.Rate != null) result += "*" + node.Rate.ToString(); if (node.Value != null) result += ":" + node.Value.ToString(); } else { result += ":" + AbsoluteValue.ToString(); } return result; } private Identifier name; [JsonIgnore] public Identifier Name { get { return name; } private set { MotionRegistry reg; if (!ChartPlayer.motionRegistry.TryGetValue(value, out reg)) throw new ArgumentException("Invalid motion name"); if (RelativeNode != null) RelativeNode.Value = reg.InitValue; else AbsoluteValue = reg.InitValue; name = value; } } [JsonIgnore] public Vector AbsoluteValue; [JsonIgnore] public MotionNode RelativeNode; [DefaultValue(TransitionType.Ease)] // TODO [Obsolete] public TransitionType transition = TransitionType.Ease; [DefaultValue(1.0f)] // TODO [Obsolete] public float rate = 1.0f; [DefaultValue(0.0f)] public float sumfix = 0.0f; public override int Priority { get { return -2; } } public Motion() { SubmitPropOp("motion", new PropOp.String(v => motion = v)); SubmitPropOp("name", new PropOp.Identifier(v => { var n = new Identifier(v); if (Name.Equals(n)) { } else if (Name.Equals(default(Identifier))) Name = n; else throw new RulesetViolationException(string.Format( "Motion name not matched, expected {0}, got {1}", n, Name )); })); SubmitPropOp("value", new VectorOp(v => { var vec = Vector.Construct(ChartPlayer.motionRegistry[Name].Type, v); if (RelativeNode != null) RelativeNode.Value = vec; else AbsoluteValue = vec; })); } } public class Note : EventContainer { public List judges = new List(); public override IEnumerable Events { get { return base.Events .Concat(judges.Cast() ); } } public override EventList GetEventsOfType(string type) { switch (type) { case "judges": return new EventList(judges); default: return base.GetEventsOfType(type); } } public override int Priority { get { return 12; } } } public class Judge : ChartEvent { [JsonIgnore] public Identifier Id; #pragma warning disable IDE1006 public string name { get { return Id.ToString(); } set { Id = new Identifier(value); } } #pragma warning restore IDE1006 public override int Priority { get { return 0; } } public override bool Standalone { get { return true; } } public Judge() { SubmitPropSrc("name", new PropSrc.Identifier(() => Id.Key)); SubmitPropOp("name", new PropOp.Identifier(v => Id = new Identifier(v))); } } // TODO [Obsolete] public List sigs; // Signatures // TODO [Obsolete] public class Signature : ChartEvent { public float? tempo; public override int Priority { get { return -4; } } } public List sounds; public class Sound : ChartEvent { [JsonRequired] public string id; // TODO [Obsolete] public float offset; public override int Priority { get { return 0; } } } } }