JD2022-TU1/main/tools/framework/JD.Serialization/Timeline/Tapes/Tape.cs

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
}
}