431 lines
11 KiB
C#
431 lines
11 KiB
C#
using Cryville.Common;
|
|
using Cryville.Common.Collections.Specialized;
|
|
using Cryville.Common.Pdt;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using UnsafeIL;
|
|
|
|
namespace Cryville.Crtr {
|
|
[JsonConverter(typeof(BeatTimeConverter))]
|
|
public struct BeatTime : IComparable<BeatTime>, IEquatable<BeatTime> {
|
|
[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<PropSrc> PropSrcs { get; private set; }
|
|
protected void SubmitPropSrc(string name, PropSrc property) {
|
|
PropSrcs.Add(IdentifierManager.SharedInstance.Request(name), property);
|
|
}
|
|
[JsonIgnore]
|
|
public IntKeyedDictionary<PropOp> PropOps { get; private set; }
|
|
protected void SubmitPropOp(string name, PropOp property) {
|
|
PropOps.Add(IdentifierManager.SharedInstance.Request(name), property);
|
|
}
|
|
|
|
protected ChartEvent() {
|
|
PropSrcs = new IntKeyedDictionary<PropSrc>();
|
|
PropOps = new IntKeyedDictionary<PropOp>();
|
|
SubmitPropSrc("event", new PropSrc.Float(() => {
|
|
int hash = GetHashCode();
|
|
return Unsafe.As<int, float>(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<Chart.Motion> motions = new List<Chart.Motion>();
|
|
|
|
[JsonIgnore]
|
|
public Clip Clip { get; private set; }
|
|
|
|
public EventContainer() {
|
|
SubmitPropOp("clip", new PropOp.Clip(v => Clip = v));
|
|
}
|
|
|
|
[JsonIgnore]
|
|
public virtual IEnumerable<ChartEvent> Events {
|
|
get {
|
|
return motions.Cast<ChartEvent>();
|
|
}
|
|
}
|
|
|
|
public virtual EventList GetEventsOfType(string type) {
|
|
switch (type) {
|
|
case "motions": return new EventList<Chart.Motion>(motions);
|
|
default: throw new ArgumentException(string.Format("Unknown event type \"{0}\"", type));
|
|
}
|
|
}
|
|
}
|
|
public abstract class EventList : ChartEvent {
|
|
public IList<ChartEvent> Events { get; private set; }
|
|
protected EventList(IList<ChartEvent> 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<IList<ChartEvent>> _cb;
|
|
|
|
public ListCountOp(Func<IList<ChartEvent>> 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<T> : EventList where T : ChartEvent, new() {
|
|
public EventList(List<T> events) : base(new CastedList<ChartEvent>(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<Group> groups = new List<Group>();
|
|
|
|
public override IEnumerable<ChartEvent> Events {
|
|
get {
|
|
return base.Events
|
|
.Concat(groups.Cast<ChartEvent>())
|
|
.Concat(sigs.Cast<ChartEvent>())
|
|
.Concat(sounds.Cast<ChartEvent>());
|
|
}
|
|
}
|
|
|
|
public override EventList GetEventsOfType(string type) {
|
|
switch (type) {
|
|
case "groups": return new EventList<Group>(groups);
|
|
default: return base.GetEventsOfType(type);
|
|
}
|
|
}
|
|
|
|
public override int Priority { get { return 10; } }
|
|
|
|
public class Group : EventContainer {
|
|
public List<Track> tracks = new List<Track>();
|
|
public List<Note> notes = new List<Note>();
|
|
public override IEnumerable<ChartEvent> Events {
|
|
get {
|
|
return base.Events
|
|
.Concat(notes.Cast<ChartEvent>()
|
|
.Concat(tracks.Cast<ChartEvent>()
|
|
));
|
|
}
|
|
}
|
|
public override EventList GetEventsOfType(string type) {
|
|
switch (type) {
|
|
case "tracks": return new EventList<Track>(tracks);
|
|
case "notes": return new EventList<Note>(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 {
|
|
static readonly PdtFragmentInterpreter _itor = new PdtFragmentInterpreter();
|
|
static readonly PropOp _vecop = new VectorOp(v => _vecbuf = v);
|
|
static float[] _vecbuf;
|
|
|
|
#pragma warning disable IDE1006
|
|
[JsonRequired]
|
|
public string motion {
|
|
get { return 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");
|
|
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.Value != null) result += ":" + node.Value.ToString();
|
|
}
|
|
else {
|
|
result += ":" + Node.Value.ToString();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private Identifier name;
|
|
[JsonIgnore]
|
|
public Identifier Name {
|
|
get {
|
|
if (name == default(Identifier)) throw new InvalidOperationException("Motion name not set");
|
|
return name;
|
|
}
|
|
private set {
|
|
MotionRegistry reg;
|
|
if (!ChartPlayer.motionRegistry.TryGetValue(value, out reg))
|
|
throw new ArgumentException("Invalid motion name");
|
|
Node = new MotionNode { Value = reg.InitValue };
|
|
name = value;
|
|
}
|
|
}
|
|
|
|
[JsonIgnore]
|
|
public MotionNode Node;
|
|
|
|
[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 == n) { }
|
|
else if (name == 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);
|
|
Node.Value = vec;
|
|
}));
|
|
}
|
|
}
|
|
|
|
public class Note : EventContainer {
|
|
public List<Judge> judges = new List<Judge>();
|
|
public override IEnumerable<ChartEvent> Events {
|
|
get {
|
|
return base.Events
|
|
.Concat(judges.Cast<ChartEvent>()
|
|
);
|
|
}
|
|
}
|
|
|
|
public override EventList GetEventsOfType(string type) {
|
|
switch (type) {
|
|
case "judges": return new EventList<Judge>(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<Signature> sigs; // Signatures
|
|
// TODO [Obsolete]
|
|
public class Signature : ChartEvent {
|
|
public float? tempo;
|
|
|
|
public override int Priority { get { return -4; } }
|
|
}
|
|
|
|
public List<Sound> sounds;
|
|
public class Sound : ChartEvent {
|
|
[JsonRequired]
|
|
public string id;
|
|
|
|
// TODO [Obsolete]
|
|
public float offset;
|
|
|
|
public override int Priority { get { return 0; } }
|
|
}
|
|
}
|
|
}
|