using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using JD.Core; using JD.Core.Collections; using JD.Core.Helpers; using JetBrains.Annotations; namespace JD.Serialization { public class MoveModel : INotifyPropertyChanged, ICloneable { #region Constants const float scoringCamera_defaultLowThreshold_PS4 = - 0.2f; const float scoringCamera_defaultHighThreshold_PS4 = 0.6f; #endregion #region Variables private string _moveName; [NotNull] private readonly Tape _tape; private Path _classifierPath; private Color _color; private float _beatDuration; private int _instancesCount; private MotionClip.MoveType_Enum _moveType; private MotionClip.MotionPlatformSpecific _camera_Xbox360 = new MotionClip.MotionPlatformSpecific(); private MotionClip.MotionPlatformSpecific _camera_XboxOne = new MotionClip.MotionPlatformSpecific(); private MotionClip.MotionPlatformSpecific _camera_PS4 = new MotionClip.MotionPlatformSpecific(); private static readonly List _palette = new List { Color.LightSteelBlue, Color.Moccasin, Color.LightGray, Color.RosyBrown, Color.SteelBlue, Color.Olive, Color.Beige, Color.LightYellow, Color.MediumPurple, Color.PaleTurquoise }; private static int _currentPaletteIndex; private static readonly char[] _validModelNameChars = Enumerable.Range('a', 26) .Union(Enumerable.Range('A', 26)) .Union(Enumerable.Range('0', 10)) .Union(new int[] { '_' }) .Select(x => (char)x).ToArray(); #endregion #region Constructor public MoveModel(Tape tape, Path classifierPath) { if (tape == null) { throw new ArgumentNullException(nameof(tape)); } _tape = tape; _classifierPath = classifierPath; _moveName = GetMoveNameFromClassifierPath(_tape, ClassifierPath); _moveType = GetMoveTypeFromClassifierPath(classifierPath); if (Instances.Any()) { MotionClip clip = Instances.First(); _color = clip.Color; _beatDuration = clip.BeatDuration; _camera_Xbox360.CopyFrom(clip.MotionPlatformSpecifics[Platform.X360]); _camera_XboxOne.CopyFrom(clip.MotionPlatformSpecifics[Platform.DURANGO]); _camera_PS4.CopyFrom(clip.MotionPlatformSpecifics[Platform.ORBIS]); } else { _color = _palette[_currentPaletteIndex]; _currentPaletteIndex = (_currentPaletteIndex + 1) % _palette.Count; _beatDuration = 2; _camera_PS4.LowThreshold = scoringCamera_defaultLowThreshold_PS4; // new move clip : for PS4 we override the UAF serialized default value _camera_PS4.HighThreshold = scoringCamera_defaultHighThreshold_PS4; // new move clip : for PS4 we override the UAF serialized default value } Camera_Xbox360.PropertyChanged += delegate { OnPropertyChanged(nameof(Camera_Xbox360)); }; Camera_XboxOne.PropertyChanged += delegate { OnPropertyChanged(nameof(Camera_XboxOne)); }; Camera_PS4.PropertyChanged += delegate { OnPropertyChanged(nameof(Camera_PS4)); }; InstancesCount = Instances.Count(); } #endregion #region Properties [Browsable(false)] public IEnumerable Instances { get { return _tape.Clips.OfType().Where(clip => clip.ClassifierPath.Equals(ClassifierPath)); } } [Browsable(true)] public string Name { get { return _moveName; } set { if (_moveName != value) { string sanitizedValue = SanitizeMoveModelName(value); string uniqueNewMoveName = sanitizedValue.MakeUniqueName(Tape.MoveModels().Where(x => x != this && x.MoveType == MoveType).Select(x => x.Name).ToList()); if (uniqueNewMoveName != sanitizedValue) { MoveNameMadeUnique?.Invoke(uniqueNewMoveName); } _moveName = uniqueNewMoveName != sanitizedValue ? uniqueNewMoveName : sanitizedValue; string tmpMoveName = _moveName; OnPropertyChanged(); ClassifierPath = new Path(GetClassifierPathFromMoveName(Tape, _moveName, _moveType)); _moveName = tmpMoveName; // when the ClassifierPath is modified, it modifies the casing of the _moveName (like a lowerCase()) } } } private static string SanitizeMoveModelName(string modelName) { string sanitizedValue = modelName; var invalidChars = modelName.ToCharArray().Distinct().Where(x => !_validModelNameChars.Contains(x)).ToList(); foreach (var invalidChar in invalidChars) { sanitizedValue = sanitizedValue.Replace(invalidChar.ToString(), string.Empty); } return sanitizedValue; } public static bool IsNameValid(string modelName) { return modelName.ToCharArray().All(@char => _validModelNameChars.Contains(@char)); } [Browsable(true)] [ReadOnly(true)] public Path ClassifierPath { get { return _classifierPath; } set { if (!_classifierPath.Equals(value)) { _classifierPath = value; _moveName = GetMoveNameFromClassifierPath(Tape, ClassifierPath); _moveType = GetMoveTypeFromClassifierPath(ClassifierPath); OnPropertyChanged(); } } } [Browsable(true)] public Color Color { get { return _color; } set { if (_color != value) { _color = value; OnPropertyChanged(); } } } [Browsable(false)] public MotionClip.MoveType_Enum MoveType { get { return _moveType; } set { if (_moveType != value) { _moveType = value; OnPropertyChanged(); ClassifierPath = new Path(GetClassifierPathFromMoveName(Tape, _moveName, _moveType)); } } } [DisplayName("Duration (in beats)")] [Description("Duration in beats")] [Browsable(true)] public float BeatDuration { get { return _beatDuration; } set { if (!_beatDuration.AlmostEquals(value)) { _beatDuration = value; OnPropertyChanged(); } } } [Browsable(true)] [ReadOnly(true)] public int InstancesCount { get { return _instancesCount; } set { if (_instancesCount != value) { _instancesCount = value; OnPropertyChanged(); } } } [Browsable(true)] public MotionClip.MotionPlatformSpecific Camera_Xbox360 { get { return _camera_Xbox360; } set { _camera_Xbox360 = value; } } [Browsable(true)] public MotionClip.MotionPlatformSpecific Camera_XboxOne { get { return _camera_XboxOne; } set { _camera_XboxOne = value; } } [Browsable(true)] public MotionClip.MotionPlatformSpecific Camera_PS4 { get { return _camera_PS4; } set { _camera_PS4 = value; } } public Tape Tape { get { return _tape; } } #endregion #region UIProperties public string MoveTypeFriendlyName { get { switch (MoveType) { case MotionClip.MoveType_Enum.MoveType_HandOnly: return "Move"; case MotionClip.MoveType_Enum.MoveType_FullBody: return "Camera Move"; } return ""; } } #endregion #region Methods public override string ToString() { return Name; } internal static string GetMoveNameFromClassifierPath(Tape tape, Path classifierPath) { string moveName = System.IO.Path.GetFileNameWithoutExtension(classifierPath.getAbsolute()); if (moveName != null) { moveName = Regex.Replace(moveName, tape.MapInfo?.Name + "_", "", RegexOptions.IgnoreCase); } return moveName; } internal static MotionClip.MoveType_Enum GetMoveTypeFromClassifierPath(Path classifierPath) { return classifierPath.ToString().EndsWith(".msm") ? MotionClip.MoveType_Enum.MoveType_HandOnly : MotionClip.MoveType_Enum.MoveType_FullBody; } internal static string GetClassifierPathFromMoveName(Tape tape, string moveName, MotionClip.MoveType_Enum moveType) { string classifierFileName = tape.MapInfo?.Name + "_" + moveName; if (moveType == MotionClip.MoveType_Enum.MoveType_HandOnly) classifierFileName += ".msm"; if (moveType == MotionClip.MoveType_Enum.MoveType_FullBody) classifierFileName += ".gesture"; string classifierPath = System.IO.Path.Combine(tape.MapInfo?.MovesFolderRelative, classifierFileName); classifierPath = classifierPath.Replace("\\", "/").ToLower(); return classifierPath; } public object Clone() { var cloneModel = (MoveModel)MemberwiseClone(); cloneModel.Camera_Xbox360 = Camera_Xbox360.Clone(); cloneModel.Camera_XboxOne = Camera_XboxOne.Clone(); cloneModel.Camera_PS4 = Camera_PS4.Clone(); return cloneModel; } #endregion #region Events public delegate void MoveNameMadeUniqueFunc(string newMoveName); public static event MoveNameMadeUniqueFunc MoveNameMadeUnique; public event PropertyChangedEventHandler PropertyChanged; //[NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChangedEventHandler handler = PropertyChanged; handler?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion } #region Manager public class MoveModelCollection : UAFList { } public static class MoveModelManager { private static void CreateMoveModelCollection(this Tape tape) { tape.Extensions.Models = new MoveModelCollection(); tape.Clips.CollectionChanged += tape.ClipsOnCollectionChanged; tape.Clips.ItemChanged += tape.ClipsOnItemChanged; tape.PropertyChanged += tape.tape_PropertyChanged; } static void tape_PropertyChanged(this Tape tape, object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "IsLoading" && !tape.IsLoading) { RefreshInstancesCount(tape); } } private static void ClipsOnItemChanged(this Tape tape, object sender, ItemChangedEventArgs args) { if (args.PropertyName == "Model" && !tape.IsLoading) { RefreshInstancesCount(tape); } } static void ClipsOnCollectionChanged(this Tape tape, object sender, NotifyCollectionChangedEventArgs e) { if (!tape.IsLoading) { if (e.NewItems != null) { foreach (var model in e.NewItems.OfType().Select(x => x.Model).Distinct()) { RefreshInstancesCount(tape, model); } } if (e.OldItems != null) { foreach (var model in e.OldItems.OfType().Select(x => x.Model).Distinct()) { RefreshInstancesCount(tape, model); } } } } private static void RefreshInstancesCount(this Tape tape) { foreach (var model in tape.MoveModels().ToList()) { RefreshInstancesCount(tape, model); } } private static void RefreshInstancesCount(this Tape tape, MoveModel model) { model.InstancesCount = tape.Clips.OfType().Count(x => x.Model == model); if (model.InstancesCount == 0) { tape.MoveModels().Remove(model); } } public static MoveModel GetOrCreateMoveModel(this Tape tape, Path classifierPath, out bool created) { created = false; var models = tape.MoveModels(); MoveModel model = models.FirstOrDefault(x => x.ClassifierPath.Equals(classifierPath)); if (model == null) { model = new MoveModel(tape, classifierPath); models.Add(model); created = true; } return model; } public static void UpdateTapeMovelModels(this Tape tape, MotionClip clip, Path classifierPath) { var models = tape.MoveModels(); MoveModel model = models.FirstOrDefault(x => x.ClassifierPath.Equals(clip.ClassifierPath)); if (model == null) { model = (MoveModel)clip.Model.Clone(); model.ClassifierPath = classifierPath; models.Add(model); } } public static MoveModel GetOrCreateMoveModel(this Tape tape, Path classifierPath) { bool created; return GetOrCreateMoveModel(tape, classifierPath, out created); } public static MoveModel GetOrCreateMoveModel(this Tape tape, string moveModelName, MotionClip.MoveType_Enum moveType, out bool created) { var classifierPath = new Path(MoveModel.GetClassifierPathFromMoveName(tape, moveModelName, moveType)); var moveModel = GetOrCreateMoveModel(tape, classifierPath, out created); return moveModel; } public static MoveModel GetOrCreateMoveModel(this Tape tape, string moveModelName, MotionClip.MoveType_Enum moveType) { bool created; return GetOrCreateMoveModel(tape, moveModelName, moveType, out created); } public static MoveModelCollection MoveModels(this Tape tape) { if (tape.Extensions.Models == null) { tape.CreateMoveModelCollection(); } return tape.Extensions.Models; } public static void ClearMoveModels(this Tape tape) { MoveModelCollection models = tape.Extensions.Models; models?.Clear(); } } #endregion }