415 lines
12 KiB
C#
415 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using JD.Core.Collections;
|
|
using JD.Core.ProjectManaging;
|
|
using JetBrains.Annotations;
|
|
using JD.Interfaces.Composition;
|
|
using Ubisoft.Collections;
|
|
|
|
namespace JD.Serialization
|
|
{
|
|
public partial class Tape
|
|
{
|
|
#region Variables
|
|
|
|
private string _id;
|
|
private TapeReferenceClip _editionContext;
|
|
private bool _isLoading;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public Tape()
|
|
{
|
|
Tracks.CollectionChanged += tracks_CollectionChanged;
|
|
Tracks.ItemChanged += tracks_ItemsChanged;
|
|
Clips.CollectionChanged += clips_CollectionChanged;
|
|
Clips.ItemChanged += clips_ItemsChanged;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Tape id in the engine.
|
|
/// </summary>
|
|
[Category("Tape")]
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
public string Id
|
|
{
|
|
get { return _id; }
|
|
set
|
|
{
|
|
_id = value;
|
|
OnPropertyChanged();
|
|
OnTapeIdChanged();
|
|
}
|
|
}
|
|
|
|
public string SongRootRelativPath
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
public bool DisableClockEdition
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// When set, means that the content of the tape contains invalid data.
|
|
/// </summary>
|
|
public Exception LoadingError
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
private bool IsTapeClockReadOnly
|
|
{
|
|
get { return EditionContext != null || DisableClockEdition; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// True when loaded from a file instead of from the engine.
|
|
/// </summary>
|
|
public bool IsOffline { get; set; }
|
|
|
|
/// <summary>
|
|
/// True when tape is loaded
|
|
/// </summary>
|
|
public bool IsLoading
|
|
{
|
|
get { return _isLoading; }
|
|
set
|
|
{
|
|
if (_isLoading != value)
|
|
{
|
|
_isLoading = value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows to edit a tape in its tape reference context.
|
|
/// </summary>
|
|
public TapeReferenceClip EditionContext
|
|
{
|
|
get { return _editionContext; }
|
|
set
|
|
{
|
|
if (_editionContext != null)
|
|
{
|
|
_editionContext.PropertyChanged -= editionContext_Changed;
|
|
}
|
|
|
|
_editionContext = value;
|
|
OnEditionContextChanged(EventArgs.Empty);
|
|
|
|
if (_editionContext != null)
|
|
{
|
|
_editionContext.PropertyChanged += editionContext_Changed;
|
|
}
|
|
}
|
|
}
|
|
|
|
public string Path => Id;
|
|
|
|
public MapInfo? MapInfo => MapInfoManager.Instance.GetMapInfo(Path);
|
|
|
|
private void OnTapeIdChanged()
|
|
{
|
|
MapName = MapInfo?.Name;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region methods
|
|
|
|
// Base interface for TapeLoadingScope. See the TapeExtensions.cs file for usage example.
|
|
public interface ITapeLoadingScope
|
|
{
|
|
IDisposable Begin(Tape tape);
|
|
}
|
|
|
|
private void Load(string filePath)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
{
|
|
throw new FileNotFoundException(filePath);
|
|
}
|
|
|
|
Id = filePath;
|
|
IsLoading = true;
|
|
|
|
// Begin all registered TapeLoadingScope.
|
|
var loadingScopes = Objects.GetAllInstances<ITapeLoadingScope>().Select(scope => scope.Begin(this));
|
|
try
|
|
{
|
|
OnBeforeLoading();
|
|
new LuaSerializer().Deserialize(File.ReadAllText(filePath), this, "params");
|
|
OnAfterLoading();
|
|
}
|
|
finally
|
|
{
|
|
loadingScopes.ForEach(scope => scope.Dispose());
|
|
}
|
|
|
|
IsLoading = false;
|
|
}
|
|
|
|
public static Tape CreateTape(string path)
|
|
{
|
|
var tape = new Tape();
|
|
tape.Load(path);
|
|
return tape;
|
|
}
|
|
|
|
private IEnumerable<string> DeclineMovePathsForEachPlatform()
|
|
{
|
|
IEnumerable<string> pathList = Clips.OfType<MotionClip>().Select(x => x.ClassifierPath.ToString().ToLower());
|
|
|
|
foreach (var path in pathList)
|
|
{
|
|
var toLowerPath = path.ToLower().Replace("\\", "/");
|
|
if (toLowerPath.Contains("/moves/"))
|
|
{
|
|
if (System.IO.Path.GetExtension(toLowerPath) == ".msm")
|
|
{
|
|
yield return toLowerPath.Replace("/moves/", "/moves/ps3/");
|
|
yield return toLowerPath.Replace("/moves/", "/moves/wii/");
|
|
yield return toLowerPath.Replace("/moves/", "/moves/wiiu/");
|
|
yield return toLowerPath.Replace("/moves/", "/moves/orbis/");
|
|
}
|
|
else if (System.IO.Path.GetExtension(toLowerPath) == ".gesture")
|
|
{
|
|
yield return toLowerPath.Replace("/moves/", "/moves/durango/");
|
|
yield return toLowerPath.Replace("/moves/", "/moves/x360/");
|
|
yield return toLowerPath.Replace("/moves/", "/moves/orbis/");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
yield return toLowerPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<string> GetReferencedFiles()
|
|
{
|
|
var moveModelFiles = DeclineMovePathsForEachPlatform();
|
|
|
|
var pictoFiles = Clips.OfType<PictogramClip>().Select(x => x.PictoPath.ToString().ToLower())
|
|
.Union(Clips.OfType<PictogramClip>().Select(x => x.PictoPath.ToString().ToLower() + ".tfi"));
|
|
if (Directory.Exists(System.IO.Path.Combine(MapInfo?.PictosFolder, "_Solo")))
|
|
{
|
|
pictoFiles = pictoFiles.Select(x => Regex.Replace(x, "Pictos", "Pictos/_Solo", RegexOptions.IgnoreCase));
|
|
}
|
|
return moveModelFiles.Union(pictoFiles).Select(DataPath.GetAbsolutePath).Where(File.Exists);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Id;
|
|
}
|
|
|
|
public T GetOrCreateTrack<T>(uint id) where T: TapeTrack, new()
|
|
{
|
|
var track = (T)Tracks.FirstOrDefault(x => x.Id == id);
|
|
if (track == null)
|
|
{
|
|
track = new T { Id = id };
|
|
Tracks.Add(track);
|
|
}
|
|
return track;
|
|
}
|
|
|
|
public T GetOrCreateTrack<T>(string trackName) where T: TapeTrack, new()
|
|
{
|
|
T track = Tracks.OfType<T>().FirstOrDefault(x => x.Name == trackName);
|
|
if (track == null)
|
|
{
|
|
track = new T
|
|
{
|
|
Id = IdGenerator.Instance.Generate(),
|
|
Name = trackName
|
|
};
|
|
Tracks.Add(track);
|
|
}
|
|
return track;
|
|
}
|
|
|
|
public TapeTrack GetOrCreateTrackAt(int index)
|
|
{
|
|
while (index >= Tracks.Count)
|
|
{
|
|
CreateTrack();
|
|
}
|
|
return Tracks[index];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes a track id, returns the next visible track, creates the track if no track fits
|
|
/// </summary>
|
|
public TapeTrack GetOrCreateNextTrack(TapeTrack initial)
|
|
{
|
|
initial = initial ?? Tracks.FirstOrDefault();
|
|
if (initial == null)
|
|
{
|
|
return CreateTrack();
|
|
}
|
|
|
|
int index = Tracks.IndexOf(initial) + 1;
|
|
return index < Tracks.Count ? Tracks[index] : CreateTrack();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new track at the end of the list.
|
|
/// </summary>
|
|
public TapeTrack CreateTrack()
|
|
{
|
|
var newTrack = new TapeTrack { Id = IdGenerator.Instance.Generate() };
|
|
Tracks.Add(newTrack);
|
|
return newTrack;
|
|
}
|
|
|
|
public TapeTrack InsertTrack(int index)
|
|
{
|
|
var newTrack = new TapeTrack { Id = IdGenerator.Instance.Generate() };
|
|
Tracks.Insert(index, newTrack);
|
|
return newTrack;
|
|
}
|
|
|
|
public TapeTrack GetTrack(uint trackId)
|
|
{
|
|
return _tracks.FirstOrDefault(t => t.Id == trackId);
|
|
}
|
|
|
|
private void tracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (e.OldItems != null)
|
|
{
|
|
foreach (TapeTrack track in e.OldItems)
|
|
{
|
|
track.Tape = null;
|
|
}
|
|
}
|
|
if (e.NewItems != null)
|
|
{
|
|
foreach (TapeTrack track in e.NewItems)
|
|
{
|
|
track.Tape = this;
|
|
}
|
|
}
|
|
OnPropertyChanged(nameof(Tracks));
|
|
}
|
|
|
|
private void tracks_ItemsChanged(object sender, ItemChangedEventArgs<TapeTrack> args)
|
|
{
|
|
OnPropertyChanged(nameof(Tracks));
|
|
}
|
|
|
|
private void clips_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
foreach (Clip clip in e.NewItems<Clip>())
|
|
{
|
|
clip.Tape = this;
|
|
}
|
|
OnPropertyChanged(nameof(Clips));
|
|
}
|
|
|
|
private void clips_ItemsChanged(object sender, ItemChangedEventArgs<Clip> args)
|
|
{
|
|
if (args.Item != null && args.Item.IsComputedProperty(args.PropertyName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
OnPropertyChanged(nameof(Clips));
|
|
}
|
|
|
|
void editionContext_Changed(object sender, EventArgs e)
|
|
{
|
|
OnEditionContextChanged(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computed properties are not considered in undo/redo and to make the document "dirty"
|
|
/// </summary>
|
|
public virtual bool IsComputedProperty(string propertyName)
|
|
{
|
|
return propertyName == "Wave";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public void RaisePropertyChanged(string propertyName)
|
|
{
|
|
OnPropertyChanged(propertyName);
|
|
}
|
|
|
|
public event EventHandler EditionContextChanged;
|
|
|
|
protected virtual void OnEditionContextChanged(EventArgs e)
|
|
{
|
|
EditionContextChanged?.Invoke(this, e);
|
|
}
|
|
|
|
public event EventHandler ExplicitCollisionRefreshRequested;
|
|
|
|
public virtual void OnExplicitCollisionRefreshRequested(EventArgs e)
|
|
{
|
|
ExplicitCollisionRefreshRequested?.Invoke(this, e);
|
|
}
|
|
|
|
public delegate void TapeActionFunc(Tape tape);
|
|
public static event TapeActionFunc BeforeLoading;
|
|
public void OnBeforeLoading()
|
|
{
|
|
BeforeLoading?.Invoke(this);
|
|
}
|
|
public static event TapeActionFunc AfterLoading;
|
|
public void OnAfterLoading()
|
|
{
|
|
AfterLoading?.Invoke(this);
|
|
}
|
|
public static event TapeActionFunc BeforeSaving;
|
|
public void OnBeforeSaving()
|
|
{
|
|
BeforeSaving?.Invoke(this);
|
|
}
|
|
public static event TapeActionFunc AfterSaving;
|
|
public void OnAfterSaving()
|
|
{
|
|
AfterSaving?.Invoke(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region DanceTape
|
|
|
|
public int DancePlayersCount
|
|
{
|
|
get
|
|
{
|
|
return Tracks.OfType<MoveTrack>().Select(x => (int) x.CoachId + 1).DefaultIfEmpty().Max();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|