BizHawk/BizHawk.Client.EmuHawk/tools/Macros/MovieZone.cs

314 lines
8.9 KiB
C#

using System;
using System.Linq;
using System.IO;
using BizHawk.Emulation.Common;
using BizHawk.Client.Common;
namespace BizHawk.Client.EmuHawk
{
public class MovieZone
{
private readonly IEmulator _emulator;
private readonly ToolManager _tools;
private readonly IMovieSession _movieSession;
private readonly string[] _log;
private readonly IMovieController _targetController;
private string _inputKey;
private IMovieController _controller;
public MovieZone(IMovie movie, IEmulator emulator, ToolManager tools, IMovieSession movieSession, int start, int length, string key = "")
{
_emulator = emulator;
_tools = tools;
_movieSession = movieSession;
var lg = movie.LogGeneratorInstance(movieSession.MovieController);
_targetController = movieSession.GenerateMovieController();
_targetController.SetFrom(_targetController); // Reference and create all buttons
if (key == "")
{
key = lg.GenerateLogKey();
}
key = key.Replace("LogKey:", "").Replace("#", "");
key = key.Substring(0, key.Length - 1);
_inputKey = key;
Length = length;
_log = new string[length];
// Get a IController that only contains buttons in key.
string[] keys = key.Split('|');
var d = new ControllerDefinition();
foreach (var k in keys)
{
if (_emulator.ControllerDefinition.BoolButtons.Contains(k))
{
d.BoolButtons.Add(k);
}
else
{
d.AxisControls.Add(k);
int rangeIndex = _emulator.ControllerDefinition.AxisControls.IndexOf(k);
d.AxisRanges.Add(_emulator.ControllerDefinition.AxisRanges[rangeIndex]);
}
}
_controller = movieSession.GenerateMovieController(d);
var logGenerator = movieSession.Movie.LogGeneratorInstance(_controller);
logGenerator.GenerateLogEntry(); // Reference and create all buttons.
string movieKey = logGenerator.GenerateLogKey().Replace("LogKey:", "").Replace("#", "");
movieKey = movieKey.Substring(0, movieKey.Length - 1);
if (key == movieKey)
{
for (int i = 0; i < length; i++)
{
_log[i] = movie.GetInputLogEntry(i + start);
}
}
else
{
for (int i = 0; i < length; i++)
{
_controller.SetFrom(movie.GetInputState(i + start));
_log[i] = logGenerator.GenerateLogEntry();
}
}
}
public string Name { get; set; }
public int Start { get; set; }
public int Length { get; set; }
public bool Replace { get; set; } = true;
public bool Overlay { get; set; }
public string InputKey
{
get => _inputKey;
set { _inputKey = value; ReSetLog(); }
}
private void ReSetLog()
{
// Get a IController that only contains buttons in key.
string[] keys = _inputKey.Split('|');
var d = new ControllerDefinition();
foreach (var key in keys)
{
if (_emulator.ControllerDefinition.BoolButtons.Contains(key))
{
d.BoolButtons.Add(key);
}
else
{
d.AxisControls.Add(key);
}
}
var newController = _movieSession.GenerateMovieController(d);
var logGenerator = _movieSession.Movie.LogGeneratorInstance(newController);
logGenerator.GenerateLogEntry(); // Reference and create all buttons.
// Reset all buttons in targetController (it may still have buttons that aren't being set here set true)
var tC = _movieSession.Movie.LogGeneratorInstance(_targetController);
_targetController.SetFromMnemonic(tC.EmptyEntry);
for (int i = 0; i < Length; i++)
{
_controller.SetFromMnemonic(_log[i]);
LatchFromSourceButtons(_targetController, _controller);
newController.SetFrom(_targetController);
_log[i] = logGenerator.GenerateLogEntry();
}
_controller = newController;
}
public void PlaceZone(IMovie movie)
{
if (movie is ITasMovie tasMovie)
{
tasMovie.ChangeLog.BeginNewBatch($"Place Macro at {Start}");
}
if (Start > movie.InputLogLength)
{
// Cannot place a frame here. Find a nice way around this.
return;
}
// Can't be done with a regular movie.
if (!Replace && movie is ITasMovie tasMovie2)
{
tasMovie2.InsertEmptyFrame(Start, Length);
}
if (Overlay)
{
for (int i = 0; i < Length; i++)
{ // Overlay the frames.
_controller.SetFromMnemonic(_log[i]);
LatchFromSourceButtons(_targetController, _controller);
ORLatchFromSource(_targetController, movie.GetInputState(i + Start));
movie.PokeFrame(i + Start, _targetController);
}
}
else
{
// Copy over the frame.
for (int i = 0; i < Length; i++)
{
_controller.SetFromMnemonic(_log[i]);
LatchFromSourceButtons(_targetController, _controller);
movie.PokeFrame(i + Start, _targetController);
}
}
if (movie is ITasMovie tasMovie3) // Assume TAStudio is open?
{
tasMovie3.ChangeLog.EndBatch();
if (_emulator.Frame > Start)
{
// TODO: Go to start of macro? Ask TAStudio to do that?
// TasMovie.InvalidateAfter(Start) [this is private]
// Load last state, Emulate to Start
// Or do this, if TAStudio has to be open.
if (_tools.IsLoaded<TAStudio>())
{
_tools.TAStudio.GoToFrame(Start);
}
_tools.UpdateToolsBefore();
_tools.UpdateToolsAfter();
}
else if (_tools.IsLoaded<TAStudio>())
{
_tools.TAStudio.UpdateValues();
}
}
if (movie.InputLogLength >= _emulator.Frame)
{
movie.SwitchToPlay();
Global.Config.MovieEndAction = MovieEndAction.Record;
}
}
public void Save(string fileName)
{
// Save the controller definition/LogKey
// Save the controller name and player count. (Only for the user.)
// Save whether or not the macro should use overlay input, and/or replace
string[] header = new string[4];
header[0] = InputKey;
header[1] = _emulator.ControllerDefinition.Name;
header[2] = _emulator.ControllerDefinition.PlayerCount.ToString();
header[3] = $"{Overlay},{Replace}";
File.WriteAllLines(fileName, header);
File.AppendAllLines(fileName, _log);
}
public MovieZone(string fileName, IEmulator emulator, ToolManager tools)
{
if (!File.Exists(fileName))
{
return;
}
_emulator = emulator;
_tools = tools;
string[] readText = File.ReadAllLines(fileName);
// If the LogKey contains buttons/controls not accepted by the emulator,
// tell the user and display the macro's controller name and player count
_inputKey = readText[0];
var lg = _movieSession.Movie.LogGeneratorInstance(_movieSession.MovieController);
string key = lg.GenerateLogKey();
key = key.Replace("LogKey:", "").Replace("#", "");
key = key.Substring(0, key.Length - 1);
string[] emuKeys = key.Split('|');
string[] macroKeys = _inputKey.Split('|');
foreach (var macro in macroKeys)
{
if (!emuKeys.Contains(macro))
{
System.Windows.Forms.MessageBox.Show($"The selected macro is not compatible with the current emulator core.\nMacro controller: {readText[1]}\nMacro player count: {readText[2]}", "Error");
return;
}
}
// Settings
string[] settings = readText[3].Split(',');
Overlay = Convert.ToBoolean(settings[0]);
Replace = Convert.ToBoolean(settings[1]);
_log = new string[readText.Length - 4];
readText.ToList().CopyTo(4, _log, 0, _log.Length);
Length = _log.Length;
Start = 0;
Name = Path.GetFileNameWithoutExtension(fileName);
// Adapters
_targetController = _movieSession.GenerateMovieController();
_targetController.SetFrom(_targetController); // Reference and create all buttons
string[] keys = _inputKey.Split('|');
var d = new ControllerDefinition(_emulator.ControllerDefinition);
foreach (var k in keys)
{
if (_emulator.ControllerDefinition.BoolButtons.Contains(k))
{
d.BoolButtons.Add(k);
}
else
{
d.AxisControls.Add(k);
}
}
_controller = _movieSession.GenerateMovieController(d);
}
#region Custom Latch
private void LatchFromSourceButtons(IMovieController latching, IController source)
{
foreach (string button in source.Definition.BoolButtons)
{
latching.SetBool(button, source.IsPressed(button));
}
foreach (string name in source.Definition.AxisControls)
{
latching.SetAxis(name, source.AxisValue(name));
}
}
private void ORLatchFromSource(IMovieController latching, IController source)
{
foreach (string button in latching.Definition.BoolButtons)
{
latching.SetBool(button, latching.IsPressed(button) | source.IsPressed(button));
}
foreach (string name in latching.Definition.AxisControls)
{
float sFloat = source.AxisValue(name);
int indexRange = source.Definition.AxisControls.IndexOf(name);
if (sFloat == source.Definition.AxisRanges[indexRange].Mid)
{
latching.SetAxis(name, sFloat);
}
}
}
#endregion
}
}