using Cryville.Common; using Cryville.Common.Collections.Specialized; using Cryville.Common.Pdt; using Cryville.Crtr.Ruleset; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using UnsafeIL; namespace Cryville.Crtr { [Flags] public enum CoeventMode { None = 0x0, Coevent = 0x1, Standalone = 0x2, } 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 CoeventMode CoeventMode { get { return CoeventMode.Coevent; } } 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 { return relev ??= new ReleaseEvent(this); } } [JsonIgnore] public IntKeyedDictionary PropSrcs { get; private set; } protected void SubmitPropSrc(string name, PropSrc property) { PropSrcs.Add(IdentifierManager.Shared.Request(name), property); } [JsonIgnore] public IntKeyedDictionary PropOps { get; private set; } protected void SubmitPropOp(string name, PropOp property) { PropOps.Add(IdentifierManager.Shared.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(); [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) => type switch { "motions" => new EventList(motions), _ => 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(); } } [JsonConverter(typeof(ChartCompatibilityHandler))] public class Chart : EventContainer { [JsonRequired] public long format; // Format Version public string ruleset; public List groups = new(); public override IEnumerable Events { get { return base.Events .Concat(groups.Cast()) .Concat(sigs.Cast()) .Concat(sounds.Cast()); } } public override EventList GetEventsOfType(string type) => type switch { "groups" => new EventList(groups), _ => base.GetEventsOfType(type), }; public override int Priority { get { return 10; } } public class Group : EventContainer { public List tracks = new(); public List notes = new(); public override IEnumerable Events { get { return base.Events .Concat(notes.Cast() .Concat(tracks.Cast() )); } } public override EventList GetEventsOfType(string type) => type switch { "tracks" => new EventList(tracks), "notes" => new EventList(notes), _ => base.GetEventsOfType(type), }; public override int Priority { get { return 12; } } } public class Track : EventContainer { public override int Priority { get { return 14; } } } public class Motion : ChartEvent { #pragma warning disable IDE1006 string m_motion; [JsonRequired] public string motion { get => m_motion ?? ToString(); set => LoadFromString(value); } #pragma warning restore IDE1006 private void LoadFromString(string s) { if (Node != null) throw new InvalidOperationException("The motion property can only be set at initialization"); if (ChartPlayer.motionRegistry == null) { m_motion = s; } else { ChartCompatibilityHandler.MotionStringParser.Parse(s, out name, out Node); SubmitPropSrc("value", new VectorSrc(() => Node.Value)); } } public override string ToString() { string result = Name.ToString(); if (Node.Id >= 0) { var node = Node; result += "#" + node.Id; if (node.Time != null) result += "@" + node.Time.ToString(); if (node.EndTime != null) result += "~" + node.EndTime.ToString(); if (node.Transition != null) result += "^" + node.Transition.ToString(); if (node.Value != null) result += ":" + node.Value.ToString(); } else { result += ":" + Node.Value.ToString(); } return result; } private Identifier name; [JsonIgnore] public Identifier Name { get { if (name == default) throw new InvalidOperationException("Motion name not set"); return name; } private set { if (!ChartPlayer.motionRegistry.TryGetValue(value, out MotionRegistry reg)) throw new ArgumentException("Invalid motion name"); Node = new MotionNode { Value = reg.InitValue }; name = value; } } [JsonIgnore] public MotionNode Node; [JsonConverter(typeof(JsonPdtExpConverter))] public PdtExpression transition; [DefaultValue(0.0f)] public float sumfix = 0.0f; public override int Priority { get { return 0x1000; } } public override CoeventMode CoeventMode { get { return IsLong ? CoeventMode.Standalone : CoeventMode.Coevent; } } public Motion() { SubmitPropOp("motion", new PropOp.String(v => motion = v)); // TODO Obsolete SubmitPropOp("name", new PropOp.Identifier(v => { var n = new Identifier(v); if (name == n) { } else if (name == default) 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); Node.Value = vec; })); } } public class Note : EventContainer { public List judges = new(); public override IEnumerable Events { get { return base.Events .Concat(judges.Cast() ); } } public override EventList GetEventsOfType(string type) => type switch { "judges" => new EventList(judges), _ => base.GetEventsOfType(type), }; public override int Priority { get { return 20; } } } 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 CoeventMode CoeventMode { get { return CoeventMode.Standalone; } } 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 -0x2000; } } } public List sounds; public class Sound : ChartEvent { [JsonRequired] public string id; // TODO [Obsolete] public float offset; public override int Priority { get { return 0; } } } } }