BizHawk/BizHawk.Client.Common/movie/tasproj/TasMovie.cs

348 lines
8.3 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
2014-06-11 21:14:13 +00:00
using BizHawk.Common;
using BizHawk.Emulation.Common;
using System.ComponentModel;
namespace BizHawk.Client.Common
{
public sealed partial class TasMovie : Bk2Movie, INotifyPropertyChanged
{
public const string DefaultProjectName = "default";
2014-08-29 02:40:45 +00:00
private readonly Bk2MnemonicConstants Mnemonics = new Bk2MnemonicConstants();
private readonly TasStateManager StateManager;
private readonly List<bool> LagLog = new List<bool>();
private readonly Dictionary<int, IController> InputStateCache = new Dictionary<int, IController>();
private readonly List<string> VerificationLog = new List<string>(); // For movies that do not begin with power-on, this is the input required to get into the initial state
public TasMovie(string path, bool startsFromSavestate = false) : base(path)
{
// TODO: how to call the default constructor AND the base(path) constructor? And is base(path) calling base() ?
StateManager = new TasStateManager(this);
Header[HeaderKeys.MOVIEVERSION] = "BizHawk v2.0 Tasproj v1.0";
Markers = new TasMovieMarkerList(this);
Markers.CollectionChanged += Markers_CollectionChanged;
Markers.Add(0, startsFromSavestate ? "Savestate" : "Power on");
}
public TasMovie(bool startsFromSavestate = false)
: base()
{
StateManager = new TasStateManager(this);
2014-07-13 14:26:40 +00:00
Header[HeaderKeys.MOVIEVERSION] = "BizHawk v2.0 Tasproj v1.0";
Markers = new TasMovieMarkerList(this);
Markers.CollectionChanged += Markers_CollectionChanged;
Markers.Add(0, startsFromSavestate ? "Savestate" : "Power on");
}
public TasMovieMarkerList Markers { get; set; }
public bool UseInputCache { get; set; }
public override string PreferredExtension
{
get { return Extension; }
}
public TasStateManager TasStateManager
{
get { return StateManager; }
}
public new const string Extension = "tasproj";
public TasMovieRecord this[int index]
{
get
{
return new TasMovieRecord
{
State = StateManager[index],
LogEntry = GetInputLogEntry(index),
Lagged = (index < LagLog.Count) ? LagLog[index] : (bool?)null
};
}
}
#region Events and Handlers
public event PropertyChangedEventHandler PropertyChanged;
private bool _changes;
public override bool Changes
{
get { return _changes; }
protected set
{
if (_changes != value)
{
_changes = value;
OnPropertyChanged("Changes");
}
}
}
// This event is Raised ony when Changes is TOGGLED.
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
// Raising the event when FirstName or LastName property value changed
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
void Markers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Changes = true;
}
#endregion
public void ClearChanges()
{
Changes = false;
}
public void FlagChanges()
{
Changes = true;
}
2014-07-08 13:33:01 +00:00
public override void StartNewRecording()
{
ClearTasprojExtras();
Markers.Add(0, StartsFromSavestate ? "Savestate" : "Power on");
base.StartNewRecording();
2014-07-08 13:33:01 +00:00
}
public override void SwitchToPlay()
{
_mode = Moviemode.Play;
}
/// <summary>
/// Removes lag log and greenzone after this frame
/// </summary>
/// <param name="frame">The last frame that can be valid.</param>
private void InvalidateAfter(int frame)
{
if (frame < LagLog.Count)
{
LagLog.RemoveRange(frame + 1, LagLog.Count - frame - 1);
}
StateManager.Invalidate(frame + 1);
Changes = true; // TODO check if this actually removed anything before flagging changes
}
2014-07-10 02:45:56 +00:00
/// <summary>
/// Returns the mnemonic value for boolean buttons, and actual value for floats,
/// for a given frame and button.
2014-07-10 02:45:56 +00:00
/// </summary>
public string DisplayValue(int frame, string buttonName)
{
if (UseInputCache && InputStateCache.ContainsKey(frame))
{
return CreateDisplayValueForButton(InputStateCache[frame], buttonName);
}
2014-07-10 02:45:56 +00:00
var adapter = GetInputState(frame);
if (UseInputCache)
{
InputStateCache.Add(frame, adapter);
}
return CreateDisplayValueForButton(adapter, buttonName);
}
public void FlushInputCache()
{
InputStateCache.Clear();
}
public string CreateDisplayValueForButton(IController adapter, string buttonName)
{
2014-07-10 02:45:56 +00:00
if (adapter.Type.BoolButtons.Contains(buttonName))
{
return adapter.IsPressed(buttonName) ?
2014-08-29 02:40:45 +00:00
Mnemonics[buttonName].ToString() :
2014-07-10 02:45:56 +00:00
string.Empty;
}
if (adapter.Type.FloatControls.Contains(buttonName))
{
2014-07-11 16:26:19 +00:00
return adapter.GetFloat(buttonName).ToString();
2014-07-10 02:45:56 +00:00
}
return "!";
}
public void ToggleBoolState(int frame, string buttonName)
{
if (frame < _log.Count)
{
var adapter = GetInputState(frame) as Bk2ControllerAdapter;
adapter[buttonName] = !adapter.IsPressed(buttonName);
var lg = LogGeneratorInstance();
lg.SetSource(adapter);
_log[frame] = lg.GenerateLogEntry();
Changes = true;
InvalidateAfter(frame);
}
}
public void SetBoolState(int frame, string buttonName, bool val)
{
if (frame < _log.Count)
{
var adapter = GetInputState(frame) as Bk2ControllerAdapter;
var old = adapter[buttonName];
adapter[buttonName] = val;
var lg = LogGeneratorInstance();
lg.SetSource(adapter);
_log[frame] = lg.GenerateLogEntry();
if (old != val)
{
InvalidateAfter(frame);
Changes = true;
}
}
}
2014-07-11 16:26:19 +00:00
public void SetFloatState(int frame, string buttonName, float val)
{
if (frame < _log.Count)
{
var adapter = GetInputState(frame) as Bk2ControllerAdapter;
var old = adapter.GetFloat(buttonName);
adapter.SetFloat(buttonName, val);
var lg = LogGeneratorInstance();
lg.SetSource(adapter);
_log[frame] = lg.GenerateLogEntry();
if (old != val)
{
InvalidateAfter(frame);
2014-07-11 16:26:19 +00:00
Changes = true;
}
}
}
public bool BoolIsPressed(int frame, string buttonName)
{
return ((Bk2ControllerAdapter)GetInputState(frame))
.IsPressed(buttonName);
}
2014-07-11 16:26:19 +00:00
public float GetFloatValue(int frame, string buttonName)
{
return ((Bk2ControllerAdapter)GetInputState(frame))
.GetFloat(buttonName);
2014-07-11 16:26:19 +00:00
}
2014-07-13 22:17:31 +00:00
// TODO: try not to need this, or at least use GetInputState and then a log entry generator
public string GetInputLogEntry(int frame)
{
2014-07-13 22:17:31 +00:00
if (frame < FrameCount && frame >= 0)
{
int getframe;
if (LoopOffset.HasValue)
{
if (frame < _log.Count)
{
getframe = frame;
}
else
{
getframe = ((frame - LoopOffset.Value) % (_log.Count - LoopOffset.Value)) + LoopOffset.Value;
}
}
else
{
getframe = frame;
}
return _log[getframe];
}
return string.Empty;
}
2014-07-11 15:43:47 +00:00
public void ClearGreenzone()
{
2014-10-02 23:50:50 +00:00
if (StateManager.Any())
{
2014-10-02 23:50:50 +00:00
StateManager.ClearGreenzone();
Changes = true;
}
}
public override IController GetInputState(int frame)
{
// TODO: states and lag capture
if (Global.Emulator.Frame == frame) // Take this opportunity to capture lag and state info if we do not have it
{
if (frame == LagLog.Count) // I intentionally did not do >=, if it were >= we missed some entries somewhere, oops, maybe this shoudl be a dictionary<int, bool> with frame values?
{
LagLog.Add(Global.Emulator.IsLagFrame);
}
if (!StateManager.HasState(frame))
{
StateManager.Capture();
}
}
return base.GetInputState(frame);
}
public void ClearLagLog()
{
LagLog.Clear();
}
public void DeleteLogBefore(int frame)
{
if (frame < _log.Count)
{
_log.RemoveRange(0, frame);
}
}
public void CopyLog(IEnumerable<string> log)
{
_log.Clear();
foreach(var entry in log)
{
_log.Add(entry);
}
}
public void CopyVerificationLog(IEnumerable<string> log)
{
VerificationLog.Clear();
foreach (var entry in log)
{
VerificationLog.Add(entry);
}
}
public List<string> GetLogEntries()
{
return _log;
}
}
}