Files
crtr/Assets/Cryville/Crtr/Chart.cs
2023-03-26 23:25:20 +08:00

458 lines
12 KiB
C#

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<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();
}
}
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 {
#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<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; } }
}
}
}