diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasBranch.cs b/src/BizHawk.Client.Common/movie/tasproj/TasBranch.cs index 9a5da7ccb0c..7cd4d243510 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasBranch.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasBranch.cs @@ -252,17 +252,7 @@ public void Load(ZipStateLoader bl, ITasMovie movie) }); b.Markers = new TasMovieMarkerList(movie); - bl.GetLump(nmarkers, abort: false, tr => - { - string line; - while ((line = tr.ReadLine()) != null) - { - if (!string.IsNullOrWhiteSpace(line)) - { - b.Markers.Add(new TasMovieMarker(line)); - } - } - }); + bl.GetLump(nmarkers, abort: false, b.Markers.LoadFromFile); bl.GetLump(nusertext, abort: false, tr => { diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs index 0eb657e96bb..aafac0c2937 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs @@ -84,17 +84,7 @@ private void LoadTasprojExtras(ZipStateLoader bl) { bl.GetLump(BinaryStateLump.LagLog, abort: false, tr => LagLog.Load(tr)); - bl.GetLump(BinaryStateLump.Markers, abort: false, tr => - { - string line; - while ((line = tr.ReadLine()) != null) - { - if (!string.IsNullOrWhiteSpace(line)) - { - Markers.Add(new TasMovieMarker(line)); - } - } - }); + bl.GetLump(BinaryStateLump.Markers, abort: false, Markers.LoadFromFile); bl.GetLump(BinaryStateLump.ClientSettings, abort: false, tr => { diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 4efd1bacf81..a20e6eae643 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -326,7 +326,7 @@ public bool IsReserved(int frame) // Why the frame before? // because we always navigate to the frame before and emulate 1 frame so that we ensure a proper frame buffer on the screen // users want instant navigation to markers, so to do this, we need to reserve the frame before the marker, not the marker itself - return Markers.Exists(m => m.Frame - 1 == frame) + return Markers.Exists(m => m.WantsState && m.Frame - 1 == frame) || Branches.Any(b => b.Frame == frame); // Branches should already be in the reserved list, but it doesn't hurt to check } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs index b878250e2f1..cb84fe41019 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text; using BizHawk.Common.CollectionExtensions; @@ -22,18 +23,32 @@ public TasMovieMarker(int frame, string message = "") /// /// Initializes a new instance of the class from a line of text /// - public TasMovieMarker(string line) + public TasMovieMarker(string line, int version) { var split = line.Split('\t'); Frame = int.Parse(split[0]); - Message = split[1]; + if (version == 1) + { + Message = split[1]; + } + else if (version == 2) + { + WantsState = bool.Parse(split[1]); + Message = split[2]; + } + else + { + throw new Exception("Invalid version."); + } } public int Frame { get; private set; } public string Message { get; set; } - public override string ToString() => Frame.ToString() + '\t' + Message; + public bool WantsState { get; set; } = true; + + public override string ToString() => $"{Frame}\t{WantsState}\t{Message}"; public override int GetHashCode() => Frame.GetHashCode(); @@ -111,6 +126,7 @@ private void OnListChanged(NotifyCollectionChangedAction action) public override string ToString() { var sb = new StringBuilder(); + sb.AppendLine("2"); // version foreach (var marker in this) { sb.AppendLine(marker.ToString()); @@ -119,6 +135,29 @@ public override string ToString() return sb.ToString(); } + public void LoadFromFile(TextReader tr) + { + string line; + int version = -1; + while ((line = tr.ReadLine()) != null) + { + if (string.IsNullOrWhiteSpace(line)) continue; + if (version == -1) + { + if (line.Contains('\t')) + { + version = 1; + } + else + { + version = int.Parse(line); + continue; + } + } + Add(new TasMovieMarker(line, version)); + } + } + // the inherited one public new void Add(TasMovieMarker item) { diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs index ceccfb2bbd7..59e05616c4d 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.Drawing.cs @@ -186,7 +186,6 @@ private void DrawData(List visibleColumns, int firstVisibleRow, int int startRow = firstVisibleRow; int range = Math.Min(lastVisibleRow, RowCount - 1) - startRow + 1; - _renderer.PrepDrawString(Font, _foreColor); Cell currentCell = new(); Cell mouseCell = null; @@ -226,28 +225,21 @@ private void DrawData(List visibleColumns, int firstVisibleRow, int int strOffsetY = 0; QueryItemText(this, f + startRow, col, out var text, ref strOffsetX, ref strOffsetY); - bool rePrep = false; - Color foreColor = _foreColor; + Color? foreColor = QueryItemForeColor?.Invoke(this, f + startRow, col); Font font = Font; currentCell.Column = col; currentCell.RowIndex = f + startRow; - if (_selectedItems.Contains(currentCell)) + if (foreColor == null && _selectedItems.Contains(currentCell)) { foreColor = SystemColors.HighlightText; - rePrep = true; } if (string.IsNullOrEmpty(text) && mouseCell == currentCell) { font = new Font(Font, FontStyle.Regular); foreColor = SystemColors.GrayText; text = col.Text; - rePrep = true; - } - if (col.Rotatable || rePrep) - { - _renderer.PrepDrawString(font, foreColor, rotate: col.Rotatable); - rePrep = true; } + _renderer.PrepDrawString(font, foreColor ?? _foreColor, rotate: col.Rotatable); int textWidth = (int)_renderer.MeasureString(text, font).Width; if (col.Rotatable) @@ -266,11 +258,6 @@ private void DrawData(List visibleColumns, int firstVisibleRow, int DrawString(text, new Rectangle(baseX + textX, baseY + textY, MaxColumnWidth, CellHeight)); } - - if (rePrep) - { - _renderer.PrepDrawString(Font, _foreColor); - } } } } @@ -299,34 +286,23 @@ private void DrawData(List visibleColumns, int firstVisibleRow, int QueryItemText(this, f + startRow, column, out var text, ref strOffsetX, ref strOffsetY); - bool rePrep = false; - Color foreColor = _foreColor; + Color? foreColor = QueryItemForeColor?.Invoke(this, f + startRow, column); Font font = Font; currentCell.Column = column; currentCell.RowIndex = f + startRow; - if (_selectedItems.Contains(currentCell)) + if (foreColor == null && _selectedItems.Contains(currentCell)) { foreColor = SystemColors.HighlightText; - rePrep = true; } if (string.IsNullOrEmpty(text) && mouseCell == currentCell) { font = new Font(Font, FontStyle.Regular); foreColor = SystemColors.GrayText; text = column.Text; - rePrep = true; - } - if (rePrep) - { - _renderer.PrepDrawString(font, foreColor); } + _renderer.PrepDrawString(font, foreColor ?? _foreColor); DrawString(text, new Rectangle(point.X + strOffsetX, point.Y + strOffsetY, column.Width, ColumnHeight)); - - if (rePrep) - { - _renderer.PrepDrawString(Font, _foreColor); - } } } } diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs index c67f70f2d1e..5e4a096c07c 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs @@ -449,6 +449,12 @@ public int HoverInterval [Category("Virtual")] public event QueryItemBkColorHandler QueryItemBkColor; + /// + /// Fire the event which requests the color of text for the passed cell + /// + [Category("Virtual")] + public event QueryItemForeColorHandler QueryItemForeColor; + [Category("Virtual")] public event QueryRowBkColorHandler QueryRowBkColor; @@ -531,6 +537,11 @@ public int HoverInterval /// public delegate void QueryItemTextHandler(InputRoll sender, int index, RollColumn column, out string text, ref int offsetX, ref int offsetY); + /// + /// Retrieve the foreground color for a cell. Return null to use the default. + /// + public delegate Color? QueryItemForeColorHandler(InputRoll sender, int index, RollColumn column); + /// /// Retrieve the background color for a cell /// diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.Designer.cs index 8e1160a9d94..72bbcb895b3 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.Designer.cs @@ -199,6 +199,7 @@ private void InitializeComponent() this.MarkerView.TabStop = false; this.MarkerView.SelectedIndexChanged += new System.EventHandler(this.MarkerView_SelectedIndexChanged); this.MarkerView.DoubleClick += new System.EventHandler(this.MarkerView_MouseDoubleClick); + this.MarkerView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MarkerView_MouseDown); // // MarkersGroupBox // diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs index b4fc528d9fd..6fe0d03c332 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs @@ -1,15 +1,18 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Drawing; using System.Linq; using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Client.EmuHawk.Properties; +using BizHawk.Emulation.Common; namespace BizHawk.Client.EmuHawk { public partial class MarkerControl : UserControl, IDialogParent { + private static string SAVESTATE_COL_NAME = "Savestate"; + public TAStudio Tastudio { get; set; } public TasMovieMarkerList Markers => Tastudio.CurrentTasMovie.Markers; @@ -37,6 +40,7 @@ public MarkerControl() SetupColumns(); MarkerView.QueryItemBkColor += MarkerView_QueryItemBkColor; MarkerView.QueryItemText += MarkerView_QueryItemText; + MarkerView.QueryItemForeColor += MarkerView_QueryItemForeColor; } public void UpdateHotkeyTooltips(Config config) @@ -50,6 +54,7 @@ private void SetupColumns() { MarkerView.AllColumns.Clear(); MarkerView.AllColumns.Add(new(name: "FrameColumn", widthUnscaled: 52, text: "Frame")); + MarkerView.AllColumns.Add(new(name: SAVESTATE_COL_NAME, widthUnscaled: 24, text: "State")); MarkerView.AllColumns.Add(new(name: "LabelColumn", widthUnscaled: 125, text: string.Empty)); } @@ -72,16 +77,16 @@ private void MarkerView_QueryItemBkColor(InputRoll sender, int index, RollColumn } else if (Tastudio.CurrentTasMovie.LagLog[marker.Frame + 1] is bool lagged) { - if (lagged) + if (column.Name == "FrameColumn") { - color = column.Name == "FrameColumn" + color = lagged ? Tastudio.Palette.LagZone_FrameCol - : Tastudio.Palette.LagZone_InputLog; + : Tastudio.Palette.GreenZone_FrameCol; } else { - color = column.Name == "LabelColumn" - ? Tastudio.Palette.GreenZone_FrameCol + color = lagged + ? Tastudio.Palette.LagZone_InputLog : Tastudio.Palette.GreenZone_InputLog; } } @@ -97,14 +102,41 @@ private void MarkerView_QueryItemText(InputRoll sender, int index, RollColumn co return; } + TasMovieMarker marker = Markers[index]; if (column.Name == "FrameColumn") { - text = Markers[index].Frame.ToString(); + text = marker.Frame.ToString(); } else if (column.Name == "LabelColumn") { - text = Markers[index].Message; + text = marker.Message; + } + else if (column.Name == SAVESTATE_COL_NAME) + { + if (marker.WantsState) + { + bool hasState = marker.Frame == 0 || Tastudio.CurrentTasMovie.TasStateManager.HasState(marker.Frame - 1); + text = hasState ? "✔" : "-"; + } + else + { + text = "x"; + } + } + } + + private Color? MarkerView_QueryItemForeColor(InputRoll sender, int index, RollColumn column) + { + if (column.Name == SAVESTATE_COL_NAME) + { + TasMovieMarker marker = Markers[index]; + if (!marker.WantsState) + { + return Color.OrangeRed; + } } + + return null; } private void MarkerContextMenu_Opening(object sender, CancelEventArgs e) @@ -188,6 +220,7 @@ public void AddMarker(int frame, bool editText = false) { marker = new TasMovieMarker(frame); } + marker.WantsState = Tastudio.Settings.StatesForMarkers; UpdateValues(); Markers.Add(marker); @@ -285,5 +318,24 @@ private void MarkerView_MouseDoubleClick(object sender, EventArgs e) { if (MarkerView.AnyRowsSelected) Tastudio.GoToFrame(FirstSelectedMarker.Frame); } + + private void MarkerView_MouseDown(object sender, MouseEventArgs e) + { + Cell cell = MarkerView.CurrentCell; + if (cell == null || cell.RowIndex < 0 || cell.RowIndex >= Markers.Count) return; + if (cell.Column?.Name != SAVESTATE_COL_NAME) return; + + TasMovieMarker marker = Markers[cell.RowIndex.Value]; + marker.WantsState = !marker.WantsState; + if (!marker.WantsState) + { + Tastudio.CurrentTasMovie.TasStateManager.Unreserve(marker.Frame - 1); + } + else if (Tastudio.Emulator.Frame == marker.Frame - 1) + { + Tastudio.CurrentTasMovie.TasStateManager.Capture(marker.Frame - 1, Tastudio.Emulator.AsStatable()); + } + Tastudio.RefreshDialog(); + } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 14a72d843bb..e7c37c45caa 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -143,6 +143,7 @@ public TAStudioSettings() public int RewindStep { get; set; } = 1; public int RewindStepFast { get; set; } = 4; public bool ScrollSync { get; set; } = true; + public bool StatesForMarkers { get; set; } = true; public PatternPaintModeEnum PatternPaintMode { get; set; } = TAStudioSettings.PatternPaintModeEnum.Never; public PatternSelectionEnum PatternSelection { get; set; } = TAStudioSettings.PatternSelectionEnum.Hold; public Font TasViewFont { get; set; } = new Font("Arial", 8.25F, FontStyle.Bold, GraphicsUnit.Point, 0); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs index ba167d958c7..76293a442e0 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.Designer.cs @@ -106,6 +106,7 @@ private void InitializeComponent() this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); this.EditInvisibleColumnsCheckbox = new System.Windows.Forms.CheckBox(); this.ScrollSyncCheckbox = new System.Windows.Forms.CheckBox(); + this.StatesForMarkersCheckbox = new System.Windows.Forms.CheckBox(); this.tabControl1.SuspendLayout(); this.tabPage2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.HideLagNum)).BeginInit(); @@ -380,6 +381,7 @@ private void InitializeComponent() // // tabPage4 // + this.tabPage4.Controls.Add(this.StatesForMarkersCheckbox); this.tabPage4.Controls.Add(this.label12); this.tabPage4.Controls.Add(this.DefaultManagerSettingsAppliedLabel); this.tabPage4.Controls.Add(this.SetDefaultStateSettingsButton); @@ -731,6 +733,7 @@ private void InitializeComponent() // // tabPage3 // + this.tabPage3.Controls.Add(this.StatesForMarkersCheckbox); this.tabPage3.Controls.Add(this.OldBranchesCheckbox); this.tabPage3.Controls.Add(this.BranchDoubleClickCheckbox); this.tabPage3.Controls.Add(this.FastRewindNum); @@ -912,6 +915,17 @@ private void InitializeComponent() this.ApplyButton.UseVisualStyleBackColor = true; this.ApplyButton.Click += new System.EventHandler(this.ApplyButton_Click); // + // StatesForMarkersCheckbox + // + this.StatesForMarkersCheckbox.AutoSize = true; + this.StatesForMarkersCheckbox.Location = new System.Drawing.Point(12, 200); + this.StatesForMarkersCheckbox.Name = "StatesForMarkersCheckbox"; + this.StatesForMarkersCheckbox.Size = new System.Drawing.Size(171, 17); + this.StatesForMarkersCheckbox.TabIndex = 518; + this.StatesForMarkersCheckbox.Text = "Markers keep states by default"; + this.toolTip1.SetToolTip(this.StatesForMarkersCheckbox, resources.GetString("StatesForMarkersCheckbox.ToolTip")); + this.StatesForMarkersCheckbox.UseVisualStyleBackColor = true; + // // tabPage6 // this.tabPage6.Controls.Add(this.ScrollSyncCheckbox); @@ -1170,5 +1184,6 @@ private void InitializeComponent() private System.Windows.Forms.Label label3; private System.Windows.Forms.NumericUpDown ScrollSpeedNum; private System.Windows.Forms.Label label2; + private System.Windows.Forms.CheckBox StatesForMarkersCheckbox; } } \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs index a7e34900e67..5ba583c9192 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.cs @@ -101,6 +101,7 @@ private void TAStudioSettingsForm_Load(object sender, EventArgs e) RewindNum.Value = _settings.GeneralClientSettings.RewindStep; FastRewindNum.Value = _settings.GeneralClientSettings.RewindStepFast; ScrollSpeedNum.Value = _settings.GeneralClientSettings.ScrollSpeed; + StatesForMarkersCheckbox.Checked = _settings.GeneralClientSettings.StatesForMarkers; // patterns foreach (var button in _controllerDef.BoolButtons) @@ -502,6 +503,7 @@ private void ApplyButton_Click(object sender, EventArgs e) _settings.GeneralClientSettings.MaxUndoSteps = (int)UndoCountNum.Value; _settings.GeneralClientSettings.RewindStep = (int)RewindNum.Value; _settings.GeneralClientSettings.RewindStepFast = (int)FastRewindNum.Value; + _settings.GeneralClientSettings.StatesForMarkers = StatesForMarkersCheckbox.Checked; if (ScrollToViewRadio.Checked) _settings.GeneralClientSettings.FollowCursorScrollMethod = "near"; else if (ScrollToTopRadio.Checked) _settings.GeneralClientSettings.FollowCursorScrollMethod = "top"; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx index 477a02dadd3..bdf8ac85214 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudioSettingsForm.resx @@ -124,6 +124,14 @@ And when "Recording mode" is on, the movie will be truncated at the branch frame 17, 17 + + TAStudio can automatically keep savestates for each marker. +If this is enabled, new markers will have savestates enabled by default. +Note: The savestate needs to be on the frame before the marker. Thus, making +a marker on the current frame will not automatically create a state. +(The reason for the state being on the frame before is so that TAStudio +does not need to store a screenshot with the savestate.) + When disabled, inputs that are not visible on the active input roll cannot be edited. This allows you to, for example, insert and delete frames on a roll with player 1's inputs