478 lines
16 KiB
C#
478 lines
16 KiB
C#
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<Color> _palette = new List<Color>
|
|
{
|
|
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<MotionClip> Instances
|
|
{
|
|
get
|
|
{
|
|
return _tape.Clips.OfType<MotionClip>().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<MoveModel>
|
|
{
|
|
|
|
}
|
|
|
|
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<Clip> 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<MotionClip>().Select(x => x.Model).Distinct())
|
|
{
|
|
RefreshInstancesCount(tape, model);
|
|
}
|
|
}
|
|
if (e.OldItems != null)
|
|
{
|
|
foreach (var model in e.OldItems.OfType<MotionClip>().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<MotionClip>().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
|
|
|
|
}
|